abiparser 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: