abicoder 0.1.1 → 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: 3bc1e75e6080a55ae3447f7cfd3630ed587edf021658dd9159584fbb3ef28743
4
- data.tar.gz: 84d27e90d278d0c2fc84db605005735164b4a63c084c087777218eadadb0044b
3
+ metadata.gz: f6aed5c766df24d4cf394f10fdaec00a7a42375cf85c5eab0a07d8f7f22ad3ec
4
+ data.tar.gz: 80c9a39735f1e383f168fdca5980d66acbf039a2bfbffe15866f0fb89a7090cf
5
5
  SHA512:
6
- metadata.gz: 597f54c3c8e21027bb10d326c7c6763653d752ad3285abc1c2086023d17c6dc9207d8e99f16a008265bff3a2ad61e1f261db501911c46df56a509660102dc019
7
- data.tar.gz: b776e32b5afd57e233aef6ffb87dba66b65272533f60f635c371e0a199fad593a1bd55cd6248f39a3002849b66e77ecf1bb7bd06f3ccc453cc87854d0c77d059
6
+ metadata.gz: 9142a552978835f45a2be5bd80190905b9feeb519f135c8ae72f428826a2e7ccec467a1dbbfbfef8ddd2b097b1397f008429379230abc8ac6bcaf4aa53f7ec99
7
+ data.tar.gz: 37a0e2773d33a8c74881ccd8ab137e362dacde34c6dd076be0eed6ffe5b22f302ac8cdd2220e94ed124b5036a836b21b7977c0629daf9e749c45b2465e4aab47
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)
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' }
@@ -7,6 +7,10 @@ class Decoder
7
7
  # Decodes multiple arguments using the head/tail mechanism.
8
8
  #
9
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
+
10
14
  ## for convenience check if types is a String
11
15
  ## otherwise assume ABI::Type already
12
16
  types = types.map { |type| type.is_a?( Type ) ? type : Type.parse( type ) }
@@ -14,7 +18,9 @@ class Decoder
14
18
  outputs = [nil] * types.size
15
19
  start_positions = [nil] * types.size + [data.size]
16
20
 
17
- # TODO: refactor, a reverse iteration will be better
21
+ # TODO: refactor, a reverse iteration will be better - why? why not?
22
+ # try to simplify / clean-up code - possible? why? why not?
23
+
18
24
  pos = 0
19
25
  types.each_with_index do |t, i|
20
26
  # If a type is static, grab the data directly, otherwise record its
@@ -29,7 +35,7 @@ class Decoder
29
35
  end
30
36
  end
31
37
 
32
- start_positions[i] = big_endian_to_int(data[pos, 32])
38
+ start_positions[i] = decode_uint256(data[pos, 32])
33
39
 
34
40
  if start_positions[i]>data.size-1
35
41
  if raise_errors
@@ -50,7 +56,19 @@ class Decoder
50
56
  else
51
57
  ## puts "step 1 - decode item [#{i}] - #{t.format} size: #{t.size} dynamic? #{t.dynamic?}"
52
58
 
53
- outputs[i] = zero_padding( data, pos, t.size, start_positions )
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
+
54
72
  pos += t.size
55
73
  end
56
74
  end
@@ -83,7 +101,7 @@ class Decoder
83
101
  end
84
102
  end
85
103
 
86
- if outputs.include?(nil)
104
+ if outputs.include?( nil )
87
105
  if raise_errors
88
106
  raise DecodingError, "Not all data can be parsed"
89
107
  else
@@ -101,106 +119,87 @@ class Decoder
101
119
 
102
120
 
103
121
 
104
- def decode_type( type, arg )
105
- return nil if arg.nil? || arg.empty?
106
122
 
123
+ def decode_type( type, data )
124
+ return nil if data.nil? || data.empty?
107
125
 
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) : []
126
+ if type.is_a?( Tuple ) ## todo: support empty (unit) tuple - why? why not?
127
+ decode( type.types, data )
114
128
  elsif type.is_a?( FixedArray ) # static-sized arrays
