abi_coder_rb 0.1.0 → 0.2.1

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: 72266c6750d4c8e137acebdc790350c7862d9c84453f1af99b1aaa9ee5e775f7
4
- data.tar.gz: 045aa60cc688c8537290ffff047df35ceb17db5de978d8943d7db9b0725ca9af
3
+ metadata.gz: 603c8a3cab98c321861f9480f1f668ffdf3559553974d09bdcd46858d97a44ea
4
+ data.tar.gz: 036b109f1f9f53088d592aeac51a66a9147707ae74a93a431a9c2be65617e37a
5
5
  SHA512:
6
- metadata.gz: 19822e060b76e17667c3deccf9ba9f2697b51b1ec0825fda594a6d4fcc3f3ed617ca15db6354b4e8702e994cadc58ee221b2b4823b5e93371bc2effd4858b3fc
7
- data.tar.gz: 863775a35f5819bddee87bcfe0cbaafc0b59042c98eeff8c9ceaa254ae3e9543c2a8df95660a40e4ac25aa8902f04b50b8493762e729f8141bc35b216e3ea704
6
+ metadata.gz: 1d6358050ac16002065cb2b379bafa1ed85f3cacdcec82998c69408cbd22d46ce47e554a73f2ca1bae9edc9279505cd18d62412a87271503dcf1488ca97f3fe5
7
+ data.tar.gz: a4b083f13af344d38aea6acbc5b267f8c0763217ec98041cb8b12d88e2896c5e92df66b8911c6a7524c83f777af05932ebe10ac56bb20523dbacd9a86de52d91
data/.DS_Store CHANGED
Binary file
data/Gemfile.lock CHANGED
@@ -1,15 +1,37 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- abi_coder_rb (0.1.0)
4
+ abi_coder_rb (0.2.1)
5
+ activesupport
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
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)
9
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)
10
25
  diff-lcs (1.5.0)
26
+ drb (2.1.1)
27
+ ruby2_keywords
28
+ i18n (1.14.1)
29
+ concurrent-ruby (~> 1.0)
11
30
  json (2.6.3)
31
+ keccak (1.3.1)
12
32
  language_server-protocol (3.17.0.3)
33
+ minitest (5.20.0)
34
+ mutex_m (0.1.2)
13
35
  parallel (1.23.0)
14
36
  parser (3.2.2.4)
15
37
  ast (~> 2.4.1)
@@ -46,6 +68,9 @@ GEM
46
68
  rubocop-ast (1.30.0)
47
69
  parser (>= 3.2.1.0)
48
70
  ruby-progressbar (1.13.0)
71
+ ruby2_keywords (0.0.5)
72
+ tzinfo (2.0.6)
73
+ concurrent-ruby (~> 1.0)
49
74
  unicode-display_width (2.5.0)
50
75
 
51
76
  PLATFORMS
@@ -53,6 +78,7 @@ PLATFORMS
53
78
 
54
79
  DEPENDENCIES
55
80
  abi_coder_rb!
81
+ keccak (~> 1.3)
56
82
  rake (~> 13.0)
57
83
  rspec (~> 3.0)
58
84
  rubocop (~> 1.21)
data/README.md CHANGED
@@ -4,7 +4,16 @@ modified from https://github.com/rubycocos/blockchain/blob/master/abicoder
4
4
 
5
5
  for better readability code and deep learning abi codec.
6
6
 
7
- The most significant difference from the original code is that '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.
7
+ Changes compared to original code:
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).
15
+
16
+ Also, some code was modified to compile to wasm. Try it online: https://wuminzhe.github.io/abi.html
8
17
 
9
18
  ## Installation
10
19
 
@@ -18,7 +27,59 @@ If bundler is not being used to manage dependencies, install the gem by executin
18
27
 
19
28
  ## Usage
20
29
 
21
- See tests
30
+ ### Way 1: extend AbiCoderRb
31
+
32
+ ```ruby
33
+ require 'abi_coder_rb'
34
+
35
+ module ABI
36
+ extend AbiCoderRb
37
+ end
38
+
39
+ type = "(bytes4)"
40
+ value = ["\x124Vx"] # or ABI.hex "0x12345678"
41
+ data = ABI.hex "1234567800000000000000000000000000000000000000000000000000000000"
42
+ ABI.decode(type, data) == value # => true
43
+ ABI.encode(type, value) == data # => true
44
+ ```
45
+
46
+ You can transform the value according to the type before encoding it. For example, you can convert the hex string to bytes before encoding it. Here is same example but the value for "bytes4" is a hex string.
47
+ ```ruby
48
+ require 'abi_coder_rb'
49
+
50
+ module ABI
51
+ extend AbiCoderRb
52
+
53
+ before_encoding ->(type, value) {
54
+ if type.start_with?("bytes")
55
+ hex(value)
56
+ else
57
+ value
58
+ end
59
+ }
60
+ end
61
+
62
+ type = "(bytes4)"
63
+ value = ["0x12345678"]
64
+ data = ABI.hex "1234567800000000000000000000000000000000000000000000000000000000"
65
+ ABI.encode(type, value) == data # => true
66
+ ```
67
+
68
+ ### Way 2: include AbiCoderRb
69
+ ```ruby
70
+ class Hello
71
+ include AbiCoderRb
72
+
73
+ def world
74
+ data = hex "0000000000000000000000000000000000000000000000000000000000000020" \
75
+ "000000000000000000000000000000000000000000000000000000000000000b" \
76
+ "48656c6c6f20576f726c64000000000000000000000000000000000000000000"
77
+ decode("(string)", data)
78
+ end
79
+ end
80
+
81
+ Hello.new.world # => ["Hello World"]
82
+ ```
22
83
 
23
84
  ## Development
24
85
 
data/flatten.rb ADDED
@@ -0,0 +1,8 @@
1
+ content = "# Generated from https://github.com/wuminzhe/abi_coder_rb\n"
2
+ Dir["./lib/**/*.rb"].each do |f|
3
+ File.open(f, "r").each_line do |line|
4
+ content += line unless line.include?("require") || line.strip.start_with?("#") || line.strip.empty?
5
+ end
6
+ end
7
+
8
+ puts content
data/lib/.DS_Store CHANGED
Binary file
@@ -1,21 +1,21 @@
1
1
  module AbiCoderRb
2
2
  def decode_array(type, data)
3
- l = decode_uint256(data[0, 32])
4
- raise DecodingError, "Too long length: #{l}" if l > 100_000
3
+ size = decode_uint256(data[0, 32])
4
+ raise DecodingError, "Too many elements: #{size}" if size > 100_000
5
5
 
6
6
  subtype = type.subtype
7
7
 
8
8
  if subtype.dynamic?
9
- raise DecodingError, "Not enough data for head" unless data.size >= 32 + 32 * l
9
+ raise DecodingError, "Not enough data for head" unless data.size >= 32 + 32 * size
10
10
 
11
- start_positions = (1..l).map { |i| 32 + decode_uint256(data[32 * i, 32]) }
11
+ start_positions = (1..size).map { |i| 32 + decode_uint256(data[32 * i, 32]) }
12
12
  start_positions.push(data.size)
13
13
 
14
- outputs = (0...l).map { |i| data[start_positions[i]...start_positions[i + 1]] }
14
+ outputs = (0...size).map { |i| data[start_positions[i]...start_positions[i + 1]] }
15
15
 
16
16
  outputs.map { |out| decode_type(subtype, out) }
17
17
  else
18
- (0...l).map { |i| decode_type(subtype, data[(32 + subtype.size * i)..]) }
18
+ (0...size).map { |i| decode_type(subtype, data[(32 + subtype.size * i)..]) }
19
19
  end
20
20
  end
21
21
  end
@@ -1,26 +1,30 @@
1
1
  module AbiCoderRb
2
2
  def decode_primitive_type(type, data)
3
- case type
4
- when Uint
5
- decode_uint256(data[0, 32])
6
- when Int
7
- u = decode_uint256(data[0, 32])
8
- u >= 2**(type.bits - 1) ? (u - 2**type.bits) : u
9
- when Bool
10
- data[31] == BYTE_ONE
11
- when String
12
- size = decode_uint256(data[0, 32])
13
- data[32...(32 + size)].force_encoding(Encoding::UTF_8)
14
- when Bytes
15
- size = decode_uint256(data[0, 32])
16
- data[32...(32 + size)]
17
- when FixedBytes
18
- data[0, type.length]
19
- when Address
20
- data[12...32].unpack1("H*").force_encoding(Encoding::UTF_8)
21
- else
22
- raise DecodingError, "Unknown primitive type: #{type.class.name} #{type.format}"
23
- end
3
+ result =
4
+ case type
5
+ when Uint
6
+ decode_uint256(data[0, 32])
7
+ when Int
8
+ abi_to_int_signed(bin_to_hex(data[0, 32]), type.bits)
9
+ when Bool
10
+ data[31] == BYTE_ONE
11
+ when String
12
+ size = decode_uint256(data[0, 32])
13
+ data[32...(32 + size)].force_encoding("UTF-8")
14
+ when Bytes
15
+ size = decode_uint256(data[0, 32])
16
+ data[32...(32 + size)]
17
+ when FixedBytes
18
+ data[0, type.length]
19
+ when Address
20
+ bin_to_hex(data[12...32]).force_encoding("UTF-8")
21
+ else
22
+ raise DecodingError, "Unknown primitive type: #{type.class.name} #{type.format}"
23
+ end
24
+
25
+ result = after_decoding_action.call(type.format, result) if after_decoding_action
26
+
27
+ result
24
28
  end
25
29
 
26
30
  private
