abi_coder_rb 0.2.8 → 0.2.9

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: 2446e20c68206c39fddd2cc887136d0188a74283c82240f95d9fd3d7fce19d15
4
- data.tar.gz: '0158a5505f945dc0655a1c80cf8722ff4d15610bf2e027fea15cd21de917aa1c'
3
+ metadata.gz: de0b1b60b90aa2361dc35e4511026c148913c1a1dffc976514c1f7b00eaa54ce
4
+ data.tar.gz: 95bfd4733c2e7590cecf11104462da65481c0c0168dea1d5c280e4167cfe4cdf
5
5
  SHA512:
6
- metadata.gz: 26d7209d606865c01c539a3e5611e25477ce8ba3f2f1e9e54eb0c879ac510ca047979c066905a430fceab5f23f18b8c680b594341834a900d03e983d2241be03
7
- data.tar.gz: cac0312067c262e52808ff92b7b36e9a85e87660e306f1e13205a19eb1107bb1a9fb423b2fd6641949c0e4e130b3bb20adb114c1d0fa83f00b5e81923173b2b3
6
+ metadata.gz: 83c52580ddf1abfba83900fe006c28ec647870d2fcc7618abafb50a5b3cc9190280124c45bd5941fa962292f13e37e02cff3b7550c6643ba63a9365df75cf043
7
+ data.tar.gz: 15285c84c9dac789f4274cc12d9ea93dd43f4be6e265402ef2efa63c592ad04ae847f9880eddd9c4684fb385f76e2f8ebf79dacca64c58254292117d2fae7120
data/README.md CHANGED
@@ -1,19 +1,22 @@
1
1
  # AbiCoderRb
2
2
 
3
- modified from https://github.com/rubycocos/blockchain/blob/master/abicoder
3
+ This is a ruby implementation of the Ethereum ABI codec. It passes all the codec tests in [web3.js].
4
4
 
5
- for better readability code and deep learning abi codec.
5
+ ## Why I write this gem
6
6
 
