abicoder 0.1.0 → 1.0.0

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