eth 0.4.18 → 0.5.2

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