7
- Changes compared to original code:
7
+ Originally, I just wanted to gain a deeper understanding of the Ethereum ABI codec by studying this [code](https://github.com/rubycocos/blockchain/blob/master/abicoder).
8
8
 
9
- 0. Adjusted files structure
10
- 1. The biggest change for readability is that the 'data' to decode in every decode_* function is no longer exact but now includes both the data needed for decoding and the remaining data. This change means that in the entry point('AbiCoderRb.decode'), there's no longer a need to calculate the precise data required for decoding for each type. This simplification streamlines the code.
11
- 2. Fixed some encoding end decoding bugs.
12
- 3. Use string to describe any abi type. This is for compatibility with other abi libs.
13
- 4. Added pre- encoding and post- decoding callbacks to facilitate transforming data before encoding and after decoding. See [1](https://github.com/wuminzhe/abi_coder_rb/blob/main/spec/transform_before_encode_spec.rb#L4C1-L12C4) [2](https://github.com/wuminzhe/abi_coder_rb/blob/main/spec/web3_js_abitests_spec.rb#L27C1-L49C4)
14
- 5. pass all web3.js tests in [encodeDecodeParams.test.ts](https://github.com/web3/web3.js/blob/c490c1814da646a83c6a5f7fee643e35507c9344/packages/web3-eth-abi/test/unit/encodeDecodeParams.test.ts). That is about 1024 unit tests from fixture [abitestsdata.json](https://github.com/web3/web3.js/blob/c490c1814da646a83c6a5f7fee643e35507c9344/packages/web3-eth-abi/test/fixtures/abitestsdata.json).
9
+ When I read the code, I found that the code is not easy to read and understand. So I decided to rewrite it. This gem is the result.
15
10
 
16
- Also, some code was modified to compile to wasm. Try it online: https://wuminzhe.github.io/abi.html
11
+ ## Features
12
+
13
+ 1. Clear files and code structure.
14
+ 2. Use string to describe any abi type. This is for compatibility with other abi libs.
15
+ 3. Pre- encoding and post- decoding callbacks to facilitate transforming data before encoding and after decoding. See [1](https://github.com/wuminzhe/abi_coder_rb/blob/main/spec/transform_before_encode_spec.rb#L4C1-L12C4) [2](https://github.com/wuminzhe/abi_coder_rb/blob/main/spec/web3_js_abitests_spec.rb#L27C1-L49C4)
16
+ 4. Passed all web3.js tests in [encodeDecodeParams.test.ts](https://github.com/web3/web3.js/blob/c490c1814da646a83c6a5f7fee643e35507c9344/packages/web3-eth-abi/test/unit/encodeDecodeParams.test.ts). That is about 1024 unit tests from fixture [abitestsdata.json](https://github.com/web3/web3.js/blob/c490c1814da646a83c6a5f7fee643e35507c9344/packages/web3-eth-abi/test/fixtures/abitestsdata.json).
17
+ 5. support packed encoding similar to `abi.encodePacked`. See [test](./spec/packed_encoding_spec.rb)
18
+ 6. Clear and robust ABI parser which parses the ABI string to an AST tree. See [abi_parser](lib/abi_coder_rb/parser/abi_parser.rb)
19
+ 7. Can be compiled to wasm. Try it online: https://wuminzhe.github.io/abi.html
17
20
 
18
21
  ## Installation
19
22
 
@@ -81,6 +84,33 @@ end
81
84
  Hello.new.world # => ["Hello World"]
82
85
  ```
83
86
 
87
+ ### Encode packed
88
+
89
+ ```ruby
90
+ # encodePacked
91
+ type = "int64"
92
+ value = 17
93
+
94
+ encode(type, value, true) # => "0x0000000000000011"
95
+ ```
96
+
97
+ ### Encode values one by one
98
+
99
+ It is similar to `abi.encode(v1, v2, ..)` or `abi.encodePacked(v1, v2, ..)` in solidity.
100
+
101
+ ```ruby
102
+ types = ["int32", "uint64"]
103
+ values = [17, 17]
104
+
105
+ # abi.encode(v1, v2)
106
+ encode(types, values) # => "0x00000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000011"
107
+
108
+ # abi.encodePacked(v1, v2)
109
+ encode(types, values, true) # => "0x000000110000000000000011"
110
+ ```
111
+
112
+
113
+
84
114
  ## Development
85
115
 
86
116
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -3,9 +3,9 @@ module AbiCoderRb
3
3
  size = decode_uint256(data[0, 32])
4
4
  raise DecodingError, "Too many elements: #{size}" if size > 100_000
5
5
 
6
- subtype = type.subtype
6
+ inner_type = type.inner_type
7
7
 
8
- if subtype.dynamic?
8
+ if inner_type.dynamic?
9
9
  raise DecodingError, "Not enough data for head" unless data.size >= 32 + 32 * size
10
10
 
11
11
  start_positions = (1..size).map { |i| 32 + decode_uint256(data[32 * i, 32]) }
@@ -13,9 +13,9 @@ module AbiCoderRb
13
13
 
14
14
  outputs = (0...size).map { |i| data[start_positions[i]...start_positions[i + 1]] }
15
15
 
16
- outputs.map { |out| decode_type(subtype, out) }
16
+ outputs.map { |out| decode_type(inner_type, out) }
17
17
  else
18
- (0...size).map { |i| decode_type(subtype, data[(32 + subtype.size * i)..]) }
18
+ (0...size).map { |i| decode_type(inner_type, data[(32 + inner_type.size * i)..]) }
19
19
  end
20
20
  end
21
21
  end
@@ -1,16 +1,16 @@
1
1
  module AbiCoderRb
2
2
  def decode_fixed_array(type, data)
3
- l = type.dim
4
- subtype = type.subtype
5
- if subtype.dynamic?
3
+ l = type.length
4
+ inner_type = type.inner_type
5
+ if inner_type.dynamic?
6
6
  start_positions = (0...l).map { |i| decode_uint256(data[32 * i, 32]) }
7
7
  start_positions.push(data.size)
8
8
 
9
9
  outputs = (0...l).map { |i| data[start_positions[i]...start_positions[i + 1]] }
10
10
 
11
- outputs.map { |out| decode_type(subtype, out) }
11
+ outputs.map { |out| decode_type(inner_type, out) }
12
12
  else
13
- (0...l).map { |i| decode_type(subtype, data[subtype.size * i, subtype.size]) }
13
+ (0...l).map { |i| decode_type(inner_type, data[inner_type.size * i, inner_type.size]) }
14
14
  end
15
15
  end
16
16
  end
@@ -1,6 +1,6 @@
1
1
  module AbiCoderRb
2
2
  def decode_tuple(type, data)
3
- decode_types(type.types, data)
3
+ decode_types(type.inner_types, data)
4
4
  end
5
5
 
6
6
  private
@@ -1,6 +1,6 @@
1
1
  module AbiCoderRb
2
2
  def encode_array(type, args, packed = false)
3
- raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
3
+ raise EncodingError, "args must be an array" unless args.is_a?(::Array)
4
4
 
5
5
  _encode_array(type: type, args: args, packed: packed)
6
6
  end
@@ -14,15 +14,14 @@ module AbiCoderRb
14
14
  # 数组长度
15
15
  head += encode_uint256(args.size) if type.is_a?(Array) && !packed
16
16
 
17
- subtype = type.subtype
18
17
  args.each do |arg|
19
- if subtype.dynamic?
20
- raise "#{type.class} with dynamic inner type is not supported in packed mode" if packed
18
+ if type.inner_type.dynamic?
19
+ raise EncodingError, "#{type.class} with dynamic inner type is not supported in packed mode" if packed
21
20
 
22
21
  head += encode_uint256(32 * args.size + tail.size) # 当前数据的位置指针
23
- tail += encode_type(subtype, arg)
22
+ tail += encode_type(type.inner_type, arg)
24
23
  else
25
- head += encode_type(subtype, arg)
24
+ head += encode_type(type.inner_type, arg)
26
25
  end
27
26
  end
28
27
 
@@ -1,7 +1,7 @@
1
1
  module AbiCoderRb
2
2
  def encode_fixed_array(type, args, packed = false)
3
- raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
4
- raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim
3
+ raise EncodingError, "args must be an array" unless args.is_a?(::Array)
4
+ raise EncodingError, "Wrong array size: found #{args.size}, expecting #{type.length}" unless args.size == type.length
5
5
 
6
6
  # fixed_array,是没有元素数量的编码de
7
7
  # 如果内部类型是静态的,就是一个一个元素编码后加起来。
@@ -25,7 +25,7 @@ module AbiCoderRb
25
25
  end
26
26
 
27
27
  def encode_uint(arg, bits, packed = false)
28
- raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
28
+ raise EncodingError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
29
29
  raise ValueOutOfBounds, arg unless arg >= 0 && arg < 2**bits
30
30
 
31
31
  if packed
@@ -51,8 +51,7 @@ module AbiCoderRb
51
51
  end
52
52
 
53
53
  def encode_bool(arg, packed = false)
54
- ## raise EncodingError or ArgumentError - why? why not?
55
- raise ArgumentError, "arg is not bool: #{arg}" unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
54
+ raise EncodingError, "arg is not bool: #{arg}" unless arg.is_a?(TrueClass) || arg.is_a?(FalseClass)
56
55
 
57
56
  if packed
58
57
  arg ? BYTE_ONE : BYTE_ZERO
@@ -62,7 +61,6 @@ module AbiCoderRb
62
61
  end
63
62
 
64
63
  def encode_string(arg, packed = false)
65
- ## raise EncodingError or ArgumentError - why? why not?
66
64
  raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
67
65
 
68
66
  arg = arg.b if arg.encoding != "BINARY" ## was: name == 'UTF-8', wasm
@@ -79,7 +77,6 @@ module AbiCoderRb
79
77
  end
80
78
 
81
79
  def encode_bytes(arg, length: nil, packed: false)
82
- ## raise EncodingError or ArgumentError - why? why not?
83
80
  raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
84
81
 
85
82
  arg = arg.b if arg.encoding != Encoding::BINARY
@@ -1,17 +1,17 @@
1
1
  module AbiCoderRb
2
2
  def encode_tuple(tuple, args, packed = false)
3
- raise "#{tuple.class} with multi inner types is not supported in packed mode" if packed && tuple.types.size > 1
3
+ raise "#{tuple.class} with multi inner type is not supported in packed mode" if packed && tuple.inner_types.size > 1
4
4
 
5
- encode_types(tuple.types, args, packed)
5
+ encode_types(tuple.inner_types, args, packed)
6
6
  end
7
7
 
8
8
  private
9
9
 
10
10
  def encode_types(types, args, packed = false)
11
- raise ArgumentError, "args must be an array" unless args.is_a?(::Array)
11
+ raise EncodingError, "args must be an array" unless args.is_a?(::Array)
12
12
 
13
13
  unless args.size == types.size
14
- raise ArgumentError,
14
+ raise EncodingError,
15
15
  "Wrong number of args: found #{args.size}, expecting #{types.size}"
16
16
  end
17
17
 
@@ -5,25 +5,14 @@ require_relative "encode/encode_primitive_type"
5
5
 
6
6
  module AbiCoderRb
7
7
  # returns byte array
8
- def encode(typestr_or_typestrs, value_or_values, packed = false)
9
- if typestr_or_typestrs.is_a?(::Array)
10
- raise EncodingError, "values should be an array" unless value_or_values.is_a?(::Array)
8
+ def encode(str, value, packed = false)
9
+ return encode_type(Type.parse(str), value, packed) if str.is_a?(::String)
11
10
 
12
- typestrs = typestr_or_typestrs
13
- values = value_or_values
14
- typestrs.map.with_index do |typestr, i|
15
- value = values[i]
16
- encode(typestr, value, packed)
17
- end.join
18
- else
19
- typestr = typestr_or_typestrs
20
- value = value_or_values
21
- # TODO: more checks?
22
- raise EncodingError, "Value can not be nil" if value.nil?
11
+ return str.map.with_index do |type, i|
12
+ encode(type, value[i], packed)
13
+ end.join("") if str.is_a?(::Array) && value.is_a?(::Array) && str.size == value.size
23
14
 
24
- parsed = Type.parse(typestr)
25
- encode_type(parsed, value, packed)
26
- end
15
+ raise EncodingError, "There is something wrong with #{str.inspect}, #{value.inspect}"
27
16
  end
28
17
 
29
18
  private
@@ -0,0 +1,96 @@
1
+ require_relative 'abi_tokenizer'
2
+
3
+ module AbiCoderRb
4
+ class AbiParser
5
+ def initialize(abi)
6
+ @tokenizer = AbiTokenizer.new(abi)
7
+ @current_token = @tokenizer.next_token
8
+ end
9
+
10
+ def parse
11
+ element =
12
+ case @current_token
13
+ when "string", "address", "bool" then parse_simple_type
14
+ when "uint", "int" then parse_numeric_type
15
+ when "bytes" then parse_bytes
16
+ when "(" then parse_tuple
17
+ else
18
+ raise ParseError, "Unexpected token: #{@current_token}"
19
+ end
20
+
21
+ @current_token == "[" ? parse_array(element) : element
22
+ end
23
+
24
+ private
25
+
26
+ def parse_bytes
27
+ result =
28
+ if @tokenizer.peek_token =~ /^\d+$/
29
+ @current_token = @tokenizer.next_token
30
+ { type: 'bytes', length: @current_token.to_i }
31
+ else
32
+ { type: 'bytes' }
33
+ end
34
+ @current_token = @tokenizer.next_token
35
+ result
36
+ end
37
+
38
+ def parse_numeric_type
39
+ type = @current_token
40
+ result =
41
+ if @tokenizer.peek_token =~ /^\d+$/
42
+ @current_token = @tokenizer.next_token
43
+ { type: type, bits: @current_token.to_i }
44
+ else
45
+ { type: type, bits: 256 }
46
+ end
47
+ @current_token = @tokenizer.next_token
48
+ result
49
+ end
50
+
51
+ def parse_simple_type
52
+ type = @current_token
53
+ @current_token = @tokenizer.next_token
54
+ { type: type }
55
+ end
56
+
57
+ def parse_tuple
58
+ inner_types = []
59
+
60
+ expect("(")
61
+ until @current_token == ")"
62
+ inner_types << parse
63
+ expect(",") if @current_token != ")"
64
+ end
65
+ expect(")")
66
+
67
+ { type: "tuple", inner_types: inner_types }
68
+ end
69
+
70
+ def parse_array(element)
71
+ arr = { type: 'array', inner_type: element, length: parse_array_length }
72
+ @current_token == "[" ? parse_array(arr) : arr
73
+ end
74
+
75
+ def parse_array_length
76
+ expect("[")
77
+ if @current_token == "]"
78
+ length = nil
79
+ elsif @current_token =~ /^\d+$/
80
+ length = @current_token.to_i
81
+ @current_token = @tokenizer.next_token
82
+ else
83
+ raise ParseError, "Expected array length or closing ']'"
84
+ end
85
+ expect("]")
86
+
87
+ length
88
+ end
89
+
90
+ def expect(token)
91
+ raise "Expected #{token}, got #{@current_token}" unless @current_token == token
92
+
93
+ @current_token = @tokenizer.next_token
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,57 @@
1
+ module AbiCoderRb
2
+ class AbiTokenizer
3
+ def initialize(abi)
4
+ @abi = abi
5
+ @index = 0
6
+ end
7
+
8
+ def next_token
9
+ skip_whitespace
10
+
11
+ return nil if @index >= @abi.length
12
+
13
+ char = @abi[@index]
14
+
15
+ case char
16
+ when '(', ')', ',', '[', ']'
17
+ @index += 1
18
+ char
19
+ else
20
+ read_identifier
21
+ end
22
+ end
23
+
24
+ def peek_token
25
+ original_index = @index
26
+ token = next_token
27
+ @index = original_index
28
+ token
29
+ end
30
+
31
+ private
32
+
33
+ def skip_whitespace
34
+ while @index < @abi.length && @abi[@index].match?(/\s/)
35
+ @index += 1
36
+ end
37
+ end
38
+
39
+ def read_identifier
40
+ start_index = @index
41
+
42
+ # if the first character is a digit, read the whole number
43
+ if @abi[@index].match?(/\d/)
44
+ while @index < @abi.length && @abi[@index].match?(/\d/)
45
+ @index += 1
46
+ end
47
+ else
48
+ # Move the index until a non-identifier character or digit is found
49
+ while @index < @abi.length && !@abi[@index].match?(/[\(\),\[\]\s\d]/)
50
+ @index += 1
51
+ end
52
+ end
53
+ @abi[start_index...@index]
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,45 @@
1
+ require_relative '../parser/abi_parser'
2
+
3
+ module AbiCoderRb
4
+ class Type
5
+ class << self
6
+ def parse(str)
7
+ abi_type_element = AbiCoderRb::AbiParser.new(str).parse
8
+ create_type(abi_type_element)
9
+ end
10
+
11
+ private
12
+
13
+ def create_type(element)
14
+ case element[:type]
15
+ when 'string' then String.new
16
+ when 'address' then Address.new
17
+ when 'bool' then Bool.new
18
+ when 'uint', 'int' then create_numeric_type(element)
19
+ when 'bytes' then create_bytes_type(element)
20
+ when 'tuple' then create_tuple_type(element)
21
+ when 'array' then create_array_type(element)
22
+ else
23
+ raise ParseError, "Unknown type: #{element}"
24
+ end
25
+ end
26
+
27
+ def create_tuple_type(abi_type)
28
+ Tuple.new(abi_type[:inner_types].map { |t| create_type(t) })
29
+ end
30
+
31
+ def create_numeric_type(abi_type)
32
+ abi_type[:type] == 'uint' ? Uint.new(abi_type[:bits]) : Int.new(abi_type[:bits])
33
+ end
34
+
35
+ def create_bytes_type(abi_type)
36
+ abi_type[:length] ? FixedBytes.new(abi_type[:length]) : Bytes.new
37
+ end
38
+
39
+ def create_array_type(abi_type)
40
+ inner_type = create_type(abi_type[:inner_type])
41
+ abi_type[:length] ? FixedArray.new(inner_type, abi_type[:length]) : Array.new(inner_type)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,199 @@
1
+ module AbiCoderRb
2
+ class Type
3
+ def size
4
+ raise NotImplementedError, "size method not defined for #{self.class.name}"
5
+ end
6
+
7
+ def dynamic?
8
+ size.nil?
9
+ end
10
+
11
+ def format
12
+ raise NotImplementedError, "format method not defined for #{self.class.name}"
13
+ end
14
+ end
15
+
16
+ class Address < Type
17
+ def size
18
+ 32
19
+ end
20
+
21
+ def format
22
+ "address"
23
+ end
24
+
25
+ def ==(other)
26
+ other.is_a?(Address)
27
+ end
28
+ end
29
+
30
+ class Bytes < Type
31
+ def size
32
+ nil
33
+ end
34
+
35
+ def format
36
+ "bytes"
37
+ end
38
+
39
+ def ==(other)
40
+ other.is_a?(Bytes)
41
+ end
42
+ end
43
+
44
+ class FixedBytes < Type
45
+ attr_reader :length
46
+
47
+ def initialize(length)
48
+ @length = length
49
+ end
50
+
51
+ def size
52
+ 32
53
+ end
54
+
55
+ def format
56
+ "bytes#{@length}"
57
+ end
58
+
59
+ def ==(other)
60
+ other.is_a?(FixedBytes) && @length == other.length
61
+ end
62
+ end
63
+
64
+ class Int < Type
65
+ attr_reader :bits
66
+
67
+ def initialize(bits = 256)
68
+ @bits = bits
69
+ end
70
+
71
+ def size
72
+ 32
73
+ end
74
+
75
+ def format
76
+ "int#{@bits}"
77
+ end
78
+
79
+ def ==(other)
80
+ other.is_a?(Int) && @bits == other.bits
81
+ end
82
+ end
83
+
84
+ class Uint < Type
85
+ attr_reader :bits
86
+
87
+ def initialize(bits = 256)
88
+ @bits = bits
89
+ end
90
+
91
+ def size
92
+ 32
93
+ end
94
+
95
+ def format
96
+ "uint#{@bits}"
97
+ end
98
+
99
+ def ==(other)
100
+ other.is_a?(Uint) && @bits == other.bits
101
+ end
102
+ end
103
+
104
+ class Bool < Type
105
+ def size
106
+ 32
107
+ end
108
+
109
+ def format
110
+ "bool"
111
+ end
112
+
113
+ def ==(other)
114
+ other.is_a?(Bool)
115
+ end
116
+ end
117
+
118
+ class String < Type
119
+ def size
120
+ nil
121
+ end
122
+
123
+ def format
124
+ "string"
125
+ end
126
+
127
+ def ==(other)
128
+ other.is_a?(String)
129
+ end
130
+ end
131
+
132
+ class Array < Type
133
+ attr_reader :inner_type
134
+
135
+ def initialize(inner_type)
136
+ @inner_type = inner_type
137
+ end
138
+
139
+ def size
140
+ nil
141
+ end
142
+
143
+ def format
144
+ "#{@inner_type.format}[]"
145
+ end
146
+
147
+ def ==(other)
148
+ other.is_a?(Array) && @inner_type == other.inner_type
149
+ end
150
+ end
151
+
152
+ class FixedArray < Type
153
+ attr_reader :inner_type, :length
154
+
155
+ def initialize(inner_type, length)
156
+ @inner_type = inner_type
157
+ @length = length
158
+ end
159
+
160
+ def size
161
+ @inner_type.dynamic? ? nil : @length * inner_type.size
162
+ end
163
+
164
+ def format
165
+ "#{@inner_type.format}[#{@length}]"
166
+ end
167
+
168
+ def ==(other)
169
+ other.is_a?(FixedArray) && @length == other.length && @inner_type == other.inner_type
170
+ end
171
+ end
172
+
173
+ class Tuple < Type
174
+ attr_reader :inner_types
175
+
176
+ def initialize(inner_types)
177
+ @inner_types = inner_types
178
+ end
179
+
180
+ def size
181
+ s = 0
182
+ has_dynamic = false
183
+ @inner_types.each do |type|
184
+ ts = type.size
185
+ has_dynamic ||= ts.nil?
186
+ s += ts unless ts.nil?
187
+ end
188
+ has_dynamic ? nil : s
189
+ end
190
+
191
+ def format
192
+ "(#{@inner_types.map(&:format).join(",")})"
193
+ end
194
+
195
+ def ==(other)
196
+ other.is_a?(Tuple) && @inner_types == other.inner_types
197
+ end
198
+ end
199
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbiCoderRb
4
- VERSION = "0.2.8"
4
+ VERSION = "0.2.9"
5
5
  end
data/lib/abi_coder_rb.rb CHANGED
@@ -4,18 +4,20 @@ require_relative "abi_coder_rb/version"
4
4
 
5
5
  require_relative "abi_coder_rb/utils"
6
6
 
7
- require_relative "abi_coder_rb/parser"
8
- require_relative "abi_coder_rb/types"
7
+ require_relative "abi_coder_rb/type/types"
8
+ require_relative "abi_coder_rb/type/parse"
9
9
  require_relative "abi_coder_rb/decode"
10
10
  require_relative "abi_coder_rb/encode"
11
11
 
12
12
  require_relative "periphery/event_decoder"
13
13
 
14
14
  module AbiCoderRb
15
- class DecodingError < StandardError; end
16
- class EncodingError < StandardError; end
17
- class ValueError < StandardError; end
18
- class ValueOutOfBounds < ValueError; end
15
+ class Error < StandardError; end
16
+ class DecodingError < Error; end
17
+ class EncodingError < Error; end
18
+ class ValueError < Error; end
19
+ class ValueOutOfBounds < Error; end
20
+ class ParseError < Error; end
19
21
 
20
22
  BYTE_EMPTY = "".b.freeze
21
23
  BYTE_ZERO = "\x00".b.freeze
@@ -0,0 +1,37 @@
1
+ module AbiCoderRb
2
+ class AbiParser
3
+ @tokenizer: AbiTokenizer
4
+ @current_token: String
5
+
6
+ def initialize: (AbiTokenizer) -> void
7
+ def parse: -> abi_type
8
+
9
+ private
10
+
11
+ def parse_bytes: -> bytes
12
+ def parse_numeric_type: -> numeric_type
13
+ def parse_simple_type: -> simple_type
14
+ def parse_tuple: -> tuple_type
15
+ def parse_array: -> array_type
16
+ def parse_array_length: -> Integer
17
+ def expect: -> void
18
+ end
19
+
20
+ type abi_type = bytes | numeric_type | simple_type | array_type | tuple_type
21
+
22
+ type bytes =
23
+ { type: "bytes", length?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 }
24
+
25
+ type numeric_type =
26
+ { type: "uint", bits: 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 | 248 | 256 } # uint will be parsed to uint256.
27
+ | { type: "int", bits: 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152 | 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232 | 240 | 248 | 256 } # int will be parsed to int256
28
+
29
+ type simple_type =
30
+ { type: "string" }
31
+ | { type: "address" }
32
+ | { type: "bool" }
33
+
34
+ type array_type = { type: "array", inner_type: abi_type, length: Integer? } # length is nil for dynamic array.
35
+
36
+ type tuple_type = { type: "tuple", inner_types: Array[abi_type] }
37
+ end
@@ -0,0 +1,15 @@
1
+ module AbiCoderRb
2
+ class AbiTokenizer
3
+ @abi: String
4
+ @index: Integer
5
+
6
+ def initialize: (String) -> void
7
+ def next_token: -> String
8
+ def peek_token: -> String
9
+
10
+ private
11
+
12
+ def read_identifier: -> String
13
+ def skip_whitespace: -> void
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module AbiCoderRb
2
+ class Type
3
+ def self.parse: -> untyped
4
+ end
5
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abi_coder_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.2.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aki Wu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-10 00:00:00.000000000 Z
11
+ date: 2024-08-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -45,19 +45,15 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - ".DS_Store"
49
48
  - ".rspec"
50
49
  - ".rubocop.yml"
51
50
  - CODE_OF_CONDUCT.md
52
51
  - Gemfile
53
- - Gemfile.lock
54
52
  - LICENSE.txt
55
53
  - README.md
56
54
  - Rakefile
57
55
  - flatten.rb
58
- - lib/.DS_Store
59
56
  - lib/abi_coder_rb.rb
60
- - lib/abi_coder_rb/.DS_Store
61
57
  - lib/abi_coder_rb/decode.rb
62
58
  - lib/abi_coder_rb/decode/decode_array.rb
63
59
  - lib/abi_coder_rb/decode/decode_fixed_array.rb
@@ -68,12 +64,17 @@ files:
68
64
  - lib/abi_coder_rb/encode/encode_fixed_array.rb
69
65
  - lib/abi_coder_rb/encode/encode_primitive_type.rb
70
66
  - lib/abi_coder_rb/encode/encode_tuple.rb
71
- - lib/abi_coder_rb/parser.rb
72
- - lib/abi_coder_rb/types.rb
67
+ - lib/abi_coder_rb/parser/abi_parser.rb
68
+ - lib/abi_coder_rb/parser/abi_tokenizer.rb
69
+ - lib/abi_coder_rb/type/parse.rb
70
+ - lib/abi_coder_rb/type/types.rb
73
71
  - lib/abi_coder_rb/utils.rb
74
72
  - lib/abi_coder_rb/version.rb
75
73
  - lib/periphery/event_decoder.rb
76
74
  - sig/abi_coder_rb.rbs
75
+ - sig/abi_coder_rb/abi_parser.rbs
76
+ - sig/abi_coder_rb/abi_tokenizer.rbs
77
+ - sig/abi_coder_rb/type.rbs
77
78
  - tea.yaml
78
79
  homepage: https://github.com/wuminzhe/abi_coder_rb
79
80
  licenses:
data/.DS_Store DELETED
Binary file
data/Gemfile.lock DELETED
@@ -1,87 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- abi_coder_rb (0.2.8)
5
- activesupport
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- activesupport (7.1.1)
11
- base64
12
- bigdecimal
13
- concurrent-ruby (~> 1.0, >= 1.0.2)
14
- connection_pool (>= 2.2.5)
15
- drb
16
- i18n (>= 1.6, < 2)
17
- minitest (>= 5.1)
18
- mutex_m
19
- tzinfo (~> 2.0)
20
- ast (2.4.2)
21
- base64 (0.1.1)
22
- bigdecimal (3.1.4)
23
- concurrent-ruby (1.2.2)
24
- connection_pool (2.4.1)
25
- diff-lcs (1.5.0)
26
- drb (2.1.1)
27
- ruby2_keywords
28
- i18n (1.14.1)
29
- concurrent-ruby (~> 1.0)
30
- json (2.6.3)
31
- keccak (1.3.1)
32
- language_server-protocol (3.17.0.3)
33
- minitest (5.20.0)
34
- mutex_m (0.1.2)
35
- parallel (1.23.0)
36
- parser (3.2.2.4)
37
- ast (~> 2.4.1)
38
- racc
39
- racc (1.7.3)
40
- rainbow (3.1.1)
41
- rake (13.1.0)
42
- regexp_parser (2.8.2)
43
- rexml (3.2.6)
44
- rspec (3.12.0)
45
- rspec-core (~> 3.12.0)
46
- rspec-expectations (~> 3.12.0)
47
- rspec-mocks (~> 3.12.0)
48
- rspec-core (3.12.2)
49
- rspec-support (~> 3.12.0)
50
- rspec-expectations (3.12.3)
51
- diff-lcs (>= 1.2.0, < 2.0)
52
- rspec-support (~> 3.12.0)
53
- rspec-mocks (3.12.6)
54
- diff-lcs (>= 1.2.0, < 2.0)
55
- rspec-support (~> 3.12.0)
56
- rspec-support (3.12.1)
57
- rubocop (1.57.2)
58
- json (~> 2.3)
59
- language_server-protocol (>= 3.17.0)
60
- parallel (~> 1.10)
61
- parser (>= 3.2.2.4)
62
- rainbow (>= 2.2.2, < 4.0)
63
- regexp_parser (>= 1.8, < 3.0)
64
- rexml (>= 3.2.5, < 4.0)
65
- rubocop-ast (>= 1.28.1, < 2.0)
66
- ruby-progressbar (~> 1.7)
67
- unicode-display_width (>= 2.4.0, < 3.0)
68
- rubocop-ast (1.30.0)
69
- parser (>= 3.2.1.0)
70
- ruby-progressbar (1.13.0)
71
- ruby2_keywords (0.0.5)
72
- tzinfo (2.0.6)
73
- concurrent-ruby (~> 1.0)
74
- unicode-display_width (2.5.0)
75
-
76
- PLATFORMS
77
- arm64-darwin-21
78
-
79
- DEPENDENCIES
80
- abi_coder_rb!
81
- keccak (~> 1.3)
82
- rake (~> 13.0)
83
- rspec (~> 3.0)
84
- rubocop (~> 1.21)
85
-
86
- BUNDLED WITH
87
- 2.4.13
data/lib/.DS_Store DELETED
Binary file
Binary file
@@ -1,156 +0,0 @@
1
- module AbiCoderRb
2
- class Type
3
- class ParseError < StandardError; end
4
-
5
- ## nested inside Type - why? why not?
6
- class Parser
7
- TUPLE_TYPE_RX = /^\((.*)\)
8
- ((\[[0-9]*\])*)
9
- /x
10
-
11
- def self.parse(type)
12
- type = type.strip
13
-
14
- if type =~ TUPLE_TYPE_RX
15
- types = _parse_tuple_type(::Regexp.last_match(1))
16
- dims = _parse_dims(::Regexp.last_match(2))
17
-
18
- parsed_types = types.map { |t| parse(t) }
19
-
20
- return _parse_array_type(Tuple.new(parsed_types), dims)
21
- end
22
-
23
- # uint256 => uint, 256, nil
24
- # uint256[2] => uint, 256, [2]
25
- # uint256[] => uint, 256, [-1]
26
- # bytes => bytes, nil, []
27
- base, sub, dims = _parse_base_type(type)
28
-
29
- sub ||= 256 if type.start_with?("uint") || type.start_with?("int") # default to 256 if no sub given
30
- _validate_base_type(base, sub)
31
-
32
- subtype = case base
33
- when "string" then String.new
34
- when "bytes" then sub ? FixedBytes.new(sub) : Bytes.new
35
- when "uint" then Uint.new(sub)
36
- when "int" then Int.new(sub)
37
- when "address" then Address.new
38
- when "bool" then Bool.new
39
- else
40
- ## puts " type: >#{type}<"
41
- raise ParseError, "Unrecognized type base: #{base}"
42
- end
43
-
44
- _parse_array_type(subtype, dims)
45
- end
46
-
47
- ##
48
- # Crazy regexp to seperate out base type component (eg. uint), size (eg.
49
- # 256, 128, nil), array component (eg. [], [45], nil)
50
- #
51
- BASE_TYPE_RX = /([a-z]*)
52
- ([0-9]*)
53
- ((\[[0-9]*\])*)
54
- /x
55
-
56
- def self._parse_base_type(str)
57
- _, base, subscript, dimension = BASE_TYPE_RX.match(str).to_a
58
-
59
- ## note: use [Integer,Integer] array in the future for sub
60
- ## for fixed (e.g. 128x128 => [128,128]) or such
61
- ## for now always assume single integer (as string)
62
- sub = subscript == "" ? nil : subscript.to_i
63
-
64
- ## e.g. turn "[][1][2]" into [-1,1,2]
65
- ## or "" into [] -- that is, empty array
66
- dims = _parse_dims(dimension)
67
-
68
- [base, sub, dims]
69
- end
70
-
71
- def self._parse_dims(str)
72
- dims = str.scan(/\[[0-9]*\]/)
73
-
74
- ## note: return -1 for dynamic array size e.g. []
75
- ## e.g. "[]"[1...-1] => ""
76
- ## "[0]"[1...-1] => "0"
77
- ## "[1]"[1...-1] => "1"
78
- dims.map do |dim|
79
- size = dim[1...-1]
80
- size == "" ? -1 : size.to_i
81
- end
82
- end
83
-
84
- def self._parse_array_type(subtype, dims)
85
- ##
86
- ## todo/check - double check if the order in reverse
87
- ## in solidity / abi encoding / decoding?
88
- ##
89
- dims.each do |dim|
90
- subtype = if dim == -1
91
- Array.new(subtype)
92
- else
93
- FixedArray.new(subtype, dim)
94
- end
95
- end
96
-
97
- subtype
98
- end
99
-
100
- def self._validate_base_type(base, sub)
101
- case base
102
- when "string"
103
- # NOTE: string can not have any suffix
104
- raise ParseError, "String cannot have suffix" if sub
105
- when "bytes"
106
- raise ParseError, "Maximum 32 bytes for fixed-length bytes" if sub && sub > 32
107
- when "uint", "int"
108
- raise ParseError, "Integer type must have numerical suffix" unless sub
109
- raise ParseError, "Integer size out of bounds" unless sub >= 8 && sub <= 256
110
- raise ParseError, "Integer size must be multiple of 8" unless sub % 8 == 0
111
- when "address"
112
- raise ParseError, "Address cannot have suffix" if sub
113
- when "bool"
114
- raise ParseError, "Bool cannot have suffix" if sub
115
- else
116
- ## puts " type: >#{type}<"
117
- raise ParseError, "Unrecognized type base: #{base}"
118
- end
119
- end
120
-
121
- def self._parse_tuple_type(str)
122
- ## note: types assumes string WITHOUT enclosing () e.g.
123
- ## tuple(string,string,bool) => expected as "string,string,bool"
124
-
125
- depth = 0
126
- collected = []
127
- current = ""
128
-
129
- ### todo/fix: replace with a simple parser!!!
130
- ## allow () and move verbose tuple() too!!!
131
- str.each_char do |c|
132
- case c
133
- when ","
134
- if depth == 0
135
- collected << current
136
- current = ""
137
- else
138
- current += c
139
- end
140
- when "("
141
- depth += 1
142
- current += c
143
- when ")"
144
- depth -= 1
145
- current += c
146
- else
147
- current += c
148
- end
149
- end
150
- collected << current unless current.empty?
151
-
152
- collected
153
- end
154
- end # class Parser
155
- end # class Type
156
- end # module ABI
@@ -1,237 +0,0 @@
1
- module AbiCoderRb
2
- #######
3
- ## for now use (get inspired)
4
- ## by the type names used by abi coder in rust
5
- ## see https://github.com/rust-ethereum/ethabi/blob/master/ethabi/src/param_type/param_type.rs
6
-
7
- class Type
8
- def self.parse(type) ## convenience helper
9
- Parser.parse(type)
10
- end
11
-
12
- ##
13
- # Get the static size of a type, or nil if dynamic.
14
- #
15
- # @return [Integer, NilClass] size of static type, or nil for dynamic type
16
- #
17
- def size
18
- ## check/todo: what error to raise for not implemented / method not defined???
19
- raise ArgumentError, "no required size method defined for Type subclass #{self.class.name}; sorry"
20
- end
21
-
22
- def dynamic?
23
- size.nil?
24
- end
25
-
26
- def format
27
- ## check/todo: what error to raise for not implemented / method not defined???
28
- raise ArgumentError, "no required format method defined for Type subclass #{self.class.name}; sorry"
29
- end
30
-
31
- ####
32
- ## default implementation
33
- ## assume equal if class match (e.g. Address == Address)
34
- ## - use format string for generic compare - why? why not?
35
- end
36
-
37
- class Address < Type
38
- ## note: address is always 20 bytes; BUT uses 32 bytes (with padding)
39
- def size
40
- 32
41
- end
42
-
43
- def format
44
- "address"
45
- end
46
-
47
- def ==(other)
48
- other.is_a?(Address)
49
- end
50
- end # class Address
51
-
52
- class Bytes < Type
53
- ## note: dynamic (not known at compile-time)
54
- def size
55
- nil
56
- end
57
-
58
- def format
59
- "bytes"
60
- end
61
-
62
- def ==(other)
63
- other.is_a?(Bytes)
64
- end
65
- end # class Bytes
66
-
67
- class FixedBytes < Type
68
- attr_reader :length
69
-
70
- def initialize(length)
71
- @length = length # in bytes (1,2,...32)
72
- end
73
-
74
- ## note: always uses 32 bytes (with padding)
75
- def size
76
- 32
77
- end
78
-
79
- def format
80
- "bytes#{@length}"
81
- end
82
-
83
- def ==(other)
84
- other.is_a?(FixedBytes) && @length == other.length
85
- end
86
- end # class FixedBytes
87
-
88
- class Int < Type
89
- attr_reader :bits
90
-
91
- def initialize(bits = 256)
92
- @bits = bits # in bits (8,16,...256)
93
- end
94
-
95
- ## note: always uses 32 bytes (with padding)
96
- def size
97
- 32
98
- end
99
-
100
- def format
101
- "int#{@bits}"
102
- end
103
-
104
- def ==(other)
105
- other.is_a?(Int) && @bits == other.bits
106
- end
107
- end # class Int
108
-
109
- class Uint < Type
110
- attr_reader :bits
111
-
112
- def initialize(bits = 256)
113
- @bits = bits # in bits (8,16,...256)
114
- end
115
-
116
- ## note: always uses 32 bytes (with padding)
117
- def size
118
- 32
119
- end
120
-
121
- def format
122
- "uint#{@bits}"
123
- end
124
-
125
- def ==(other)
126
- other.is_a?(Uint) && @bits == other.bits
127
- end
128
- end # class Uint
129
-
130
- class Bool < Type
131
- ## note: always uses 32 bytes (with padding)
132
- def size
133
- 32
134
- end
135
-
136
- def format
137
- "bool"
138
- end
139
-
140
- def ==(other)
141
- other.is_a?(Bool)
142
- end
143
- end # class Bool
144
-
145
- class String < Type
146
- ## note: dynamic (not known at compile-time)
147
- def size
148
- nil
149
- end
150
-
151
- def format
152
- "string"
153
- end
154
-
155
- def ==(other)
156
- other.is_a?(String)
157
- end
158
- end # class String
159
-
160
- class Array < Type
161
- attr_reader :subtype
162
-
163
- def initialize(subtype)
164
- @subtype = subtype
165
- end
166
-
167
- ## note: dynamic (not known at compile-time)
168
- def size
169
- nil
170
- end
171
-
172
- def format
173
- "#{@subtype.format}[]"
174
- end
175
-
176
- def ==(other)
177
- other.is_a?(Array) && @subtype == other.subtype
178
- end
179
- end # class Array
180
-
181
- class FixedArray < Type
182
- attr_reader :subtype, :dim
183
-
184
- def initialize(subtype, dim)
185
- @subtype = subtype
186
- @dim = dim
187
- end
188
-
189
- def size
190
- @subtype.dynamic? ? nil : @dim * subtype.size
191
- end
192
-
193
- def format
194
- "#{@subtype.format}[#{@dim}]"
195
- end
196
-
197
- def ==(other)
198
- other.is_a?(FixedArray) &&
199
- @dim == other.dim &&
200
- @subtype == other.subtype
201
- end
202
- end # class FixedArray
203
-
204
- class Tuple < Type
205
- attr_reader :types
206
-
207
- def initialize(types)
208
- @types = types
209
- end
210
-
211
- def size
212
- s = 0
213
- has_dynamic = false
214
- @types.each do |type|
215
- ts = type.size
216
- if ts.nil?
217
- # can not return nil here? if wasm
218
- has_dynamic = true
219
- else
220
- s += ts
221
- end
222
- end
223
-
224
- return if has_dynamic
225
-
226
- s
227
- end
228
-
229
- def format
230
- "(#{@types.map { |t| t.format }.join(",")})" ## rebuild minimal string
231
- end
232
-
233
- def ==(other)
234
- other.is_a?(Tuple) && @types == other.types
235
- end
236
- end # class Tuple
237
- end # module ABI