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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23ce318ecd9bff53b9586cb30f15aac0f405991baae8ed3823fd8a1f1e357ca6
4
- data.tar.gz: 4760a854236914e5d4fe42db0ced4f51bad4a7132fe14fe813f02af3f99043ca
3
+ metadata.gz: f6aed5c766df24d4cf394f10fdaec00a7a42375cf85c5eab0a07d8f7f22ad3ec
4
+ data.tar.gz: 80c9a39735f1e383f168fdca5980d66acbf039a2bfbffe15866f0fb89a7090cf
5
5
  SHA512:
6
- metadata.gz: 82f6dfb3fb1f7eb2cb5352196d7a33476da85a684a93d72785ee37ca47af712f2864b46ddfbe6bc7bac76be3c0cb6f152620e0de9e883f8eaff44f12b57b9241
7
- data.tar.gz: 3ca6cdc5aaabb5bfaa4c13aff8f5daaba0880e5b3c33e4ada1aa02053617a20e0681e27bf8f2a6451919d5233923c5ca69fe97a0bd4bafb3aa6cbbecf5405ee4
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/codec.rb
7
- lib/abicoder/type.rb
8
- lib/abicoder/type_tuple.rb
9
- lib/abicoder/utils.rb
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
- ABI.encode( [ 'uint256', 'string' ],
24
- [ 1234, 'Hello World' ]).hexdigest
25
- #=> "00000000000000000000000000000000000000000000000000000000000004d2"+
26
- # "0000000000000000000000000000000000000000000000000000000000000040"+
27
- # "000000000000000000000000000000000000000000000000000000000000000b"+
28
- # "48656c6c6f20576f726c64000000000000000000000000000000000000000000"
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
- ABI.encode([ 'uint256[]', 'string' ],
32
- [ [1234, 5678] , 'Hello World' ]).hexdigest
33
- #=> "0000000000000000000000000000000000000000000000000000000000000040"+
34
- # "00000000000000000000000000000000000000000000000000000000000000a0"+
35
- # "0000000000000000000000000000000000000000000000000000000000000002"+
36
- # "00000000000000000000000000000000000000000000000000000000000004d2"+
37
- # "000000000000000000000000000000000000000000000000000000000000162e"+
38
- # "000000000000000000000000000000000000000000000000000000000000000b"+
39
- # "48656c6c6f20576f726c64000000000000000000000000000000000000000000"
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([ 'uint256', 'string' ], data)
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([ 'uint256[]', 'string' ], data )
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([ 'uint256', '(uint256,string)'], data)
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
+