@@ -30,6 +34,7 @@ module AbiCoderRb
30
34
  ### todo/check - allow nil - why? why not?
31
35
  ## raise DeserializationError, "Invalid serialization (not minimal length)" if !@size && serial.size > 0 && serial[0] == BYTE_ZERO
32
36
  # bin = bin || BYTE_ZERO
33
- bin.unpack1("H*").to_i(16)
37
+ bin_to_hex(bin).to_i(16)
38
+ # bin.bytes.reduce { |acc, byte| (acc << 8) + byte }
34
39
  end
35
40
  end
@@ -2,4 +2,33 @@ module AbiCoderRb
2
2
  def decode_tuple(type, data)
3
3
  decode_types(type.types, data)
4
4
  end
5
+
6
+ private
7
+
8
+ def decode_types(types, data)
9
+ start_positions = start_positions(types, data)
10
+
11
+ types.map.with_index do |type, index|
12
+ start_position = start_positions[index]
13
+ decode_type(type, data[start_position..])
14
+ end
15
+ end
16
+
17
+ def start_positions(types, data)
18
+ start_positions = ::Array.new(types.size)
19
+ offset = 0
20
+
21
+ types.each_with_index do |type, index|
22
+ if type.dynamic?
23
+ # 读取动态类型的偏移量
24
+ start_positions[index] = decode_uint256(data[offset, 32])
25
+ offset += 32
26
+ else
27
+ start_positions[index] = offset
28
+ offset += type.size
29
+ end
30
+ end
31
+
32
+ start_positions
33
+ end
5
34
  end
@@ -4,18 +4,15 @@ require_relative "decode/decode_array"
4
4
  require_relative "decode/decode_primitive_type"
5
5
 
6
6
  module AbiCoderRb
7
- def decode(types, data)
8
- # Convert types to ABI::Type if they are not already
9
- types = types.map { |type| type.is_a?(Type) ? type : Type.parse(type) }
7
+ def decode(type_str, data)
8
+ raise DecodingError, "Empty data" if data.nil? || data.empty?
10
9
 
11
- decode_types(types, data)
10
+ decode_type(Type.parse(type_str), data)
12
11
  end
13
12
 
14
13
  private
15
14
 
16
15
  def decode_type(type, data)
17
- return nil if data.nil? || data.empty?
18
-
19
16
  case type
20
17
  when Tuple ## todo: support empty (unit) tuple - why? why not?
21
18
  decode_tuple(type, data)
@@ -27,31 +24,4 @@ module AbiCoderRb
27
24
  decode_primitive_type(type, data)
28
25
  end
29
26
  end
30
-
31
- def decode_types(types, data)
32
- start_positions = start_positions(types, data)
33
-
34
- types.map.with_index do |type, index|
35
- start_position = start_positions[index]
36
- decode_type(type, data[start_position..])
37
- end
38
- end
39
-
40
- def start_positions(types, data)
41
- start_positions = ::Array.new(types.size)
42
- offset = 0
43
-
44
- types.each_with_index do |type, index|
45
- if type.dynamic?
46
- # 读取动态类型的偏移量
47
- start_positions[index] = decode_uint256(data[offset, 32])
48
- offset += 32
49
- else
50
- start_positions[index] = offset
51
- offset += type.size
52
- end
53
- end
54
-
55
- start_positions
56
- end
57
27
  end
@@ -3,6 +3,20 @@ module AbiCoderRb
3
3
  raise ArgumentError, "arg must be an array" unless args.is_a?(::Array)
4
4
  raise ArgumentError, "Wrong array size: found #{args.size}, expecting #{type.dim}" unless args.size == type.dim
5
5
 
6
- args.map { |arg| encode_type(type.subtype, arg) }.join
6
+ # fixed_array,是没有元素数量的编码de
7
+ # 如果内部类型是静态的,就是一个一个元素编码后加起来。
8
+ # 如果内部类型是动态的,先用位置一个一个编码加起来,然后是元素本体
9
+ subtype = type.subtype
10
+ if subtype.dynamic?
11
+ head = "".b
12
+ tail = "".b
13
+ args.each do |arg|
14
+ head += encode_uint256(32 * args.size + tail.size)
15
+ tail += encode_type(subtype, arg)
16
+ end
17
+ head + tail
18
+ else
19
+ args.map { |arg| encode_type(type.subtype, arg) }.join
20
+ end
7
21
  end
8
22
  end
@@ -1,5 +1,6 @@
1
1
  module AbiCoderRb
2
2
  def encode_primitive_type(type, arg)
3
+ arg = before_encoding_action.call(type.format, arg) if before_encoding_action
3
4
  # 根据类型选择相应的编码方法
4
5
  case type
5
6
  when Uint
@@ -34,12 +35,11 @@ module AbiCoderRb
34
35
  encode_uint(arg, 256)
35
36
  end
36
37
 
