abiparser 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77899a7da00b8be7b61c97d2f1ebad297a6d33f74bd081c59b17f8b66bca78f1
4
- data.tar.gz: 15a102dc8505eb359fcb5d117de026e9308641e714ea50096895ee15fb4d655e
3
+ metadata.gz: 43f7d1f45a66ea6fd2deb690979c26f4289a666692bcb84c81420dbab5c2ab8b
4
+ data.tar.gz: 487f1407542dc6d6f5e22b2975f4a7828fc0f08f5147961dc44230486bccbe71
5
5
  SHA512:
6
- metadata.gz: 2bf5a3d5928df6f9d5a650f50ca05df22edb82f0d305a5ef66c552b36fee9b33d6166b63dfd4e878c8a32f7a86e042517159cc6b0155cf96734b36a0ab596817
7
- data.tar.gz: 8181ddd11ad89481204f1796ef4eebc854d10ec83fb8cd6d3fd45ce8da830cff2fd1273c726c507853843442c174c603bd66fcb735ae4aabf433141827befebb
6
+ metadata.gz: 5fc14d110513a91161272beb5a9e8d41401fe8ba5407d52beb43b782ac2b173108aaea79738b4f75d00439bf17908257376100c1d4166dc34434f936019eddbf
7
+ data.tar.gz: ed21d7bcdbb58144832ba46c82d6fae1c1ec22f501936b34902e68c56ab44a556f219c35294cf09a836382252d77e67de231476b027724fcb60e681d3925d90f
data/Manifest.txt CHANGED
@@ -7,5 +7,7 @@ lib/abiparser/constructor.rb
7
7
  lib/abiparser/contract.rb
8
8
  lib/abiparser/export/interface.rb
9
9
  lib/abiparser/function.rb
10
+ lib/abiparser/interface.rb
10
11
  lib/abiparser/param.rb
12
+ lib/abiparser/utils.rb
11
13
  lib/abiparser/version.rb
data/README.md CHANGED
@@ -13,6 +13,133 @@ abiparser - application binary interface (abi) parser machinery / helper for Eth
13
13
  ## Usage
14
14
 
15
15
 