115
129
  l = type.dim
116
130
  subtype = type.subtype
117
131
  if subtype.dynamic?
118
- start_positions = (0...l).map {|i| big_endian_to_int(arg[32*i, 32]) }
119
- start_positions.push arg.size
132
+ start_positions = (0...l).map {|i| decode_uint256(data[32*i, 32]) }
133
+ start_positions.push( data.size )
120
134
 
121
- outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
135
+ outputs = (0...l).map {|i| data[start_positions[i]...start_positions[i+1]] }
122
136
 
123
137
  outputs.map {|out| decode_type(subtype, out) }
124
138
  else
125
- (0...l).map {|i| decode_type(subtype, arg[subtype.size*i, subtype.size]) }
139
+ (0...l).map {|i| decode_type(subtype, data[subtype.size*i, subtype.size]) }
126
140
  end
127
141
  elsif type.is_a?( Array )
128
- l = big_endian_to_int( arg[0,32] )
142
+ l = decode_uint256( data[0,32] )
129
143
  raise DecodingError, "Too long length: #{l}" if l > 100000
130
144
  subtype = type.subtype
131
145
 
132
146
  if subtype.dynamic?
133
- raise DecodingError, "Not enough data for head" unless arg.size >= 32 + 32*l
147
+ raise DecodingError, "Not enough data for head" unless data.size >= 32 + 32*l
134
148
 
135
- start_positions = (1..l).map {|i| 32+big_endian_to_int(arg[32*i, 32]) }
136
- start_positions.push arg.size
149
+ start_positions = (1..l).map {|i| 32+decode_uint256(data[32*i, 32]) }
150
+ start_positions.push( data.size )
137
151
 
138
- outputs = (0...l).map {|i| arg[start_positions[i]...start_positions[i+1]] }
152
+ outputs = (0...l).map {|i| data[start_positions[i]...start_positions[i+1]] }
139
153
 
140
154
  outputs.map {|out| decode_type(subtype, out) }
141
155
  else
142
- (0...l).map {|i| decode_type(subtype, arg[32 + subtype.size*i, subtype.size]) }
156
+ (0...l).map {|i| decode_type(subtype, data[32 + subtype.size*i, subtype.size]) }
143
157
  end
144
158
  else
145
- decode_primitive_type( type, arg )
159
+ decode_primitive_type( type, data )
146
160
  end
147
161
  end
148
162
 
149
163
 
150
164
  def decode_primitive_type(type, data)
151
165
  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
166
  when Uint
164
- big_endian_to_int( data )
167
+ decode_uint256( data )
165
168
  when Int
166
- u = big_endian_to_int( data )
169
+ u = decode_uint256( data )
167
170
  u >= 2**(type.bits-1) ? (u - 2**type.bits) : u
168
171
  when Bool
169
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 )
170
185
  else
171
186
  raise DecodingError, "Unknown primitive type: #{type.class.name} #{type.format}"
172
187
  end
173
188
  end
174
189
 
175
190
 
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
191
 
189
192
  ###########
190
193
  # decoding helpers / utils
191
194
 
192
- def big_endian_to_int( bin )
193
- bin = bin.sub( /\A(\x00)+/, '' ) ## keep "performance" shortcut - why? why not?
195
+ def decode_uint256( bin )
196
+ # bin = bin.sub( /\A(\x00)+/, '' ) ## keep "performance" shortcut - why? why not?
194
197
  ### todo/check - allow nil - why? why not?
195
198
  ## raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
196
- bin = bin || BYTE_ZERO
199
+ # bin = bin || BYTE_ZERO
197
200
  bin.unpack("H*").first.to_i(16)
198
201
  end
199
202
 
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
203
 
205
204
 
206
205
  end # class Decoder
@@ -18,11 +18,11 @@ class Encoder
18
18
 
19
19
  ##
20
20
  # Encodes multiple arguments using the head/tail mechanism.
