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
@@ -53,5 +53,26 @@ module Eth
|
|
53
53
|
def self.encoded_function_signature(signature)
|
54
54
|
Util.bin_to_hex Util.keccak256(signature)[0..3]
|
55
55
|
end
|
56
|
+
|
57
|
+
# Encodes a function call arguments
|
58
|
+
#
|
59
|
+
# @param args [Array] function arguments
|
60
|
+
# @return [String] encoded function call data
|
61
|
+
def encode_call(*args)
|
62
|
+
types = inputs.map(&:parsed_type)
|
63
|
+
encoded_str = Util.bin_to_hex(Eth::Abi.encode(types, args))
|
64
|
+
Util.prefix_hex(signature + (encoded_str.empty? ? "0" * 64 : encoded_str))
|
65
|
+
end
|
66
|
+
|
67
|
+
# Decodes a function call result
|
68
|
+
#
|
69
|
+
# @param data [String] eth_call result in hex format
|
70
|
+
# @return [Array]
|
71
|
+
def decode_call_result(data)
|
72
|
+
return nil if data == "0x"
|
73
|
+
|
74
|
+
types = outputs.map(&:parsed_type)
|
75
|
+
Eth::Abi.decode(types, data)
|
76
|
+
end
|
56
77
|
end
|
57
78
|
end
|
@@ -19,19 +19,27 @@ module Eth
|
|
19
19
|
|
20
20
|
# Provide classes for contract function output.
|
21
21
|
class Contract::FunctionOutput
|
22
|
-
attr_accessor :type, :name
|
22
|
+
attr_accessor :type, :raw_type, :name
|
23
23
|
|
24
24
|
# Constructor of the {Eth::Contract::FunctionOutput} class.
|
25
25
|
#
|
26
26
|
# @param data [Hash] contract abi data.
|
27
27
|
def initialize(data)
|
28
|
-
@
|
28
|
+
@raw_type = data["type"]
|
29
|
+
@type = Eth::Abi::Type.parse(data["type"], data["components"])
|
29
30
|
@name = data["name"]
|
30
31
|
end
|
31
32
|
|
32
33
|
# Returns complete types with subtypes, e.g., `uint256`.
|
33
34
|
def type
|
34
|
-
@type.base_type +
|
35
|
+
@type.base_type +
|
36
|
+
@type.sub_type +
|
37
|
+
@type.dimensions.map { |dimension| "[#{dimension > 0 ? dimension : ""}]" }.join("")
|
38
|
+
end
|
39
|
+
|
40
|
+
# Returns parsed types.
|
41
|
+
def parsed_type
|
42
|
+
@type
|
35
43
|
end
|
36
44
|
end
|
37
45
|
end
|
data/lib/eth/contract.rb
CHANGED
@@ -25,7 +25,7 @@ module Eth
|
|
25
25
|
attr_accessor :key
|
26
26
|
attr_accessor :gas_limit, :gas_price, :max_fee_per_gas, :max_priority_fee_per_gas, :nonce
|
27
27
|
attr_accessor :bin, :name, :abi, :class_object
|
28
|
-
attr_accessor :events, :functions, :constructor_inputs
|
28
|
+
attr_accessor :events, :functions, :constructor_inputs, :errors
|
29
29
|
|
30
30
|
# Constructor of the {Eth::Contract} class.
|
31
31
|
#
|
@@ -44,7 +44,7 @@ module Eth
|
|
44
44
|
@name = _name
|
45
45
|
@bin = bin
|
46
46
|
@abi = abi
|
47
|
-
@constructor_inputs, @functions, @events = parse_abi(abi)
|
47
|
+
@constructor_inputs, @functions, @events, @errors = parse_abi(abi)
|
48
48
|
end
|
49
49
|
|
50
50
|
# Creates a contract wrapper from a Solidity file.
|
@@ -106,6 +106,52 @@ module Eth
|
|
106
106
|
end
|
107
107
|
end
|
108
108
|
|
109
|
+
# Finds a function by name.
|
110
|
+
#
|
111
|
+
# @param name [String] function name.
|
112
|
+
# @param args [Integer, nil] number of arguments of a function.
|
113
|
+
# @return [Eth::Contract::Function] function object.
|
114
|
+
# @raise [ArgumentError] if function not found.
|
115
|
+
def function(name, args: nil)
|
116
|
+
functions.find do |f|
|
117
|
+
f.name == name && (args.nil? || args == f.inputs.size)
|
118
|
+
end || raise(ArgumentError, "this function does not exist!")
|
119
|
+
end
|
120
|
+
|
121
|
+
# Finds an error by name.
|
122
|
+
#
|
123
|
+
# @param name [String] error name.
|
124
|
+
# @param args [Integer, nil] number of arguments of an error.
|
125
|
+
# @return [Eth::Contract::Error] error object.
|
126
|
+
# @raise [ArgumentError] if error not found.
|
127
|
+
def error(name, args: nil)
|
128
|
+
errors.find do |e|
|
129
|
+
e.name == name && (args.nil? || args == e.inputs.size)
|
130
|
+
end || raise(ArgumentError, "this error does not exist!")
|
131
|
+
end
|
132
|
+
|
133
|
+
# Decodes a custom error returned by an RPC error using the contract ABI.
|
134
|
+
#
|
135
|
+
# @param rpc_error [RpcError] the RPC error containing revert data.
|
136
|
+
# @return [String] a human readable error message.
|
137
|
+
def decode_error(rpc_error)
|
138
|
+
data = rpc_error.data
|
139
|
+
return rpc_error.message if data.nil? || errors.nil?
|
140
|
+
|
141
|
+
signature = data[0, 10]
|
142
|
+
if (err = errors.find { |e| e.signature == signature })
|
143
|
+
values = err.decode(data)
|
144
|
+
args = values&.map { |v| v.is_a?(String) ? v : v.inspect }&.join(",")
|
145
|
+
args ||= ""
|
146
|
+
"execution reverted: #{err.name}(#{args})"
|
147
|
+
elsif signature == "0x08c379a0"
|
148
|
+
reason = Abi.decode(["string"], "0x" + data[10..])&.first
|
149
|
+
"execution reverted: #{reason}"
|
150
|
+
else
|
151
|
+
rpc_error.message
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
109
155
|
# Create meta classes for smart contracts.
|
110
156
|
def build
|
111
157
|
class_name = @name
|
@@ -116,9 +162,12 @@ module Eth
|
|
116
162
|
def_delegators :parent, :name, :abi, :bin
|
117
163
|
def_delegators :parent, :gas_limit, :gas_price, :gas_limit=, :gas_price=, :nonce, :nonce=
|
118
164
|
def_delegators :parent, :max_fee_per_gas, :max_fee_per_gas=, :max_priority_fee_per_gas, :max_priority_fee_per_gas=
|
119
|
-
def_delegators :parent, :events
|
165
|
+
def_delegators :parent, :events, :errors
|
120
166
|
def_delegators :parent, :address, :address=
|
121
167
|
def_delegator :parent, :functions
|
168
|
+
def_delegator :parent, :function
|
169
|
+
def_delegator :parent, :error
|
170
|
+
def_delegator :parent, :decode_error
|
122
171
|
def_delegator :parent, :constructor_inputs
|
123
172
|
define_method :parent do
|
124
173
|
parent
|
@@ -140,7 +189,8 @@ module Eth
|
|
140
189
|
end
|
141
190
|
functions = abi.select { |x| x["type"] == "function" }.map { |fun| Eth::Contract::Function.new(fun) }
|
142
191
|
events = abi.select { |x| x["type"] == "event" }.map { |evt| Eth::Contract::Event.new(evt) }
|
143
|
-
[
|
192
|
+
errors = abi.select { |x| x["type"] == "error" }.map { |err| Eth::Contract::Error.new(err) }
|
193
|
+
[constructor_inputs, functions, events, errors]
|
144
194
|
end
|
145
195
|
end
|
146
196
|
end
|
@@ -151,3 +201,4 @@ require "eth/contract/function"
|
|
151
201
|
require "eth/contract/function_input"
|
152
202
|
require "eth/contract/function_output"
|
153
203
|
require "eth/contract/initializer"
|
204
|
+
require "eth/contract/error"
|
data/lib/eth/eip712.rb
CHANGED
@@ -47,7 +47,12 @@ module Eth
|
|
47
47
|
|
48
48
|
# recursively look for further nested dependencies
|
49
49
|
types[primary_type.to_sym].each do |t|
|
50
|
-
|
50
|
+
nested_type = t[:type]
|
51
|
+
# unpack arrays to their inner types to resolve dependencies
|
52
|
+
if nested_type.end_with?("]")
|
53
|
+
nested_type = nested_type.partition("[").first
|
54
|
+
end
|
55
|
+
dependency = type_dependencies nested_type, types, result
|
51
56
|
end
|
52
57
|
return result
|
53
58
|
end
|
@@ -113,19 +118,12 @@ module Eth
|
|
113
118
|
types[primary_type.to_sym].each do |field|
|
114
119
|
value = data[field[:name].to_sym]
|
115
120
|
type = field[:type]
|
116
|
-
if type
|
117
|
-
encoded_types.push
|
118
|
-
encoded_values.push
|
119
|
-
elsif type == "bytes"
|
120
|
-
encoded_types.push "bytes32"
|
121
|
-
value = Util.hex_to_bin value
|
122
|
-
encoded_values.push Util.keccak256 value
|
123
|
-
elsif !types[type.to_sym].nil?
|
121
|
+
if type.end_with?("]")
|
122
|
+
encoded_types.push type
|
123
|
+
encoded_values.push encode_array(type, value, types)
|
124
|
+
elsif type == "string" || type == "bytes" || !types[type.to_sym].nil?
|
124
125
|
encoded_types.push "bytes32"
|
125
|
-
|
126
|
-
encoded_values.push Util.keccak256 value
|
127
|
-
elsif type.end_with? "]"
|
128
|
-
raise NotImplementedError, "Arrays currently unimplemented for EIP-712."
|
126
|
+
encoded_values.push encode_value(type, value, types)
|
129
127
|
else
|
130
128
|
encoded_types.push type
|
131
129
|
encoded_values.push value
|
@@ -136,6 +134,43 @@ module Eth
|
|
136
134
|
return Abi.encode encoded_types, encoded_values
|
137
135
|
end
|
138
136
|
|
137
|
+
# Encodes a single value according to its type following EIP-712 rules.
|
138
|
+
# Returns a 32-byte binary string.
|
139
|
+
def encode_value(type, value, types)
|
140
|
+
if type == "string"
|
141
|
+
return Util.keccak256 value
|
142
|
+
elsif type == "bytes"
|
143
|
+
value = Util.hex_to_bin value
|
144
|
+
return Util.keccak256 value
|
145
|
+
elsif !types[type.to_sym].nil?
|
146
|
+
nested = encode_data type, value, types
|
147
|
+
return Util.keccak256 nested
|
148
|
+
else
|
149
|
+
# encode basic types via ABI to get 32-byte representation
|
150
|
+
return Abi.encode([type], [value])
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Prepares array values by encoding each element according to its
|
155
|
+
# base type. Returns an array compatible with Abi.encode.
|
156
|
+
def encode_array(type, value, types)
|
157
|
+
inner_type = type.slice(0, type.rindex("["))
|
158
|
+
return [] if value.nil?
|
159
|
+
value.map do |v|
|
160
|
+
if inner_type.end_with?("]")
|
161
|
+
encode_array inner_type, v, types
|
162
|
+
elsif inner_type == "string"
|
163
|
+
Util.keccak256 v
|
164
|
+
elsif inner_type == "bytes"
|
165
|
+
Util.keccak256 Util.hex_to_bin(v)
|
166
|
+
elsif !types[inner_type.to_sym].nil?
|
167
|
+
Util.keccak256 encode_data(inner_type, v, types)
|
168
|
+
else
|
169
|
+
v
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
139
174
|
# Recursively ABI-encodes and hashes all data and types.
|
140
175
|
#
|
141
176
|
# @param primary_type [String] the primary type which we want to hash.
|
data/lib/eth/key.rb
CHANGED
@@ -75,7 +75,7 @@ module Eth
|
|
75
75
|
signature = compact.bytes
|
76
76
|
v = Chain.to_v recovery_id, chain_id
|
77
77
|
leading_zero = true
|
78
|
-
[v].pack("
|
78
|
+
[v].pack("Q>").unpack("C*").each do |byte|
|
79
79
|
leading_zero = false if byte > 0 and leading_zero
|
80
80
|
signature.append byte unless leading_zero and byte === 0
|
81
81
|
end
|
data/lib/eth/tx/eip1559.rb
CHANGED
@@ -78,7 +78,7 @@ module Eth
|
|
78
78
|
# @option params [Integer] :max_gas_fee the max transaction fee per gas.
|
79
79
|
# @option params [Integer] :gas_limit the gas limit.
|
80
80
|
# @option params [Eth::Address] :from the sender address.
|
81
|
-
# @option params [Eth::Address] :to the
|
81
|
+
# @option params [Eth::Address] :to the receiver address.
|
82
82
|
# @option params [Integer] :value the transaction value.
|
83
83
|
# @option params [String] :data the transaction data payload.
|
84
84
|
# @option params [Array] :access_list an optional access list.
|
@@ -148,13 +148,13 @@ module Eth
|
|
148
148
|
raise ParameterError, "Transaction missing fields!" if tx.size < 9
|
149
149
|
|
150
150
|
# populate the 9 payload fields
|
151
|
-
chain_id = Util.
|
152
|
-
nonce = Util.
|
153
|
-
priority_fee = Util.
|
154
|
-
max_gas_fee = Util.
|
155
|
-
gas_limit = Util.
|
151
|
+
chain_id = Util.deserialize_rlp_int tx[0]
|
152
|
+
nonce = Util.deserialize_rlp_int tx[1]
|
153
|
+
priority_fee = Util.deserialize_rlp_int tx[2]
|
154
|
+
max_gas_fee = Util.deserialize_rlp_int tx[3]
|
155
|
+
gas_limit = Util.deserialize_rlp_int tx[4]
|
156
156
|
to = Util.bin_to_hex tx[5]
|
157
|
-
value = Util.
|
157
|
+
value = Util.deserialize_rlp_int tx[6]
|
158
158
|
data = tx[7]
|
159
159
|
access_list = tx[8]
|
160
160
|
|
@@ -257,6 +257,31 @@ module Eth
|
|
257
257
|
return hash
|
258
258
|
end
|
259
259
|
|
260
|
+
# Signs the transaction with a provided signature blob.
|
261
|
+
#
|
262
|
+
# @param signature [String] the concatenated `r`, `s`, and `v` values.
|
263
|
+
# @return [String] a transaction hash.
|
264
|
+
# @raise [Signature::SignatureError] if transaction is already signed.
|
265
|
+
# @raise [Signature::SignatureError] if sender address does not match signer.
|
266
|
+
def sign_with(signature)
|
267
|
+
if Tx.signed? self
|
268
|
+
raise Signature::SignatureError, "Transaction is already signed!"
|
269
|
+
end
|
270
|
+
|
271
|
+
# ensure the sender address matches the signature
|
272
|
+
unless @sender.nil? or sender.empty?
|
273
|
+
public_key = Signature.recover(unsigned_hash, signature, @chain_id)
|
274
|
+
signer_address = Tx.sanitize_address Util.public_key_to_address(public_key).to_s
|
275
|
+
from_address = Tx.sanitize_address @sender
|
276
|
+
raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
|
277
|
+
end
|
278
|
+
|
279
|
+
r, s, v = Signature.dissect signature
|
280
|
+
recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id
|
281
|
+
send :_set_signature, recovery_id, r, s
|
282
|
+
return hash
|
283
|
+
end
|
284
|
+
|
260
285
|
# Encodes a raw transaction object, wraps it in an EIP-2718 envelope
|
261
286
|
# with an EIP-1559 type prefix.
|
262
287
|
#
|
data/lib/eth/tx/eip2930.rb
CHANGED
@@ -76,7 +76,7 @@ module Eth
|
|
76
76
|
# @option params [Integer] :gas_price the gas price.
|
77
77
|
# @option params [Integer] :gas_limit the gas limit.
|
78
78
|
# @option params [Eth::Address] :from the sender address.
|
79
|
-
# @option params [Eth::Address] :to the
|
79
|
+
# @option params [Eth::Address] :to the receiver address.
|
80
80
|
# @option params [Integer] :value the transaction value.
|
81
81
|
# @option params [String] :data the transaction data payload.
|
82
82
|
# @option params [Array] :access_list an optional access list.
|
@@ -145,12 +145,12 @@ module Eth
|
|
145
145
|
raise ParameterError, "Transaction missing fields!" if tx.size < 8
|
146
146
|
|
147
147
|
# populate the 8 payload fields
|
148
|
-
chain_id = Util.
|
149
|
-
nonce = Util.
|
150
|
-
gas_price = Util.
|
151
|
-
gas_limit = Util.
|
148
|
+
chain_id = Util.deserialize_rlp_int tx[0]
|
149
|
+
nonce = Util.deserialize_rlp_int tx[1]
|
150
|
+
gas_price = Util.deserialize_rlp_int tx[2]
|
151
|
+
gas_limit = Util.deserialize_rlp_int tx[3]
|
152
152
|
to = Util.bin_to_hex tx[4]
|
153
|
-
value = Util.
|
153
|
+
value = Util.deserialize_rlp_int tx[5]
|
154
154
|
data = tx[6]
|
155
155
|
access_list = tx[7]
|
156
156
|
|
@@ -251,6 +251,31 @@ module Eth
|
|
251
251
|
return hash
|
252
252
|
end
|
253
253
|
|
254
|
+
# Signs the transaction with a provided signature blob.
|
255
|
+
#
|
256
|
+
# @param signature [String] the concatenated `r`, `s`, and `v` values.
|
257
|
+
# @return [String] a transaction hash.
|
258
|
+
# @raise [Signature::SignatureError] if transaction is already signed.
|
259
|
+
# @raise [Signature::SignatureError] if sender address does not match signer.
|
260
|
+
def sign_with(signature)
|
261
|
+
if Tx.signed? self
|
262
|
+
raise Signature::SignatureError, "Transaction is already signed!"
|
263
|
+
end
|
264
|
+
|
265
|
+
# ensure the sender address matches the signature
|
266
|
+
unless @sender.nil? or sender.empty?
|
267
|
+
public_key = Signature.recover(unsigned_hash, signature, @chain_id)
|
268
|
+
signer_address = Tx.sanitize_address Util.public_key_to_address(public_key).to_s
|
269
|
+
from_address = Tx.sanitize_address @sender
|
270
|
+
raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
|
271
|
+
end
|
272
|
+
|
273
|
+
r, s, v = Signature.dissect signature
|
274
|
+
recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id
|
275
|
+
send :_set_signature, recovery_id, r, s
|
276
|
+
return hash
|
277
|
+
end
|
278
|
+
|
254
279
|
# Encodes a raw transaction object, wraps it in an EIP-2718 envelope
|
255
280
|
# with an EIP-2930 type prefix.
|
256
281
|
#
|