16
+ ### Functions Signature Hashes / Selectors & Interface (Type) Ids
17
+
18
+
19
+ You can calculate the function selectors (or "sighash",
20
+ that is, signature hash)
21
+ by hashing the function signature
22
+ e.g. `supportsInterface(bytes4)` with the Keccak 256-Bit algorithm
23
+ and than use the first 4 bytes, that is, `0x01ffc9a7` (out of 32 bytes),
24
+ that is, `0x01ffc9a7a5cef8baa21ed3c5c0d7e23accb804b619e9333b597f47a0d84076e2`. Example:
25
+
26
+
27
+ ``` ruby
28
+ require 'abiparser'
29
+
30
+ sig = 'supportsInterface(bytes4)'
31
+ pp keccak256( sig )[0,4].hexdigest
32
+ #=> "0x01ffc9a7"
33
+ ```
34
+
35
+ Note: The `String#hexdigest` (also known as `String#bin_to_hex`) helper
36
+ converts a binary string (with `BINARY`/`ASCII-8BIT` encoding)
37
+ into a hex(adecimal) string.
38
+
39
+
40
+ You can calcuate interface (type) ids
41
+ by xor-ing (`^`) together the sighashes.
42
+ If the interface only has one function than
43
+ the interface (type) id equals the function sighash (by definition).
44
+
45
+
46
+ ``` solidity
47
+ interface ERC165 {
48
+ /// @notice Query if a contract implements an interface
49
+ /// @param interfaceID The interface identifier, as specified in ERC-165
50
+ /// @dev Interface identification is specified in ERC-165.
51
+ /// @return `true` if the contract implements `interfaceID` and
52
+ /// `interfaceID` is not 0xffffffff, `false` otherwise
53
+ function supportsInterface(bytes4 interfaceID) external view returns (bool);
54
+ }
55
+ // The interface identifier for this interface is 0x01ffc9a7.
56
+ ```
57
+
58
+ If you check the sighash for `supportsInterface(bytes4)`,
59
+ that is, `0x01ffc9a7` (see above)
60
+ than - bingo! - the interface id for ERC165 matches up.
61
+
62
+
63
+ Let's try to calculate the ERC20 standard (fungible) token interface
64
+ where the official id is `0x36372b07` by xor-ing (`^`) together all function sighashes:
65
+
66
+ ``` ruby
67
+ pp (keccak256('totalSupply()')[0,4] ^
68
+ keccak256('balanceOf(address)')[0,4] ^
69
+ keccak256('allowance(address,address)')[0,4] ^
70
+ keccak256('transfer(address,uint256)')[0,4] ^
71
+ keccak256('approve(address,uint256)')[0,4] ^
72
+ keccak256('transferFrom(address,address,uint256)')[0,4]).hexdigest
73
+ #=> "0x36372b07"
74
+
75
+ # or where def sig(bin) = keccak256(bin)[0,4])
76
+
77
+ pp (sig('totalSupply()') ^
78
+ sig('balanceOf(address)') ^
79
+ sig('allowance(address,address)') ^
80
+ sig('transfer(address,uint256)') ^
81
+ sig('approve(address,uint256)') ^
82
+ sig('transferFrom(address,address,uint256)')).hexdigest
83
+ #=> "0x36372b07"
84
+ ```
85
+
86
+ Voila!
87
+ Or re(use) the builtin pre-defined interfaces. Example:
88
+
89
+ ``` ruby
90
+ pp IERC165.inteface_id #=> "0x01ffc9a7"
91
+ pp IERC20.interface_id #=> "0x36372b07"
92
+ pp IERC721.interface_id #=> "0x80ac58cd"
93
+ pp IERC721_METADATA.interface_id #=> "0x5b5e139f"
94
+ pp IERC721_ENUMERABLE.interface_id #=> "0x780e9d63"
95
+ ```
96
+
97
+ Yes, you can. Define your own interface. Let's have a looksie
98
+ at the built-ins. Example:
99
+
100
+ ``` ruby
101
+ IERC165 = ABI::Interface.new(
102
+ 'supportsInterface(bytes4)'
103
+ )
104
+
105
+ IERC20 = ABI::Interface.new(
106
+ 'totalSupply()',
107
+ 'balanceOf(address)',
108
+ 'allowance(address,address)',
109
+ 'transfer(address,uint256)',
110
+ 'approve(address,uint256)',
111
+ 'transferFrom(address,address,uint256)'
112
+ )
113
+
114
+ IERC721 = ABI::Interface.new(
115
+ 'balanceOf(address)',
116
+ 'ownerOf(uint256)',
117
+ 'approve(address,uint256)',
118
+ 'getApproved(uint256)',
119
+ 'setApprovalForAll(address,bool)',
120
+ 'isApprovedForAll(address,address)',
121
+ 'transferFrom(address,address,uint256)',
122
+ 'safeTransferFrom(address,address,uint256)',
123
+ 'safeTransferFrom(address,address,uint256,bytes)' )
124
+
125
+ IERC721_METADATA = ABI::Interface.new(
126
+ 'name()',
127
+ 'symbol()',
128
+ 'tokenURI(uint256)' )
129
+
130
+ IERC721_ENUMERABLE = ABI::Interface.new(
131
+ 'tokenOfOwnerByIndex(address,uint256)',
132
+ 'totalSupply()',
133
+ 'tokenByIndex(uint256)' )
134
+
135
+ ...
136
+ ```
137
+
138
+
139
+ To be continued...
140
+
141
+
142
+
16
143
 
17
144
 
18
145
  ## License
@@ -74,7 +74,7 @@ class Constructor
74
74
  buf << "(#{buf2.join(',')})"
75
75
  end
76
76
  buf
77
- end
77
+ end
78
78
 
79
79
 
80
80
  def doc
@@ -98,9 +98,7 @@ class Constructor
98
98
  end
99
99
  buf << ";"
100
100
  buf
101
- end
102
-
103
-
101
+ end
104
102
 
105
103
 
106
104
  end # class Constructor
@@ -63,8 +63,37 @@ class Contract
63
63
  @events = events
64
64
  @has_receive = has_receive
65
65
  @has_fallback = has_fallback
66
+
67
+ @selectors = {}
68
+
69
+ ## auto-add selectors (hashed signatures)
70
+ @funcs.each do |func|
71
+ sighash = func.sighash
72
+ puts "0x#{sighash} => #{func.sig}"
73
+
74
+ ## assert - no duplicates allowed
75
+ if @selectors[sighash]
76
+ puts "!! ERROR - duplicate function signature #{func.sig}; already in use; sorry"
77
+ exit 1
78
+ end
79
+
80
+ @selectors[sighash] = func
81
+ end
66
82
  end
