eth 0.5.10 → 0.5.12

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: 1fd0b420dd7b4641fe8e87f57f13fdb74b3a23a2c11e7c2e23355f1cc5f6b4a3
4
- data.tar.gz: e0c219b1c827ec4fcd4d730bfbb7d7e819a9d3049df6f01271cb79d7a81191ea
3
+ metadata.gz: a6da48b3e3128860af79efba95ded96258dfe87a8bf0a374a44bff060fc2f9c3
4
+ data.tar.gz: 9ad4cbabd4d5b31419d02b9363cdff915c4e1c61ba95197b42cf2a61ed31ac50
5
5
  SHA512:
6
- metadata.gz: cdfa9a6cc91db7fba7e6ffe02dba0c4ec5a3e93a4948c5080f461f20415a9803b566dbf8049a63951e82c85007b31202d1826bc2261fc0e6d806bc4af0caade9
7
- data.tar.gz: 97d962828b3c98387127ba69505a1170ef43ac8a40ba8aa70dc017cdc9ed91ca790b331fca7ca22eea3ba4d25158fcf3fb9e214bd195472a5b27b4bbc22ad790
6
+ metadata.gz: bbc07ab8bb0fc56f89619037ee16da5eeadb7d0fcde714572bd9b678e8c4da18b70e3fa042207c57195dd7e6e2700ebf5650bf5ca6ddf848da78661a2b8eda16
7
+ data.tar.gz: 51206666cc4a84bd05d0995ab6b7476699b17cc8bb37d3d78f789cf93116844dd3cd0f4e24360b59d7758d3588cc9fbebbe0504cdf1bd7b8a00a67e3600ac0ad
@@ -24,18 +24,18 @@ jobs:
24
24
  - ruby
25
25
  steps:
26
26
  - name: "Checkout repository"
27
- uses: actions/checkout@v3
27
+ uses: actions/checkout@v4
28
28
  - name: "Initialize CodeQL"
29
- uses: github/codeql-action/init@v2
29
+ uses: github/codeql-action/init@v3
30
30
  with:
31
31
  languages: "${{ matrix.language }}"
32
32
  - name: Autobuild
33
- uses: github/codeql-action/autobuild@v2
33
+ uses: github/codeql-action/autobuild@v3
34
34
  - name: "Perform CodeQL Analysis"
35
- uses: github/codeql-action/analyze@v2
35
+ uses: github/codeql-action/analyze@v3
36
36
  - uses: ruby/setup-ruby@v1
37
37
  with:
38
- ruby-version: '3.0'
38
+ ruby-version: '3.3'
39
39
  bundler-cache: true
40
40
  - name: "Run rufo code formatting checks"
41
41
  run: |
@@ -10,17 +10,17 @@ jobs:
10
10
  docs:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v3
13
+ - uses: actions/checkout@v4
14
14
  - uses: ruby/setup-ruby@v1
15
15
  with:
16
- ruby-version: '3.0'
16
+ ruby-version: '3.3'
17
17
  bundler-cache: true
18
18
  - name: Run Yard Doc
19
19
  run: |
20
20
  gem install yard
21
21
  yard doc
22
22
  - name: Deploy GH Pages
23
- uses: JamesIves/github-pages-deploy-action@v4.4.1
23
+ uses: JamesIves/github-pages-deploy-action@v4.6.1
24
24
  with:
25
25
  branch: gh-pages
26
26
  folder: doc/
@@ -18,10 +18,10 @@ jobs:
18
18
  strategy:
19
19
  fail-fast: false
20
20
  matrix:
21
- os: [ubuntu-22.04, macos-12]
22
- ruby: ['3.0', '3.1']
21
+ os: [ubuntu-latest, macos-latest]
22
+ ruby: ['3.1', '3.2', '3.3']
23
23
  steps:
24
- - uses: actions/checkout@v3
24
+ - uses: actions/checkout@v4
25
25
  - uses: ruby/setup-ruby@v1
26
26
  with:
27
27
  ruby-version: ${{ matrix.ruby }}
@@ -49,5 +49,9 @@ jobs:
49
49
  run: |
50
50
  bundle exec rspec
51
51
  env:
52
- COVERAGE: true
53
52
  INFURA_TOKEN: ${{ secrets.INFURA_TOKEN }}
53
+ - name: Upload coverage to Codecov
54
+ uses: codecov/codecov-action@v4
55
+ with:
56
+ fail_ci_if_error: false
57
+ token: ${{ secrets.CODECOV_TOKEN }}
data/CHANGELOG.md CHANGED
@@ -1,6 +1,58 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [0.5.12]
5
+ ### Added
6
+ ### Changed
7
+
8
+ ## [0.5.11]
9
+ ### Added
10
+ * Eth/abi: allow encoding address types [#242](https://github.com/q9f/eth.rb/pull/242)
11
+ * Eth/solidity: enable --via-ir [#232](https://github.com/q9f/eth.rb/pull/232)
12
+ * Checking userinfo with the uri method [#233](https://github.com/q9f/eth.rb/pull/233)
13
+ * Eth/abi: add abicoder gem tests collection [#218](https://github.com/q9f/eth.rb/pull/218)
14
+ * Manual default_account [#215](https://github.com/q9f/eth.rb/pull/215)
15
+ * Add moonbeam networks in [#209](https://github.com/q9f/eth.rb/pull/209)
16
+
17
+ ### Changed
18
+ * Spec: run rufo [#245](https://github.com/q9f/eth.rb/pull/245)
19
+ * Fix the decoding of unsigned transactions [#243](https://github.com/q9f/eth.rb/pull/243)
20
+ * Build(deps): bump JamesIves/github-pages-deploy-action from 4.4.2 to 4.4.3 [#244](https://github.com/q9f/eth.rb/pull/244)
21
+ * Build(deps): bump JamesIves/github-pages-deploy-action from 4.4.1 to 4.4.2 [#240](https://github.com/q9f/eth.rb/pull/240)
22
+ * Eth/tx: update tx initcode cost for shanghai [#237](https://github.com/q9f/eth.rb/pull/237)
23
+ * Eth/client: remove default gas limit attribute [#235](https://github.com/q9f/eth.rb/pull/235)
24
+ * Docs: minor fixups [#229](https://github.com/q9f/eth.rb/pull/229)
25
+ * Eth/contract: ensure contract name is title case [#228](https://github.com/q9f/eth.rb/pull/228)
26
+ * Deps: require forwardable for contracts [#227](https://github.com/q9f/eth.rb/pull/227)
27
+ * Ens/resolver: remove pending for etc coin type [#219](https://github.com/q9f/eth.rb/pull/219)
28
+ * Deps: update secp256k1 to 6 [#214](https://github.com/q9f/eth.rb/pull/214)
29
+ * Eth/solidity: add docs for solc path override [#213](https://github.com/q9f/eth.rb/pull/213)
30
+ * Manually overwrite solc path [#212](https://github.com/q9f/eth.rb/pull/212)
31
+ * Abi.decoder handles arrays of string and bytes [#207](https://github.com/q9f/eth.rb/pull/207)
32
+ * Eth/util: fix compressed public key to address in [#206](https://github.com/q9f/eth.rb/pull/206)
33
+ * Eth/api: update execution apis to latest spec [#204](https://github.com/q9f/eth.rb/pull/204)
34
+ * Eth/abi: split abi class into encoder and decoder [#203](https://github.com/q9f/eth.rb/pull/203)
35
+ * Eth/client: deduplicate code [#202](https://github.com/q9f/eth.rb/pull/202)
36
+ * Eth/client: rewrite send to send_request [#201](https://github.com/q9f/eth.rb/pull/201)
37
+ * Docs: update changelog for 0.5.10 [#200](https://github.com/q9f/eth.rb/pull/200)
38
+ * Tested with Ruby 3.2 [#199](https://github.com/q9f/eth.rb/pull/199)
39
+
40
+ ## [0.5.10]
41
+ ### Added
42
+ * Eth/client: add transfer_erc20 function [#197](https://github.com/q9f/eth.rb/pull/197)
43
+ * Eth/client: add resolve_ens function [#192](https://github.com/q9f/eth.rb/pull/192)
44
+
45
+ ### Changed
46
+ * Eth/ens: restore docs for normalize [#198](https://github.com/q9f/eth.rb/pull/198)
47
+ * Docs: update readme [#195](https://github.com/q9f/eth.rb/pull/195)
48
+ * Eth/contract: ensure address arrays support [#194](https://github.com/q9f/eth.rb/pull/194)
49
+ * Eth/client: do not allow accessing local accounts on remote connections [#193](https://github.com/q9f/eth.rb/pull/193)
50
+ * Eth/client: correctly select functions [#191](https://github.com/q9f/eth.rb/pull/191)
51
+ * Docs: create security policy [#190](https://github.com/q9f/eth.rb/pull/190)
52
+ * Docs: add contribution guidelines [#189](https://github.com/q9f/eth.rb/pull/189)
53
+ * Docs: add coc [#188](https://github.com/q9f/eth.rb/pull/188)
54
+ * Docs: update changelog for 0.5.9 [#187](https://github.com/q9f/eth.rb/pull/187)
55
+
4
56
  ## [0.5.9]
5
57
  ### Added
6
58
  * Eth/abi: dynamic struct encoding (#135) [#185](https://github.com/q9f/eth.rb/pull/185)
data/eth.gemspec CHANGED
@@ -34,6 +34,9 @@ Gem::Specification.new do |spec|
34
34
  spec.platform = Gem::Platform::RUBY
35
35
  spec.required_ruby_version = ">= 2.7", "< 4.0"
36
36
 
37
+ # forwardable for contracts meta programming
38
+ spec.add_dependency "forwardable", "~> 1.3"
39
+
37
40
  # keccak for hashing everything in ethereum
38
41
  spec.add_dependency "keccak", "~> 1.3"
39
42
 
@@ -41,7 +44,7 @@ Gem::Specification.new do |spec|
41
44
  spec.add_dependency "konstructor", "~> 1.0"
42
45
 
43
46
  # rbsecp256k1 for key-pairs and signatures
44
- spec.add_dependency "rbsecp256k1", "~> 5.1"
47
+ spec.add_dependency "rbsecp256k1", "~> 6.0"
45
48
 
46
49
  # openssl for encrypted key derivation
47
50
  spec.add_dependency "openssl", ">= 2.2", "< 4.0"
@@ -0,0 +1,154 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -*- encoding : ascii-8bit -*-
16
+
17
+ # Provides the {Eth} module.
18
+ module Eth
19
+
20
+ # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI).
21
+ module Abi
22
+
23
+ # Provides a utility module to assist decoding ABIs.
24
+ module Decoder
25
+ extend self
26
+
27
+ # Decodes a specific value, either static or dynamic.
28
+ #
29
+ # @param type [Eth::Abi::Type] type to be decoded.
30
+ # @param arg [String] encoded type data string.
31
+ # @return [String] the decoded data for the type.
32
+ # @raise [DecodingError] if decoding fails for type.
33
+ def type(type, arg)
34
+ if %w(string bytes).include?(type.base_type) and type.sub_type.empty?
35
+ # Case: decoding a string/bytes
36
+ if type.dimensions.empty?
37
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
38
+ data = arg[32..-1]
39
+ raise DecodingError, "Wrong data size for string/bytes object" unless data.size == Util.ceil32(l)
40
+
41
+ # decoded strings and bytes
42
+ data[0, l]
43
+ # Case: decoding array of string/bytes
44
+ else
45
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
46
+
47
+ # Decode each element of the array
48
+ (1..l).map do |i|
49
+ pointer = Util.deserialize_big_endian_to_int arg[i * 32, 32] # Pointer to the size of the array's element
50
+ data_l = Util.deserialize_big_endian_to_int arg[32 + pointer, 32] # length of the element
51
+ type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32])
52
+ end
53
+ end
54
+ elsif type.base_type == "tuple"
55
+ offset = 0
56
+ data = {}
57
+ type.components.each do |c|
58
+ if c.dynamic?
59
+ pointer = Util.deserialize_big_endian_to_int arg[offset, 32] # Pointer to the size of the array's element
60
+ data_len = Util.deserialize_big_endian_to_int arg[pointer, 32] # length of the element
61
+
62
+ data[c.name] = type(c, arg[pointer, Util.ceil32(data_len) + 32])
63
+ # puts data
64
+ offset += 32
65
+ else
66
+ size = c.size
67
+ data[c.name] = type(c, arg[offset, size])
68
+ offset += size
69
+ # puts data
70
+ end
71
+ end
72
+ data
73
+ elsif type.dynamic?
74
+ l = Util.deserialize_big_endian_to_int arg[0, 32]
75
+ nested_sub = type.nested_sub
76
+
77
+ # ref https://github.com/ethereum/tests/issues/691
78
+ raise NotImplementedError, "Decoding dynamic arrays with nested dynamic sub-types is not implemented for ABI." if nested_sub.dynamic?
79
+
80
+ # decoded dynamic-sized arrays
81
+ (0...l).map { |i| type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
82
+ elsif !type.dimensions.empty?
83
+ l = type.dimensions.first
84
+ nested_sub = type.nested_sub
85
+
86
+ # decoded static-size arrays
87
+ (0...l).map { |i| type(nested_sub, arg[nested_sub.size * i, nested_sub.size]) }
88
+ else
89
+
90
+ # decoded primitive types
91
+ primitive_type type, arg
92
+ end
93
+ end
94
+
95
+ # Decodes primitive types.
96
+ #
97
+ # @param type [Eth::Abi::Type] type to be decoded.
98
+ # @param data [String] encoded primitive type data string.
99
+ # @return [String] the decoded data for the type.
100
+ # @raise [DecodingError] if decoding fails for type.
101
+ def primitive_type(type, data)
102
+ case type.base_type
103
+ when "address"
104
+
105
+ # decoded address with 0x-prefix
106
+ "0x#{Util.bin_to_hex data[12..-1]}"
107
+ when "string", "bytes"
108
+ if type.sub_type.empty?
109
+ size = Util.deserialize_big_endian_to_int data[0, 32]
110
+
111
+ # decoded dynamic-sized array
112
+ data[32..-1][0, size]
113
+ else
114
+
115
+ # decoded static-sized array
116
+ data[0, type.sub_type.to_i]
117
+ end
118
+ when "hash"
119
+
120
+ # decoded hash
121
+ data[(32 - type.sub_type.to_i), type.sub_type.to_i]
122
+ when "uint"
123
+
124
+ # decoded unsigned integer
125
+ Util.deserialize_big_endian_to_int data
126
+ when "int"
127
+ u = Util.deserialize_big_endian_to_int data
128
+ i = u >= 2 ** (type.sub_type.to_i - 1) ? (u - 2 ** 256) : u
129
+
130
+ # decoded integer
131
+ i
132
+ when "ureal", "ufixed"
133
+ high, low = type.sub_type.split("x").map(&:to_i)
134
+
135
+ # decoded unsigned fixed point numeric
136
+ Util.deserialize_big_endian_to_int(data) * 1.0 / 2 ** low
137
+ when "real", "fixed"
138
+ high, low = type.sub_type.split("x").map(&:to_i)
139
+ u = Util.deserialize_big_endian_to_int data
140
+ i = u >= 2 ** (high + low - 1) ? (u - 2 ** (high + low)) : u
141
+
142
+ # decoded fixed point numeric
143
+ i * 1.0 / 2 ** low
144
+ when "bool"
145
+
146
+ # decoded boolean
147
+ data[-1] == Constant::BYTE_ONE
148
+ else
149
+ raise DecodingError, "Unknown primitive type: #{type.base_type}"
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -0,0 +1,304 @@
1
+ # Copyright (c) 2016-2023 The Ruby-Eth Contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # -*- encoding : ascii-8bit -*-
16
+
17
+ # Provides the {Eth} module.
18
+ module Eth
19
+
20
+ # Provides a Ruby implementation of the Ethereum Application Binary Interface (ABI).
21
+ module Abi
22
+
23
+ # Provides a utility module to assist encoding ABIs.
24
+ module Encoder
25
+ extend self
26
+
27
+ # Encodes a specific value, either static or dynamic.
28
+ #
29
+ # @param type [Eth::Abi::Type] type to be encoded.
30
+ # @param arg [String|Number] value to be encoded.
31
+ # @return [String] the encoded type.
32
+ # @raise [EncodingError] if value does not match type.
33
+ def type(type, arg)
34
+ if %w(string bytes).include? type.base_type and type.sub_type.empty? and type.dimensions.empty?
35
+ raise EncodingError, "Argument must be a String" unless arg.instance_of? String
36
+
37
+ # encodes strings and bytes
38
+ size = type Type.size_type, arg.size
39
+ padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
40
+ "#{size}#{arg}#{padding}"
41
+ elsif type.base_type == "tuple" && type.dimensions.size == 1 && type.dimensions[0] != 0
42
+ result = ""
43
+ result += struct_offsets(type.nested_sub, arg)
44
+ result += arg.map { |x| type(type.nested_sub, x) }.join
45
+ result
46
+ elsif type.dynamic? && arg.is_a?(Array)
47
+
48
+ # encodes dynamic-sized arrays
49
+ head, tail = "", ""
50
+ head += type(Type.size_type, arg.size)
51
+ nested_sub = type.nested_sub
52
+ nested_sub_size = type.nested_sub.size
53
+
54
+ # calculate offsets
55
+ if %w(string bytes).include?(type.base_type) && type.sub_type.empty?
56
+ offset = 0
57
+ arg.size.times do |i|
58
+ if i == 0
59
+ offset = arg.size * 32
60
+ else
61
+ number_of_words = ((arg[i - 1].size + 32 - 1) / 32).floor
62
+ total_bytes_length = number_of_words * 32
63
+ offset += total_bytes_length + 32
64
+ end
65
+
66
+ head += type(Type.size_type, offset)
67
+ end
68
+ elsif nested_sub.base_type == "tuple" && nested_sub.dynamic?
69
+ head += struct_offsets(nested_sub, arg)
70
+ end
71
+
72
+ arg.size.times do |i|
73
+ head += type nested_sub, arg[i]
74
+ end
75
+ "#{head}#{tail}"
76
+ else
77
+ if type.dimensions.empty?
78
+
79
+ # encode a primitive type
80
+ primitive_type type, arg
81
+ else
82
+
83
+ # encode static-size arrays
84
+ arg.map { |x| type(type.nested_sub, x) }.join
85
+ end
86
+ end
87
+ end
88
+
89
+ # Encodes primitive types.
90
+ #
91
+ # @param type [Eth::Abi::Type] type to be encoded.
92
+ # @param arg [String|Number] value to be encoded.
93
+ # @return [String] the encoded primitive type.
94
+ # @raise [EncodingError] if value does not match type.
95
+ # @raise [ValueOutOfBounds] if value is out of bounds for type.
96
+ # @raise [EncodingError] if encoding fails for type.
97
+ def primitive_type(type, arg)
98
+ case type.base_type
99
+ when "uint"
100
+ uint arg, type
101
+ when "bool"
102
+ bool arg
103
+ when "int"
104
+ int arg, type
105
+ when "ureal", "ufixed"
106
+ ufixed arg, type
107
+ when "real", "fixed"
108
+ fixed arg, type
109
+ when "string", "bytes"
110
+ bytes arg, type
111
+ when "tuple"
112
+ tuple arg, type
113
+ when "hash"
114
+ hash arg, type
115
+ when "address"
116
+ address arg
117
+ else
118
+ raise EncodingError, "Unhandled type: #{type.base_type} #{type.sub_type}"
119
+ end
120
+ end
121
+
122
+ private
123
+
124
+ # Properly encodes unsigned integers.
125
+ def uint(arg, type)
126
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
127
+ raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::UINT_MAX or arg < Constant::UINT_MIN
128
+ real_size = type.sub_type.to_i
129
+ i = arg.to_i
130
+ raise ValueOutOfBounds, arg unless i >= 0 and i < 2 ** real_size
131
+ Util.zpad_int i
132
+ end
133
+
134
+ # Properly encodes signed integers.
135
+ def int(arg, type)
136
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
137
+ raise ValueOutOfBounds, "Number out of range: #{arg}" if arg > Constant::INT_MAX or arg < Constant::INT_MIN
138
+ real_size = type.sub_type.to_i
139
+ i = arg.to_i
140
+ raise ValueOutOfBounds, arg unless i >= -2 ** (real_size - 1) and i < 2 ** (real_size - 1)
141
+ Util.zpad_int(i % 2 ** 256)
142
+ end
143
+
144
+ # Properly encodes booleans.
145
+ def bool(arg)
146
+ raise EncodingError, "Argument is not bool: #{arg}" unless arg.instance_of? TrueClass or arg.instance_of? FalseClass
147
+ Util.zpad_int(arg ? 1 : 0)
148
+ end
149
+
150
+ # Properly encodes unsigned fixed-point numbers.
151
+ def ufixed(arg, type)
152
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
153
+ high, low = type.sub_type.split("x").map(&:to_i)
154
+ raise ValueOutOfBounds, arg unless arg >= 0 and arg < 2 ** high
155
+ Util.zpad_int((arg * 2 ** low).to_i)
156
+ end
157
+
158
+ # Properly encodes signed fixed-point numbers.
159
+ def fixed(arg, type)
160
+ raise ArgumentError, "Don't know how to handle this input." unless arg.is_a? Numeric
161
+ high, low = type.sub_type.split("x").map(&:to_i)
162
+ raise ValueOutOfBounds, arg unless arg >= -2 ** (high - 1) and arg < 2 ** (high - 1)
163
+ i = (arg * 2 ** low).to_i
164
+ Util.zpad_int(i % 2 ** (high + low))
165
+ end
166
+
167
+ # Properly encodes byte-strings.
168
+ def bytes(arg, type)
169
+ raise EncodingError, "Expecting String: #{arg}" unless arg.instance_of? String
170
+ arg = handle_hex_string arg, type
171
+
172
+ if type.sub_type.empty?
173
+ size = Util.zpad_int arg.size
174
+ padding = Constant::BYTE_ZERO * (Util.ceil32(arg.size) - arg.size)
175
+
176
+ # variable length string/bytes
177
+ "#{size}#{arg}#{padding}"
178
+ else
179
+ raise ValueOutOfBounds, arg unless arg.size <= type.sub_type.to_i
180
+ padding = Constant::BYTE_ZERO * (32 - arg.size)
181
+
182
+ # fixed length string/bytes
183
+ "#{arg}#{padding}"
184
+ end
185
+ end
186
+
187
+ # Properly encodes tuples.
188
+ def tuple(arg, type)
189
+ raise EncodingError, "Expecting Hash: #{arg}" unless arg.instance_of? Hash
190
+ raise EncodingError, "Expecting #{type.components.size} elements: #{arg}" unless arg.size == type.components.size
191
+
192
+ static_size = 0
193
+ type.components.each_with_index do |component, i|
194
+ if type.components[i].dynamic?
195
+ static_size += 32
196
+ else
197
+ static_size += Util.ceil32(type.components[i].size || 0)
198
+ end
199
+ end
200
+
201
+ dynamic_offset = static_size
202
+ offsets_and_static_values = []
203
+ dynamic_values = []
204
+
205
+ type.components.each_with_index do |component, i|
206
+ component_type = type.components[i]
207
+ if component_type.dynamic?
208
+ offsets_and_static_values << type(Type.size_type, dynamic_offset)
209
+ dynamic_value = type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name])
210
+ dynamic_values << dynamic_value
211
+ dynamic_offset += dynamic_value.size
212
+ else
213
+ offsets_and_static_values << type(component_type, arg.is_a?(Array) ? arg[i] : arg[component_type.name])
214
+ end
215
+ end
216
+
217
+ offsets_and_static_values.join + dynamic_values.join
218
+ end
219
+
220
+ # Properly encode struct offsets.
221
+ def struct_offsets(type, arg)
222
+ result = ""
223
+ offset = arg.size
224
+ tails_encoding = arg.map { |a| type(type, a) }
225
+ arg.size.times do |i|
226
+ if i == 0
227
+ offset *= 32
228
+ else
229
+ offset += tails_encoding[i - 1].size
230
+ end
231
+ offset_string = type(Type.size_type, offset)
232
+ result += offset_string
233
+ end
234
+ result
235
+ end
236
+
237
+ # Properly encodes hash-strings.
238
+ def hash(arg, type)
239
+ size = type.sub_type.to_i
240
+ raise EncodingError, "Argument too long: #{arg}" unless size > 0 and size <= 32
241
+ if arg.is_a? Integer
242
+
243
+ # hash from integer
244
+ Util.zpad_int arg
245
+ elsif arg.size == size
246
+
247
+ # hash from encoded hash
248
+ Util.zpad arg, 32
249
+ elsif arg.size == size * 2
250
+
251
+ # hash from hexadecimal hash
252
+ Util.zpad_hex arg
253
+ else
254
+ raise EncodingError, "Could not parse hash: #{arg}"
255
+ end
256
+ end
257
+
258
+ # Properly encodes addresses.
259
+ def address(arg)
260
+ if arg.is_a? Address
261
+
262
+ # from checksummed address with 0x prefix
263
+ Util.zpad_hex arg.to_s[2..-1]
264
+ elsif arg.is_a? Integer
265
+
266
+ # address from integer
267
+ Util.zpad_int arg
268
+ elsif arg.size == 20
269
+
270
+ # address from encoded address
271
+ Util.zpad arg, 32
272
+ elsif arg.size == 40
273
+
274
+ # address from hexadecimal address
275
+ Util.zpad_hex arg
276
+ elsif arg.size == 42 and arg[0, 2] == "0x"
277
+
278
+ # address from hexadecimal address with 0x prefix
279
+ Util.zpad_hex arg[2..-1]
280
+ else
281
+ raise EncodingError, "Could not parse address: #{arg}"
282
+ end
283
+ end
284
+
285
+ # The ABI encoder needs to be able to determine between a hex `"123"`
286
+ # and a binary `"123"` string.
287
+ def handle_hex_string(arg, type)
288
+ if Util.prefixed? arg or
289
+ (arg.size === type.sub_type.to_i * 2 and Util.hex? arg)
290
+
291
+ # There is no way telling whether a string is hex or binary with certainty
292
+ # in Ruby. Therefore, we assume a `0x` prefix to indicate a hex string.
293
+ # Additionally, if the string size is exactly the double of the expected
294
+ # binary size, we can assume a hex value.
295
+ Util.hex_to_bin arg
296
+ else
297
+
298
+ # Everything else will be assumed binary or raw string.
299
+ arg.b
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
data/lib/eth/abi/event.rb CHANGED
@@ -29,10 +29,31 @@ module Eth
29
29
  # @param interface [Hash] ABI event interface.
30
30
  # @return [String] a hex-string topic.
31
31
  def compute_topic(interface)
32
- sig = Abi.signature(interface)
32
+ sig = signature(interface)
33
33
  Util.prefix_hex(Util.bin_to_hex(Util.keccak256(sig)))
34
34
  end
35
35
 
36
+ # Build event signature string from ABI interface.
37
+ #
38
+ # @param interface [Hash] ABI event interface.
39
+ # @return [String] interface signature string.
40
+ def signature(interface)
41
+ name = interface.fetch("name")
42
+ inputs = interface.fetch("inputs", [])
43
+ types = inputs.map { |i| type(i) }
44
+ "#{name}(#{types.join(",")})"
45
+ end
46
+
47
+ def type(input)
48
+ if input["type"] == "tuple"
49
+ "(#{input["components"].map { |c| type(c) }.join(",")})"
50
+ elsif input["type"] == "enum"
51
+ "uint8"
52
+ else
53
+ input["type"]
54
+ end
55
+ end
56
+
36
57
  # A decoded event log.
37
58
  class LogDescription
38
59
  # The event ABI interface used to decode the log.
@@ -70,7 +91,7 @@ module Eth
70
91
 
71
92
  # The event signature. (e.g. Transfer(address,address,uint256))
72
93
  def signature
73
- @signature ||= Abi.signature(event_interface)
94
+ @signature ||= Abi::Event.signature(event_interface)
74
95
  end
75
96
  end
76
97
 
@@ -105,9 +126,20 @@ module Eth
105
126
  def decode_log(inputs, data, topics, anonymous = false)
106
127
  topic_inputs, data_inputs = inputs.partition { |i| i["indexed"] }
107
128
 
108
- topic_types = topic_inputs.map { |i| i["type"] }
109
- data_types = data_inputs.map { |i| i["type"] }
110
-
129
+ topic_types = topic_inputs.map do |i|
130
+ if i["type"] == "tuple"
131
+ Type.parse(i["type"], i["components"], i["name"])
132
+ else
133
+ i["type"]
134
+ end
135
+ end
136
+ data_types = data_inputs.map do |i|
137
+ if i["type"] == "tuple"
138
+ Type.parse(i["type"], i["components"], i["name"])
139
+ else
140
+ i["type"]
141
+ end
142
+ end
111
143
  # If event is anonymous, all topics are arguments. Otherwise, the first
112
144
  # topic will be the event signature.
113
145
  if anonymous == false