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 +4 -4
- data/.DS_Store +0 -0
- data/Gemfile.lock +27 -1
- data/README.md +63 -2
- data/flatten.rb +8 -0
- data/lib/.DS_Store +0 -0
- data/lib/abi_coder_rb/decode/decode_array.rb +6 -6
- data/lib/abi_coder_rb/decode/decode_primitive_type.rb +27 -22
- data/lib/abi_coder_rb/decode/decode_tuple.rb +29 -0
- data/lib/abi_coder_rb/decode.rb +3 -33
- data/lib/abi_coder_rb/encode/encode_fixed_array.rb +15 -1
- data/lib/abi_coder_rb/encode/encode_primitive_type.rb +5 -51
- data/lib/abi_coder_rb/encode/encode_tuple.rb +32 -0
- data/lib/abi_coder_rb/encode.rb +13 -43
- data/lib/abi_coder_rb/parser.rb +8 -1
- data/lib/abi_coder_rb/types.rb +10 -3
- data/lib/abi_coder_rb/utils.rb +105 -0
- data/lib/abi_coder_rb/version.rb +1 -1
- data/lib/abi_coder_rb.rb +11 -16
- data/lib/periphery/event_decoder.rb +177 -0
- metadata +34 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 603c8a3cab98c321861f9480f1f668ffdf3559553974d09bdcd46858d97a44ea
|
4
|
+
data.tar.gz: 036b109f1f9f53088d592aeac51a66a9147707ae74a93a431a9c2be65617e37a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
4
|
-
raise DecodingError, "Too
|
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 *
|
9
|
+
raise DecodingError, "Not enough data for head" unless data.size >= 32 + 32 * size
|
10
10
|
|
11
|
-
start_positions = (1..
|
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...
|
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...
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
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
|
data/lib/abi_coder_rb/decode.rb
CHANGED
@@ -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(
|
8
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
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 !=
|
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
|
data/lib/abi_coder_rb/encode.rb
CHANGED
@@ -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
|
-
|
9
|
-
|
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
|
-
|
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,
|
18
|
+
def encode_type(type, value)
|
21
19
|
if type.is_a?(Tuple)
|
22
|
-
encode_tuple(type,
|
23
|
-
elsif type.is_a?(Array)
|
24
|
-
|
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,
|
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
|
data/lib/abi_coder_rb/parser.rb
CHANGED
@@ -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"
|
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
|
data/lib/abi_coder_rb/types.rb
CHANGED
@@ -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
|
-
|
216
|
-
|
217
|
-
|
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
|
data/lib/abi_coder_rb/version.rb
CHANGED
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
|
-
|
34
|
-
|
35
|
-
|
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
|
40
|
-
|
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
|
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-
|
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:
|