67
83
 
84
+
85
+ ## return hexstrings of sig(natures) - why? why not?
86
+ ## rename to sighashes - why? why not?
87
+ def selectors() @selectors.keys; end
88
+
89
+
90
+ def support?( sig )
91
+ Utils.support?( @selectors.keys, sig )
92
+ end
93
+ alias_method :supports?, :support? ## add alternate spelling - why? why not?
94
+
95
+
96
+
68
97
  def constructor() @ctor; end
69
98
  def functions() @funcs; end
70
99
 
@@ -6,11 +6,14 @@ class Contract
6
6
  buf = ''
7
7
  buf << "interface #{name} {"
8
8
 
9
- if @ctor
10
- buf << "\n"
11
- buf << "// Constructor\n"
12
- buf << "#{@ctor.decl}\n"
13
- end
9
+
10
+ # include constructor - why? why not?
11
+ #
12
+ # if @ctor
13
+ # buf << "\n"
14
+ # buf << "// Constructor\n"
15
+ # buf << "#{@ctor.decl}\n"
16
+ # end
14
17
 
15
18
  if payable_functions.size > 0
16
19
  buf << "\n"
@@ -7,9 +7,9 @@ class Function
7
7
  inputs = o['inputs'].map {|param| Param.parse( param ) }
8
8
  outputs = o['outputs'].map {|param| Param.parse( param ) }
9
9
 
10
- payable = nil
11
- contant = nil
12
- pure = nil
10
+ payable = nil
11
+ constant = nil
12
+ pure = nil
13
13
 
14
14
  ## old soliditity before v0.6
15
15
  ## newer version uses stateMutability
@@ -92,11 +92,16 @@ class Function
92
92
  buf << "(#{buf2.join(',')})"
93
93
  end
94
94
  buf
95
- end
95
+ end
96
+
97
+ def sighash
98
+ keccak256( sig )[0,4].hexdigest
99
+ end
96
100
 
97
101
 
98
102
  def doc
99
- buf = "function #{@name}"
103
+ ## note: text with markdown formatting
104
+ buf = "function **#{@name}**"
100
105
  if @inputs.empty?
101
106
  buf << "()"
102
107
  else
@@ -134,7 +139,7 @@ class Function
134
139
  end
135
140
  buf << ";"
136
141
  buf
137
- end
142
+ end
138
143
 
139
144
 
140
145
  def types
@@ -0,0 +1,59 @@
1
+
2
+ module ABI
3
+
4
+
5
+ ## rename to QueryInterface or SupportInterface
6
+ ## or InterfaceType or InterfaceId or such - why? why not?
7
+ class Interface
8
+
9
+ attr_reader :interface_id
10
+
11
+
12
+ ##
13
+ ## todo/fix: make sure full function defs get passed in (not only sigs!!!)
14
+ def initialize( *functions )
15
+ @functions = functions
16
+ @selectors = {}
17
+
18
+ @functions.each do |func|
19
+ sig = func
20
+ sighash = keccak256( sig )[0,4].hexdigest
21
+ puts "0x#{sighash} => #{sig}"
22
+
23
+ ## assert - no duplicates allowed
24
+ if @selectors[sighash]
25
+ puts "!! ERROR - duplicate function signature #{sig}; already in use; sorry"
26
+ exit 1
27
+ end
28
+
29
+ @selectors[sighash] = sig
30
+ end
31
+ @interface_id = calc_interface_id
32
+ end
33
+
34
+
35
+ def calc_interface_id
36
+ interface_id = nil
37
+ @selectors.each do |sighash,_|
38
+ sighash = sighash.hex_to_bin ## note: convert to binary string (from hexstring)!!
39
+ interface_id = if interface_id.nil?
40
+ sighash ## init with sighash
41
+ else
42
+ interface_id ^ sighash ## use xor
43
+ end
44
+ end
45
+ interface_id.hexdigest
46
+ end
47
+
48
+
49
+ ## return hexstrings of sig(natures) - why? why not?
50
+ ## rename to sighashes - why? why not?
51
+ def selectors() @selectors.keys; end
52
+
53
+ def support?( sig )
54
+ Utils.support?( @selectors.keys, sig )
55
+ end
56
+ alias_method :supports?, :support? ## add alternate spelling - why? why not?
57
+
58
+ end ## class Interface
59
+ end # module ABI
@@ -1,19 +1,35 @@
1
1
  module ABI
