abicoder 0.1.0 → 0.1.1

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: 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
+