eth 0.5.13 → 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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/codeql.yml +1 -1
  3. data/.github/workflows/docs.yml +2 -2
  4. data/.github/workflows/spec.yml +1 -3
  5. data/CHANGELOG.md +33 -0
  6. data/CODE_OF_CONDUCT.md +3 -5
  7. data/Gemfile +3 -3
  8. data/LICENSE.txt +1 -1
  9. data/README.md +6 -6
  10. data/SECURITY.md +2 -2
  11. data/eth.gemspec +4 -1
  12. data/lib/eth/abi/decoder.rb +18 -7
  13. data/lib/eth/abi/encoder.rb +14 -26
  14. data/lib/eth/abi/event.rb +5 -1
  15. data/lib/eth/abi/function.rb +124 -0
  16. data/lib/eth/abi/packed/encoder.rb +196 -0
  17. data/lib/eth/abi/type.rb +77 -16
  18. data/lib/eth/abi.rb +29 -2
  19. data/lib/eth/address.rb +3 -1
  20. data/lib/eth/api.rb +1 -1
  21. data/lib/eth/chain.rb +9 -1
  22. data/lib/eth/client/http.rb +7 -3
  23. data/lib/eth/client/ipc.rb +1 -1
  24. data/lib/eth/client.rb +38 -37
  25. data/lib/eth/constant.rb +1 -1
  26. data/lib/eth/contract/error.rb +62 -0
  27. data/lib/eth/contract/event.rb +69 -16
  28. data/lib/eth/contract/function.rb +22 -1
  29. data/lib/eth/contract/function_input.rb +1 -1
  30. data/lib/eth/contract/function_output.rb +12 -4
  31. data/lib/eth/contract/initializer.rb +1 -1
  32. data/lib/eth/contract.rb +56 -5
  33. data/lib/eth/eip712.rb +49 -13
  34. data/lib/eth/ens/coin_type.rb +1 -1
  35. data/lib/eth/ens/resolver.rb +1 -1
  36. data/lib/eth/ens.rb +1 -1
  37. data/lib/eth/key/decrypter.rb +1 -1
  38. data/lib/eth/key/encrypter.rb +1 -1
  39. data/lib/eth/key.rb +2 -2
  40. data/lib/eth/rlp/decoder.rb +1 -1
  41. data/lib/eth/rlp/encoder.rb +1 -1
  42. data/lib/eth/rlp/sedes/big_endian_int.rb +1 -1
  43. data/lib/eth/rlp/sedes/binary.rb +1 -1
  44. data/lib/eth/rlp/sedes/list.rb +1 -1
  45. data/lib/eth/rlp/sedes.rb +1 -1
  46. data/lib/eth/rlp.rb +1 -1
  47. data/lib/eth/signature.rb +1 -1
  48. data/lib/eth/solidity.rb +1 -1
  49. data/lib/eth/tx/eip1559.rb +33 -8
  50. data/lib/eth/tx/eip2930.rb +32 -7
  51. data/lib/eth/tx/eip4844.rb +389 -0
  52. data/lib/eth/tx/eip7702.rb +520 -0
  53. data/lib/eth/tx/legacy.rb +31 -7
  54. data/lib/eth/tx.rb +88 -1
  55. data/lib/eth/unit.rb +1 -1
  56. data/lib/eth/util.rb +20 -8
  57. data/lib/eth/version.rb +2 -2
  58. data/lib/eth.rb +1 -1
  59. metadata +26 -16