37
- def encode_int(arg, bits)
38
+ def encode_int(arg, _bits)
38
39
  ## raise EncodingError or ArgumentError - why? why not?
39
40
  raise ArgumentError, "arg is not integer: #{arg}" unless arg.is_a?(Integer)
40
- raise ValueOutOfBounds, arg unless arg >= -2**(bits - 1) && arg < 2**(bits - 1)
41
41
 
42
- lpad_int(arg % 2**bits)
42
+ hex_to_bin(int_to_abi_signed_256bit(arg))
43
43
  end
44
44
 
45
45
  def encode_bool(arg)
@@ -53,7 +53,7 @@ module AbiCoderRb
53
53
  ## raise EncodingError or ArgumentError - why? why not?
54
54
  raise EncodingError, "Expecting string: #{arg}" unless arg.is_a?(::String)
55
55
 
56
- arg = arg.b if arg.encoding != Encoding::BINARY ## was: name == 'UTF-8'
56
+ arg = arg.b if arg.encoding != "BINARY" ## was: name == 'UTF-8', wasm
57
57
 
58
58
  raise ValueOutOfBounds, "Integer invalid or out of range: #{arg.size}" if arg.size > UINT_MAX
59
59
 
@@ -69,7 +69,7 @@ module AbiCoderRb
69
69
  arg = arg.b if arg.encoding != Encoding::BINARY
70
70
 
71
71
  if length # fixed length type
72
- raise ValueOutOfBounds, "invalid bytes length #{length}" if arg.size > length
72
+ raise ValueOutOfBounds, "invalid bytes length #{arg.size}, should be #{length}" if arg.size > length
73
73
  raise ValueOutOfBounds, "invalid bytes length #{length}" if length < 0 || length > 32
74
74
 
75
75
  rpad(arg)
@@ -97,50 +97,4 @@ module AbiCoderRb
97
97
  raise EncodingError, "Could not parse address: #{arg}"
98
98
  end
99
99
  end
100
-
101
- private
102
-
103
- ###########
104
- # encoding helpers / utils
105
- # with "hard-coded" fill symbol as BYTE_ZERO
106
-
107
- def rpad(bin, l = 32) ## note: same as builtin String#ljust !!!
108
- # note: default l word is 32 bytes
109
- return bin if bin.size >= l
110
-
111
- bin + BYTE_ZERO * (l - bin.size)
112
- end
113
-
114
- ## rename to lpad32 or such - why? why not?
115
- def lpad(bin) ## note: same as builtin String#rjust !!!
116
- l = 32 # NOTE: default l word is 32 bytes
117
- return bin if bin.size >= l
118
-
119
- BYTE_ZERO * (l - bin.size) + bin
120
- end
121
-
122
- ## rename to lpad32_int or such - why? why not?
123
- def lpad_int(n)
124
- raise ArgumentError, "Integer invalid or out of range: #{n}" unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
125
-
126
- hex = n.to_s(16)
127
- hex = "0" + hex if hex.size.odd?
128
- bin = [hex].pack("H*")
129
-
130
- lpad(bin)
131
- end
132
-
133
- ## rename to lpad32_hex or such - why? why not?
134
- def lpad_hex(hex)
135
- raise TypeError, "Value must be a string" unless hex.is_a?(::String)
136
- raise TypeError, "Non-hexadecimal digit found" unless hex =~ /\A[0-9a-fA-F]*\z/
137
-
138
- bin = [hex].pack("H*")
139
-
140
- lpad(bin)
141
- end
142
-
143
- def ceil32(x)
144
- x % 32 == 0 ? x : (x + 32 - x % 32)
145
- end
146
100
  end
@@ -2,4 +2,36 @@ module AbiCoderRb
2
2
  def encode_tuple(tuple, args)
3
3
  encode_types(tuple.types, args)
4
4
  end
5
+
6
+ private
7
+
8
+ def encode_types(types, args)
9
+ raise ArgumentError, "args must be an array" unless args.is_a?(::Array)
10
+
11
+ unless args.size == types.size
12
+ raise ArgumentError,
13
+ "Wrong number of args: found #{args.size}, expecting #{types.size}"
14
+ end
15
+
16
+ # 计算头部大小
17
+ head_size = types.map { |type| type.size || 32 }.sum
18
+
19
+ # 初始化头部和尾部
20
+ head = "".b
21
+ tail = "".b # 使用二进制字符串
22
+
23
+ # 遍历类型并编码
24
+ types.each_with_index do |type, i|
25
+ if type.dynamic?
26
+ # 动态类型: 更新头部和尾部
27
+ head += encode_uint256(head_size + tail.size)
28
+ tail += encode_type(type, args[i])
29
+ else
30
+ # 静态类型: 只更新头部
31
+ head += encode_type(type, args[i])
32
+ end
33
+ end
34
+
35
+ head + tail
36
+ end
5
37
  end
