abicoder 0.0.1 → 0.1.1
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 -0
- data/README.md +131 -2
- data/Rakefile +8 -1
- data/lib/abicoder/decoder.rb +208 -0
- data/lib/abicoder/encoder.rb +279 -0
- data/lib/abicoder/parser.rb +158 -0
- data/lib/abicoder/types.rb +155 -0
- data/lib/abicoder/version.rb +1 -1
- data/lib/abicoder.rb +53 -1
- metadata +12 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3bc1e75e6080a55ae3447f7cfd3630ed587edf021658dd9159584fbb3ef28743
|
4
|
+
data.tar.gz: 84d27e90d278d0c2fc84db605005735164b4a63c084c087777218eadadb0044b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 597f54c3c8e21027bb10d326c7c6763653d752ad3285abc1c2086023d17c6dc9207d8e99f16a008265bff3a2ad61e1f261db501911c46df56a509660102dc019
|
7
|
+
data.tar.gz: b776e32b5afd57e233aef6ffb87dba66b65272533f60f635c371e0a199fad593a1bd55cd6248f39a3002849b66e77ecf1bb7bd06f3ccc453cc87854d0c77d059
|
data/Manifest.txt
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Application Binary Inteface (ABI) Coder For Ethereum & Co.
|
2
2
|
|
3
|
-
abicoder - application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts
|
3
|
+
abicoder - "lite" application binary interface (abi) encoding / decoding machinery / helper 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)
|
@@ -12,7 +12,136 @@ abicoder - application binary interface (abi) encoding / decoding machinery / he
|
|
12
12
|
|
13
13
|
## Usage
|
14
14
|
|
15
|
-
|
15
|
+
|
16
|
+
### Encode & Decode Types (Contract Function Call Data)
|
17
|
+
|
18
|
+
|
19
|
+
``` ruby
|
20
|
+
require 'abicode'
|
21
|
+
|
22
|
+
#####
|
23
|
+
# try ABI.encode
|
24
|
+
|
25
|
+
## Encoding simple types
|
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"
|
33
|
+
|
34
|
+
## Encoding with arrays types
|
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'
|
56
|
+
|
57
|
+
|
58
|
+
#####
|
59
|
+
# try ABI.decode
|
60
|
+
|
61
|
+
## Decoding simple types
|
62
|
+
types = [ 'uint256', 'string' ]
|
63
|
+
data = hex'00000000000000000000000000000000000000000000000000000000000004d2'+
|
64
|
+
'0000000000000000000000000000000000000000000000000000000000000040'+
|
65
|
+
'000000000000000000000000000000000000000000000000000000000000000b'+
|
66
|
+
'48656c6c6f20576f726c64000000000000000000000000000000000000000000'
|
67
|
+
ABI.decode( types, data) # returns args
|
68
|
+
#=> [1234, "Hello World"]
|
69
|
+
|
70
|
+
|
71
|
+
## Decoding with arrays types
|
72
|
+
types = [ 'uint256[]', 'string' ]
|
73
|
+
data = hex'0000000000000000000000000000000000000000000000000000000000000040'+
|
74
|
+
'00000000000000000000000000000000000000000000000000000000000000a0'+
|
75
|
+
'0000000000000000000000000000000000000000000000000000000000000002'+
|
76
|
+
'00000000000000000000000000000000000000000000000000000000000004d2'+
|
77
|
+
'000000000000000000000000000000000000000000000000000000000000162e'+
|
78
|
+
'000000000000000000000000000000000000000000000000000000000000000b'+
|
79
|
+
'48656c6c6f20576f726c64000000000000000000000000000000000000000000'
|
80
|
+
ABI.decode( types, data ) # returns args
|
81
|
+
#=> [[1234, 5678], "Hello World"]
|
82
|
+
|
83
|
+
|
84
|
+
## Decoding complex structs (also known as tuples)
|
85
|
+
types = [ 'uint256', '(uint256,string)']
|
86
|
+
data = hex'00000000000000000000000000000000000000000000000000000000000004d2'+
|
87
|
+
'0000000000000000000000000000000000000000000000000000000000000040'+
|
88
|
+
'000000000000000000000000000000000000000000000000000000000000162e'+
|
89
|
+
'0000000000000000000000000000000000000000000000000000000000000040'+
|
90
|
+
'000000000000000000000000000000000000000000000000000000000000000b'+
|
91
|
+
'48656c6c6f20576f726c64000000000000000000000000000000000000000000'
|
92
|
+
ABI.decode( types, data ) # returns args
|
93
|
+
#=> [1234, [5678, "Hello World"]]
|
94
|
+
```
|
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
|
+
-->
|
16
145
|
|
17
146
|
|
18
147
|
|
data/Rakefile
CHANGED
@@ -2,11 +2,18 @@ require 'hoe'
|
|
2
2
|
require './lib/abicoder/version.rb'
|
3
3
|
|
4
4
|
|
5
|
+
###
|
6
|
+
# hack/ quick fix for broken intuit_values - overwrite with dummy
|
7
|
+
class Hoe
|
8
|
+
def intuit_values( input ); end
|
9
|
+
end
|
10
|
+
|
11
|
+
|
5
12
|
Hoe.spec 'abicoder' do
|
6
13
|
|
7
14
|
self.version = ABICoder::VERSION
|
8
15
|
|
9
|
-
self.summary = "abicoder - application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts"
|
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"
|
10
17
|
self.description = summary
|
11
18
|
|
12
19
|
self.urls = { home: 'https://github.com/rubycocos/blockchain' }
|
@@ -0,0 +1,208 @@
|
|
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
|
+
## for convenience check if types is a String
|
11
|
+
## otherwise assume ABI::Type already
|
12
|
+
types = types.map { |type| type.is_a?( Type ) ? type : Type.parse( type ) }
|
13
|
+
|
14
|
+
outputs = [nil] * types.size
|
15
|
+
start_positions = [nil] * types.size + [data.size]
|
16
|
+
|
17
|
+
# TODO: refactor, a reverse iteration will be better
|
18
|
+
pos = 0
|
19
|
+
types.each_with_index do |t, i|
|
20
|
+
# If a type is static, grab the data directly, otherwise record its
|
21
|
+
# start position
|
22
|
+
if t.dynamic?
|
23
|
+
|
24
|
+
if pos>data.size-1
|
25
|
+
if raise_errors
|
26
|
+
raise DecodingError, "Position out of bounds #{pos}>#{data.size-1}"
|
27
|
+
else
|
28
|
+
puts "!! WARN - DecodingError: Position out of bounds #{pos}>#{data.size-1}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
start_positions[i] = big_endian_to_int(data[pos, 32])
|
33
|
+
|
34
|
+
if start_positions[i]>data.size-1
|
35
|
+
if raise_errors
|
36
|
+
raise DecodingError, "Start position out of bounds #{start_positions[i]}>#{data.size-1}"
|
37
|
+
else
|
38
|
+
puts "!! WARN - DecodingError: Start position out of bounds #{start_positions[i]}>#{data.size-1}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
j = i - 1
|
44
|
+
while j >= 0 && start_positions[j].nil?
|
45
|
+
start_positions[j] = start_positions[i]
|
46
|
+
j -= 1
|
47
|
+
end
|
48
|
+
|
49
|
+
pos += 32
|
50
|
+
else
|
51
|
+
## puts "step 1 - decode item [#{i}] - #{t.format} size: #{t.size} dynamic? #{t.dynamic?}"
|
52
|
+
|
53
|
+
outputs[i] = zero_padding( data, pos, t.size, start_positions )
|
54
|
+
pos += t.size
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
# We add a start position equal to the length of the entire data for
|
61
|
+
# convenience.
|
62
|
+
j = types.size - 1
|
63
|
+
while j >= 0 && start_positions[j].nil?
|
64
|
+
start_positions[j] = start_positions[types.size]
|
65
|
+
j -= 1
|
66
|
+
end
|
67
|
+
|
68
|
+
if pos > data.size
|
69
|
+
if raise_errors
|
70
|
+
raise DecodingError, "Not enough data for head"
|
71
|
+
else
|
72
|
+
puts "!! WARN - DecodingError: Not enough data for head"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
types.each_with_index do |t, i|
|
78
|
+
if t.dynamic?
|
79
|
+
offset, next_offset = start_positions[i, 2]
|
80
|
+
if offset<=data.size && next_offset<=data.size
|
81
|
+
outputs[i] = data[offset...next_offset]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
if outputs.include?(nil)
|
87
|
+
if raise_errors
|
88
|
+
raise DecodingError, "Not all data can be parsed"
|
89
|
+
else
|
90
|
+
puts "!! WARN: DecodingError - Not all data can be parsed"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
types.zip(outputs).map do |(t, out)|
|
96
|
+
## puts "step 2 - decode item - #{t.format} got: #{out.size} byte(s) - size: #{t.size} dynamic? #{t.dynamic?}"
|
97
|
+
|
98
|
+
decode_type(t, out)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
|
104
|
+
def decode_type( type, arg )
|
105
|
+
return nil if arg.nil? || arg.empty?
|
106
|
+
|
107
|
+
|
108
|
+
if type.is_a?( String ) || type.is_a?( Bytes )
|
109
|
+
l = big_endian_to_int( arg[0,32] )
|
110
|
+
data = arg[32..-1]
|
111
|
+
data[0, l]
|
112
|
+
elsif type.is_a?( Tuple )
|
113
|
+
arg ? decode(type.types, arg) : []
|
114
|
+
elsif type.is_a?( FixedArray ) # static-sized arrays
|
115
|
+
l = type.dim
|
116
|
+
subtype = type.subtype
|
117
|
+
if subtype.dynamic?
|
118
|
+
start_positions = (0...l).map {|i| big_endian_to_int(arg[32*i, 32]) }
|
119
|
+
start_positions.push arg.size
|
120
|
+
|
121
|
+
outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
|
122
|
+
|
123
|
+
outputs.map {|out| decode_type(subtype, out) }
|
124
|
+
else
|
125
|
+
(0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
|
126
|
+
end
|
127
|
+
elsif type.is_a?( Array )
|
128
|
+
l = big_endian_to_int( arg[0,32] )
|
129
|
+
raise DecodingError, "Too long length: #{l}" if l > 100000
|
130
|
+
subtype = type.subtype
|
131
|
+
|
132
|
+
if subtype.dynamic?
|
133
|
+
raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
|
134
|
+
|
135
|
+
start_positions = (1..l).map {|i| 32+big_endian_to_int(arg[32*i, 32]) }
|
136
|
+
start_positions.push arg.size
|
137
|
+
|
138
|
+
outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
|
139
|
+
|
140
|
+
outputs.map {|out| decode_type(subtype, out) }
|
141
|
+
else
|
142
|
+
(0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
|
143
|
+
end
|
144
|
+
else
|
145
|
+
decode_primitive_type( type, arg )
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def decode_primitive_type(type, data)
|
151
|
+
case type
|
152
|
+
when Address
|
153
|
+
encode_hex( data[12..-1] )
|
154
|
+
when String, Bytes
|
155
|
+
if data.length == 32
|
156
|
+
data[0..32]
|
157
|
+
else
|
158
|
+
size = big_endian_to_int( data[0,32] )
|
159
|
+
data[32..-1][0,size]
|
160
|
+
end
|
161
|
+
when FixedBytes
|
162
|
+
data[0, type.length]
|
163
|
+
when Uint
|
164
|
+
big_endian_to_int( data )
|
165
|
+
when Int
|
166
|
+
u = big_endian_to_int( data )
|
167
|
+
u >= 2**(type.bits-1) ? (u - 2**type.bits) : u
|
168
|
+
when Bool
|
169
|
+
data[-1] == BYTE_ONE
|
170
|
+
else
|
171
|
+
raise DecodingError, "Unknown primitive type: #{type.class.name} #{type.format}"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
def zero_padding( data, pos, count, start_positions )
|
177
|
+
if pos >= data.size
|
178
|
+
start_positions[start_positions.size-1] += count
|
179
|
+
"\x00"*count
|
180
|
+
elsif pos + count > data.size
|
181
|
+
start_positions[start_positions.size-1] += ( count - (data.size-pos))
|
182
|
+
data[pos,data.size-pos] + "\x00"*( count - (data.size-pos))
|
183
|
+
else
|
184
|
+
data[pos, count]
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
###########
|
190
|
+
# decoding helpers / utils
|
191
|
+
|
192
|
+
def big_endian_to_int( bin )
|
193
|
+
bin = bin.sub( /\A(\x00)+/, '' ) ## keep "performance" shortcut - why? why not?
|
194
|
+
### todo/check - allow nil - why? why not?
|
195
|
+
## raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
|
196
|
+
bin = bin || BYTE_ZERO
|
197
|
+
bin.unpack("H*").first.to_i(16)
|
198
|
+
end
|
199
|
+
|
200
|
+
def encode_hex( bin ) ## bin_to_hex
|
201
|
+
raise TypeError, "Value must be a String" unless bin.is_a?(::String)
|
202
|
+
bin.unpack("H*").first
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
end # class Decoder
|
207
|
+
|
208
|
+
end ## module ABI
|
@@ -0,0 +1,279 @@
|
|
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
|
+
#
|
22
|
+
def encode( types, args )
|
23
|
+
|
24
|
+
## todo/fix: enforce args.size and types.size must match!!
|
25
|
+
## raise ArgumentError - expected x arguments got y!!!
|
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 = '', ''
|
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
|
+
## case 1) string or bytes (note:are dynamic too!!! most go first)
|
61
|
+
## use type == Type.new( 'string', nil, [] ) - same as Type.new('string'))
|
62
|
+
## or type == Type.new( 'bytes', nil, [] ) - same as Type.new('bytes')
|
63
|
+
## - why? why not?
|
64
|
+
if type.is_a?( String )
|
65
|
+
encode_string( arg )
|
66
|
+
elsif type.is_a?( Bytes )
|
67
|
+
encode_bytes( arg )
|
68
|
+
elsif type.is_a?( Tuple )
|
69
|
+
encode_tuple( type, arg )
|
70
|
+
elsif type.is_a?( Array ) || type.is_a?( FixedArray )
|
71
|
+
if type.dynamic?
|
72
|
+
encode_dynamic_array( type, arg )
|
73
|
+
else
|
74
|
+
encode_static_array( type, arg )
|
75
|
+
end
|
76
|
+
else # assume static (primitive) type
|
77
|
+
encode_primitive_type( type, arg )
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def encode_dynamic_array( type, args )
|
83
|
+
raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
|
84
|
+
|
85
|
+
head, tail = '', ''
|
86
|
+
|
87
|
+
if type.is_a?( Array ) ## dynamic array
|
88
|
+
head += encode_uint256( args.size )
|
89
|
+
else ## fixed array
|
90
|
+
raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim
|
91
|
+
end
|
92
|
+
|
93
|
+
subtype = type.subtype
|
94
|
+
args.each do |arg|
|
95
|
+
if subtype.dynamic?
|
96
|
+
head += encode_uint256( 32*args.size + tail.size )
|
97
|
+
tail += encode_type( subtype, arg )
|
98
|
+
else
|
99
|
+
head += encode_type( subtype, arg )
|
100
|
+
end
|
101
|
+
end
|
102
|
+
head + tail
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def encode_static_array( type, args )
|
107
|
+
raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
|
108
|
+
raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim
|
109
|
+
|
110
|
+
args.map {|arg| encode_type( type.subtype, arg ) }.join
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
##
|
115
|
+
## todo/check: if static tuple gets encoded different
|
116
|
+
## without offset (head/tail)
|
117
|
+
|
118
|
+
def encode_tuple( tuple, args )
|
119
|
+
raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
|
120
|
+
raise ArgumentError, "Wrong array size (for tuple): found #{args.size}, expecting #{tuple.type.size} tuple elements" unless args.size == tuple.types.size
|
121
|
+
|
122
|
+
head_size = tuple.types
|
123
|
+
.map {|type| type.size || 32 }
|
124
|
+
.sum
|
125
|
+
|
126
|
+
head, tail = '', ''
|
127
|
+
tuple.types.each_with_index do |type, i|
|
128
|
+
if type.dynamic?
|
129
|
+
head += encode_uint256( head_size + tail.size )
|
130
|
+
tail += encode_type( type, args[i] )
|
131
|
+
else
|
132
|
+
head += encode_type( type, args[i] )
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
head + tail
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
|
141
|
+
def encode_primitive_type( type, arg )
|
142
|
+
case type
|
143
|
+
when Uint
|
144
|
+
## note: for now size in bits always required
|
145
|
+
encode_uint( arg, type.bits )
|
146
|
+
when Int
|
147
|
+
## note: for now size in bits always required
|
148
|
+
encode_int( arg, type.bits )
|
149
|
+
when Bool
|
150
|
+
encode_bool( arg )
|
151
|
+
when String
|
152
|
+
encode_string( arg )
|
153
|
+
when FixedBytes
|
154
|
+
encode_bytes( arg, type.length )
|
155
|
+
when Bytes
|
156
|
+
encode_bytes( arg )
|
157
|
+
when Address
|
158
|
+
encode_address( arg )
|
159
|
+
else
|
160
|
+
raise EncodingError, "Unhandled type: #{type.class.name} #{type.format}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
|
166
|
+
def encode_bool( arg )
|
167
|
+
raise ArgumentError, "arg is not bool: #{arg}" unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
|
168
|
+
lpad_int( arg ? 1 : 0 )
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
def encode_uint256( arg ) encode_uint( arg, 256 ); end
|
173
|
+
def encode_uint( arg, bits )
|
174
|
+
raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
|
175
|
+
raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**bits
|
176
|
+
lpad_int( arg )
|
177
|
+
end
|
178
|
+
|
179
|
+
def encode_int( arg, bits )
|
180
|
+
raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
|
181
|
+
raise ValueOutOfBounds, arg unless arg >= -2**(bits-1) && arg < 2**(bits-1)
|
182
|
+
lpad_int( arg % 2**bits )
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def encode_string( arg )
|
187
|
+
|
188
|
+
## todo/fix: do NOT auto-unpack hexstring EVER!!!
|
189
|
+
## remove arg.unpack('U*')
|
190
|
+
|
191
|
+
if arg.encoding == Encoding::UTF_8 ## was: name == 'UTF-8'
|
192
|
+
arg = arg.b
|
193
|
+
else
|
194
|
+
begin
|
195
|
+
arg.unpack('U*')
|
196
|
+
rescue ArgumentError
|
197
|
+
raise ValueError, "string must be UTF-8 encoded"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
|
202
|
+
size = lpad_int( arg.size )
|
203
|
+
value = rpad( arg, ceil32(arg.size) )
|
204
|
+
|
205
|
+
size + value
|
206
|
+
end
|
207
|
+
|
208
|
+
|
209
|
+
def encode_bytes( arg, length=nil )
|
210
|
+
raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
|
211
|
+
arg = arg.b
|
212
|
+
|
213
|
+
if length # fixed length type
|
214
|
+
raise ValueOutOfBounds, "invalid bytes length #{length}" if arg.size > length
|
215
|
+
raise ValueOutOfBounds, "invalid bytes length #{length}" if length < 0 || length > 32
|
216
|
+
rpad( arg )
|
217
|
+
else # variable length type (if length is nil)
|
218
|
+
raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
|
219
|
+
size = lpad_int( arg.size )
|
220
|
+
value = rpad( arg, ceil32(arg.size) )
|
221
|
+
size + value
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
def encode_address( arg )
|
227
|
+
if arg.is_a?( Integer )
|
228
|
+
lpad_int( arg )
|
229
|
+
elsif arg.size == 20
|
230
|
+
lpad( arg )
|
231
|
+
elsif arg.size == 40
|
232
|
+
lpad_hex( arg )
|
233
|
+
elsif arg.size == 42 && arg[0,2] == '0x' ## todo/fix: allow 0X too - why? why not?
|
234
|
+
lpad_hex( arg[2..-1] )
|
235
|
+
else
|
236
|
+
raise EncodingError, "Could not parse address: #{arg}"
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
|
241
|
+
###########
|
242
|
+
# encoding helpers / utils
|
243
|
+
|
244
|
+
def rpad( bin, l=32, symbol=BYTE_ZERO ) ## note: same as builtin String#ljust !!!
|
245
|
+
return bin if bin.size >= l
|
246
|
+
bin + symbol * (l - bin.size)
|
247
|
+
end
|
248
|
+
|
249
|
+
def lpad( bin, l=32, symbol=BYTE_ZERO ) ## note: same as builtin String#rjust !!!
|
250
|
+
return bin if bin.size >= l
|
251
|
+
symbol * (l - bin.size) + bin
|
252
|
+
end
|
253
|
+
|
254
|
+
def lpad_int( n, l=32, symbol=BYTE_ZERO )
|
255
|
+
raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
|
256
|
+
hex = n.to_s(16)
|
257
|
+
hex = "0#{hex}" if hex.size.odd?
|
258
|
+
bin = [hex].pack("H*")
|
259
|
+
|
260
|
+
lpad( bin, l, symbol )
|
261
|
+
end
|
262
|
+
|
263
|
+
def lpad_hex( hex, l=32, symbol=BYTE_ZERO )
|
264
|
+
raise TypeError, "Value must be a string" unless hex.is_a?( ::String )
|
265
|
+
raise TypeError, 'Non-hexadecimal digit found' unless hex =~ /\A[0-9a-fA-F]*\z/
|
266
|
+
bin = [hex].pack("H*")
|
267
|
+
|
268
|
+
lpad( bin, l, symbol )
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
|
273
|
+
def ceil32(x)
|
274
|
+
x % 32 == 0 ? x : (x + 32 - x%32)
|
275
|
+
end
|
276
|
+
|
277
|
+
end # class Encoder
|
278
|
+
end # module ABI
|
279
|
+
|
@@ -0,0 +1,158 @@
|
|
1
|
+
module ABI
|
2
|
+
class Type
|
3
|
+
|
4
|
+
class ParseError < StandardError; end
|
5
|
+
|
6
|
+
|
7
|
+
## nested inside Type - why? why not?
|
8
|
+
class Parser
|
9
|
+
|
10
|
+
TUPLE_TYPE_RX = /^\((.*)\)
|
11
|
+
((\[[0-9]*\])*)
|
12
|
+
/x
|
13
|
+
|
14
|
+
def self.parse( type )
|
15
|
+
if type =~ TUPLE_TYPE_RX
|
16
|
+
types = _parse_tuple_type( $1 )
|
17
|
+
dims = _parse_dims( $2 )
|
18
|
+
|
19
|
+
parsed_types = types.map{ |t| parse( t ) }
|
20
|
+
|
21
|
+
return _parse_array_type( Tuple.new( parsed_types ), dims )
|
22
|
+
end
|
23
|
+
|
24
|
+
base, sub, dims = _parse_base_type( type )
|
25
|
+
|
26
|
+
_validate_base_type( base, sub )
|
27
|
+
|
28
|
+
subtype = case base
|
29
|
+
when 'string' then String.new
|
30
|
+
when 'bytes' then sub ? FixedBytes.new( sub ) : Bytes.new
|
31
|
+
when 'uint' then Uint.new( sub )
|
32
|
+
when 'int' then Int.new( sub )
|
33
|
+
when 'address' then Address.new
|
34
|
+
when 'bool' then Bool.new
|
35
|
+
else
|
36
|
+
## puts " type: >#{type}<"
|
37
|
+
raise ParseError, "Unrecognized type base: #{base}"
|
38
|
+
end
|
39
|
+
|
40
|
+
_parse_array_type( subtype, dims )
|
41
|
+
end
|
42
|
+
|
43
|
+
##
|
44
|
+
# Crazy regexp to seperate out base type component (eg. uint), size (eg.
|
45
|
+
# 256, 128, nil), array component (eg. [], [45], nil)
|
46
|
+
#
|
47
|
+
BASE_TYPE_RX = /([a-z]*)
|
48
|
+
([0-9]*)
|
49
|
+
((\[[0-9]*\])*)
|
50
|
+
/x
|
51
|
+
|
52
|
+
def self._parse_base_type( str )
|
53
|
+
_, base, subscript, dimension = BASE_TYPE_RX.match( str ).to_a
|
54
|
+
|
55
|
+
## note: use [Integer,Integer] array in the future for sub
|
56
|
+
## for fixed (e.g. 128x128 => [128,128]) or such
|
57
|
+
## for now always assume single integer (as string)
|
58
|
+
sub = subscript == '' ? nil : subscript.to_i
|
59
|
+
|
60
|
+
## e.g. turn "[][1][2]" into [-1,1,2]
|
61
|
+
## or "" into [] -- that is, empty array
|
62
|
+
dims = _parse_dims( dimension )
|
63
|
+
|
64
|
+
[base, sub, dims]
|
65
|
+
end
|
66
|
+
|
67
|
+
def self._parse_dims( str )
|
68
|
+
dims = str.scan( /\[[0-9]*\]/ )
|
69
|
+
|
70
|
+
## note: return -1 for dynamic array size e.g. []
|
71
|
+
## e.g. "[]"[1...-1] => ""
|
72
|
+
## "[0]"[1...-1] => "0"
|
73
|
+
## "[1]"[1...-1] => "1"
|
74
|
+
dims = dims.map do |dim|
|
75
|
+
size = dim[1...-1]
|
76
|
+
size == '' ? -1 : size.to_i
|
77
|
+
end
|
78
|
+
dims
|
79
|
+
end
|
80
|
+
|
81
|
+
def self._parse_array_type( subtype, dims )
|
82
|
+
##
|
83
|
+
## todo/check - double check if the order in reverse
|
84
|
+
## in solidity / abi encoding / decoding?
|
85
|
+
##
|
86
|
+
dims.each do |dim|
|
87
|
+
subtype = if dim == -1
|
88
|
+
Array.new( subtype )
|
89
|
+
else
|
90
|
+
FixedArray.new( subtype, dim )
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
subtype
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def self._validate_base_type( base, sub )
|
99
|
+
case base
|
100
|
+
when 'string'
|
101
|
+
# note: string can not have any suffix
|
102
|
+
raise ParseError, "String cannot have suffix" if sub
|
103
|
+
when 'bytes'
|
104
|
+
raise ParseError, "Maximum 32 bytes for fixed-length bytes" if sub && sub > 32
|
105
|
+
when 'uint', 'int'
|
106
|
+
raise ParseError, "Integer type must have numerical suffix" unless sub
|
107
|
+
raise ParseError, "Integer size out of bounds" unless sub >= 8 && sub <= 256
|
108
|
+
raise ParseError, "Integer size must be multiple of 8" unless sub % 8 == 0
|
109
|
+
when 'address'
|
110
|
+
raise ParseError, "Address cannot have suffix" if sub
|
111
|
+
when 'bool'
|
112
|
+
raise ParseError, "Bool cannot have suffix" if sub
|
113
|
+
else
|
114
|
+
## puts " type: >#{type}<"
|
115
|
+
raise ParseError, "Unrecognized type base: #{base}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
|
121
|
+
def self._parse_tuple_type( str )
|
122
|
+
## note: types assumes string WITHOUT enclosing () e.g.
|
123
|
+
## tuple(string,string,bool) => expected as "string,string,bool"
|
124
|
+
|
125
|
+
depth = 0
|
126
|
+
collected = []
|
127
|
+
current = ''
|
128
|
+
|
129
|
+
### todo/fix: replace with a simple parser!!!
|
130
|
+
## allow () and move verbose tuple() too!!!
|
131
|
+
str.each_char do |c|
|
132
|
+
case c
|
133
|
+
when ',' then
|
134
|
+
if depth == 0
|
135
|
+
collected << current
|
136
|
+
current = ''
|
137
|
+
else
|
138
|
+
current += c
|
139
|
+
end
|
140
|
+
when '(' then
|
141
|
+
depth += 1
|
142
|
+
current += c
|
143
|
+
when ')' then
|
144
|
+
depth -= 1
|
145
|
+
current += c
|
146
|
+
else
|
147
|
+
current += c
|
148
|
+
end
|
149
|
+
end
|
150
|
+
collected << current unless current.empty?
|
151
|
+
|
152
|
+
collected
|
153
|
+
end
|
154
|
+
|
155
|
+
end # class Parser
|
156
|
+
end # class Type
|
157
|
+
end # module ABI
|
158
|
+
|
@@ -0,0 +1,155 @@
|
|
1
|
+
module ABI
|
2
|
+
|
3
|
+
|
4
|
+
#######
|
5
|
+
## for now use (get inspired)
|
6
|
+
## by the type names used by abi coder in rust
|
7
|
+
## see https://github.com/rust-ethereum/ethabi/blob/master/ethabi/src/param_type/param_type.rs
|
8
|
+
|
9
|
+
|
10
|
+
class Type
|
11
|
+
|
12
|
+
def self.parse( type ) ## convenience helper
|
13
|
+
Parser.parse( type )
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# Get the static size of a type, or nil if dynamic.
|
18
|
+
#
|
19
|
+
# @return [Integer, NilClass] size of static type, or nil for dynamic type
|
20
|
+
#
|
21
|
+
def size
|
22
|
+
## check/todo: what error to raise for not implemented / method not defined???
|
23
|
+
raise ArgumentError, "no required size method defined for Type subclass #{self.class.name}; sorry"
|
24
|
+
end
|
25
|
+
def dynamic?() size.nil?; end
|
26
|
+
|
27
|
+
def format
|
28
|
+
## check/todo: what error to raise for not implemented / method not defined???
|
29
|
+
raise ArgumentError, "no required format method defined for Type subclass #{self.class.name}; sorry"
|
30
|
+
end
|
31
|
+
|
32
|
+
####
|
33
|
+
## default implementation
|
34
|
+
## assume equal if class match (e.g. Address == Address)
|
35
|
+
## - use format string for generic compare - why? why not?
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
|
40
|
+
class Address < Type
|
41
|
+
def size() 32; end ## note: address is always 20 bytes; BUT uses 32 bytes (with padding)
|
42
|
+
def format() 'address'; end
|
43
|
+
def ==(another_type) another_type.kind_of?( Address ); end
|
44
|
+
end # class Address
|
45
|
+
|
46
|
+
class Bytes < Type
|
47
|
+
def size() nil; end ## note: dynamic (not known at compile-time)
|
48
|
+
def format() 'bytes'; end
|
49
|
+
def ==(another_type) another_type.kind_of?( Bytes ); end
|
50
|
+
end # class Bytes
|
51
|
+
|
52
|
+
class FixedBytes < Type
|
53
|
+
attr_reader :length
|
54
|
+
def initialize( length )
|
55
|
+
@length = length # in bytes (1,2,...32)
|
56
|
+
end
|
57
|
+
def size() 32; end ## note: always uses 32 bytes (with padding)
|
58
|
+
def format() "bytes#{@length}"; end
|
59
|
+
def ==(another_type)
|
60
|
+
another_type.kind_of?( FixedBytes ) && @length == another_type.length
|
61
|
+
end
|
62
|
+
end # class FixedBytes
|
63
|
+
|
64
|
+
class Int < Type
|
65
|
+
attr_reader :bits
|
66
|
+
def initialize( bits=256 )
|
67
|
+
@bits = bits # in bits (8,16,...256)
|
68
|
+
end
|
69
|
+
def size() 32; end ## note: always uses 32 bytes (with padding)
|
70
|
+
def format() "int#{@bits}"; end
|
71
|
+
def ==(another_type)
|
72
|
+
another_type.kind_of?( Int ) && @bits == another_type.bits
|
73
|
+
end
|
74
|
+
end # class Int
|
75
|
+
|
76
|
+
class Uint < Type
|
77
|
+
attr_reader :bits
|
78
|
+
def initialize( bits=256 )
|
79
|
+
@bits = bits # in bits (8,16,...256)
|
80
|
+
end
|
81
|
+
def size() 32; end ## note: always uses 32 bytes (with padding)
|
82
|
+
def format() "uint#{@bits}"; end
|
83
|
+
def ==(another_type)
|
84
|
+
another_type.kind_of?( Uint ) && @bits == another_type.bits
|
85
|
+
end
|
86
|
+
end # class Uint
|
87
|
+
|
88
|
+
class Bool < Type
|
89
|
+
def size() 32; end ## note: always uses 32 bytes (with padding)
|
90
|
+
def format() 'bool'; end
|
91
|
+
def ==(another_type) another_type.kind_of?( Bool ); end
|
92
|
+
end # class Bool
|
93
|
+
|
94
|
+
class String < Type
|
95
|
+
def size() nil; end ## note: dynamic (not known at compile-time)
|
96
|
+
def format() 'string'; end
|
97
|
+
def ==(another_type) another_type.kind_of?( String ); end
|
98
|
+
end # class String
|
99
|
+
|
100
|
+
class Array < Type
|
101
|
+
attr_reader :subtype
|
102
|
+
def initialize( subtype )
|
103
|
+
@subtype = subtype
|
104
|
+
end
|
105
|
+
def size() nil; end ## note: dynamic (not known at compile-time)
|
106
|
+
def format() "#{@subtype.format}[]"; end
|
107
|
+
def ==(another_type)
|
108
|
+
another_type.kind_of?( Array ) && @subtype == another_type.subtype
|
109
|
+
end
|
110
|
+
end # class Array
|
111
|
+
|
112
|
+
class FixedArray < Type
|
113
|
+
attr_reader :subtype
|
114
|
+
attr_reader :dim
|
115
|
+
def initialize( subtype, dim )
|
116
|
+
@subtype = subtype
|
117
|
+
@dim = dim
|
118
|
+
end
|
119
|
+
|
120
|
+
def size
|
121
|
+
@subtype.dynamic? ? nil : @dim * subtype.size
|
122
|
+
end
|
123
|
+
def format() "#{@subtype.format}[#{@dim}]"; end
|
124
|
+
def ==(another_type)
|
125
|
+
another_type.kind_of?( FixedArray ) &&
|
126
|
+
@dim == another_type.dim &&
|
127
|
+
@subtype == another_type.subtype
|
128
|
+
end
|
129
|
+
end # class FixedArray
|
130
|
+
|
131
|
+
class Tuple < Type
|
132
|
+
attr_reader :types
|
133
|
+
|
134
|
+
def initialize( types )
|
135
|
+
@types = types
|
136
|
+
end
|
137
|
+
|
138
|
+
def size
|
139
|
+
s = 0
|
140
|
+
@types.each do |type|
|
141
|
+
ts = type.size
|
142
|
+
return nil if ts.nil?
|
143
|
+
s += ts
|
144
|
+
end
|
145
|
+
s
|
146
|
+
end
|
147
|
+
def format
|
148
|
+
"(#{@types.map {|t| t.format }.join(',')})" ## rebuild minimal string
|
149
|
+
end
|
150
|
+
def ==(another_type)
|
151
|
+
another_type.kind_of?( Tuple ) && @types == another_type.types
|
152
|
+
end
|
153
|
+
end # class Tuple
|
154
|
+
end # module ABI
|
155
|
+
|
data/lib/abicoder/version.rb
CHANGED
data/lib/abicoder.rb
CHANGED
@@ -4,13 +4,65 @@
|
|
4
4
|
require_relative 'abicoder/version' # note: let version always go first
|
5
5
|
|
6
6
|
|
7
|
-
|
8
7
|
module ABI
|
8
|
+
###################
|
9
|
+
### some (shared) constants (move to constants.rb or such - why? why not?)
|
10
|
+
|
11
|
+
## todo/check: use auto-freeze string literals magic comment - why? why not?
|
12
|
+
##
|
13
|
+
## todo/fix: move BYTE_EMPTY, BYTE_ZERO, BYTE_ONE to upstream to bytes gem
|
14
|
+
## and make "global" constants - why? why not?
|
15
|
+
|
16
|
+
BYTE_EMPTY = "".b.freeze
|
17
|
+
BYTE_ZERO = "\x00".b.freeze
|
18
|
+
BYTE_ONE = "\x01".b.freeze ## note: used for encoding bool for now
|
19
|
+
|
9
20
|
|
21
|
+
UINT_MAX = 2**256 - 1 ## same as 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
22
|
+
UINT_MIN = 0
|
23
|
+
INT_MAX = 2**255 - 1 ## same as 57896044618658097711785492504343953926634992332820282019728792003956564819967
|
24
|
+
INT_MIN = -2**255 ## same as -57896044618658097711785492504343953926634992332820282019728792003956564819968
|
10
25
|
|
11
26
|
end # module ABI
|
12
27
|
|
13
28
|
|
29
|
+
require_relative 'abicoder/types'
|
30
|
+
require_relative 'abicoder/parser'
|
31
|
+
|
32
|
+
|
33
|
+
require_relative 'abicoder/encoder'
|
34
|
+
require_relative 'abicoder/decoder'
|
35
|
+
|
36
|
+
|
37
|
+
module ABI
|
38
|
+
def self.encoder
|
39
|
+
@encoder ||= Encoder.new
|
40
|
+
end
|
41
|
+
def self.decoder
|
42
|
+
@decoder ||= Decoder.new
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def self.encode( types, args )
|
47
|
+
encoder.encode( types, args )
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.decode( types, data, raise_errors = false )
|
51
|
+
decoder.decode( types, data, raise_errors )
|
52
|
+
end
|
53
|
+
|
54
|
+
## add alternate _abi names - why? why not?
|
55
|
+
class << self
|
56
|
+
alias_method :encode_abi, :encode
|
57
|
+
alias_method :decode_abi, :decode
|
58
|
+
end
|
59
|
+
end ## module ABI
|
60
|
+
|
61
|
+
|
62
|
+
################
|
63
|
+
## add convenience alternate spellings - why? why not?
|
64
|
+
Abi = ABI
|
65
|
+
|
14
66
|
|
15
67
|
|
16
68
|
puts ABICoder.banner ## say hello
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: abicoder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rdoc
|
@@ -44,8 +44,9 @@ dependencies:
|
|
44
44
|
- - "~>"
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: '3.23'
|
47
|
-
description: abicoder - application binary interface (abi) encoding / decoding
|
48
|
-
/ helper for Ethereum & Co. (blockchain) contracts
|
47
|
+
description: abicoder - 'lite' application binary interface (abi) encoding / decoding
|
48
|
+
machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies
|
49
|
+
for easy (re)use
|
49
50
|
email: wwwmake@googlegroups.com
|
50
51
|
executables: []
|
51
52
|
extensions: []
|
@@ -59,6 +60,10 @@ files:
|
|
59
60
|
- README.md
|
60
61
|
- Rakefile
|
61
62
|
- lib/abicoder.rb
|
63
|
+
- lib/abicoder/decoder.rb
|
64
|
+
- lib/abicoder/encoder.rb
|
65
|
+
- lib/abicoder/parser.rb
|
66
|
+
- lib/abicoder/types.rb
|
62
67
|
- lib/abicoder/version.rb
|
63
68
|
homepage: https://github.com/rubycocos/blockchain
|
64
69
|
licenses:
|
@@ -84,6 +89,7 @@ requirements: []
|
|
84
89
|
rubygems_version: 3.3.7
|
85
90
|
signing_key:
|
86
91
|
specification_version: 4
|
87
|
-
summary: abicoder - application binary interface (abi) encoding / decoding
|
88
|
-
/ helper for Ethereum & Co. (blockchain) contracts
|
92
|
+
summary: abicoder - 'lite' application binary interface (abi) encoding / decoding
|
93
|
+
machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies
|
94
|
+
for easy (re)use
|
89
95
|
test_files: []
|