abicoder 0.1.0 → 0.1.1

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: 3bc1e75e6080a55ae3447f7cfd3630ed587edf021658dd9159584fbb3ef28743
4
+ data.tar.gz: 84d27e90d278d0c2fc84db605005735164b4a63c084c087777218eadadb0044b
5
5
  SHA512:
6
- metadata.gz: 82f6dfb3fb1f7eb2cb5352196d7a33476da85a684a93d72785ee37ca47af712f2864b46ddfbe6bc7bac76be3c0cb6f152620e0de9e883f8eaff44f12b57b9241
7
- data.tar.gz: 3ca6cdc5aaabb5bfaa4c13aff8f5daaba0880e5b3c33e4ada1aa02053617a20e0681e27bf8f2a6451919d5233923c5ca69fe97a0bd4bafb3aa6cbbecf5405ee4
6
+ metadata.gz: 597f54c3c8e21027bb10d326c7c6763653d752ad3285abc1c2086023d17c6dc9207d8e99f16a008265bff3a2ad61e1f261db501911c46df56a509660102dc019
7
+ data.tar.gz: b776e32b5afd57e233aef6ffb87dba66b65272533f60f635c371e0a199fad593a1bd55cd6248f39a3002849b66e77ecf1bb7bd06f3ccc453cc87854d0c77d059
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
@@ -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
 
@@ -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
+