@@ -4,56 +4,26 @@ require_relative "encode/encode_array"
4
4
  require_relative "encode/encode_primitive_type"
5
5
 
6
6
  module AbiCoderRb
7
- ##
8
- # Encodes multiple arguments using the head/tail mechanism.
9
- # returns binary string (with BINARY / ASCII_8BIT encoding)
10
- #
11
- def encode(types, args)
12
- # 如果 types 是字符串,则转换为 ABI::Type 实例
13
- types = types.map { |type| type.is_a?(Type) ? type : Type.parse(type) }
7
+ # returns byte array
8
+ def encode(type, value)
9
+ # TODO: more checks?
10
+ raise EncodingError, "Value can not be nil" if value.nil?
14
11
 
15
- encode_types(types, args)
12
+ parsed = Type.parse(type)
13
+ encode_type(parsed, value)
16
14
  end
17
15
 
18
16
  private
19
17
 
20
- def encode_type(type, arg)
18
+ def encode_type(type, value)
21
19
  if type.is_a?(Tuple)
22
- encode_tuple(type, arg)
23
- elsif type.is_a?(Array) || type.is_a?(FixedArray)
24
- type.dynamic? ? encode_array(type, arg) : encode_fixed_array(type, arg)
20
+ encode_tuple(type, value)
21
+ elsif type.is_a?(Array)
22
+ encode_array(type, value)
23
+ elsif type.is_a?(FixedArray)
24
+ encode_fixed_array(type, value)
25
25
  else
26
- encode_primitive_type(type, arg)
26
+ encode_primitive_type(type, value)
27
27
  end
28
28
  end
29
-
30
- def encode_types(types, args)
31
- raise ArgumentError, "args must be an array" unless args.is_a?(::Array)
32
-
33
- unless args.size == types.size
34
- raise ArgumentError,
35
- "Wrong number of args: found #{args.size}, expecting #{types.size}"
36
- end
37
-
38
- # 计算头部大小
39
- head_size = types.map { |type| type.size || 32 }.sum
40
-
41
- # 初始化头部和尾部
42
- head = "".b
43
- tail = "".b # 使用二进制字符串
44
-
45
- # 遍历类型并编码
46
- types.each_with_index do |type, i|
47
- if type.dynamic?
48
- # 动态类型: 更新头部和尾部
49
- head += encode_uint256(head_size + tail.size)
50
- tail += encode_type(type, args[i])
51
- else
52
- # 静态类型: 只更新头部
53
- head += encode_type(type, args[i])
54
- end
55
- end
56
-
57
- head + tail
58
- end
59
29
  end
@@ -9,6 +9,8 @@ module AbiCoderRb
9
9
  /x
10
10
 
11
11
  def self.parse(type)
12
+ type = type.strip
13
+
12
14
  if type =~ TUPLE_TYPE_RX
13
15
  types = _parse_tuple_type(::Regexp.last_match(1))
14
16
  dims = _parse_dims(::Regexp.last_match(2))
@@ -18,8 +20,13 @@ module AbiCoderRb
18
20
  return _parse_array_type(Tuple.new(parsed_types), dims)
19
21
  end
20
22
 
23
+ # uint256 => uint, 256, nil
24
+ # uint256[2] => uint, 256, [2]
25
+ # uint256[] => uint, 256, [-1]
26
+ # bytes => bytes, nil, []
21
27
  base, sub, dims = _parse_base_type(type)
22
28
 
29
+ sub ||= 256 if type.start_with?("uint") || type.start_with?("int") # default to 256 if no sub given
23
30
  _validate_base_type(base, sub)
24
31
 
25
32
  subtype = case base
@@ -96,7 +103,7 @@ module AbiCoderRb
96
103
  # NOTE: string can not have any suffix
97
104
  raise ParseError, "String cannot have suffix" if sub
98
105
  when "bytes"
99
- raise ParseError, "Maximum 32 bytes for fixed-length bytes" if sub && sub > 32
106
+ raise ParseError, "Maximum 32 bytes for fixed-length bytes" if sub && sub > 32
100
107
  when "uint", "int"
101
108
  raise ParseError, "Integer type must have numerical suffix" unless sub
102
109
  raise ParseError, "Integer size out of bounds" unless sub >= 8 && sub <= 256
@@ -210,12 +210,19 @@ module AbiCoderRb
210
210
 
211
211
  def size
212
212
  s = 0
213
+ has_dynamic = false
213
214
  @types.each do |type|
214
215
  ts = type.size
215
- return nil if ts.nil?
216
-
217
- s += ts
216
+ if ts.nil?
217
+ # can not return nil here? if wasm
218
+ has_dynamic = true
219
+ else
220
+ s += ts
221
+ end
218
222
  end
223
+
224
+ return if has_dynamic
225
+
219
226
  s
220
227
  end
221
228
 
