abi_coder_rb 0.2.8 → 0.2.9

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