@@ -0,0 +1,389 @@
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
+ # Provides the {Eth} module.
16
+ module Eth
17
+
18
+ # Provides the `Tx` module supporting various transaction types.
19
+ module Tx
20
+
21
+ # Provides support for EIP-4844 transactions utilizing EIP-2718
22
+ # types and envelopes.
23
+ # Ref: https://eips.ethereum.org/EIPS/eip-4844
24
+ class Eip4844
25
+
26
+ # The EIP-155 Chain ID.
27
+ # Ref: https://eips.ethereum.org/EIPS/eip-155
28
+ attr_reader :chain_id
29
+
30
+ # The transaction nonce provided by the signer.
31
+ attr_reader :signer_nonce
32
+
33
+ # The transaction max priority fee per gas in Wei.
34
+ attr_reader :max_priority_fee_per_gas
35
+
36
+ # The transaction max fee per gas in Wei.
37
+ attr_reader :max_fee_per_gas
38
+
39
+ # The gas limit for the transaction.
40
+ attr_reader :gas_limit
41
+
42
+ # The recipient address.
43
+ attr_reader :destination
44
+
45
+ # The transaction amount in Wei.
46
+ attr_reader :amount
47
+
48
+ # The transaction data payload.
49
+ attr_reader :payload
50
+
51
+ # An optional EIP-2930 access list.
52
+ # Ref: https://eips.ethereum.org/EIPS/eip-2930
53
+ attr_reader :access_list
54
+
55
+ # The transaction max fee per blob gas in Wei.
56
+ attr_reader :max_fee_per_blob_gas
57
+
58
+ # The list of KZG commitment versioned hashes.
59
+ attr_reader :blob_versioned_hashes
60
+
61
+ # The signature's y-parity byte (not v).
62
+ attr_reader :signature_y_parity
63
+
64
+ # The signature `r` value.
65
+ attr_reader :signature_r
66
+
67
+ # The signature `s` value.
68
+ attr_reader :signature_s
69
+
70
+ # The sender address.
71
+ attr_reader :sender
72
+
73
+ # The transaction type.
74
+ attr_reader :type
75
+
76
+ # Create a type-3 (EIP-4844) transaction payload object that
77
+ # can be prepared for envelope, signature and broadcast.
78
+ # Ref: https://eips.ethereum.org/EIPS/eip-4844
79
+ #
80
+ # @param params [Hash] all necessary transaction fields.
81
+ # @option params [Integer] :chain_id the chain ID.
82
+ # @option params [Integer] :nonce the signer nonce.
83
+ # @option params [Integer] :priority_fee the max priority fee per gas.
84
+ # @option params [Integer] :max_gas_fee the max transaction fee per gas.
85
+ # @option params [Integer] :gas_limit the gas limit.
86
+ # @option params [Eth::Address] :from the sender address.
87
+ # @option params [Eth::Address] :to the receiver address.
88
+ # @option params [Integer] :value the transaction value.
89
+ # @option params [String] :data the transaction data payload.
90
+ # @option params [Array] :access_list an optional access list.
91
+ # @option params [Integer] :max_fee_per_blob_gas the max blob fee per gas.
92
+ # @option params [Array] :blob_versioned_hashes the blob versioned hashes.
93
+ # @raise [ParameterError] if gas limit is too low.
94
+ def initialize(params)
95
+ fields = { recovery_id: nil, r: 0, s: 0 }.merge params
96
+
97
+ # populate optional fields with serializable empty values
98
+ fields[:chain_id] = Tx.sanitize_chain fields[:chain_id]
99
+ fields[:from] = Tx.sanitize_address fields[:from]
100
+ fields[:to] = Tx.sanitize_address fields[:to]
101
+ fields[:value] = Tx.sanitize_amount fields[:value]
102
+ fields[:data] = Tx.sanitize_data fields[:data]
103
+
104
+ # ensure sane values for all mandatory fields
105
+ fields = Tx.validate_params fields
106
+ fields = Tx.validate_eip1559_params fields
107
+ fields = Tx.validate_eip4844_params fields
108
+ fields[:access_list] = Tx.sanitize_list fields[:access_list]
109
+ fields[:blob_versioned_hashes] = Tx.sanitize_hashes fields[:blob_versioned_hashes]
110
+
111
+ # ensure gas limit is not too low
112
+ minimum_cost = Tx.estimate_intrinsic_gas fields[:data], fields[:access_list]
113
+ raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost
114
+
115
+ # populate class attributes
116
+ @signer_nonce = fields[:nonce].to_i
117
+ @max_priority_fee_per_gas = fields[:priority_fee].to_i
118
+ @max_fee_per_gas = fields[:max_gas_fee].to_i
119
+ @gas_limit = fields[:gas_limit].to_i
120
+ @sender = fields[:from].to_s
121
+ @destination = fields[:to].to_s
122
+ @amount = fields[:value].to_i
123
+ @payload = fields[:data]
124
+ @access_list = fields[:access_list]
125
+ @max_fee_per_blob_gas = fields[:max_fee_per_blob_gas].to_i
126
+ @blob_versioned_hashes = fields[:blob_versioned_hashes]
127
+
128
+ # the signature v is set to the chain id for unsigned transactions
129
+ @signature_y_parity = fields[:recovery_id]
130
+ @chain_id = fields[:chain_id]
131
+
132
+ # the signature fields are empty for unsigned transactions.
133
+ @signature_r = fields[:r]
134
+ @signature_s = fields[:s]
135
+
136
+ # last but not least, set the type.
137
+ @type = TYPE_4844
138
+ end
139
+
140
+ # Overloads the constructor for decoding raw transactions and creating unsigned copies.
141
+ konstructor :decode, :unsigned_copy
142
+
143
+ # Decodes a raw transaction hex into an {Eth::Tx::Eip4844}
144
+ # transaction object.
145
+ #
146
+ # @param hex [String] the raw transaction hex-string.
147
+ # @return [Eth::Tx::Eip4844] transaction payload.
148
+ # @raise [TransactionTypeError] if transaction type is invalid.
149
+ # @raise [ParameterError] if transaction is missing fields.
150
+ # @raise [DecoderError] if transaction decoding fails.
151
+ def decode(hex)
152
+ hex = Util.remove_hex_prefix hex
153
+ type = hex[0, 2]
154
+ raise TransactionTypeError, "Invalid transaction type #{type}!" if type.to_i(16) != TYPE_4844
155
+
156
+ bin = Util.hex_to_bin hex[2..]
157
+ tx = Rlp.decode bin
158
+
159
+ # decoded transactions always have 11 + 3 fields, even if they are empty or zero
160
+ raise ParameterError, "Transaction missing fields!" if tx.size < 11
161
+
162
+ # populate the 11 payload fields
163
+ chain_id = Util.deserialize_rlp_int tx[0]
164
+ nonce = Util.deserialize_rlp_int tx[1]
165
+ priority_fee = Util.deserialize_rlp_int tx[2]
166
+ max_gas_fee = Util.deserialize_rlp_int tx[3]
167
+ gas_limit = Util.deserialize_rlp_int tx[4]
168
+ to = Util.bin_to_hex tx[5]
169
+ value = Util.deserialize_rlp_int tx[6]
170
+ data = tx[7]
171
+ access_list = tx[8]
172
+ max_fee_per_blob_gas = Util.deserialize_rlp_int tx[9]
173
+ blob_versioned_hashes = tx[10]
174
+
175
+ # populate class attributes
176
+ @chain_id = chain_id.to_i
177
+ @signer_nonce = nonce.to_i
178
+ @max_priority_fee_per_gas = priority_fee.to_i
179
+ @max_fee_per_gas = max_gas_fee.to_i
180
+ @gas_limit = gas_limit.to_i
181
+ @destination = to.to_s
182
+ @amount = value.to_i
183
+ @payload = data
184
+ @access_list = access_list
185
+ @max_fee_per_blob_gas = max_fee_per_blob_gas.to_i
186
+ @blob_versioned_hashes = blob_versioned_hashes
187
+
188
+ # populate the 3 signature fields
189
+ if tx.size == 11
190
+ _set_signature(nil, 0, 0)
191
+ elsif tx.size == 14
192
+ recovery_id = Util.bin_to_hex(tx[11]).to_i(16)
193
+ r = Util.bin_to_hex tx[12]
194
+ s = Util.bin_to_hex tx[13]
195
+
196
+ # allows us to force-setting a signature if the transaction is signed already
197
+ _set_signature(recovery_id, r, s)
198
+ else
199
+ raise DecoderError, "Cannot decode EIP-4844 payload!"
200
+ end
201
+
202
+ # last but not least, set the type.
203
+ @type = TYPE_4844
204
+
205
+ unless recovery_id.nil?
206
+ # recover sender address
207
+ v = Chain.to_v recovery_id, chain_id
208
+ public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v.to_s(16)}", chain_id)
209
+ address = Util.public_key_to_address(public_key).to_s
210
+ @sender = Tx.sanitize_address address
211
+ else
212
+ # keep the 'from' field blank
213
+ @sender = Tx.sanitize_address nil
214
+ end
215
+ end
216
+
217
+ # Creates an unsigned copy of a transaction payload.
218
+ #
219
+ # @param tx [Eth::Tx::Eip4844] an EIP-4844 transaction payload.
220
+ # @return [Eth::Tx::Eip4844] an unsigned EIP-4844 transaction payload.
221
+ # @raise [TransactionTypeError] if transaction type does not match.
222
+ def unsigned_copy(tx)
223
+
224
+ # not checking transaction validity unless it's of a different class
225
+ raise TransactionTypeError, "Cannot copy transaction of different payload type!" unless tx.instance_of? Tx::Eip4844
226
+
227
+ # populate class attributes
228
+ @signer_nonce = tx.signer_nonce
229
+ @max_priority_fee_per_gas = tx.max_priority_fee_per_gas
230
+ @max_fee_per_gas = tx.max_fee_per_gas
231
+ @gas_limit = tx.gas_limit
232
+ @destination = tx.destination
233
+ @amount = tx.amount
234
+ @payload = tx.payload
235
+ @access_list = tx.access_list
236
+ @max_fee_per_blob_gas = tx.max_fee_per_blob_gas
237
+ @blob_versioned_hashes = tx.blob_versioned_hashes
238
+ @chain_id = tx.chain_id
239
+
240
+ # force-set signature to unsigned
241
+ _set_signature(nil, 0, 0)
242
+
243
+ # keep the 'from' field blank
244
+ @sender = Tx.sanitize_address nil
245
+
246
+ # last but not least, set the type.
247
+ @type = TYPE_4844
248
+ end
249
+
250
+ # Sign the transaction with a given key.
251
+ #
252
+ # @param key [Eth::Key] the key-pair to use for signing.
253
+ # @return [String] a transaction hash.
254
+ # @raise [Signature::SignatureError] if transaction is already signed.
255
+ # @raise [Signature::SignatureError] if sender address does not match signing key.
256
+ def sign(key)
257
+ if Tx.signed? self
258
+ raise Signature::SignatureError, "Transaction is already signed!"
259
+ end
260
+
261
+ # ensure the sender address matches the given key
262
+ unless @sender.nil? or sender.empty?
263
+ signer_address = Tx.sanitize_address key.address.to_s
264
+ from_address = Tx.sanitize_address @sender
265
+ raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
266
+ end
267
+
268
+ # sign a keccak hash of the unsigned, encoded transaction
269
+ signature = key.sign(unsigned_hash, @chain_id)
270
+ r, s, v = Signature.dissect signature
271
+ recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id
272
+ @signature_y_parity = recovery_id
273
+ @signature_r = r
274
+ @signature_s = s
275
+ return hash
276
+ end
277
+
278
+ # Signs the transaction with a provided signature blob.
279
+ #
280
+ # @param signature [String] the concatenated `r`, `s`, and `v` values.
281
+ # @return [String] a transaction hash.
282
+ # @raise [Signature::SignatureError] if transaction is already signed.
283
+ # @raise [Signature::SignatureError] if sender address does not match signer.
284
+ def sign_with(signature)
285
+ if Tx.signed? self
286
+ raise Signature::SignatureError, "Transaction is already signed!"
287
+ end
288
+
289
+ # ensure the sender address matches the signature
290
+ unless @sender.nil? or sender.empty?
291
+ public_key = Signature.recover(unsigned_hash, signature, @chain_id)
292
+ signer_address = Tx.sanitize_address Util.public_key_to_address(public_key).to_s
293
+ from_address = Tx.sanitize_address @sender
294
+ raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
295
+ end
296
+
297
+ r, s, v = Signature.dissect signature
298
+ recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id
299
+ send :_set_signature, recovery_id, r, s
300
+ return hash
301
+ end
302
+
303
+ # Encodes a raw transaction object, wraps it in an EIP-2718 envelope
304
+ # with an EIP-4844 type prefix.
305
+ #
306
+ # @return [String] a raw, RLP-encoded EIP-4844 type transaction object.
307
+ # @raise [Signature::SignatureError] if the transaction is not yet signed.
308
+ def encoded
309
+ unless Tx.signed? self
310
+ raise Signature::SignatureError, "Transaction is not signed!"
311
+ end
312
+ tx_data = []
313
+ tx_data.push Util.serialize_int_to_big_endian @chain_id
314
+ tx_data.push Util.serialize_int_to_big_endian @signer_nonce
315
+ tx_data.push Util.serialize_int_to_big_endian @max_priority_fee_per_gas
316
+ tx_data.push Util.serialize_int_to_big_endian @max_fee_per_gas
317
+ tx_data.push Util.serialize_int_to_big_endian @gas_limit
318
+ tx_data.push Util.hex_to_bin @destination
319
+ tx_data.push Util.serialize_int_to_big_endian @amount
320
+ tx_data.push Rlp::Sedes.binary.serialize @payload
321
+ tx_data.push Rlp::Sedes.infer(@access_list).serialize @access_list
322
+ tx_data.push Util.serialize_int_to_big_endian @max_fee_per_blob_gas
323
+ tx_data.push Rlp::Sedes.infer(@blob_versioned_hashes).serialize @blob_versioned_hashes
324
+ tx_data.push Util.serialize_int_to_big_endian @signature_y_parity
325
+ tx_data.push Util.serialize_int_to_big_endian @signature_r
326
+ tx_data.push Util.serialize_int_to_big_endian @signature_s
327
+ tx_encoded = Rlp.encode tx_data
328
+
329
+ # create an EIP-2718 envelope with EIP-4844 type payload
330
+ tx_type = Util.serialize_int_to_big_endian @type
331
+ return "#{tx_type}#{tx_encoded}"
332
+ end
333
+
334
+ # Gets the encoded, enveloped, raw transaction hex.
335
+ #
336
+ # @return [String] the raw transaction hex.
337
+ def hex
338
+ Util.bin_to_hex encoded
339
+ end
340
+
341
+ # Gets the transaction hash.
342
+ #
343
+ # @return [String] the transaction hash.
344
+ def hash
345
+ Util.bin_to_hex Util.keccak256 encoded
346
+ end
347
+
348
+ # Encodes the unsigned transaction payload in an EIP-4844 envelope,
349
+ # required for signing.
350
+ #
351
+ # @return [String] an RLP-encoded, unsigned, enveloped EIP-4844 transaction.
352
+ def unsigned_encoded
353
+ tx_data = []
354
+ tx_data.push Util.serialize_int_to_big_endian @chain_id
355
+ tx_data.push Util.serialize_int_to_big_endian @signer_nonce
356
+ tx_data.push Util.serialize_int_to_big_endian @max_priority_fee_per_gas
357
+ tx_data.push Util.serialize_int_to_big_endian @max_fee_per_gas
358
+ tx_data.push Util.serialize_int_to_big_endian @gas_limit
359
+ tx_data.push Util.hex_to_bin @destination
360
+ tx_data.push Util.serialize_int_to_big_endian @amount
361
+ tx_data.push Rlp::Sedes.binary.serialize @payload
362
+ tx_data.push Rlp::Sedes.infer(@access_list).serialize @access_list
363
+ tx_data.push Util.serialize_int_to_big_endian @max_fee_per_blob_gas
364
+ tx_data.push Rlp::Sedes.infer(@blob_versioned_hashes).serialize @blob_versioned_hashes
365
+ tx_encoded = Rlp.encode tx_data
366
+
367
+ # create an EIP-2718 envelope with EIP-4844 type payload (unsigned)
368
+ tx_type = Util.serialize_int_to_big_endian @type
369
+ return "#{tx_type}#{tx_encoded}"
370
+ end
371
+
372
+ # Gets the sign-hash required to sign a raw transaction.
373
+ #
374
+ # @return [String] a Keccak-256 hash of an unsigned transaction.
375
+ def unsigned_hash
376
+ Util.keccak256 unsigned_encoded
377
+ end
378
+
379
+ private
380
+
381
+ # Force-sets an existing signature of a decoded transaction.
382
+ def _set_signature(recovery_id, r, s)
383
+ @signature_y_parity = recovery_id
384
+ @signature_r = r
385
+ @signature_s = s
386
+ end
387
+ end
388
+ end
389
+ end