21
+ # returns binary string (with BINARY / ASCII_8BIT encoding)
21
22
  #
22
23
  def encode( types, args )
23
-
24
- ## todo/fix: enforce args.size and types.size must match!!
25
- ## raise ArgumentError - expected x arguments got y!!!
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
26
 
27
27
 
28
28
  ## for convenience check if types is a String
@@ -35,7 +35,7 @@ class Encoder
35
35
  .map {|type| type.size || 32 }
36
36
  .sum
37
37
 
38
- head, tail = '', ''
38
+ head, tail = ''.b, ''.b ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
39
39
  types.each_with_index do |type, i|
40
40
  if type.dynamic?
41
41
  head += encode_uint256( head_size + tail.size )
@@ -57,10 +57,6 @@ class Encoder
57
57
  # @return [String] encoded bytes
58
58
  #
59
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
60
  if type.is_a?( String )
65
61
  encode_string( arg )
66
62
  elsif type.is_a?( Bytes )
@@ -82,7 +78,7 @@ class Encoder
82
78
  def encode_dynamic_array( type, args )
83
79
  raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
84
80
 
85
- head, tail = '', ''
81
+ head, tail = ''.b, ''.b ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
86
82
 
87
83
  if type.is_a?( Array ) ## dynamic array
88
84
  head += encode_uint256( args.size )
@@ -123,7 +119,7 @@ class Encoder
123
119
  .map {|type| type.size || 32 }
124
120
  .sum
125
121
 
126
- head, tail = '', ''
122
+ head, tail = ''.b, ''.b ## note: use string buffer with BINARY / ASCII_8BIT encoding!!!
127
123
  tuple.types.each_with_index do |type, i|
128
124
  if type.dynamic?
129
125
  head += encode_uint256( head_size + tail.size )
@@ -164,19 +160,22 @@ class Encoder
164
160
 
165
161
 
166
162
  def encode_bool( arg )
163
+ ## raise EncodingError or ArgumentError - why? why not?
167
164
  raise ArgumentError, "arg is not bool: #{arg}" unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
168
- lpad_int( arg ? 1 : 0 )
165
+ lpad( arg ? BYTE_ONE : BYTE_ZERO ) ## was lpad_int( arg ? 1 : 0 )
169
166
  end
170
167
 
171
168
 
172
169
  def encode_uint256( arg ) encode_uint( arg, 256 ); end
173
170
  def encode_uint( arg, bits )
171
+ ## raise EncodingError or ArgumentError - why? why not?
174
172
  raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
175
173
  raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**bits
176
174
  lpad_int( arg )
177
175
  end
178
176
 
179
177
  def encode_int( arg, bits )
178
+ ## raise EncodingError or ArgumentError - why? why not?
180
179
  raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
181
180
  raise ValueOutOfBounds, arg unless arg >= -2**(bits-1) && arg < 2**(bits-1)
182
181
  lpad_int( arg % 2**bits )
@@ -184,31 +183,21 @@ class Encoder
184
183
 
185
184
 
186
185
  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
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'
200
189
 
201
190
  raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
202
191
  size = lpad_int( arg.size )
203
192
  value = rpad( arg, ceil32(arg.size) )
204
-
205
193
  size + value
206
194
  end
207
195
 
208
196
 
209
197
  def encode_bytes( arg, length=nil )
198
+ ## raise EncodingError or ArgumentError - why? why not?
210
199
  raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
211
- arg = arg.b
200
+ arg = arg.b if arg.encoding != Encoding::BINARY
212
201
 
213
202
  if length # fixed length type
214
203
  raise ValueOutOfBounds, "invalid bytes length #{length}" if arg.size > length
@@ -227,6 +216,8 @@ class Encoder
227
216
  if arg.is_a?( Integer )
228
217
  lpad_int( arg )
229
218
  elsif arg.size == 20
219
+ ## note: make sure encoding is always binary!!!
220
+ arg = arg.b if arg.encoding != Encoding::BINARY
230
221
  lpad( arg )