@@ -0,0 +1,105 @@
1
+ module AbiCoderRb
2
+ def hex_to_bin(hex)
3
+ hex = hex[2..] if %w[0x 0X].include?(hex[0, 2]) ## cut-of leading 0x or 0X if present
4
+ hex.scan(/../).map { |x| x.hex.chr }.join
5
+ end
6
+ alias hex hex_to_bin
7
+
8
+ def bin_to_hex(bin)
9
+ bin.each_byte.map { |byte| "%02x" % byte }.join
10
+ end
11
+
12
+ def hex?(str)
13
+ str.start_with?("0x") && str.length.even? && str[2..].match?(/\A\b[0-9a-fA-F]+\b\z/)
14
+ end
15
+
16
+ ###########
17
+ # encoding helpers / utils
18
+ # with "hard-coded" fill symbol as BYTE_ZERO
19
+
20
+ def rpad(bin, l = 32) ## note: same as builtin String#ljust !!!
21
+ # note: default l word is 32 bytes
22
+ return bin if bin.size >= l
23
+
24
+ bin + BYTE_ZERO * (l - bin.size)
25
+ end
26
+
27
+ ## rename to lpad32 or such - why? why not?
28
+ # example:
29
+ # lpad("hello", 'x', 10) => "xxxxxxhello"
30
+ def lpad(bin) ## note: same as builtin String#rjust !!!
31
+ l = 32 # NOTE: default l word is 32 bytes
32
+ return bin if bin.size >= l
33
+
34
+ BYTE_ZERO * (l - bin.size) + bin
35
+ end
36
+
37
+ ## rename to lpad32_int or such - why? why not?
38
+ def lpad_int(n)
39
+ unless n.is_a?(Integer) && n >= 0 && n <= UINT_MAX
40
+ raise ArgumentError,
41
+ "Integer invalid or out of range: #{n}"
42
+ end
43
+
44
+ hex = n.to_s(16)
45
+ hex = "0#{hex}" if hex.length.odd? # wasm, no .odd?
46
+ bin = hex_to_bin(hex)
47
+
48
+ lpad(bin)
49
+ end
50
+
51
+ ## rename to lpad32_hex or such - why? why not?
52
+ def lpad_hex(hex)
53
+ raise TypeError, "Value must be a string" unless hex.is_a?(::String)
54
+ raise TypeError, "Non-hexadecimal digit found" unless hex =~ /\A[0-9a-fA-F]*\z/
55
+
56
+ bin = hex_to_bin(hex)
57
+
58
+ lpad(bin)
59
+ end
60
+
61
+ def ceil32(x)
62
+ x % 32 == 0 ? x : (x + 32 - x % 32)
63
+ end
64
+
65
+ def int_to_abi_signed_256bit(value)
66
+ # 确保值在256位有符号整数范围内
67
+ min = -2**255
68
+ max = 2**255 - 1
69
+ raise "Value out of range" if value < min || value > max
70
+
71
+ # 为负数计算补码
72
+ value = (1 << 256) + value if value < 0
73
+
74
+ # 转换为十六进制字符串
75
+ hex_str = value.to_s(16)
76
+
77
+ # 确保字符串长度为64字符(256位)
78
+ hex_str.rjust(64, "0")
79
+ end
80
+
81
+ def abi_to_int_signed(hex_str, bits)
82
+ hex_str = "0x#{hex_str}" if hex_str[0, 2] != "0x" || hex_str[0, 2] != "0X"
83
+
84
+ # 计算预期的十六进制字符串长度
85
+ expected_length = bits / 4
86
+ extended_hex_str = if hex_str.length < expected_length
87
+ # 如果输入长度小于预期,根据首位字符扩展字符串
88
+ extend_char = hex_str[0] == "f" ? "f" : "0"
89
+ extend_char * (expected_length - hex_str.length) + hex_str
90
+ else
91
+ hex_str
92
+ end
93
+
94
+ # 将十六进制字符串转换为二进制字符串
95
+ binary_str = extended_hex_str.to_i(16).to_s(2).rjust(bits, extended_hex_str[0])
96
+
97
+ # 检查符号位并转换为整数
98
+ if binary_str[0] == "1" # 负数
99
+ # 取反加一以计算补码,然后转换为负数
100
+ -((binary_str.tr("01", "10").to_i(2) + 1) & ((1 << bits) - 1))
101
+ else # 正数
102
+ binary_str.to_i(2)
103
+ end
104
+ end
105
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbiCoderRb
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
data/lib/abi_coder_rb.rb CHANGED
@@ -2,26 +2,22 @@
2
2
 
3
3
  require_relative "abi_coder_rb/version"
4
4
 
5
+ require_relative "abi_coder_rb/utils"
6
+
5
7
  require_relative "abi_coder_rb/parser"
6
8
  require_relative "abi_coder_rb/types"
7
9
  require_relative "abi_coder_rb/decode"
8
10
  require_relative "abi_coder_rb/encode"
9
11
 
12
+ require_relative "periphery/event_decoder"
13
+
10
14
  module AbiCoderRb
11
15
  class DecodingError < StandardError; end
12
16
  class EncodingError < StandardError; end
13
17
  class ValueError < StandardError; end
14
18
  class ValueOutOfBounds < ValueError; end
15
19
 
16
- ###################
17
- ### some (shared) constants (move to constants.rb or such - why? why not?)
18
-
19
- ## todo/check: use auto-freeze string literals magic comment - why? why not?
20
- ##
21
- ## todo/fix: move BYTE_EMPTY, BYTE_ZERO, BYTE_ONE to upstream to bytes gem
22
- ## and make "global" constants - why? why not?
23
-
24
- ## BYTE_EMPTY = "".b.freeze
20
+ BYTE_EMPTY = "".b.freeze
25
21
  BYTE_ZERO = "\x00".b.freeze
26
22
  BYTE_ONE = "\x01".b.freeze ## note: used for encoding bool for now
27
23
 
@@ -30,14 +26,13 @@ module AbiCoderRb
30
26
  INT_MAX = 2**255 - 1 ## same as 57896044618658097711785492504343953926634992332820282019728792003956564819967
31
27
  INT_MIN = -2**255 ## same as -57896044618658097711785492504343953926634992332820282019728792003956564819968
32
28
 
33
- def hex_to_bin(hex) # convert hex(adecimal) string to binary string
34
- hex = hex[2..] if %w[0x 0X].include?(hex[0, 2]) ## cut-of leading 0x or 0X if present
35
- [hex].pack("H*")
29
+ attr_accessor :before_encoding_action, :after_decoding_action
30
+
31
+ def before_encoding(action)
32
+ self.before_encoding_action = action
36
33
  end
37
- alias hex hex_to_bin
38
34
 
39
- def bin_to_hex(bin) # convert binary string to hex string
40
- bin.unpack1("H*")
35
+ def after_decoding(action)
36
+ self.after_decoding_action = action
41
37
  end
42
- alias bin bin_to_hex
43
38
  end
@@ -0,0 +1,177 @@
1
+ require "active_support"
2
+ require "active_support/core_ext/string"
3
+
4
+ class EventDecoder
5
+ include AbiCoderRb
6
+
7
+ attr_reader :event_abi,
8
+ # indexed_topic_inputs
9
+ :indexed_topic_inputs, :indexed_topic_types,
10
+ # data_inputs
11
+ :data_inputs, :data_type_str, :data_field_names, :data_field_names_flattened
12
+
13
+ def initialize(event_abi)
14
+ @event_abi = event_abi
15
+ @indexed_topic_inputs, @data_inputs = event_abi["inputs"].partition { |input| input["indexed"] }
16
+
17
+ # indexed_topic_inputs:
18
+ @indexed_topic_types = @indexed_topic_inputs.map { |input| input["type"] }
19
+
20
+ # data_inputs:
21
+ @data_fields = fields_of(@data_inputs)
22
+
23
+ @data_type_str = fields_type_str(@data_fields)
24
+
25
+ @data_field_names = fields_names(@data_fields)
26
+ @data_field_names_flattened = fields_names_flatten(@data_fields)
27
+
28
+ # add after_decoding action
29
+ after_decoding lambda { |type, value|
30
+ if type == "address"
31
+ "0x#{value}"
32
+ elsif type.start_with?("bytes")
33
+ "0x#{bin_to_hex(value)}"
34
+ else
35
+ value
36
+ end
37
+ }
38
+ end
39
+
40
+ def decode_topics(topics, with_names: false)
41
+ topics = topics[1..] if topics.count == @indexed_topic_inputs.count + 1 && @event_abi["anonymous"] == false
42
+
43
+ raise "topics count not match" if topics.count != @indexed_topic_inputs.count
44
+
45
+ values = topics.each_with_index.map do |topic, i|
46
+ indexed_topic_type = @indexed_topic_types[i]
47
+ decode(indexed_topic_type, hex_to_bin(topic))
48
+ end
49
+
50
+ if with_names
51
+ combine(@indexed_topic_inputs.map { |input| input["name"].underscore }, values)
52
+ else
53
+ values
54
+ end
55
+ end
56
+
57
+ def decode_data(data, flatten: true, with_names: false)
58
+ data_values = decode(@data_type_str, hex_to_bin(data))
59
+
60
+ case flatten
61
+ when true
62
+ if with_names
63
+ combine(@data_field_names_flattened, data_values.flatten)
64
+ else
65
+ data_values.flatten
66
+ end
67
+ when false
68
+ if with_names
69
+ combine(@data_field_names, data_values)
70
+ else
71
+ data_values
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ # fields:
79
+ # [
80
+ # ["root", "bytes32"],
81
+ # ["message", [["channel", "address"], ["index", "uint256"], ["fromChainId", "uint256"], ["from", "address"], ["toChainId", "uint256"], ["to", "address"], ["encoded", "bytes"]]]
82
+ # ]
83
+ #
84
+ # returns:
85
+ # '(bytes32,(address,uint256,uint256,address,uint256,address,bytes))'
86
+ def fields_type_str(fields)
87
+ "(#{
88
+ fields.map do |_name, type|
89
+ if type.is_a?(::Array)
90
+ fields_type_str(type)
91
+ else
92
+ type
93
+ end
94
+ end.join(",")
95
+ })"
96
+ end
97
+
98
+ # fields:
99
+ # [
100
+ # ["root", "bytes32"],
101
+ # ["message", [["channel", "address"], ["index", "uint256"], ["fromChainId", "uint256"], ["from", "address"], ["toChainId", "uint256"], ["to", "address"], ["encoded", "bytes"]]]
102
+ # ]
103
+ #
104
+ # returns:
105
+ # ["root", {"message" => ["channel", "index", "fromChainId", "from", "toChainId", "to", "gasLimit", "encoded"]}
106
+ def fields_names(fields)
107
+ fields.map do |name, type|
108
+ name = name.underscore
109
+ if type.is_a?(::Array)
110
+ { name => fields_names(type) }
111
+ elsif type.is_a?(::String)
112
+ name
113
+ end
114
+ end
115
+ end
116
+
117
+ # fields:
118
+ # [
119
+ # ["root", "bytes32"],
120
+ # ["message", [["channel", "address"], ["index", "uint256"], ["fromChainId", "uint256"], ["from", "address"], ["toChainId", "uint256"], ["to", "address"], ["encoded", "bytes"]]]
121
+ # ]
122
+ #
123
+ # returns:
124
+ # ["root", "message_channel", "message_index", "message_fromChainId", "message_from", "message_toChainId", "message_to", "message_gasLimit", "message_encoded"]
125
+ def fields_names_flatten(fields, prefix = nil)
126
+ fields.map do |name, type|
127
+ name = name.underscore
128
+ if type.is_a?(::Array)
129
+ fields_names_flatten(
130
+ type,
131
+ prefix.nil? ? name : "#{prefix}.#{name}"
132
+ )
133
+ elsif type.is_a?(::String)
134
+ prefix.nil? ? name : "#{prefix}.#{name}"
135
+ end
136
+ end.flatten
137
+ end
138
+
139
+ # returns:
140
+ # [
141
+ # ["root", "bytes32"],
142
+ # ["message", [["channel", "address"], ["index", "uint256"], ["fromChainId", "uint256"], ["from", "address"], ["toChainId", "uint256"], ["to", "address"], ["encoded", "bytes"]]]
143
+ # ]
144
+ def fields_of(inputs)
145
+ inputs.map do |input|
146
+ if input["type"] == "tuple"
147
+ [input["name"], fields_of(input["components"])]
148
+ elsif input["type"] == "enum"
149
+ [input["name"], "uint8"]
150
+ else
151
+ [input["name"], input["type"]]
152
+ end
153
+ end
154
+ end
155
+
156
+ # params:
157
+ # keys = ['key1', 'key2' => ['key2_1', 'key2_2']]
158
+ # values = [1, [2.1, 2.2]]
159
+ #
160
+ # returns:
161
+ # {"key1"=>1, "key2"=>{"key2_1"=>2.1, "key2_2"=>2.2}}
162
+ def combine(keys, values)
163
+ result = {}
164
+
165
+ keys.each_with_index do |key, index|
166
+ if key.is_a?(Hash)
167
+ key.each do |k, v|
168
+ result[k] = combine(v, values[index])
169
+ end
170
+ else
171
+ result[key] = values[index]
172
+ end
173
+ end
174
+
175
+ result
176
+ end
177
+ end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abi_coder_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aki Wu
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-13 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2023-11-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: keccak
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
13
41
  description: An EVM ABI encoding and decoding tool
14
42
  email:
15
43
  - wuminzhe@gmail.com
@@ -26,6 +54,7 @@ files:
26
54
  - LICENSE.txt
27
55
  - README.md
28
56
  - Rakefile
57
+ - flatten.rb
29
58
  - lib/.DS_Store
30
59
  - lib/abi_coder_rb.rb
31
60
  - lib/abi_coder_rb/.DS_Store
@@ -41,7 +70,9 @@ files:
41
70
  - lib/abi_coder_rb/encode/encode_tuple.rb
42
71
  - lib/abi_coder_rb/parser.rb
43
72
  - lib/abi_coder_rb/types.rb
73
+ - lib/abi_coder_rb/utils.rb
44
74
  - lib/abi_coder_rb/version.rb
75
+ - lib/periphery/event_decoder.rb
45
76
  - sig/abi_coder_rb.rbs
46
77
  homepage: https://github.com/wuminzhe/abi_coder_rb
47
78
  licenses: