abicoder 0.1.1 → 1.0.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: 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: []