abicoder 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Manifest.txt +4 -4
- data/README.md +88 -19
- data/Rakefile +1 -1
- data/lib/abicoder/decoder.rb +207 -0
- data/lib/abicoder/encoder.rb +278 -0
- data/lib/abicoder/parser.rb +158 -0
- data/lib/abicoder/types.rb +155 -0
- data/lib/abicoder/version.rb +2 -2
- data/lib/abicoder.rb +37 -15
- metadata +10 -10
- data/lib/abicoder/codec.rb +0 -413
- data/lib/abicoder/type.rb +0 -141
- data/lib/abicoder/type_tuple.rb +0 -106
- data/lib/abicoder/utils.rb +0 -81
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f6aed5c766df24d4cf394f10fdaec00a7a42375cf85c5eab0a07d8f7f22ad3ec
|
4
|
+
data.tar.gz: 80c9a39735f1e383f168fdca5980d66acbf039a2bfbffe15866f0fb89a7090cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9142a552978835f45a2be5bd80190905b9feeb519f135c8ae72f428826a2e7ccec467a1dbbfbfef8ddd2b097b1397f008429379230abc8ac6bcaf4aa53f7ec99
|
7
|
+
data.tar.gz: 37a0e2773d33a8c74881ccd8ab137e362dacde34c6dd076be0eed6ffe5b22f302ac8cdd2220e94ed124b5036a836b21b7977c0629daf9e749c45b2465e4aab47
|
data/Manifest.txt
CHANGED
@@ -3,8 +3,8 @@ Manifest.txt
|
|
3
3
|
README.md
|
4
4
|
Rakefile
|
5
5
|
lib/abicoder.rb
|
6
|
-
lib/abicoder/
|
7
|
-
lib/abicoder/
|
8
|
-
lib/abicoder/
|
9
|
-
lib/abicoder/
|
6
|
+
lib/abicoder/decoder.rb
|
7
|
+
lib/abicoder/encoder.rb
|
8
|
+
lib/abicoder/parser.rb
|
9
|
+
lib/abicoder/types.rb
|
10
10
|
lib/abicoder/version.rb
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Application Binary Inteface (ABI) Coder For Ethereum & Co.
|
2
2
|
|
3
|
-
abicoder - "lite" application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies for easy (re)use
|
3
|
+
abicoder - "lite" application binary interface (abi) encoding / decoding machinery / helper (incl. nested arrays and/or tuples) for Ethereum & Co. (blockchain) contracts with zero-dependencies for easy (re)use
|
4
4
|
|
5
5
|
|
6
6
|
* home :: [github.com/rubycocos/blockchain](https://github.com/rubycocos/blockchain)
|
@@ -13,6 +13,9 @@ abicoder - "lite" application binary interface (abi) encoding / decoding machine
|
|
13
13
|
## Usage
|
14
14
|
|
15
15
|
|
16
|
+
### Encode & Decode Types (Contract Function Call Data)
|
17
|
+
|
18
|
+
|
16
19
|
``` ruby
|
17
20
|
require 'abicode'
|
18
21
|
|
@@ -20,38 +23,53 @@ require 'abicode'
|
|
20
23
|
# try ABI.encode
|
21
24
|
|
22
25
|
## Encoding simple types
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
#
|
28
|
-
#
|
26
|
+
types = [ 'uint256', 'string' ]
|
27
|
+
args = [ 1234, 'Hello World' ]
|
28
|
+
ABI.encode( types, args ) # returns binary blob / string
|
29
|
+
#=> hex"00000000000000000000000000000000000000000000000000000000000004d2"+
|
30
|
+
# "0000000000000000000000000000000000000000000000000000000000000040"+
|
31
|
+
# "000000000000000000000000000000000000000000000000000000000000000b"+
|
32
|
+
# "48656c6c6f20576f726c64000000000000000000000000000000000000000000"
|
29
33
|
|
30
34
|
## Encoding with arrays types
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
35
|
+
types = [ 'uint256[]', 'string' ]
|
36
|
+
args = [ [1234, 5678] , 'Hello World' ]
|
37
|
+
ABI.encode( types, args ) # returns binary blob / string
|
38
|
+
#=> hex"0000000000000000000000000000000000000000000000000000000000000040"+
|
39
|
+
# "00000000000000000000000000000000000000000000000000000000000000a0"+
|
40
|
+
# "0000000000000000000000000000000000000000000000000000000000000002"+
|
41
|
+
# "00000000000000000000000000000000000000000000000000000000000004d2"+
|
42
|
+
# "000000000000000000000000000000000000000000000000000000000000162e"+
|
43
|
+
# "000000000000000000000000000000000000000000000000000000000000000b"+
|
44
|
+
# "48656c6c6f20576f726c64000000000000000000000000000000000000000000"
|
45
|
+
|
46
|
+
## Encoding complex structs (also known as tuples)
|
47
|
+
types = [ 'uint256', '(uint256,string)']
|
48
|
+
args = [1234, [5678, 'Hello World']]
|
49
|
+
ABI.encode( types, args ) # returns binary blob / string
|
50
|
+
#=> hex'00000000000000000000000000000000000000000000000000000000000004d2'+
|
51
|
+
# '0000000000000000000000000000000000000000000000000000000000000040'+
|
52
|
+
# '000000000000000000000000000000000000000000000000000000000000162e'+
|
53
|
+
# '0000000000000000000000000000000000000000000000000000000000000040'+
|
54
|
+
# '000000000000000000000000000000000000000000000000000000000000000b'+
|
55
|
+
# '48656c6c6f20576f726c64000000000000000000000000000000000000000000'
|
40
56
|
|
41
57
|
|
42
58
|
#####
|
43
59
|
# try ABI.decode
|
44
60
|
|
45
61
|
## Decoding simple types
|
62
|
+
types = [ 'uint256', 'string' ]
|
46
63
|
data = hex'00000000000000000000000000000000000000000000000000000000000004d2'+
|
47
64
|
'0000000000000000000000000000000000000000000000000000000000000040'+
|
48
65
|
'000000000000000000000000000000000000000000000000000000000000000b'+
|
49
66
|
'48656c6c6f20576f726c64000000000000000000000000000000000000000000'
|
50
|
-
ABI.decode(
|
67
|
+
ABI.decode( types, data) # returns args
|
51
68
|
#=> [1234, "Hello World"]
|
52
69
|
|
53
70
|
|
54
71
|
## Decoding with arrays types
|
72
|
+
types = [ 'uint256[]', 'string' ]
|
55
73
|
data = hex'0000000000000000000000000000000000000000000000000000000000000040'+
|
56
74
|
'00000000000000000000000000000000000000000000000000000000000000a0'+
|
57
75
|
'0000000000000000000000000000000000000000000000000000000000000002'+
|
@@ -59,21 +77,72 @@ data = hex'0000000000000000000000000000000000000000000000000000000000000040'+
|
|
59
77
|
'000000000000000000000000000000000000000000000000000000000000162e'+
|
60
78
|
'000000000000000000000000000000000000000000000000000000000000000b'+
|
61
79
|
'48656c6c6f20576f726c64000000000000000000000000000000000000000000'
|
62
|
-
ABI.decode(
|
80
|
+
ABI.decode( types, data ) # returns args
|
63
81
|
#=> [[1234, 5678], "Hello World"]
|
64
82
|
|
65
83
|
|
66
84
|
## Decoding complex structs (also known as tuples)
|
85
|
+
types = [ 'uint256', '(uint256,string)']
|
67
86
|
data = hex'00000000000000000000000000000000000000000000000000000000000004d2'+
|
68
87
|
'0000000000000000000000000000000000000000000000000000000000000040'+
|
69
88
|
'000000000000000000000000000000000000000000000000000000000000162e'+
|
70
89
|
'0000000000000000000000000000000000000000000000000000000000000040'+
|
71
90
|
'000000000000000000000000000000000000000000000000000000000000000b'+
|
72
91
|
'48656c6c6f20576f726c64000000000000000000000000000000000000000000'
|
73
|
-
ABI.decode(
|
92
|
+
ABI.decode( types, data ) # returns args
|
74
93
|
#=> [1234, [5678, "Hello World"]]
|
75
94
|
```
|
76
95
|
|
96
|
+
and so on.
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
## Reference
|
103
|
+
|
104
|
+
For the full (and latest) official application
|
105
|
+
binary inteface (abi) specification
|
106
|
+
see the [**Contract ABI Specification**](https://docs.soliditylang.org/en/develop/abi-spec.html).
|
107
|
+
|
108
|
+
|
109
|
+
### Types
|
110
|
+
|
111
|
+
The following elementary types are supported:
|
112
|
+
|
113
|
+
- `uint<M>`: unsigned integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`. e.g. `uint32`, `uint8`, `uint256`.
|
114
|
+
- `int<M>`: two's complement signed integer type of `M` bits, `0 < M <= 256`, `M % 8 == 0`.
|
115
|
+
- `address`: equivalent to `uint160`, except for the assumed interpretation and language typing.
|
116
|
+
For computing the function selector, `address` is used.
|
117
|
+
- `bool`: equivalent to `uint8` restricted to the values 0 and 1. For computing the function selector, `bool` is used.
|
118
|
+
- `bytes<M>`: binary type of `M` bytes, `0 < M <= 32`.
|
119
|
+
|
120
|
+
|
121
|
+
The following (fixed-size) array types are supported:
|
122
|
+
|
123
|
+
- `<type>[M]`: a fixed-length array of `M` elements, `M >= 0`, of the given type.
|
124
|
+
|
125
|
+
<!--
|
126
|
+
Note: While this ABI specification can express fixed-length arrays with zero elements,
|
127
|
+
they're not supported by the compiler.
|
128
|
+
-->
|
129
|
+
|
130
|
+
The following non-fixed-size types are supported:
|
131
|
+
|
132
|
+
- `bytes`: dynamic sized byte sequence.
|
133
|
+
- `string`: dynamic sized unicode string assumed to be UTF-8 encoded.
|
134
|
+
- `<type>[]`: a variable-length array of elements of the given type.
|
135
|
+
|
136
|
+
Types can be combined to a tuple by enclosing them inside parentheses, separated by commas:
|
137
|
+
|
138
|
+
- `(T1,T2,...,Tn)`: tuple consisting of the types `T1`, ..., `Tn`, `n >= 0`
|
139
|
+
|
140
|
+
It is possible to form tuples of tuples, arrays of tuples and so on.
|
141
|
+
|
142
|
+
<!--
|
143
|
+
It is also possible to form zero-tuples (where `n == 0`).
|
144
|
+
-->
|
145
|
+
|
77
146
|
|
78
147
|
|
79
148
|
|
data/Rakefile
CHANGED
@@ -13,7 +13,7 @@ Hoe.spec 'abicoder' do
|
|
13
13
|
|
14
14
|
self.version = ABICoder::VERSION
|
15
15
|
|
16
|
-
self.summary = "abicoder - 'lite' application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies for easy (re)use"
|
16
|
+
self.summary = "abicoder - 'lite' application binary interface (abi) encoding / decoding machinery / helper (incl. nested arrays and/or tuples) for Ethereum & Co. (blockchain) contracts with zero-dependencies for easy (re)use"
|
17
17
|
self.description = summary
|
18
18
|
|
19
19
|
self.urls = { home: 'https://github.com/rubycocos/blockchain' }
|
@@ -0,0 +1,207 @@
|
|
1
|
+
module ABI
|
2
|
+
|
3
|
+
|
4
|
+
class Decoder
|
5
|
+
|
6
|
+
##
|
7
|
+
# Decodes multiple arguments using the head/tail mechanism.
|
8
|
+
#
|
9
|
+
def decode( types, data, raise_errors = false )
|
10
|
+
##
|
11
|
+
## todo/check: always change data (string) to binary encoding (e.g. data = data.b )
|
12
|
+
## or such - why? why not?
|
13
|
+
|
14
|
+
## for convenience check if types is a String
|
15
|
+
## otherwise assume ABI::Type already
|
16
|
+
types = types.map { |type| type.is_a?( Type ) ? type : Type.parse( type ) }
|
17
|
+
|
18
|
+
outputs = [nil] * types.size
|
19
|
+
start_positions = [nil] * types.size + [data.size]
|
20
|
+
|
21
|
+
# TODO: refactor, a reverse iteration will be better - why? why not?
|
22
|
+
# try to simplify / clean-up code - possible? why? why not?
|
23
|
+
|
24
|
+
pos = 0
|
25
|
+
types.each_with_index do |t, i|
|
26
|
+
# If a type is static, grab the data directly, otherwise record its
|
27
|
+
# start position
|
28
|
+
if t.dynamic?
|
29
|
+
|
30
|
+
if pos>data.size-1
|
31
|
+
if raise_errors
|
32
|
+
raise DecodingError, "Position out of bounds #{pos}>#{data.size-1}"
|
33
|
+
else
|
34
|
+
puts "!! WARN - DecodingError: Position out of bounds #{pos}>#{data.size-1}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
start_positions[i] = decode_uint256(data[pos, 32])
|
39
|
+
|
40
|
+
if start_positions[i]>data.size-1
|
41
|
+
if raise_errors
|
42
|
+
raise DecodingError, "Start position out of bounds #{start_positions[i]}>#{data.size-1}"
|
43
|
+
else
|
44
|
+
puts "!! WARN - DecodingError: Start position out of bounds #{start_positions[i]}>#{data.size-1}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
j = i - 1
|
50
|
+
while j >= 0 && start_positions[j].nil?
|
51
|
+
start_positions[j] = start_positions[i]
|
52
|
+
j -= 1
|
53
|
+
end
|
54
|
+
|
55
|
+
pos += 32
|
56
|
+
else
|
57
|
+
## puts "step 1 - decode item [#{i}] - #{t.format} size: #{t.size} dynamic? #{t.dynamic?}"
|
58
|
+
|
59
|
+
count = t.size
|
60
|
+
## was zero_padding( data, pos, t.size, start_positions )
|
61
|
+
## inline for now and clean-up later - why? why not?
|
62
|
+
outputs[i] = if pos >= data.size
|
63
|
+
start_positions[start_positions.size-1] += count
|
64
|
+
BYTE_ZERO*count
|
65
|
+
elsif pos + count > data.size
|
66
|
+
start_positions[start_positions.size-1] += ( count - (data.size-pos))
|
67
|
+
data[pos,data.size-pos] + BYTE_ZERO*( count - (data.size-pos))
|
68
|
+
else
|
69
|
+
data[pos, count]
|
70
|
+
end
|
71
|
+
|
72
|
+
pos += t.size
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
|
78
|
+
# We add a start position equal to the length of the entire data for
|
79
|
+
# convenience.
|
80
|
+
j = types.size - 1
|
81
|
+
while j >= 0 && start_positions[j].nil?
|
82
|
+
start_positions[j] = start_positions[types.size]
|
83
|
+
j -= 1
|
84
|
+
end
|
85
|
+
|
86
|
+
if pos > data.size
|
87
|
+
if raise_errors
|
88
|
+
raise DecodingError, "Not enough data for head"
|
89
|
+
else
|
90
|
+
puts "!! WARN - DecodingError: Not enough data for head"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
types.each_with_index do |t, i|
|
96
|
+
if t.dynamic?
|
97
|
+
offset, next_offset = start_positions[i, 2]
|
98
|
+
if offset<=data.size && next_offset<=data.size
|
99
|
+
outputs[i] = data[offset...next_offset]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
if outputs.include?( nil )
|
105
|
+
if raise_errors
|
106
|
+
raise DecodingError, "Not all data can be parsed"
|
107
|
+
else
|
108
|
+
puts "!! WARN: DecodingError - Not all data can be parsed"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
types.zip(outputs).map do |(t, out)|
|
114
|
+
## puts "step 2 - decode item - #{t.format} got: #{out.size} byte(s) - size: #{t.size} dynamic? #{t.dynamic?}"
|
115
|
+
|
116
|
+
decode_type(t, out)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
def decode_type( type, data )
|
124
|
+
return nil if data.nil? || data.empty?
|
125
|
+
|
126
|
+
if type.is_a?( Tuple ) ## todo: support empty (unit) tuple - why? why not?
|
127
|
+
decode( type.types, data )
|
128
|
+
elsif type.is_a?( FixedArray ) # static-sized arrays
|
129
|
+
l = type.dim
|
130
|
+
subtype = type.subtype
|
131
|
+
if subtype.dynamic?
|
132
|
+
start_positions = (0...l).map {|i| decode_uint256(data[32*i, 32]) }
|
133
|
+
start_positions.push( data.size )
|
134
|
+
|
135
|
+
outputs = (0...l).map {|i| data[start_positions[i]...start_positions[i+1]] }
|
136
|
+
|
137
|
+
outputs.map {|out| decode_type(subtype, out) }
|
138
|
+
else
|
139
|
+
(0...l).map {|i| decode_type(subtype, data[subtype.size*i, subtype.size]) }
|
140
|
+
end
|
141
|
+
elsif type.is_a?( Array )
|
142
|
+
l = decode_uint256( data[0,32] )
|
143
|
+
raise DecodingError, "Too long length: #{l}" if l > 100000
|
144
|
+
subtype = type.subtype
|
145
|
+
|
146
|
+
if subtype.dynamic?
|
147
|
+
raise DecodingError, "Not enough data for head" unless data.size >= 32 + 32*l
|
148
|
+
|
149
|
+
start_positions = (1..l).map {|i| 32+decode_uint256(data[32*i, 32]) }
|
150
|
+
start_positions.push( data.size )
|
151
|
+
|
152
|
+
outputs = (0...l).map {|i| data[start_positions[i]...start_positions[i+1]] }
|
153
|
+
|
154
|
+
outputs.map {|out| decode_type(subtype, out) }
|
155
|
+
else
|
156
|
+
(0...l).map {|i| decode_type(subtype, data[32 + subtype.size*i, subtype.size]) }
|
157
|
+
end
|
158
|
+
else
|
159
|
+
decode_primitive_type( type, data )
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def decode_primitive_type(type, data)
|
165
|
+
case type
|
166
|
+
when Uint
|
167
|
+
decode_uint256( data )
|
168
|
+
when Int
|
169
|
+
u = decode_uint256( data )
|
170
|
+
u >= 2**(type.bits-1) ? (u - 2**type.bits) : u
|
171
|
+
when Bool
|
172
|
+
data[-1] == BYTE_ONE
|
173
|
+
when String
|
174
|
+
## note: convert to a string (with UTF_8 encoding NOT BINARY!!!)
|
175
|
+
size = decode_uint256( data[0,32] )
|
176
|
+
data[32..-1][0,size].force_encoding( Encoding::UTF_8 )
|
177
|
+
when Bytes
|
178
|
+
size = decode_uint256( data[0,32] )
|
179
|
+
data[32..-1][0,size]
|
180
|
+
when FixedBytes
|
181
|
+
data[0, type.length]
|
182
|
+
when Address
|
183
|
+
## note: convert to a hex string (with UTF_8 encoding NOT BINARY!!!)
|
184
|
+
data[12..-1].unpack("H*").first.force_encoding( Encoding::UTF_8 )
|
185
|
+
else
|
186
|
+
raise DecodingError, "Unknown primitive type: #{type.class.name} #{type.format}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
|
192
|
+
###########
|
193
|
+
# decoding helpers / utils
|
194
|
+
|
195
|
+
def decode_uint256( bin )
|
196
|
+
# bin = bin.sub( /\A(\x00)+/, '' ) ## keep "performance" shortcut - why? why not?
|
197
|
+
### todo/check - allow nil - why? why not?
|
198
|
+
## raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
|
199
|
+
# bin = bin || BYTE_ZERO
|
200
|
+
bin.unpack("H*").first.to_i(16)
|
201
|
+
end
|
202
|
+
|
203
|
+
|
204
|
+
|
205
|
+
end # class Decoder
|
206
|
+
|
207
|
+
end ## module ABI
|
@@ -0,0 +1,278 @@
|
|
1
|
+
|
2
|
+
module ABI
|
3
|
+
##
|
4
|
+
# Contract ABI encoding and decoding.
|
5
|
+
#
|
6
|
+
# @see https://github.com/ethereum/wiki/wiki/Ethereum-Contract-ABI
|
7
|
+
#
|
8
|
+
|
9
|
+
class EncodingError < StandardError; end
|
10
|
+
class DecodingError < StandardError; end
|
11
|
+
class ValueError < StandardError; end
|
12
|
+
|
13
|
+
class ValueOutOfBounds < ValueError; end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
class Encoder
|
18
|
+
|
19
|
+
##
|
20
|
+
# Encodes multiple arguments using the head/tail mechanism.
|
21
|
+
# returns binary string (with BINARY / ASCII_8BIT encoding)
|
22
|
+
#
|
23
|
+
def encode( types, args )
|
24
|
+
## enforce args.size and types.size must match - why? why not?
|
25
|
+
raise ArgumentError, "Wrong number of args: found #{args.size}, expecting #{types.size}" unless args.size == types.size
|
26
|
+
|
27
|
+
|
28
|
+
## for convenience check if types is a String
|
29
|
+
## otherwise assume ABI::Type already
|
30
|
+
types = types.map { |type| type.is_a?( Type ) ? type : Type.parse( type ) }
|
31
|
+
|
32
|
+
## todo/check: use args.map (instead of types)
|
33
|
+
## might allow encoding less args than types? - why? why not?
|
34
|
+
head_size = types
|
35
|
+
.map {|type| type.size || 32 }
|
36
|
+
.sum
|
37
|
+
|
38
|
+
head, tail = ''.b, ''.b ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
|
39
|
+
types.each_with_index do |type, i|
|
40
|
+
if type.dynamic?
|
41
|
+
head += encode_uint256( head_size + tail.size )
|
42
|
+
tail += encode_type( type, args[i] )
|
43
|
+
else
|
44
|
+
head += encode_type( type, args[i] )
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
head + tail
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Encodes a single value (static or dynamic).
|
53
|
+
#
|
54
|
+
# @param type [ABI::Type] value type
|
55
|
+
# @param arg [Object] value
|
56
|
+
#
|
57
|
+
# @return [String] encoded bytes
|
58
|
+
#
|
59
|
+
def encode_type( type, arg )
|
60
|
+
if type.is_a?( String )
|
61
|
+
encode_string( arg )
|
62
|
+
elsif type.is_a?( Bytes )
|
63
|
+
encode_bytes( arg )
|
64
|
+
elsif type.is_a?( Tuple )
|
65
|
+
encode_tuple( type, arg )
|
66
|
+
elsif type.is_a?( Array ) || type.is_a?( FixedArray )
|
67
|
+
if type.dynamic?
|
68
|
+
encode_dynamic_array( type, arg )
|
69
|
+
else
|
70
|
+
encode_static_array( type, arg )
|
71
|
+
end
|
72
|
+
else # assume static (primitive) type
|
73
|
+
encode_primitive_type( type, arg )
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def encode_dynamic_array( type, args )
|
79
|
+
raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
|
80
|
+
|
81
|
+
head, tail = ''.b, ''.b ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
|
82
|
+
|
83
|
+
if type.is_a?( Array ) ## dynamic array
|
84
|
+
head += encode_uint256( args.size )
|
85
|
+
else ## fixed array
|
86
|
+
raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim
|
87
|
+
end
|
88
|
+
|
89
|
+
subtype = type.subtype
|
90
|
+
args.each do |arg|
|
91
|
+
if subtype.dynamic?
|
92
|
+
head += encode_uint256( 32*args.size + tail.size )
|
93
|
+
tail += encode_type( subtype, arg )
|
94
|
+
else
|
95
|
+
head += encode_type( subtype, arg )
|
96
|
+
end
|
97
|
+
end
|
98
|
+
head + tail
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
def encode_static_array( type, args )
|
103
|
+
raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
|
104
|
+
raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim
|
105
|
+
|
106
|
+
args.map {|arg| encode_type( type.subtype, arg ) }.join
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
##
|
111
|
+
## todo/check: if static tuple gets encoded different
|
112
|
+
## without offset (head/tail)
|
113
|
+
|
114
|
+
def encode_tuple( tuple, args )
|
115
|
+
raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
|
116
|
+
raise ArgumentError, "Wrong array size (for tuple): found #{args.size}, expecting #{tuple.type.size} tuple elements" unless args.size == tuple.types.size
|
117
|
+
|
118
|
+
head_size = tuple.types
|
119
|
+
.map {|type| type.size || 32 }
|
120
|
+
.sum
|
121
|
+
|
122
|
+
head, tail = ''.b, ''.b ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
|
123
|
+
tuple.types.each_with_index do |type, i|
|
124
|
+
if type.dynamic?
|
125
|
+
head += encode_uint256( head_size + tail.size )
|
126
|
+
tail += encode_type( type, args[i] )
|
127
|
+
else
|
128
|
+
head += encode_type( type, args[i] )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
head + tail
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
def encode_primitive_type( type, arg )
|
138
|
+
case type
|
139
|
+
when Uint
|
140
|
+
## note: for now size in bits always required
|
141
|
+
encode_uint( arg, type.bits )
|
142
|
+
when Int
|
143
|
+
## note: for now size in bits always required
|
144
|
+
encode_int( arg, type.bits )
|
145
|
+
when Bool
|
146
|
+
encode_bool( arg )
|
147
|
+
when String
|
148
|
+
encode_string( arg )
|
149
|
+
when FixedBytes
|
150
|
+
encode_bytes( arg, type.length )
|
151
|
+
when Bytes
|
152
|
+
encode_bytes( arg )
|
153
|
+
when Address
|
154
|
+
encode_address( arg )
|
155
|
+
else
|
156
|
+
raise EncodingError, "Unhandled type: #{type.class.name} #{type.format}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
|
162
|
+
def encode_bool( arg )
|
163
|
+
## raise EncodingError or ArgumentError - why? why not?
|
164
|
+
raise ArgumentError, "arg is not bool: #{arg}" unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
|
165
|
+
lpad( arg ? BYTE_ONE : BYTE_ZERO ) ## was lpad_int( arg ? 1 : 0 )
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def encode_uint256( arg ) encode_uint( arg, 256 ); end
|
170
|
+
def encode_uint( arg, bits )
|
171
|
+
## raise EncodingError or ArgumentError - why? why not?
|
172
|
+
raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
|
173
|
+
raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**bits
|
174
|
+
lpad_int( arg )
|
175
|
+
end
|
176
|
+
|
177
|
+
def encode_int( arg, bits )
|
178
|
+
## raise EncodingError or ArgumentError - why? why not?
|
179
|
+
raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
|
180
|
+
raise ValueOutOfBounds, arg unless arg >= -2**(bits-1) && arg < 2**(bits-1)
|
181
|
+
lpad_int( arg % 2**bits )
|
182
|
+
end
|
183
|
+
|
184
|
+
|
185
|
+
def encode_string( arg )
|
186
|
+
## raise EncodingError or ArgumentError - why? why not?
|
187
|
+
raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
|
188
|
+
arg = arg.b if arg.encoding != Encoding::BINARY ## was: name == 'UTF-8'
|
189
|
+
|
190
|
+
raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
|
191
|
+
size = lpad_int( arg.size )
|
192
|
+
value = rpad( arg, ceil32(arg.size) )
|
193
|
+
size + value
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
def encode_bytes( arg, length=nil )
|
198
|
+
## raise EncodingError or ArgumentError - why? why not?
|
199
|
+
raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
|
200
|
+
arg = arg.b if arg.encoding != Encoding::BINARY
|
201
|
+
|
202
|
+
if length # fixed length type
|
203
|
+
raise ValueOutOfBounds, "invalid bytes length #{length}" if arg.size > length
|
204
|
+
raise ValueOutOfBounds, "invalid bytes length #{length}" if length < 0 || length > 32
|
205
|
+
rpad( arg )
|
206
|
+
else # variable length type (if length is nil)
|
207
|
+
raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
|
208
|
+
size = lpad_int( arg.size )
|
209
|
+
value = rpad( arg, ceil32(arg.size) )
|
210
|
+
size + value
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
|
215
|
+
def encode_address( arg )
|
216
|
+
if arg.is_a?( Integer )
|
217
|
+
lpad_int( arg )
|
218
|
+
elsif arg.size == 20
|
219
|
+
## note: make sure encoding is always binary!!!
|
220
|
+
arg = arg.b if arg.encoding != Encoding::BINARY
|
221
|
+
lpad( arg )
|
222
|
+
elsif arg.size == 40
|
223
|
+
lpad_hex( arg )
|
224
|
+
elsif arg.size == 42 && arg[0,2] == '0x' ## todo/fix: allow 0X too - why? why not?
|
225
|
+
lpad_hex( arg[2..-1] )
|
226
|
+
else
|
227
|
+
raise EncodingError, "Could not parse address: #{arg}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
|
233
|
+
###########
|
234
|
+
# encoding helpers / utils
|
235
|
+
# with "hard-coded" fill symbol as BYTE_ZERO
|
236
|
+
|
237
|
+
def rpad( bin, l=32 ) ## note: same as builtin String#ljust !!!
|
238
|
+
# note: default l word is 32 bytes
|
239
|
+
return bin if bin.size >= l
|
240
|
+
bin + BYTE_ZERO * (l - bin.size)
|
241
|
+
end
|
242
|
+
|
243
|
+
|
244
|
+
## rename to lpad32 or such - why? why not?
|
245
|
+
def lpad( bin ) ## note: same as builtin String#rjust !!!
|
246
|
+
l=32 # note: default l word is 32 bytes
|
247
|
+
return bin if bin.size >= l
|
248
|
+
BYTE_ZERO * (l - bin.size) + bin
|
249
|
+
end
|
250
|
+
|
251
|
+
## rename to lpad32_int or such - why? why not?
|
252
|
+
def lpad_int( n )
|
253
|
+
raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
|
254
|
+
hex = n.to_s(16)
|
255
|
+
hex = "0#{hex}" if hex.size.odd?
|
256
|
+
bin = [hex].pack("H*")
|
257
|
+
|
258
|
+
lpad( bin )
|
259
|
+
end
|
260
|
+
|
261
|
+
## rename to lpad32_hex or such - why? why not?
|
262
|
+
def lpad_hex( hex )
|
263
|
+
raise TypeError, "Value must be a string" unless hex.is_a?( ::String )
|
264
|
+
raise TypeError, 'Non-hexadecimal digit found' unless hex =~ /\A[0-9a-fA-F]*\z/
|
265
|
+
bin = [hex].pack("H*")
|
266
|
+
|
267
|
+
lpad( bin )
|
268
|
+
end
|
269
|
+
|
270
|
+
|
271
|
+
|
272
|
+
def ceil32(x)
|
273
|
+
x % 32 == 0 ? x : (x + 32 - x%32)
|
274
|
+
end
|
275
|
+
|
276
|
+
end # class Encoder
|
277
|
+
end # module ABI
|
278
|
+
|