2
2
  class Param
3
-
4
- attr_reader :type, :name
3
+ attr_reader :type, :name, :internal_type
5
4
 
6
5
  def self.parse( o )
7
- type = o['type']
8
- name = o['name']
9
- new( type, name )
6
+ type = o['type']
7
+ internal_type = o['internalType']
8
+ name = o['name']
9
+
10
+ new( type, name,
11
+ internal_type: internal_type )
10
12
  end
11
13
 
12
- def initialize( type, name ) ## note: type goes first!!!
13
- @type = type
14
- @name = name
14
+ ### check - find a "better" name for internal_type
15
+ ## use a keyword param - why? why not?
16
+ def initialize( type, name=nil,
17
+ internal_type: nil ) ## note: type goes first!!!
18
+ @type = type
19
+ ## note: convert empty string "" to nil - why? why not?
20
+ @name = if name && name.empty?
21
+ nil
22
+ else
23
+ name
24
+ end
25
+ @internal_type = if internal_type && internal_type.empty?
26
+ nil
27
+ else
28
+ internal_type
29
+ end
15
30
  end
16
31
 
32
+
17
33
  def sig
18
34
  buf = "#{@type}"
19
35
  buf
@@ -21,16 +37,26 @@ class Param
21
37
 
22
38
  def doc
23
39
  buf = ''
24
- buf << "#{@type} "
25
- buf << (@name.empty? ? '_' : @name)
40
+ if @internal_type && @internal_type != @type
41
+ buf << "#{@internal_type} "
42
+ else
43
+ buf << "#{@type} "
44
+ end
45
+ buf << (@name ? @name : '_')
26
46
  buf
27
47
  end
28
48
 
29
49
  def decl
30
50
  buf = ''
31
51
  buf << "#{@type} "
32
- buf << (@name.empty? ? '_' : @name)
52
+ buf << (@name ? @name : '_')
53
+ ## use inline comment - why? why not?
54
+ if @internal_type && @internal_type != @type
55
+ buf << " /* #{@internal_type} */"
56
+ end
33
57
  buf
34
58
  end
59
+
60
+
35
61
  end ## class Param
36
62
  end ## module ABI
@@ -0,0 +1,40 @@
1
+ module ABI
2
+ module Helpers
3
+
4
+
5
+ SIGHASH_RX = /\A
6
+ (0x)?
7
+ (?<sighash>[0-9a-f]{8})
8
+ \z/ix
9
+
10
+ def support?( selectors, sig )
11
+ if sig.is_a?( Interface )
12
+ iface = sig
13
+ iface.selectors.each do |sighash|
14
+ unless selectors.include?( sighash )
15
+ puts " sighash >#{sighash}< not found in interface"
16
+ return false
17
+ end
18
+ end
19
+ true
20
+ else
21
+ sighash = if m=SIGHASH_RX.match( sig )
22
+ m[:sighash].downcase ## assume it's sighash (hexstring)
23
+ else
24
+ ## for convenience allow (white)spaces; auto-strip - why? why not?
25
+ sig = sig.gsub( /[ \r\t\n]/, '' )
26
+ keccak256( sig )[0,4].hexdigest
27
+ end
28
+
29
+ selectors.include?( sighash ) ? true : false
30
+ end
31
+ end
32
+ end # module Helpers
33
+
34
+
35
+ module Utils
36
+ extend Helpers
37
+ ## e.g. Utils.supports?( selectors, sig ) etc.
38
+ end
39
+
40
+ end # module ABI
@@ -1,9 +1,8 @@
1
1
 
2
2
  module ABIParser
3
-
4
3
  MAJOR = 0
5
- MINOR = 0
6
- PATCH = 1
4
+ MINOR = 1
5
+ PATCH = 0
7
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
8
7
 
9
8
  def self.version
data/lib/abiparser.rb CHANGED
@@ -6,9 +6,27 @@ require 'digest-lite'
6
6
  ## extend String
7
7
  class String
8
8
  alias_method :hexdigest, :bin_to_hex ## note: bin_to_hex added via Bytes!!!