231
222
  elsif arg.size == 40
232
223
  lpad_hex( arg )
@@ -238,34 +229,42 @@ class Encoder
238
229
  end
239
230
 
240
231
 
232
+
241
233
  ###########
242
234
  # encoding helpers / utils
235
+ # with "hard-coded" fill symbol as BYTE_ZERO
243
236
 
244
- def rpad( bin, l=32, symbol=BYTE_ZERO ) ## note: same as builtin String#ljust !!!
237
+ def rpad( bin, l=32 ) ## note: same as builtin String#ljust !!!
238
+ # note: default l word is 32 bytes
245
239
  return bin if bin.size >= l
246
- bin + symbol * (l - bin.size)
240
+ bin + BYTE_ZERO * (l - bin.size)
247
241
  end
248
242
 
249
- def lpad( bin, l=32, symbol=BYTE_ZERO ) ## note: same as builtin String#rjust !!!
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
250
247
  return bin if bin.size >= l
251
- symbol * (l - bin.size) + bin
248
+ BYTE_ZERO * (l - bin.size) + bin
252
249
  end
253
250
 
254
- def lpad_int( n, l=32, symbol=BYTE_ZERO )
251
+ ## rename to lpad32_int or such - why? why not?
252
+ def lpad_int( n )
255
253
  raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
256
254
  hex = n.to_s(16)
257
255
  hex = "0#{hex}" if hex.size.odd?
258
256
  bin = [hex].pack("H*")
259
257
 
260
- lpad( bin, l, symbol )
258
+ lpad( bin )
261
259
  end
262
260
 
263
- def lpad_hex( hex, l=32, symbol=BYTE_ZERO )
261
+ ## rename to lpad32_hex or such - why? why not?
262
+ def lpad_hex( hex )
264
263
  raise TypeError, "Value must be a string" unless hex.is_a?( ::String )
265
264
  raise TypeError, 'Non-hexadecimal digit found' unless hex =~ /\A[0-9a-fA-F]*\z/
266
265
  bin = [hex].pack("H*")
267
266
 
268
- lpad( bin, l, symbol )
267
+ lpad( bin )
269
268
  end
270
269
 
271
270
 
@@ -1,8 +1,8 @@
1
1
 
2
2
  module ABICoder
3
- MAJOR = 0
4
- MINOR = 1
5
- PATCH = 1
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ PATCH = 0
6
6
  VERSION = [MAJOR,MINOR,PATCH].join('.')
7
7
 
8
8
  def self.version
data/lib/abicoder.rb CHANGED
@@ -13,7 +13,7 @@ module ABI
13
13
  ## todo/fix: move BYTE_EMPTY, BYTE_ZERO, BYTE_ONE to upstream to bytes gem
14
14
  ## and make "global" constants - why? why not?
15
15
 
16
- BYTE_EMPTY = "".b.freeze
16
+ ## BYTE_EMPTY = "".b.freeze
17
17
  BYTE_ZERO = "\x00".b.freeze
18
18
  BYTE_ONE = "\x01".b.freeze ## note: used for encoding bool for now
19
19
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abicoder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gerald Bauer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-01-04 00:00:00.000000000 Z
11
+ date: 2023-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rdoc
@@ -45,8 +45,8 @@ dependencies:
45
45
  - !ruby/object:Gem::Version
46
46
  version: '3.23'
47
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
48
+ machinery / helper (incl. nested arrays and/or tuples) for Ethereum & Co. (blockchain)
49
+ contracts with zero-dependencies for easy (re)use
50
50
  email: wwwmake@googlegroups.com
51
51
  executables: []
52
52
  extensions: []
@@ -90,6 +90,6 @@ rubygems_version: 3.3.7
90
90
  signing_key:
91
91
  specification_version: 4
92
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
93
+ machinery / helper (incl. nested arrays and/or tuples) for Ethereum & Co. (blockchain)
94
+ contracts with zero-dependencies for easy (re)use
95
95
  test_files: []