abicoder 0.0.1 → 0.1.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: 45fa5adeeafa44036fa386f4ab36d3f822e5f91706423d578ea822e0d6fc01a8
4
- data.tar.gz: 8430f2cdfeed351cee319596152b3c6d23ae506d6a51823ac909175df264eaff
3
+ metadata.gz: 23ce318ecd9bff53b9586cb30f15aac0f405991baae8ed3823fd8a1f1e357ca6
4
+ data.tar.gz: 4760a854236914e5d4fe42db0ced4f51bad4a7132fe14fe813f02af3f99043ca
5
5
  SHA512:
6
- metadata.gz: 4bb46c9aa7376a6d3ff7a0ed76b51c60b6068e9705ed8b7ce2980d89dc1a38e7331ffd3681e616264138d7f0d9819cf7981d4a26c323a7fef0ec42792c956c9e
7
- data.tar.gz: 97ea5904ab15a98eea02f3a62d123c674bd34145828d04b93853dc615cf07031556143b6f51d4fa484a1b33479d26c7a6a1916acbb2df6dd503d4629e1dacac8
6
+ metadata.gz: 82f6dfb3fb1f7eb2cb5352196d7a33476da85a684a93d72785ee37ca47af712f2864b46ddfbe6bc7bac76be3c0cb6f152620e0de9e883f8eaff44f12b57b9241
7
+ data.tar.gz: 3ca6cdc5aaabb5bfaa4c13aff8f5daaba0880e5b3c33e4ada1aa02053617a20e0681e27bf8f2a6451919d5233923c5ca69fe97a0bd4bafb3aa6cbbecf5405ee4
data/Manifest.txt CHANGED
@@ -3,4 +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
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 - application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts
3
+ abicoder - "lite" application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies for easy (re)use
4
4
 
5
5
 
6
6
  * home :: [github.com/rubycocos/blockchain](https://github.com/rubycocos/blockchain)
@@ -12,7 +12,67 @@ abicoder - application binary interface (abi) encoding / decoding machinery / he
12
12
 
13
13
  ## Usage
14
14
 
15
- To be done
15
+
16
+ ``` ruby
17
+ require 'abicode'
18
+
19
+ #####
20
+ # try ABI.encode
21
+
22
+ ## Encoding simple types
23
+ ABI.encode( [ 'uint256', 'string' ],
24
+ [ 1234, 'Hello World' ]).hexdigest
25
+ #=> "00000000000000000000000000000000000000000000000000000000000004d2"+
26
+ # "0000000000000000000000000000000000000000000000000000000000000040"+
27
+ # "000000000000000000000000000000000000000000000000000000000000000b"+
28
+ # "48656c6c6f20576f726c64000000000000000000000000000000000000000000"
29
+
30
+ ## 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"
40
+
41
+
42
+ #####
43
+ # try ABI.decode
44
+
45
+ ## Decoding simple types
46
+ data = hex'00000000000000000000000000000000000000000000000000000000000004d2'+
47
+ '0000000000000000000000000000000000000000000000000000000000000040'+
48
+ '000000000000000000000000000000000000000000000000000000000000000b'+
49
+ '48656c6c6f20576f726c64000000000000000000000000000000000000000000'
50
+ ABI.decode([ 'uint256', 'string' ], data)
51
+ #=> [1234, "Hello World"]
52
+
53
+
54
+ ## Decoding with arrays types
55
+ data = hex'0000000000000000000000000000000000000000000000000000000000000040'+
56
+ '00000000000000000000000000000000000000000000000000000000000000a0'+
57
+ '0000000000000000000000000000000000000000000000000000000000000002'+
58
+ '00000000000000000000000000000000000000000000000000000000000004d2'+
59
+ '000000000000000000000000000000000000000000000000000000000000162e'+
60
+ '000000000000000000000000000000000000000000000000000000000000000b'+
61
+ '48656c6c6f20576f726c64000000000000000000000000000000000000000000'
62
+ ABI.decode([ 'uint256[]', 'string' ], data )
63
+ #=> [[1234, 5678], "Hello World"]
64
+
65
+
66
+ ## Decoding complex structs (also known as tuples)
67
+ data = hex'00000000000000000000000000000000000000000000000000000000000004d2'+
68
+ '0000000000000000000000000000000000000000000000000000000000000040'+
69
+ '000000000000000000000000000000000000000000000000000000000000162e'+
70
+ '0000000000000000000000000000000000000000000000000000000000000040'+
71
+ '000000000000000000000000000000000000000000000000000000000000000b'+
72
+ '48656c6c6f20576f726c64000000000000000000000000000000000000000000'
73
+ ABI.decode([ 'uint256', '(uint256,string)'], data)
74
+ #=> [1234, [5678, "Hello World"]]
75
+ ```
16
76
 
17
77
 
18
78
 
data/Rakefile CHANGED
@@ -2,11 +2,18 @@ require 'hoe'
2
2
  require './lib/abicoder/version.rb'
3
3
 
4
4
 
5
+ ###
6
+ # hack/ quick fix for broken intuit_values - overwrite with dummy
7
+ class Hoe
8
+ def intuit_values( input ); end
9
+ end
10
+
11
+
5
12
  Hoe.spec 'abicoder' do
6
13
 
7
14
  self.version = ABICoder::VERSION
8
15
 
9
- self.summary = "abicoder - application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts"
16
+ self.summary = "abicoder - 'lite' application binary interface (abi) encoding / decoding machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies for easy (re)use"
10
17
  self.description = summary
11
18
 
12
19
  self.urls = { home: 'https://github.com/rubycocos/blockchain' }
@@ -0,0 +1,413 @@
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
+ class Codec
9
+ class EncodingError < StandardError; end
10
+ class DecodingError < StandardError; end
11
+ class ValueError < StandardError; end
12
+
13
+ class ValueOutOfBounds < ValueError; end
14
+
15
+ ##
16
+ # Encodes multiple arguments using the head/tail mechanism.
17
+ #
18
+ def encode_abi(types, args)
19
+ ## for convenience check if types is a String
20
+ ## otherwise assume ABI::Type already
21
+ types = types.map { |type| type.is_a?( String ) ? Type.parse( type ) : type }
22
+
23
+ head_size = (0...args.size)
24
+ .map {|i| types[i].size || 32 }
25
+ .reduce(0, &:+)
26
+
27
+ head, tail = '', ''
28
+ args.each_with_index do |arg, i|
29
+ if types[i].dynamic?
30
+ head += encode_type(Type.size_type, head_size + tail.size)
31
+ tail += encode_type(types[i], arg)
32
+ else
33
+ head += encode_type(types[i], arg)
34
+ end
35
+ end
36
+
37
+ "#{head}#{tail}"
38
+ end
39
+
40
+ ##
41
+ # Encodes a single value (static or dynamic).
42
+ #
43
+ # @param type [ABI::Type] value type
44
+ # @param arg [Object] value
45
+ #
46
+ # @return [String] encoded bytes
47
+ #
48
+ def encode_type(type, arg)
49
+ if ['string', 'bytes'].include?(type.base) && type.sub.empty?
50
+ encode_primitive_type type, arg
51
+ elsif type.dynamic?
52
+ raise ArgumentError, "arg must be an array" unless arg.instance_of?(Array)
53
+
54
+ head, tail = '', ''
55
+ if type.dims.last == 0
56
+ head += encode_type(Type.size_type, arg.size)
57
+ else
58
+ raise ArgumentError, "Wrong array size: found #{arg.size}, expecting #{type.dims.last}" unless arg.size == type.dims.last
59
+ end
60
+
61
+ sub_type = type.subtype
62
+ sub_size = type.subtype.size
63
+ arg.size.times do |i|
64
+ if sub_size.nil?
65
+ head += encode_type(Type.size_type, 32*arg.size + tail.size)
66
+ tail += encode_type(sub_type, arg[i])
67
+ else
68
+ head += encode_type(sub_type, arg[i])
69
+ end
70
+ end
71
+
72
+ "#{head}#{tail}"
73
+ else # static type
74
+ if type.dims.empty?
75
+ encode_primitive_type type, arg
76
+ else
77
+ arg.map {|x| encode_type(type.subtype, x) }.join
78
+ end
79
+ end
80
+ end
81
+
82
+ def encode_primitive_type(type, arg)
83
+ case type.base
84
+ when 'uint'
85
+ begin
86
+ real_size = type.sub.to_i
87
+ i = get_uint arg
88
+
89
+ raise ValueOutOfBounds, arg unless i >= 0 && i < 2**real_size
90
+ Utils.zpad_int i
91
+ rescue EncodingError
92
+ raise ValueOutOfBounds, arg
93
+ end
94
+ when 'bool'
95
+ raise ArgumentError, "arg is not bool: #{arg}" unless arg.instance_of?(TrueClass) || arg.instance_of?(FalseClass)
96
+ Utils.zpad_int(arg ? 1 : 0)
97
+ when 'int'
98
+ begin
99
+ real_size = type.sub.to_i
100
+ i = get_int arg
101
+
102
+ raise ValueOutOfBounds, arg unless i >= -2**(real_size-1) && i < 2**(real_size-1)
103
+ Utils.zpad_int(i % 2**type.sub.to_i)
104
+ rescue EncodingError
105
+ raise ValueOutOfBounds, arg
106
+ end
107
+ when 'string'
108
+ if arg.encoding.name == 'UTF-8'
109
+ arg = arg.b
110
+ else
111
+ begin
112
+ arg.unpack('U*')
113
+ rescue ArgumentError
114
+ raise ValueError, "string must be UTF-8 encoded"
115
+ end
116
+ end
117
+
118
+ if type.sub.empty? # variable length type
119
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
120
+ size = Utils.zpad_int arg.size
121
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
122
+ "#{size}#{value}"
123
+ else # fixed length type
124
+ sub = type.sub.to_i
125
+ raise ValueOutOfBounds, "invalid string length #{sub}" if arg.size > sub
126
+ raise ValueOutOfBounds, "invalid string length #{sub}" if sub < 0 || sub > 32
127
+ Utils.rpad(arg, BYTE_ZERO, 32)
128
+ end
129
+ when 'bytes'
130
+ raise EncodingError, "Expecting string: #{arg}" unless arg.instance_of?(String)
131
+ arg = arg.b
132
+
133
+ if type.sub.empty? # variable length type
134
+ raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size >= TT256
135
+ size = Utils.zpad_int arg.size
136
+ value = Utils.rpad arg, BYTE_ZERO, Utils.ceil32(arg.size)
137
+ "#{size}#{value}"
138
+ else # fixed length type
139
+ sub = type.sub.to_i
140
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if arg.size > sub
141
+ raise ValueOutOfBounds, "invalid bytes length #{sub}" if sub < 0 || sub > 32
142
+ Utils.rpad(arg, BYTE_ZERO, 32)
143
+ end
144
+ when 'address'
145
+ if arg.is_a?(Integer)
146
+ Utils.zpad_int arg
147
+ elsif arg.size == 20
148
+ Utils.zpad arg, 32
149
+ elsif arg.size == 40
150
+ Utils.zpad_hex arg
151
+ elsif arg.size == 42 && arg[0,2] == '0x'
152
+ Utils.zpad_hex arg[2..-1]
153
+ else
154
+ raise EncodingError, "Could not parse address: #{arg}"
155
+ end
156
+ else
157
+ raise EncodingError, "Unhandled type: #{type.base} #{type.sub}"
158
+ end
159
+ end
160
+
161
+
162
+ def min_data_size( types )
163
+ types.size*32
164
+ end
165
+
166
+ ##
167
+ # Decodes multiple arguments using the head/tail mechanism.
168
+ #
169
+ def decode_abi( types, data, raise_errors = false )
170
+ ## for convenience check if types is a String
171
+ ## otherwise assume ABI::Type already
172
+ types = types.map { |type| type.is_a?( String ) ? Type.parse( type ) : type }
173
+
174
+ outputs = [nil] * types.size
175
+ start_positions = [nil] * types.size + [data.size]
176
+
177
+ # TODO: refactor, a reverse iteration will be better
178
+ pos = 0
179
+ types.each_with_index do |t, i|
180
+ # If a type is static, grab the data directly, otherwise record its
181
+ # start position
182
+ if t.dynamic?
183
+
184
+ if raise_errors && pos>data.size-1
185
+ raise DecodingError, "Position out of bounds #{pos}>#{data.size-1}"
186
+ end
187
+
188
+ start_positions[i] = Utils.big_endian_to_int(data[pos, 32])
189
+
190
+ if raise_errors && start_positions[i]>data.size-1
191
+ raise DecodingError, "Start position out of bounds #{start_positions[i]}>#{data.size-1}"
192
+ end
193
+
194
+ j = i - 1
195
+ while j >= 0 && start_positions[j].nil?
196
+ start_positions[j] = start_positions[i]
197
+ j -= 1
198
+ end
199
+
200
+ pos += 32
201
+ else
202
+ outputs[i] = zero_padding data, pos, t.size, start_positions
203
+ pos += t.size
204
+ end
205
+ end
206
+
207
+ # We add a start position equal to the length of the entire data for
208
+ # convenience.
209
+ j = types.size - 1
210
+ while j >= 0 && start_positions[j].nil?
211
+ start_positions[j] = start_positions[types.size]
212
+ j -= 1
213
+ end
214
+
215
+ if raise_errors && pos > data.size
216
+ raise DecodingError, "Not enough data for head"
217
+ end
218
+
219
+
220
+ types.each_with_index do |t, i|
221
+ if t.dynamic?
222
+ offset, next_offset = start_positions[i, 2]
223
+ if offset<=data.size && next_offset<=data.size
224
+ outputs[i] = data[offset...next_offset]
225
+ end
226
+ end
227
+ end
228
+
229
+ if raise_errors && outputs.include?(nil)
230
+ raise DecodingError, "Not all data can be parsed"
231
+ end
232
+
233
+ types.zip(outputs).map {|(type, out)| decode_type(type, out) }
234
+ end
235
+
236
+
237
+ def zero_padding( data, pos, count, start_positions )
238
+ if pos >= data.size
239
+ start_positions[start_positions.size-1] += count
240
+ "\x00"*count
241
+ elsif pos + count > data.size
242
+ start_positions[start_positions.size-1] += ( count - (data.size-pos))
243
+ data[pos,data.size-pos] + "\x00"*( count - (data.size-pos))
244
+ else
245
+ data[pos, count]
246
+ end
247
+ end
248
+
249
+ def decode_typed_data( type_name, data )
250
+ decode_primitive_type Type.parse(type_name), data
251
+ end
252
+
253
+ def decode_type(type, arg)
254
+ return nil if arg.nil? || arg.empty?
255
+ if type.kind_of?( Tuple ) && type.dims.empty?
256
+ arg ? decode_abi(type.types, arg) : []
257
+ elsif %w(string bytes).include?(type.base) && type.sub.empty?
258
+ l = Utils.big_endian_to_int arg[0,32]
259
+ data = arg[32..-1]
260
+ data[0, l]
261
+ elsif !type.dims.empty? && (l = type.dims.last)>0 # static-sized arrays
262
+ subtype = type.subtype
263
+ if subtype.dynamic?
264
+ start_positions = (0...l).map {|i| Utils.big_endian_to_int(arg[32*i, 32]) }
265
+ start_positions.push arg.size
266
+
267
+ outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
268
+
269
+ outputs.map {|out| decode_type(subtype, out) }
270
+ else
271
+ (0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
272
+ end
273
+
274
+ elsif type.dynamic?
275
+ l = Utils.big_endian_to_int arg[0,32]
276
+ raise DecodingError, "Too long length: #{l}" if l>100000
277
+ subtype = type.subtype
278
+
279
+ if subtype.dynamic?
280
+ raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
281
+
282
+ start_positions = (1..l).map {|i| 32+Utils.big_endian_to_int(arg[32*i, 32]) }
283
+ start_positions.push arg.size
284
+
285
+ outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
286
+
287
+ outputs.map {|out| decode_type(subtype, out) }
288
+ else
289
+ (0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
290
+ end
291
+
292
+ else
293
+ decode_primitive_type type, arg
294
+ end
295
+ end
296
+
297
+ def decode_primitive_type(type, data)
298
+ case type.base
299
+ when 'address'
300
+ Utils.encode_hex data[12..-1]
301
+ when 'string', 'bytes'
302
+ if type.sub.empty? # dynamic
303
+ if data.length==32
304
+ data[0..32]
305
+ else
306
+ size = Utils.big_endian_to_int data[0,32]
307
+ data[32..-1][0,size]
308
+ end
309
+ else # fixed
310
+ data[0, type.sub.to_i]
311
+ end
312
+ when 'hash'
313
+ data[(32 - type.sub.to_i), type.sub.to_i]
314
+ when 'uint'
315
+ Utils.big_endian_to_int data
316
+ when 'int'
317
+ u = Utils.big_endian_to_int data
318
+ u >= 2**(type.sub.to_i-1) ? (u - 2**type.sub.to_i) : u
319
+ when 'ufixed'
320
+ high, low = type.sub.split('x').map(&:to_i)
321
+ Utils.big_endian_to_int(data) * 1.0 / 2**low
322
+ when 'fixed'
323
+ high, low = type.sub.split('x').map(&:to_i)
324
+ u = Utils.big_endian_to_int data
325
+ i = u >= 2**(high+low-1) ? (u - 2**(high+low)) : u
326
+ i * 1.0 / 2**low
327
+ when 'bool'
328
+ data[-1] == BYTE_ONE
329
+ else
330
+ raise DecodingError, "Unknown primitive type: #{type.base}"
331
+ end
332
+ end
333
+
334
+ private
335
+
336
+ def get_uint(n)
337
+ case n
338
+ when Integer
339
+ raise EncodingError, "Number out of range: #{n}" if n > UINT_MAX || n < UINT_MIN
340
+ n
341
+ when String
342
+ i = if n.size == 40
343
+ Utils.decode_hex(n)
344
+ elsif n.size <= 32
345
+ n
346
+ else
347
+ raise EncodingError, "String too long: #{n}"
348
+ end
349
+ i = Utils.big_endian_to_int i
350
+
351
+ raise EncodingError, "Number out of range: #{i}" if i > UINT_MAX || i < UINT_MIN
352
+ i
353
+ when true
354
+ 1
355
+ when false, nil
356
+ 0
357
+ else
358
+ raise EncodingError, "Cannot decode uint: #{n}"
359
+ end
360
+ end
361
+
362
+ def get_int(n)
363
+ case n
364
+ when Integer
365
+ raise EncodingError, "Number out of range: #{n}" if n > INT_MAX || n < INT_MIN
366
+ n
367
+ when String
368
+ i = if n.size == 40
369
+ Utils.decode_hex(n)
370
+ elsif n.size <= 32
371
+ n
372
+ else
373
+ raise EncodingError, "String too long: #{n}"
374
+ end
375
+ i = Utils.big_endian_to_int i
376
+
377
+ i = i > INT_MAX ? (i-TT256) : i
378
+ raise EncodingError, "Number out of range: #{i}" if i > INT_MAX || i < INT_MIN
379
+ i
380
+ when true
381
+ 1
382
+ when false, nil
383
+ 0
384
+ else
385
+ raise EncodingError, "Cannot decode int: #{n}"
386
+ end
387
+ end
388
+ end # class Codec
389
+
390
+
391
+
392
+ def self.codec
393
+ @codec ||= Codec.new
394
+ end
395
+
396
+ def self.encode_abi( types, args )
397
+ codec.encode_abi( types, args )
398
+ end
399
+
400
+ def self.decode_abi( types, data, raise_errors = false )
401
+ codec.decode_abi( types, data, raise_errors )
402
+ end
403
+
404
+ ## add alternate names
405
+ ## todo/fix: change to encode / decode by default
406
+ ## from encode_abi / decode_abi - why? why not?
407
+ class << self
408
+ alias_method :encode, :encode_abi
409
+ alias_method :decode, :decode_abi
410
+ end
411
+
412
+ end # module ABI
413
+
@@ -0,0 +1,141 @@
1
+ module ABI
2
+ class Type
3
+
4
+ class ParseError < StandardError; end
5
+
6
+ ##
7
+ # Crazy regexp to seperate out base type component (eg. uint), size (eg.
8
+ # 256, 128, nil), array component (eg. [], [45], nil)
9
+ #
10
+ BASE_TYPE_RX = /([a-z]*)
11
+ ([0-9]*)
12
+ ((\[[0-9]*\])*)
13
+ /x
14
+
15
+ def self._parse_base_type( str )
16
+ _, base, sub, dimension = BASE_TYPE_RX.match( str ).to_a
17
+
18
+ dims = dimension.scan( /\[[0-9]*\]/ )
19
+ if dims.join != dimension
20
+ raise ParseError, "Unknown characters found in array declaration"
21
+ end
22
+
23
+ dims = dims.map {|x| x[1...-1].to_i }
24
+
25
+ [base, sub, dims]
26
+ end
27
+
28
+
29
+
30
+ TUPLE_TYPE_RX = /^\((.*)\)
31
+ ((\[[0-9]*\])*)
32
+ /x
33
+
34
+ def self.parse( type )
35
+ if type =~ TUPLE_TYPE_RX
36
+ types = $1
37
+ dims = $2.scan( /\[[0-9]*\]/ )
38
+ dims = dims.map {|x| x[1...-1].to_i}
39
+ return Tuple._parse( types, dims )
40
+ end
41
+
42
+
43
+ base, sub, dims = _parse_base_type( type )
44
+
45
+ case base
46
+ when 'bytes', 'string'
47
+ raise ParseError, "Maximum 32 bytes for fixed-length string or bytes" unless sub.empty? || sub.to_i <= 32
48
+ when 'uint', 'int'
49
+ raise ParseError, "Integer type must have numerical suffix" unless sub =~ /\A[0-9]+\z/
50
+
51
+ size = sub.to_i
52
+ raise ParseError, "Integer size out of bounds" unless size >= 8 && size <= 256
53
+ raise ParseError, "Integer size must be multiple of 8" unless size % 8 == 0
54
+ when 'address'
55
+ raise ParseError, "Address cannot have suffix" unless sub.empty?
56
+ when 'bool'
57
+ raise ParseError, "Bool cannot have suffix" unless sub.empty?
58
+ else
59
+ puts " type: >#{type}<"
60
+ raise ParseError, "Unrecognized type base: #{base}"
61
+ end
62
+
63
+ new( base, sub, dims )
64
+ end
65
+
66
+
67
+ def self.size_type
68
+ @size_type ||= new( 'uint', '256' )
69
+ end
70
+
71
+
72
+ attr :base, :sub, :dims
73
+
74
+ ##
75
+ # @param base [String] base name of type, e.g. uint for uint256[4]
76
+ # @param sub [String] subscript of type, e.g. 256 for uint256[4]
77
+ # @param dims [Array[Integer]] dimensions of array type, e.g. [1,2,0]
78
+ # for uint256[1][2][], [] for non-array type
79
+ #
80
+ def initialize( base, sub, dims=[] )
81
+ @base = base
82
+ @sub = sub
83
+ @dims = dims
84
+ end
85
+
86
+ def ==(another_type)
87
+ @base == another_type.base &&
88
+ @sub == another_type.sub &&
89
+ @dims == another_type.dims
90
+ end
91
+
92
+ ##
93
+ # Get the static size of a type, or nil if dynamic.
94
+ #
95
+ # @return [Integer, NilClass] size of static type, or nil for dynamic type
96
+ #
97
+ def size
98
+ @size ||= calculate_size
99
+ end
100
+
101
+ def calculate_size
102
+ if @dims.empty?
103
+ if ['string','bytes'].include?( @base ) && @sub.empty?
104
+ nil
105
+ else
106
+ 32
107
+ end
108
+ else
109
+ if @dims.last == 0 # note: 0 used for dynamic array []
110
+ nil
111
+ else
112
+ subtype.dynamic? ? nil : @dims.last * subtype.size
113
+ end
114
+ end
115
+ end
116
+
117
+ def dynamic?() size.nil?; end
118
+
119
+
120
+ ##
121
+ # Type with one dimension lesser.
122
+ #
123
+ # @example
124
+ # Type.parse("uint256[2][]").subtype # => Type.new('uint', '256', [2])
125
+ #
126
+ # @return [ABI::Type]
127
+ #
128
+ def subtype
129
+ @subtype ||= Type.new( @base, @sub, @dims[0...-1] )
130
+ end
131
+
132
+
133
+ def format
134
+ ## rebuild minimal type string
135
+ buf = "#{@base}"
136
+ buf << @sub unless @sub.empty?
137
+ buf << (@dims.map {|dim| dim==0 ? '[]' : "[#{dim}]"}.join) unless @dims.empty?
138
+ buf
139
+ end
140
+ end # class Type
141
+ end # module ABI
@@ -0,0 +1,106 @@
1
+ module ABI
2
+
3
+
4
+ class Tuple < Type
5
+
6
+ def self._parse_tuple_type( str )
7
+ ## note: types assumes string WITHOUT enclosing () e.g.
8
+ ## tuple(string,string,bool) => expected as "string,string,bool"
9
+
10
+ depth = 0
11
+ collected = []
12
+ current = ''
13
+
14
+ ### todo/fix: replace with a simple parser!!!
15
+ ## allow () and move verbose tuple() too!!!
16
+ str.each_char do |c|
17
+ case c
18
+ when ',' then
19
+ if depth == 0
20
+ collected << current
21
+ current = ''
22
+ else
23
+ current += c
24
+ end
25
+ when '(' then
26
+ depth += 1
27
+ current += c
28
+ when ')' then
29
+ depth -= 1
30
+ current += c
31
+ else
32
+ current += c
33
+ end
34
+ end
35
+ collected << current unless current.empty?
36
+
37
+ collected
38
+ end
39
+
40
+
41
+ ## note: use Type.parse NOT Tuple._parse
42
+ ## to parse Tuple!!!
43
+ def self._parse( tuple, dims=[] )
44
+ ## puts " enter Tuple.parse( types: >#{tuple.inspect}<, dims: >#{dims.inspect}< )"
45
+
46
+ # e.g.
47
+ #=> enter Tuple.parse( types: >"string,string,bool"<, dims: >[]< )
48
+ types = _parse_tuple_type( tuple )
49
+ parsed_types = types.map{ |t| Type.parse( t ) }
50
+
51
+ Tuple.new( parsed_types, dims )
52
+ end
53
+
54
+
55
+
56
+ attr_reader :types
57
+
58
+ def initialize( types, dims=[] )
59
+ super( 'tuple', '', dims )
60
+ @types = types
61
+
62
+ ## puts "tuple:"
63
+ ## pp self
64
+ end
65
+
66
+
67
+ def ==(another_type)
68
+ another_type.kind_of?(Tuple) &&
69
+ @types == another_type.types &&
70
+ @dims == another_type.dims
71
+ end
72
+
73
+ def size
74
+ @size ||= calculate_size
75
+ end
76
+
77
+ def calculate_size
78
+ if @dims.empty?
79
+ s = 0
80
+ @types.each do |type|
81
+ ts = type.size
82
+ return nil if ts.nil?
83
+ s += ts
84
+ end
85
+ s
86
+ else
87
+ if @dims.last == 0 # note: 0 used for dynamic array []
88
+ nil
89
+ else
90
+ subtype.dynamic? ? nil : @dims.last * subtype.size
91
+ end
92
+ end
93
+ end
94
+
95
+ def subtype
96
+ @subtype ||= Tuple.new( types, dims[0...-1] )
97
+ end
98
+
99
+ def format
100
+ ## rebuild minimal string
101
+ buf = "(#{@types.map {|t| t.format }.join(',')})"
102
+ end
103
+
104
+
105
+ end # class Tuple
106
+ end ## module ABI
@@ -0,0 +1,81 @@
1
+ module ABI
2
+ module Helpers
3
+
4
+ ########################
5
+ # encoding / decoding helpers
6
+
7
+ def lpad(x, symbol, l)
8
+ return x if x.size >= l
9
+ symbol * (l - x.size) + x
10
+ end
11
+
12
+ def rpad(x, symbol, l)
13
+ return x if x.size >= l
14
+ x + symbol * (l - x.size)
15
+ end
16
+
17
+ def zpad(x, l)
18
+ lpad( x, BYTE_ZERO, l )
19
+ end
20
+
21
+ def zpad_int(n, l=32)
22
+ zpad( encode_int(n), l )
23
+ end
24
+
25
+ def zpad_hex(s, l=32)
26
+ zpad( decode_hex(s), l )
27
+ end
28
+
29
+
30
+
31
+ def big_endian_to_int(s)
32
+ s = s.sub( /\A(\x00)+/, '' ) ## keep "performance" shortcut - why? why not?
33
+ ### todo/check - allow nil - why? why not?
34
+ ## raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
35
+ s = s || BYTE_ZERO
36
+ s.unpack("H*").first.to_i(16)
37
+ end
38
+
39
+
40
+ def int_to_big_endian(n)
41
+ if n == 0
42
+ BYTE_EMPTY
43
+ else
44
+ hex = n.to_s(16)
45
+ hex = "0#{hex}" if hex.size.odd?
46
+
47
+ [hex].pack("H*") ## note Util.hex_to_bin() "inline" shortcut
48
+ end
49
+ end
50
+
51
+
52
+ def encode_int(n)
53
+ raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
54
+ int_to_big_endian( n )
55
+ end
56
+
57
+
58
+ def encode_hex(b)
59
+ raise TypeError, "Value must be an instance of String" unless b.instance_of?(String)
60
+ b.unpack("H*").first
61
+ end
62
+
63
+ def decode_hex(str)
64
+ raise TypeError, "Value must be an instance of string" unless str.instance_of?(String)
65
+ raise TypeError, 'Non-hexadecimal digit found' unless str =~ /\A[0-9a-fA-F]*\z/
66
+ [str].pack("H*")
67
+ end
68
+
69
+ def ceil32(x)
70
+ x % 32 == 0 ? x : (x + 32 - x%32)
71
+ end
72
+ end # module Helpers
73
+
74
+
75
+ module Utils
76
+ extend Helpers
77
+ ## e.g. Utils.encode_hex( ) etc.
78
+ end
79
+
80
+ end # module ABI
81
+
@@ -1,8 +1,8 @@
1
1
 
2
2
  module ABICoder
3
3
  MAJOR = 0
4
- MINOR = 0
5
- PATCH = 1
4
+ MINOR = 1
5
+ PATCH = 0
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
8
8
  def self.version
data/lib/abicoder.rb CHANGED
@@ -3,13 +3,43 @@
3
3
  ## our own code
4
4
  require_relative 'abicoder/version' # note: let version always go first
5
5
 
6
+ module ABI
7
+ ###################
8
+ ### some (shared) constants (move to constants.rb or such - why? why not?)
6
9
 
10
+ ## todo/check: use auto-freeze string literals magic comment - why? why not?
11
+ ##
12
+ ## todo/fix: move BYTE_EMPTY, BYTE_ZERO, BYTE_ONE to upstream to bytes gem
13
+ ## and make "global" constants - why? why not?
14
+
15
+ BYTE_EMPTY = "".b.freeze
16
+ BYTE_ZERO = "\x00".b.freeze
17
+ BYTE_ONE = "\x01".b.freeze ## note: used for encoding bool for now
7
18
 
8
- module ABI
9
19
 
20
+ UINT_MAX = 2**256 - 1
21
+ UINT_MIN = 0
22
+ INT_MAX = 2**255 - 1
23
+ INT_MIN = -2**255
10
24
 
25
+
26
+ ### todo/check - what is TT the abbrevation for ???
27
+ TT32 = 2**32
28
+ TT40 = 2**40 ## todo/check - used where and why???
29
+ TT160 = 2**160 ## todo/check - used where and why???
30
+ TT256 = 2**256 ## todo/check - used where and why???
31
+ TT64M1 = 2**64 - 1 ## todo/check - used where and why???
11
32
  end # module ABI
12
33
 
34
+ require_relative 'abicoder/utils'
35
+
36
+
37
+ require_relative 'abicoder/type'
38
+ require_relative 'abicoder/type_tuple'
39
+
40
+ require_relative 'abicoder/codec'
41
+
42
+
13
43
 
14
44
 
15
45
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abicoder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
@@ -44,8 +44,9 @@ dependencies:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
46
  version: '3.23'
47
- description: abicoder - application binary interface (abi) encoding / decoding machinery
48
- / helper for Ethereum & Co. (blockchain) contracts
47
+ description: abicoder - 'lite' application binary interface (abi) encoding / decoding
48
+ machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies
49
+ for easy (re)use
49
50
  email: wwwmake@googlegroups.com
50
51
  executables: []
51
52
  extensions: []
@@ -59,6 +60,10 @@ files:
59
60
  - README.md
60
61
  - Rakefile
61
62
  - lib/abicoder.rb
63
+ - lib/abicoder/codec.rb
64
+ - lib/abicoder/type.rb
65
+ - lib/abicoder/type_tuple.rb
66
+ - lib/abicoder/utils.rb
62
67
  - lib/abicoder/version.rb
63
68
  homepage: https://github.com/rubycocos/blockchain
64
69
  licenses:
@@ -84,6 +89,7 @@ requirements: []
84
89
  rubygems_version: 3.3.7
85
90
  signing_key:
86
91
  specification_version: 4
87
- summary: abicoder - application binary interface (abi) encoding / decoding machinery
88
- / helper for Ethereum & Co. (blockchain) contracts
92
+ summary: abicoder - 'lite' application binary interface (abi) encoding / decoding
93
+ machinery / helper for Ethereum & Co. (blockchain) contracts with zero-dependencies
94
+ for easy (re)use
89
95
  test_files: []