9
+
10
+ # given two numeric strings,
11
+ # returns the bitwise xor string
12
+ def xor(other)
13
+ a = self.bytes
14
+ b = other.bytes
15
+ ## todo/check: cut-off on lower count (lc) - why? why not?
16
+ lc = (a.size < b.size) ? a.size : b.size
17
+ c = []
18
+ lc.times do |i|
19
+ c << (a[i] ^ b[i])
20
+ end
21
+ c = c.pack( 'C*' )
22
+ puts "#{self.bin_to_hex} ^ #{other.bin_to_hex} = #{c.bin_to_hex}<"
23
+ c
24
+ end
25
+ alias_method :^, :xor
9
26
  end # class String
10
27
 
11
28
 
29
+
12
30
  def keccak256( bin )
13
31
  Digest::KeccakLite.new( 256 ).digest( bin )
14
32
  end
@@ -24,12 +42,78 @@ require_relative 'abiparser/version' # note: let version always go first
24
42
  require_relative 'abiparser/param'
25
43
  require_relative 'abiparser/constructor'
26
44
  require_relative 'abiparser/function'
45
+ require_relative 'abiparser/utils'
27
46
  require_relative 'abiparser/contract'
47
+ require_relative 'abiparser/interface'
28
48
 
29
49
  require_relative 'abiparser/export/interface.rb'
30
50
 
31
51
 
32
52
 
53
+
54
+ ## note: make "global" constants - why? why not?
55
+
56
+ ## IERC165 0x01ffc9a7
57
+ IERC165 = ABI::Interface.new(
58
+ 'supportsInterface(bytes4)',
59
+ )
60
+
61
+ ## IERC20 0x36372b07
62
+ IERC20 = ABI::Interface.new(
63
+ 'totalSupply()',
64
+ 'balanceOf(address)',
65
+ 'allowance(address,address)',
66
+ 'transfer(address,uint256)',
67
+ 'approve(address,uint256)',
68
+ 'transferFrom(address,address,uint256)'
69
+ )
70
+
71
+ ## IERC20_NAME 0x06fdde03
72
+ IERC20_NAME = ABI::Interface.new(
73
+ 'name()'
74
+ )
75
+
76
+ ## IERC20_SYMBOL 0x95d89b41
77
+ IERC20_SYMBOL = ABI::Interface.new(
78
+ 'symbol()'
79
+ )
80
+
81
+ ## IERC20_DECIMALS 0x313ce567
82
+ IERC20_DECIMALS = ABI::Interface.new(
83
+ 'decimals()'
84
+ )
85
+
86
+ ## IERC721 0x80ac58cd
87
+ IERC721 = ABI::Interface.new(
88
+ 'balanceOf(address)',
89
+ 'ownerOf(uint256)',
90
+ 'approve(address,uint256)',
91
+ 'getApproved(uint256)',
92
+ 'setApprovalForAll(address,bool)',
93
+ 'isApprovedForAll(address,address)',
94
+ 'transferFrom(address,address,uint256)',
95
+ 'safeTransferFrom(address,address,uint256)',
96
+ 'safeTransferFrom(address,address,uint256,bytes)'
97
+ )
98
+
99
+ ## IERC721_METADATA 0x5b5e139f
100
+ IERC721_METADATA = ABI::Interface.new(
101
+ 'name()',
102
+ 'symbol()',
103
+ 'tokenURI(uint256)'
104
+ )
105
+
106
+ ## IERC721_ENUMERABLE 0x780e9d63
107
+ IERC721_ENUMERABLE = ABI::Interface.new(
108
+ 'tokenOfOwnerByIndex(address,uint256)',
109
+ 'totalSupply()',
110
+ 'tokenByIndex(uint256)'
111
+ )
112
+
113
+
114
+
115
+
116
+
33
117
  module ABI
34
118
  def self.read( path ) Contract.read( path ); end
35
119
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abiparser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-26 00:00:00.000000000 Z
11
+ date: 2022-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cocos
@@ -105,7 +105,9 @@ files:
105
105
  - lib/abiparser/contract.rb
106
106
  - lib/abiparser/export/interface.rb
107
107
  - lib/abiparser/function.rb
108
+ - lib/abiparser/interface.rb
108
109
  - lib/abiparser/param.rb
110
+ - lib/abiparser/utils.rb
109
111
  - lib/abiparser/version.rb
110
112
  homepage: https://github.com/rubycocos/blockchain
111
113
  licenses: