eth 0.5.14 → 0.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/CODE_OF_CONDUCT.md +3 -5
- data/Gemfile +3 -3
- data/README.md +5 -3
- data/SECURITY.md +2 -2
- data/eth.gemspec +1 -1
- data/lib/eth/abi/decoder.rb +16 -5
- data/lib/eth/abi/encoder.rb +10 -21
- data/lib/eth/abi/function.rb +124 -0
- data/lib/eth/abi/type.rb +66 -5
- data/lib/eth/abi.rb +2 -0
- data/lib/eth/chain.rb +3 -0
- data/lib/eth/client.rb +37 -36
- data/lib/eth/contract/error.rb +62 -0
- data/lib/eth/contract/function.rb +21 -0
- data/lib/eth/contract/function_output.rb +11 -3
- data/lib/eth/contract.rb +55 -4
- data/lib/eth/eip712.rb +48 -13
- data/lib/eth/key.rb +1 -1
- data/lib/eth/tx/eip1559.rb +32 -7
- data/lib/eth/tx/eip2930.rb +31 -6
- data/lib/eth/tx/eip4844.rb +389 -0
- data/lib/eth/tx/eip7702.rb +34 -9
- data/lib/eth/tx/legacy.rb +30 -6
- data/lib/eth/tx.rb +42 -4
- data/lib/eth/util.rb +19 -7
- data/lib/eth/version.rb +1 -1
- metadata +10 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 58842e5f3990be268758249080b56b70c1f26f8299ef27e7549db6cc2f675e51
|
4
|
+
data.tar.gz: 388a599dde8f50f34d46287e028e4dfe0c855815baa10de4a8c258b549429286
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d4e42a99aebc7a47673b0706ce8476e08beca249ecae812f10c76e4f8767088b6a185c232abf9cc977a6680b08ac7b38de9d211cfc741f3bb2aa5fce9a4730c
|
7
|
+
data.tar.gz: cb29ed0c90b56eeae5ce519a436fada62e47690c1ca447e230d0fa2f3915fe97cd3346506c6b67e9c93a88d333197f0735529dd3c66c764d1895e76b1aa82b57
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,39 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## [0.5.15]
|
5
|
+
### Added
|
6
|
+
* Eth/tx: add support for EIP-4844 transactions [#345](https://github.com/q9f/eth.rb/pull/345)
|
7
|
+
* Eth/contract: support solidity custom errors as per ERC-6093 [#344](https://github.com/q9f/eth.rb/pull/344)
|
8
|
+
* Eth/abi: decode transaction input [#354](https://github.com/q9f/eth.rb/pull/354)
|
9
|
+
|
10
|
+
### Fixed
|
11
|
+
* Eth/abi: handle tuple type without components [#335](https://github.com/q9f/eth.rb/issues/335)
|
12
|
+
|
13
|
+
## [0.5.14]
|
14
|
+
### Added
|
15
|
+
* Add ability to decode event parameters using ABI reference. [#328](https://github.com/q9f/eth.rb/pull/328)
|
16
|
+
* Add support for EIP-7702 transactions [#320](https://github.com/q9f/eth.rb/pull/320)
|
17
|
+
* Eth/abi: implement packed encoder [#310](https://github.com/q9f/eth.rb/pull/310)
|
18
|
+
|
19
|
+
### Changed
|
20
|
+
* Chore: run rufo, add docs [#332](https://github.com/q9f/eth.rb/pull/332)
|
21
|
+
* Spec/client: fix nonce too low error handling in spec [#331](https://github.com/q9f/eth.rb/pull/331)
|
22
|
+
* Move the tests that are failing due to a geth upgrade to pending [#330](https://github.com/q9f/eth.rb/pull/330)
|
23
|
+
* Spec/client: don't require any rpc api token for tests [#326](https://github.com/q9f/eth.rb/pull/326)
|
24
|
+
* Build(deps): bump JamesIves/github-pages-deploy-action from 4.7.2 to 4.7.3 [#327](https://github.com/q9f/eth.rb/pull/327)
|
25
|
+
* Eth/eip712: prepare tests for packed encoding [#216](https://github.com/q9f/eth.rb/pull/216)
|
26
|
+
* Spec/solidity: mute system call output [#319](https://github.com/q9f/eth.rb/pull/319)
|
27
|
+
* Updated nesting of describe blocks in the EIP-1559 spec. [#318](https://github.com/q9f/eth.rb/pull/318)
|
28
|
+
* Update README.md [#317](https://github.com/q9f/eth.rb/pull/317)
|
29
|
+
* Docs: update README.md [#314](https://github.com/q9f/eth.rb/pull/314)
|
30
|
+
* Gem: update copyright headers [#312](https://github.com/q9f/eth.rb/pull/312)
|
31
|
+
* Build(deps): bump JamesIves/github-pages-deploy-action from 4.7.1 to 4.7.2 [#309](https://github.com/q9f/eth.rb/pull/309)
|
32
|
+
* Spec: switch from infura to drpc [#308](https://github.com/q9f/eth.rb/pull/308)
|
33
|
+
* Ci: update ruby version [#307](https://github.com/q9f/eth.rb/pull/307)
|
34
|
+
* Gem: bump version to 0.5.14 [#305](https://github.com/q9f/eth.rb/pull/305)
|
35
|
+
* Docs: update changelog [#304](https://github.com/q9f/eth.rb/pull/304)
|
36
|
+
|
4
37
|
## [0.5.13]
|
5
38
|
### Changed
|
6
39
|
* Eth/api: update to latest available go-ethereum apis [#301](https://github.com/q9f/eth.rb/pull/301)
|
data/CODE_OF_CONDUCT.md
CHANGED
@@ -115,8 +115,6 @@ community.
|
|
115
115
|
|
116
116
|
## Attribution
|
117
117
|
|
118
|
-
This Code of Conduct is adapted from the [Contributor Covenant]
|
119
|
-
version 2.1, available at
|
120
|
-
|
121
|
-
Community Impact Guidelines were inspired by
|
122
|
-
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
118
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
119
|
+
version 2.1, available at [v2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
|
120
|
+
Community Impact Guidelines were inspired by Mozilla's code of conduct enforcement ladder.
|
data/Gemfile
CHANGED
@@ -3,14 +3,14 @@
|
|
3
3
|
source "https://rubygems.org"
|
4
4
|
|
5
5
|
group :test, :development do
|
6
|
-
gem "bundler", "~> 2.
|
6
|
+
gem "bundler", "~> 2.5"
|
7
7
|
gem "pry", "~> 0.15"
|
8
8
|
gem "rake", "~> 13.2"
|
9
|
-
gem "rdoc", "~> 6.
|
9
|
+
gem "rdoc", "~> 6.13"
|
10
10
|
gem "rspec", "~> 3.13"
|
11
11
|
gem "rufo", "~> 0.18"
|
12
12
|
gem "simplecov", "~> 0.22"
|
13
|
-
gem "simplecov-cobertura", "~>
|
13
|
+
gem "simplecov-cobertura", "~> 3.0"
|
14
14
|
gem "yard", "~> 0.9"
|
15
15
|
end
|
16
16
|
|
data/README.md
CHANGED
@@ -10,9 +10,7 @@
|
|
10
10
|
[](https://github.com/q9f/eth.rb/releases)
|
11
11
|
[](https://rubygems.org/gems/eth)
|
12
12
|
[](https://rubygems.org/gems/eth)
|
13
|
-
[](https://hits.seeyoufarm.com)
|
14
13
|
[](https://codecov.io/gh/q9f/eth.rb)
|
15
|
-
[](https://codeclimate.com/github/q9f/eth.rb/maintainability)
|
16
14
|
[](https://github.com/q9f/eth.rb/pulse)
|
17
15
|
[](https://q9f.github.io/eth.rb)
|
18
16
|
[](https://github.com/q9f/eth.rb/wiki)
|
@@ -34,11 +32,15 @@ What you get:
|
|
34
32
|
- [x] EIP-2028 Call-data intrinsic gas cost estimates (plus access lists)
|
35
33
|
- [x] EIP-2718 Ethereum Transaction Envelopes (and types)
|
36
34
|
- [x] EIP-2930 Ethereum Type-1 Transactions (with access lists)
|
35
|
+
- [x] EIP-4844 Ethereum Type-3 Transactions (with shard blobs)
|
36
|
+
- [x] EIP-7702 Ethereum Type-4 Transactions (with authorization lists)
|
37
37
|
- [x] ABI-Encoder and Decoder (including type parser)
|
38
|
+
- [x] Packed ABI-Encoder for Solidity smart contracts
|
38
39
|
- [x] RLP-Encoder and Decoder (including sedes)
|
39
40
|
- [x] RPC-Client (IPC/HTTP) for Execution-Layer APIs
|
40
41
|
- [x] Solidity bindings (compile contracts from Ruby)
|
41
42
|
- [x] Full smart-contract support (deploy, transact, and call)
|
43
|
+
- [x] ERC-6093 custom Solidity errors
|
42
44
|
|
43
45
|
## Installation
|
44
46
|
Add this line to your application's Gemfile:
|
@@ -88,7 +90,7 @@ bundle install
|
|
88
90
|
rspec
|
89
91
|
```
|
90
92
|
|
91
|
-
The goal is to have 100%
|
93
|
+
The goal is to have 100% unit-test coverage for all code inside this gem.
|
92
94
|
|
93
95
|
## Contributing
|
94
96
|
Pull requests are welcome! To contribute, please consider the following:
|
data/SECURITY.md
CHANGED
@@ -10,8 +10,8 @@ later.
|
|
10
10
|
|
11
11
|
| Gem | Version | Supported |
|
12
12
|
| -------------- | ------- | ------------------ |
|
13
|
-
| `eth` | 0.5
|
14
|
-
| `eth` | <
|
13
|
+
| `eth` | >= 0.5 | :white_check_mark: |
|
14
|
+
| `eth` | < 0.5 | :x: |
|
15
15
|
| `ethereum` | _any_ | :x: |
|
16
16
|
| `ethereum-abi` | _any_ | :x: |
|
17
17
|
| `rlp` | _any_ | :x: |
|
data/eth.gemspec
CHANGED
@@ -50,7 +50,7 @@ Gem::Specification.new do |spec|
|
|
50
50
|
spec.add_dependency "rbsecp256k1", "~> 6.0"
|
51
51
|
|
52
52
|
# openssl for encrypted key derivation
|
53
|
-
spec.add_dependency "openssl", "
|
53
|
+
spec.add_dependency "openssl", "~> 3.3"
|
54
54
|
|
55
55
|
# scrypt for encrypted key derivation
|
56
56
|
spec.add_dependency "scrypt", "~> 3.0"
|
data/lib/eth/abi/decoder.rb
CHANGED
@@ -43,11 +43,14 @@ module Eth
|
|
43
43
|
# Case: decoding array of string/bytes
|
44
44
|
else
|
45
45
|
l = Util.deserialize_big_endian_to_int arg[0, 32]
|
46
|
+
raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + 32 * l
|
46
47
|
|
47
48
|
# Decode each element of the array
|
48
49
|
(1..l).map do |i|
|
49
50
|
pointer = Util.deserialize_big_endian_to_int arg[i * 32, 32] # Pointer to the size of the array's element
|
51
|
+
raise DecodingError, "Offset out of bounds" if pointer < 32 * l || pointer > arg.size - 64
|
50
52
|
data_l = Util.deserialize_big_endian_to_int arg[32 + pointer, 32] # length of the element
|
53
|
+
raise DecodingError, "Offset out of bounds" if pointer + 32 + Util.ceil32(data_l) > arg.size
|
51
54
|
type(Type.parse(type.base_type), arg[pointer + 32, Util.ceil32(data_l) + 32])
|
52
55
|
end
|
53
56
|
end
|
@@ -73,11 +76,19 @@ module Eth
|
|
73
76
|
l = Util.deserialize_big_endian_to_int arg[0, 32]
|
74
77
|
nested_sub = type.nested_sub
|
75
78
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
if nested_sub.dynamic?
|
80
|
+
raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + 32 * l
|
81
|
+
offsets = (0...l).map do |i|
|
82
|
+
off = Util.deserialize_big_endian_to_int arg[32 + 32 * i, 32]
|
83
|
+
raise DecodingError, "Offset out of bounds" if off < 32 * l || off > arg.size - 64
|
84
|
+
off
|
85
|
+
end
|
86
|
+
offsets.map { |off| type(nested_sub, arg[32 + off..]) }
|
87
|
+
else
|
88
|
+
raise DecodingError, "Wrong data size for dynamic array" unless arg.size >= 32 + nested_sub.size * l
|
89
|
+
# decoded dynamic-sized arrays with static sub-types
|
90
|
+
(0...l).map { |i| type(nested_sub, arg[32 + nested_sub.size * i, nested_sub.size]) }
|
91
|
+
end
|
81
92
|
elsif !type.dimensions.empty?
|
82
93
|
l = type.dimensions.first
|
83
94
|
nested_sub = type.nested_sub
|
data/lib/eth/abi/encoder.rb
CHANGED
@@ -46,32 +46,21 @@ module Eth
|
|
46
46
|
elsif type.dynamic? && arg.is_a?(Array)
|
47
47
|
|
48
48
|
# encodes dynamic-sized arrays
|
49
|
-
head
|
50
|
-
head += type(Type.size_type, arg.size)
|
49
|
+
head = type(Type.size_type, arg.size)
|
51
50
|
nested_sub = type.nested_sub
|
52
51
|
|
53
|
-
|
54
|
-
|
55
|
-
offset =
|
56
|
-
|
57
|
-
if i == 0
|
58
|
-
offset = arg.size * 32
|
59
|
-
else
|
60
|
-
number_of_words = ((arg[i - 1].size + 32 - 1) / 32).floor
|
61
|
-
total_bytes_length = number_of_words * 32
|
62
|
-
offset += total_bytes_length + 32
|
63
|
-
end
|
64
|
-
|
52
|
+
if nested_sub.dynamic?
|
53
|
+
tails = arg.map { |a| type(nested_sub, a) }
|
54
|
+
offset = arg.size * 32
|
55
|
+
tails.each do |t|
|
65
56
|
head += type(Type.size_type, offset)
|
57
|
+
offset += t.size
|
66
58
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
arg.size.times do |i|
|
72
|
-
head += type nested_sub, arg[i]
|
59
|
+
head + tails.join
|
60
|
+
else
|
61
|
+
arg.each { |a| head += type(nested_sub, a) }
|
62
|
+
head
|
73
63
|
end
|
74
|
-
"#{head}#{tail}"
|
75
64
|
else
|
76
65
|
if type.dimensions.empty?
|
77
66
|
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# Copyright (c) 2016-2025 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 module to decode transaction input data.
|
24
|
+
module Function
|
25
|
+
extend self
|
26
|
+
|
27
|
+
# Build function signature string from ABI interface.
|
28
|
+
#
|
29
|
+
# @param interface [Hash] ABI function interface.
|
30
|
+
# @return [String] interface signature string.
|
31
|
+
def signature(interface)
|
32
|
+
name = interface.fetch("name")
|
33
|
+
inputs = interface.fetch("inputs", [])
|
34
|
+
types = inputs.map { |i| type(i) }
|
35
|
+
"#{name}(#{types.join(",")})"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Compute selector for ABI function interface.
|
39
|
+
#
|
40
|
+
# @param interface [Hash] ABI function interface.
|
41
|
+
# @return [String] a hex-string selector.
|
42
|
+
def selector(interface)
|
43
|
+
sig = signature(interface)
|
44
|
+
Util.prefix_hex(Util.bin_to_hex(Util.keccak256(sig))[0, 8])
|
45
|
+
end
|
46
|
+
|
47
|
+
# Gets the input type for functions.
|
48
|
+
#
|
49
|
+
# @param input [Hash] function input.
|
50
|
+
# @return [String] input type.
|
51
|
+
def type(input)
|
52
|
+
if input["type"] == "tuple"
|
53
|
+
"(#{input["components"].map { |c| type(c) }.join(",")})"
|
54
|
+
elsif input["type"] == "enum"
|
55
|
+
"uint8"
|
56
|
+
else
|
57
|
+
input["type"]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# A decoded function call.
|
62
|
+
class CallDescription
|
63
|
+
# The function ABI interface used to decode the call.
|
64
|
+
attr_accessor :function_interface
|
65
|
+
|
66
|
+
# The positional arguments of the call.
|
67
|
+
attr_accessor :args
|
68
|
+
|
69
|
+
# The named arguments of the call.
|
70
|
+
attr_accessor :kwargs
|
71
|
+
|
72
|
+
# The function selector.
|
73
|
+
attr_accessor :selector
|
74
|
+
|
75
|
+
# Creates a description object for a decoded function call.
|
76
|
+
#
|
77
|
+
# @param function_interface [Hash] function ABI type.
|
78
|
+
# @param selector [String] function selector hex-string.
|
79
|
+
# @param args [Array] decoded positional arguments.
|
80
|
+
# @param kwargs [Hash] decoded keyword arguments.
|
81
|
+
def initialize(function_interface, selector, args, kwargs)
|
82
|
+
@function_interface = function_interface
|
83
|
+
@selector = selector
|
84
|
+
@args = args
|
85
|
+
@kwargs = kwargs
|
86
|
+
end
|
87
|
+
|
88
|
+
# The function name. (e.g. transfer)
|
89
|
+
def name
|
90
|
+
@name ||= function_interface.fetch("name")
|
91
|
+
end
|
92
|
+
|
93
|
+
# The function signature. (e.g. transfer(address,uint256))
|
94
|
+
def signature
|
95
|
+
@signature ||= Function.signature(function_interface)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Decodes a transaction input with a set of ABI interfaces.
|
100
|
+
#
|
101
|
+
# @param interfaces [Array] function ABI types.
|
102
|
+
# @param data [String] transaction input data.
|
103
|
+
# @return [CallDescription, nil] a CallDescription object or nil if selector unknown.
|
104
|
+
def decode(interfaces, data)
|
105
|
+
data = Util.remove_hex_prefix(data)
|
106
|
+
selector = Util.prefix_hex(data[0, 8])
|
107
|
+
payload = Util.prefix_hex(data[8..] || "")
|
108
|
+
|
109
|
+
selector_to_interfaces = Hash[interfaces.map { |i| [selector(i), i] }]
|
110
|
+
if (interface = selector_to_interfaces[selector])
|
111
|
+
inputs = interface.fetch("inputs", [])
|
112
|
+
types = inputs.map { |i| type(i) }
|
113
|
+
args = Abi.decode(types, payload)
|
114
|
+
kwargs = {}
|
115
|
+
inputs.each_with_index do |input, i|
|
116
|
+
name = input.fetch("name", "")
|
117
|
+
kwargs[name.to_sym] = args[i] unless name.empty?
|
118
|
+
end
|
119
|
+
CallDescription.new(interface, selector, args, kwargs)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/eth/abi/type.rb
CHANGED
@@ -80,10 +80,24 @@ module Eth
|
|
80
80
|
return
|
81
81
|
end
|
82
82
|
|
83
|
-
|
83
|
+
# ensure the type string is reasonable before attempting to parse
|
84
|
+
raise ParseError, "Invalid type format" unless type.is_a?(String) && type.bytesize <= 256
|
85
|
+
|
86
|
+
if type.start_with?("tuple(")
|
87
|
+
inner, rest = extract_tuple(type)
|
88
|
+
inner_types = split_tuple_types(inner)
|
89
|
+
inner_types.each { |t| Type.parse(t) }
|
90
|
+
base_type = "tuple"
|
91
|
+
sub_type = ""
|
92
|
+
dimension = rest
|
93
|
+
else
|
94
|
+
match = /\A([a-z]+)([0-9]*x?[0-9]*)((?:\[\d+\]|\[\])*)\z/.match(type)
|
95
|
+
raise ParseError, "Invalid type format" unless match
|
96
|
+
_, base_type, sub_type, dimension = match.to_a
|
97
|
+
end
|
84
98
|
|
85
|
-
# type dimension can only be numeric
|
86
|
-
dims = dimension.scan(/\[[
|
99
|
+
# type dimension can only be numeric or empty for dynamic arrays
|
100
|
+
dims = dimension.scan(/\[\d+\]|\[\]/)
|
87
101
|
raise ParseError, "Unknown characters found in array declaration" if dims.join != dimension
|
88
102
|
|
89
103
|
# enforce base types
|
@@ -93,7 +107,7 @@ module Eth
|
|
93
107
|
sub_type = sub_type.to_s
|
94
108
|
@base_type = base_type
|
95
109
|
@sub_type = sub_type
|
96
|
-
@dimensions = dims.map { |x| x[1...-1].to_i }
|
110
|
+
@dimensions = dims.map { |x| x == "[]" ? 0 : x[1...-1].to_i }
|
97
111
|
@components = components.map { |component| Abi::Type.parse(component["type"], component.dig("components"), component.dig("name")) } if components&.any?
|
98
112
|
@name = component_name
|
99
113
|
end
|
@@ -123,7 +137,7 @@ module Eth
|
|
123
137
|
if dimensions.empty?
|
124
138
|
if !(["string", "bytes", "tuple"].include?(base_type) and sub_type.empty?)
|
125
139
|
s = 32
|
126
|
-
elsif base_type == "tuple" and components
|
140
|
+
elsif base_type == "tuple" and components&.none?(&:dynamic?)
|
127
141
|
s = components.sum(&:size)
|
128
142
|
end
|
129
143
|
elsif dimensions.last != 0 and !nested_sub.dynamic?
|
@@ -215,6 +229,53 @@ module Eth
|
|
215
229
|
raise ParseError, "Unknown base type"
|
216
230
|
end
|
217
231
|
end
|
232
|
+
|
233
|
+
# Extracts the inner type list and trailing dimensions from an inline tuple definition.
|
234
|
+
def extract_tuple(type)
|
235
|
+
idx = 6 # skip "tuple("
|
236
|
+
depth = 1
|
237
|
+
while idx < type.length && depth > 0
|
238
|
+
case type[idx]
|
239
|
+
when "("
|
240
|
+
depth += 1
|
241
|
+
when ")"
|
242
|
+
depth -= 1
|
243
|
+
end
|
244
|
+
idx += 1
|
245
|
+
end
|
246
|
+
raise ParseError, "Invalid tuple format" unless depth.zero?
|
247
|
+
inner = type[6...(idx - 1)]
|
248
|
+
rest = type[idx..] || ""
|
249
|
+
[inner, rest]
|
250
|
+
end
|
251
|
+
|
252
|
+
# Splits a tuple component list into individual type strings, handling nested tuples.
|
253
|
+
def split_tuple_types(str)
|
254
|
+
types = []
|
255
|
+
depth = 0
|
256
|
+
current = ""
|
257
|
+
str.each_char do |ch|
|
258
|
+
case ch
|
259
|
+
when "("
|
260
|
+
depth += 1
|
261
|
+
current << ch
|
262
|
+
when ")"
|
263
|
+
depth -= 1
|
264
|
+
current << ch
|
265
|
+
when ","
|
266
|
+
if depth.zero?
|
267
|
+
types << current
|
268
|
+
current = ""
|
269
|
+
else
|
270
|
+
current << ch
|
271
|
+
end
|
272
|
+
else
|
273
|
+
current << ch
|
274
|
+
end
|
275
|
+
end
|
276
|
+
types << current unless current.empty?
|
277
|
+
types
|
278
|
+
end
|
218
279
|
end
|
219
280
|
end
|
220
281
|
end
|
data/lib/eth/abi.rb
CHANGED
@@ -44,6 +44,7 @@ module Eth
|
|
44
44
|
return solidity_packed(types, args) if packed
|
45
45
|
types = [types] unless types.instance_of? Array
|
46
46
|
args = [args] unless args.instance_of? Array
|
47
|
+
raise ArgumentError, "Types and values must be the same length" if types.length != args.length
|
47
48
|
|
48
49
|
# parse all types
|
49
50
|
parsed_types = types.map { |t| Type === t ? t : Type.parse(t) }
|
@@ -151,4 +152,5 @@ require "eth/abi/packed/encoder"
|
|
151
152
|
require "eth/abi/decoder"
|
152
153
|
require "eth/abi/encoder"
|
153
154
|
require "eth/abi/event"
|
155
|
+
require "eth/abi/function"
|
154
156
|
require "eth/abi/type"
|
data/lib/eth/chain.rb
CHANGED
data/lib/eth/client.rb
CHANGED
@@ -40,6 +40,21 @@ module Eth
|
|
40
40
|
# A custom error type if a contract interaction fails.
|
41
41
|
class ContractExecutionError < StandardError; end
|
42
42
|
|
43
|
+
# Raised when an RPC call returns an error. Carries the optional
|
44
|
+
# hex-encoded error data to support custom error decoding.
|
45
|
+
class RpcError < IOError
|
46
|
+
attr_reader :data
|
47
|
+
|
48
|
+
# Constructor for the {RpcError} class.
|
49
|
+
#
|
50
|
+
# @param message [String] the error message returned by the RPC.
|
51
|
+
# @param data [String] optional hex encoded error data.
|
52
|
+
def initialize(message, data = nil)
|
53
|
+
super(message)
|
54
|
+
@data = data
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
43
58
|
# Creates a new RPC-Client, either by providing an HTTP/S host or
|
44
59
|
# an IPC path. Supports basic authentication with username and password.
|
45
60
|
#
|
@@ -251,21 +266,28 @@ module Eth
|
|
251
266
|
# @param **sender_key [Eth::Key] the sender private key.
|
252
267
|
# @param **legacy [Boolean] enables legacy transactions (pre-EIP-1559).
|
253
268
|
# @return [Object] returns the result of the call.
|
269
|
+
# @see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
|
254
270
|
def call(contract, function, *args, **kwargs)
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
271
|
+
function = contract.function(function, args: args.size)
|
272
|
+
output = function.decode_call_result(
|
273
|
+
eth_call(
|
274
|
+
{
|
275
|
+
data: function.encode_call(*args),
|
276
|
+
to: kwargs[:address] || contract.address,
|
277
|
+
from: kwargs[:from],
|
278
|
+
gas: kwargs[:gas],
|
279
|
+
gasPrice: kwargs[:gas_price],
|
280
|
+
value: kwargs[:value],
|
281
|
+
}.compact
|
282
|
+
)["result"]
|
283
|
+
)
|
264
284
|
if output&.length == 1
|
265
285
|
output[0]
|
266
286
|
else
|
267
287
|
output
|
268
288
|
end
|
289
|
+
rescue RpcError => e
|
290
|
+
raise ContractExecutionError, contract.decode_error(e)
|
269
291
|
end
|
270
292
|
|
271
293
|
# Executes a contract function with a transaction (transactional
|
@@ -298,13 +320,12 @@ module Eth
|
|
298
320
|
else
|
299
321
|
Tx.estimate_intrinsic_gas(contract.bin)
|
300
322
|
end
|
301
|
-
fun = contract.functions.select { |func| func.name == function }[0]
|
302
323
|
params = {
|
303
324
|
value: kwargs[:tx_value] || 0,
|
304
325
|
gas_limit: gas_limit,
|
305
326
|
chain_id: chain_id,
|
306
327
|
to: kwargs[:address] || contract.address,
|
307
|
-
data:
|
328
|
+
data: contract.function(function, args: args.size).encode_call(*args),
|
308
329
|
}
|
309
330
|
send_transaction(params, kwargs[:legacy], kwargs[:sender_key], kwargs[:nonce])
|
310
331
|
end
|
@@ -320,8 +341,8 @@ module Eth
|
|
320
341
|
begin
|
321
342
|
hash = wait_for_tx(transact(contract, function, *args, **kwargs))
|
322
343
|
return hash, tx_succeeded?(hash)
|
323
|
-
rescue
|
324
|
-
raise ContractExecutionError, e
|
344
|
+
rescue RpcError => e
|
345
|
+
raise ContractExecutionError, contract.decode_error(e)
|
325
346
|
end
|
326
347
|
end
|
327
348
|
|
@@ -442,28 +463,6 @@ module Eth
|
|
442
463
|
end
|
443
464
|
end
|
444
465
|
|
445
|
-
# Non-transactional function call called from call().
|
446
|
-
# @see https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call
|
447
|
-
def call_raw(contract, func, *args, **kwargs)
|
448
|
-
params = {
|
449
|
-
data: call_payload(func, args),
|
450
|
-
to: kwargs[:address] || contract.address,
|
451
|
-
from: kwargs[:from],
|
452
|
-
}.compact
|
453
|
-
|
454
|
-
raw_result = eth_call(params)["result"]
|
455
|
-
types = func.outputs.map { |i| i.type }
|
456
|
-
return nil if raw_result == "0x"
|
457
|
-
Eth::Abi.decode(types, raw_result)
|
458
|
-
end
|
459
|
-
|
460
|
-
# Encodes function call payloads.
|
461
|
-
def call_payload(fun, args)
|
462
|
-
types = fun.inputs.map(&:parsed_type)
|
463
|
-
encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
|
464
|
-
Util.prefix_hex(fun.signature + (encoded_str.empty? ? "0" * 64 : encoded_str))
|
465
|
-
end
|
466
|
-
|
467
466
|
# Encodes constructor params
|
468
467
|
def encode_constructor_params(contract, args)
|
469
468
|
types = contract.constructor_inputs.map { |input| input.type }
|
@@ -481,7 +480,9 @@ module Eth
|
|
481
480
|
id: next_id,
|
482
481
|
}
|
483
482
|
output = JSON.parse(send_request(payload.to_json))
|
484
|
-
|
483
|
+
if (err = output["error"])
|
484
|
+
raise RpcError.new(err["message"], err["data"])
|
485
|
+
end
|
485
486
|
output
|
486
487
|
end
|
487
488
|
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Copyright (c) 2016-2025 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
|
+
# Provide classes for contract custom errors.
|
20
|
+
class Contract::Error
|
21
|
+
attr_accessor :name, :inputs, :signature, :error_string
|
22
|
+
|
23
|
+
# Constructor of the {Eth::Contract::Error} class.
|
24
|
+
#
|
25
|
+
# @param data [Hash] contract abi data for the error.
|
26
|
+
def initialize(data)
|
27
|
+
@name = data["name"]
|
28
|
+
@inputs = data.fetch("inputs", []).map do |input|
|
29
|
+
Eth::Contract::FunctionInput.new(input)
|
30
|
+
end
|
31
|
+
@error_string = self.class.calc_signature(@name, @inputs)
|
32
|
+
@signature = self.class.encoded_error_signature(@error_string)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Creates error strings.
|
36
|
+
#
|
37
|
+
# @param name [String] error name.
|
38
|
+
# @param inputs [Array<Eth::Contract::FunctionInput>] error input class list.
|
39
|
+
# @return [String] error string.
|
40
|
+
def self.calc_signature(name, inputs)
|
41
|
+
"#{name}(#{inputs.map { |x| x.parsed_type.to_s }.join(",")})"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Encodes an error signature.
|
45
|
+
#
|
46
|
+
# @param signature [String] error signature.
|
47
|
+
# @return [String] encoded error signature string.
|
48
|
+
def self.encoded_error_signature(signature)
|
49
|
+
Util.prefix_hex(Util.bin_to_hex(Util.keccak256(signature)[0..3]))
|
50
|
+
end
|
51
|
+
|
52
|
+
# Decodes a revert error payload.
|
53
|
+
#
|
54
|
+
# @param data [String] the hex-encoded revert data including selector.
|
55
|
+
# @return [Array] decoded error arguments.
|
56
|
+
def decode(data)
|
57
|
+
types = inputs.map(&:type)
|
58
|
+
payload = "0x" + data[10..]
|
59
|
+
Eth::Abi.decode(types, payload)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|