Skip to content

Commit b1a33dc

Browse files
committed
up
1 parent 6256150 commit b1a33dc

15 files changed

+554
-169
lines changed

.gitignore

+9
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
# do NOT check in dev/test enviroment variables in .env (dot env files)
22
.env
3+
4+
5+
##
6+
# ignore tmp directories
7+
8+
tmp/
9+
tmp2/
10+
11+

abiparser/Manifest.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ README.md
44
Rakefile
55
lib/abiparser.rb
66
lib/abiparser/constructor.rb
7+
lib/abiparser/contract.rb
78
lib/abiparser/export/interface.rb
89
lib/abiparser/function.rb
9-
lib/abiparser/interface.rb
1010
lib/abiparser/param.rb
1111
lib/abiparser/version.rb

abiparser/README.md

+127
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,133 @@ abiparser - application binary interface (abi) parser machinery / helper for Eth
1313
## Usage
1414

1515

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 IERC721Metadata.interface_id #=> "0x5b5e139f"
94+
pp IERC721Enumerable.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+
IERC721Metadata = ABI::Interface.new(
126+
'name()',
127+
'symbol()',
128+
'tokenURI(uint256)' )
129+
130+
IERC721Enumerable = ABI::Interface.new(
131+
'tokenOfOwnerByIndex(address,uint256)',
132+
'totalSupply()',
133+
'tokenByIndex(uint256)' )
134+
135+
...
136+
```
137+
138+
139+
To be continued...
140+
141+
142+
16143

17144

18145
## License

abiparser/lib/abiparser.rb

+29-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class String
99

1010
# given two numeric strings,
1111
# returns the bitwise xor string
12-
def ^(other)
12+
def xor(other)
1313
a = self.bytes
1414
b = other.bytes
1515
## todo/check: cut-off on lower count (lc) - why? why not?
@@ -22,9 +22,11 @@ def ^(other)
2222
puts "#{self.bin_to_hex} ^ #{other.bin_to_hex} = #{c.bin_to_hex}<"
2323
c
2424
end
25+
alias_method :^, :xor
2526
end # class String
2627

2728

29+
2830
def keccak256( bin )
2931
Digest::KeccakLite.new( 256 ).digest( bin )
3032
end
@@ -40,7 +42,7 @@ def sig( bin )
4042
require_relative 'abiparser/param'
4143
require_relative 'abiparser/constructor'
4244
require_relative 'abiparser/function'
43-
require_relative 'abiparser/interface'
45+
require_relative 'abiparser/contract'
4446

4547
require_relative 'abiparser/export/interface.rb'
4648

@@ -50,10 +52,9 @@ def sig( bin )
5052
module ABI
5153

5254

53-
5455
## rename to QueryInterface or SupportInterface
55-
## or InterfaceType or such - why? why not?
56-
class InterfaceId
56+
## or InterfaceType or InterfaceId or such - why? why not?
57+
class Interface
5758

5859
attr_reader :interface_id
5960

@@ -113,7 +114,7 @@ def support?( sig )
113114
end
114115
alias_method :supports?, :support? ## add alternate spelling - why? why not?
115116

116-
end ## class InterfaceId
117+
end ## class Interface
117118

118119

119120

@@ -125,7 +126,7 @@ def support?( sig )
125126
## note: make "global" constants - why? why not?
126127

127128
## IERC20 0x36372b07
128-
IERC20 = ABI::InterfaceId.new(
129+
IERC20 = ABI::Interface.new(
129130
'totalSupply()',
130131
'balanceOf(address)',
131132
'allowance(address,address)',
@@ -135,7 +136,7 @@ def support?( sig )
135136

136137

137138
## IERC721 0x80ac58cd
138-
IERC721 = ABI::InterfaceId.new(
139+
IERC721 = ABI::Interface.new(
139140
'balanceOf(address)',
140141
'ownerOf(uint256)',
141142
'approve(address,uint256)',
@@ -146,15 +147,33 @@ def support?( sig )
146147
'safeTransferFrom(address,address,uint256)',
147148
'safeTransferFrom(address,address,uint256,bytes)' )
148149

150+
## IERC165 0x01ffc9a7
151+
IERC165 = ABI::Interface.new(
152+
'supportsInterface(bytes4)',
153+
)
154+
155+
## IERC721_METADATA 0x5b5e139f
156+
IERC721Metadata = ABI::Interface.new(
157+
'name()',
158+
'symbol()',
159+
'tokenURI(uint256)',
160+
)
161+
162+
## ERC721_ENUMERABLE 0x780e9d63
163+
IERC721Enumerable = ABI::Interface.new(
164+
'tokenOfOwnerByIndex(address,uint256)',
165+
'totalSupply()',
166+
'tokenByIndex(uint256)',
167+
)
149168

150169

151170

152171

153172

154173
module ABI
155-
def self.read( path ) Interface.read( path ); end
174+
def self.read( path ) Contract.read( path ); end
156175

157-
def self.parse( data ) Interface.parse( data ); end
176+
def self.parse( data ) Contract.parse( data ); end
158177
end # module ABI
159178

160179

abiparser/lib/abiparser/interface.rb renamed to abiparser/lib/abiparser/contract.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
module ABI
2-
class Interface
2+
class Contract
33

44
def self.read( path )
55
data = read_json( path )
@@ -68,7 +68,7 @@ def initialize( constructor: nil,
6868

6969
## auto-add selectors (hashed signatures)
7070
@funcs.each do |func|
71-
sighash = keccak256( func.sig )[0,4].hexdigest
71+
sighash = func.sighash
7272
puts "0x#{sighash} => #{func.sig}"
7373

7474
## assert - no duplicates allowed
@@ -81,6 +81,7 @@ def initialize( constructor: nil,
8181
end
8282
end
8383

84+
8485
## return hexstrings of sig(natures) - why? why not?
8586
## rename to sighashes - why? why not?
8687
def selectors() @selectors.keys; end
@@ -132,5 +133,5 @@ def helper_functions ## add pure ?? funcs alias - why? why not?
132133
@funcs.select { |func| func.pure? }
133134
end
134135

135-
end # class Interface
136+
end # class Contract
136137
end # module ABI

abiparser/lib/abiparser/export/interface.rb

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,19 @@
11
module ABI
2-
class Interface
2+
class Contract
33

44

55
def generate_interface( name: ) ## interface declarations
66
buf = ''
77
buf << "interface #{name} {"
88

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
1417

1518
if payable_functions.size > 0
1619
buf << "\n"
@@ -49,5 +52,5 @@ def generate_interface( name: ) ## interface declarations
4952
end
5053

5154

52-
end ## class Interface
55+
end ## class Contract
5356
end ## module ABI

abiparser/lib/abiparser/function.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -94,9 +94,14 @@ def sig
9494
buf
9595
end
9696

97+
def sighash
98+
keccak256( sig )[0,4].hexdigest
99+
end
100+
97101

98102
def doc
99-
buf = "function #{@name}"
103+
## note: text with markdown formatting
104+
buf = "function **#{@name}**"
100105
if @inputs.empty?
101106
buf << "()"
102107
else

0 commit comments

Comments
 (0)