eth-custom 0.5.7
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 +7 -0
- data/.github/dependabot.yml +18 -0
- data/.github/workflows/codeql.yml +48 -0
- data/.github/workflows/docs.yml +26 -0
- data/.github/workflows/spec.yml +52 -0
- data/.gitignore +43 -0
- data/.gitmodules +3 -0
- data/.rspec +4 -0
- data/.yardopts +1 -0
- data/AUTHORS.txt +29 -0
- data/CHANGELOG.md +218 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +202 -0
- data/README.md +347 -0
- data/Rakefile +6 -0
- data/bin/console +10 -0
- data/bin/setup +9 -0
- data/codecov.yml +6 -0
- data/eth.gemspec +51 -0
- data/lib/eth/abi/event.rb +137 -0
- data/lib/eth/abi/type.rb +178 -0
- data/lib/eth/abi.rb +446 -0
- data/lib/eth/address.rb +106 -0
- data/lib/eth/api.rb +223 -0
- data/lib/eth/chain.rb +157 -0
- data/lib/eth/client/http.rb +63 -0
- data/lib/eth/client/ipc.rb +50 -0
- data/lib/eth/client.rb +499 -0
- data/lib/eth/constant.rb +71 -0
- data/lib/eth/contract/event.rb +42 -0
- data/lib/eth/contract/function.rb +57 -0
- data/lib/eth/contract/function_input.rb +38 -0
- data/lib/eth/contract/function_output.rb +37 -0
- data/lib/eth/contract/initializer.rb +47 -0
- data/lib/eth/contract.rb +143 -0
- data/lib/eth/eip712.rb +184 -0
- data/lib/eth/key/decrypter.rb +146 -0
- data/lib/eth/key/encrypter.rb +207 -0
- data/lib/eth/key.rb +167 -0
- data/lib/eth/rlp/decoder.rb +114 -0
- data/lib/eth/rlp/encoder.rb +78 -0
- data/lib/eth/rlp/sedes/big_endian_int.rb +66 -0
- data/lib/eth/rlp/sedes/binary.rb +97 -0
- data/lib/eth/rlp/sedes/list.rb +84 -0
- data/lib/eth/rlp/sedes.rb +74 -0
- data/lib/eth/rlp.rb +63 -0
- data/lib/eth/signature.rb +163 -0
- data/lib/eth/solidity.rb +75 -0
- data/lib/eth/tx/eip1559.rb +337 -0
- data/lib/eth/tx/eip2930.rb +329 -0
- data/lib/eth/tx/legacy.rb +297 -0
- data/lib/eth/tx.rb +322 -0
- data/lib/eth/unit.rb +49 -0
- data/lib/eth/util.rb +235 -0
- data/lib/eth/version.rb +20 -0
- data/lib/eth.rb +35 -0
- metadata +184 -0
@@ -0,0 +1,297 @@
|
|
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, EIP-2718, or EIP-2930.
|
23
|
+
class Legacy
|
24
|
+
|
25
|
+
# The transaction nonce provided by the signer.
|
26
|
+
attr_reader :signer_nonce
|
27
|
+
|
28
|
+
# The gas price for the transaction in Wei.
|
29
|
+
attr_reader :gas_price
|
30
|
+
|
31
|
+
# The gas limit for the transaction.
|
32
|
+
attr_reader :gas_limit
|
33
|
+
|
34
|
+
# The recipient address.
|
35
|
+
attr_reader :destination
|
36
|
+
|
37
|
+
# The transaction amount in Wei.
|
38
|
+
attr_reader :amount
|
39
|
+
|
40
|
+
# The transaction data payload.
|
41
|
+
attr_reader :payload
|
42
|
+
|
43
|
+
# The signature `v` byte.
|
44
|
+
attr_reader :signature_v
|
45
|
+
|
46
|
+
# The signature `r` value.
|
47
|
+
attr_reader :signature_r
|
48
|
+
|
49
|
+
# The signature `s` value.
|
50
|
+
attr_reader :signature_s
|
51
|
+
|
52
|
+
# The EIP-155 chain ID field.
|
53
|
+
# Ref: https://eips.ethereum.org/EIPS/eip-155
|
54
|
+
attr_reader :chain_id
|
55
|
+
|
56
|
+
# The sender address.
|
57
|
+
attr_reader :sender
|
58
|
+
|
59
|
+
# The transaction type.
|
60
|
+
attr_reader :type
|
61
|
+
|
62
|
+
# Create a legacy transaction object that can be prepared for
|
63
|
+
# signature and broadcast. Should not be used unless there is
|
64
|
+
# no EIP-1559 support.
|
65
|
+
#
|
66
|
+
# @param params [Hash] all necessary transaction fields.
|
67
|
+
# @option params [Integer] :nonce the signer nonce.
|
68
|
+
# @option params [Integer] :gas_price the gas price.
|
69
|
+
# @option params [Integer] :gas_limit the gas limit.
|
70
|
+
# @option params [Eth::Address] :from the sender address.
|
71
|
+
# @option params [Eth::Address] :to the reciever address.
|
72
|
+
# @option params [Integer] :value the transaction value.
|
73
|
+
# @option params [String] :data the transaction data payload.
|
74
|
+
# @param chain_id [Integer] the EIP-155 Chain ID.
|
75
|
+
# @raise [ParameterError] if gas limit is too low.
|
76
|
+
def initialize(params, chain_id = Chain::ETHEREUM)
|
77
|
+
fields = { v: chain_id, r: 0, s: 0 }.merge params
|
78
|
+
|
79
|
+
# populate optional fields with serializable empty values
|
80
|
+
fields[:value] = Tx.sanitize_amount fields[:value]
|
81
|
+
fields[:from] = Tx.sanitize_address fields[:from]
|
82
|
+
fields[:to] = Tx.sanitize_address fields[:to]
|
83
|
+
fields[:data] = Tx.sanitize_data fields[:data]
|
84
|
+
|
85
|
+
# ensure sane values for all mandatory fields
|
86
|
+
fields = Tx.validate_params fields
|
87
|
+
fields = Tx.validate_legacy_params fields
|
88
|
+
|
89
|
+
# ensure gas limit is not too low
|
90
|
+
minimum_cost = Tx.estimate_intrinsic_gas fields[:data]
|
91
|
+
raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost
|
92
|
+
|
93
|
+
# populate class attributes
|
94
|
+
@signer_nonce = fields[:nonce].to_i
|
95
|
+
@gas_price = fields[:gas_price].to_i
|
96
|
+
@gas_limit = fields[:gas_limit].to_i
|
97
|
+
@sender = fields[:from].to_s
|
98
|
+
@destination = fields[:to].to_s
|
99
|
+
@amount = fields[:value].to_i
|
100
|
+
@payload = fields[:data]
|
101
|
+
|
102
|
+
# the signature v is set to the chain id for unsigned transactions
|
103
|
+
@signature_v = fields[:v]
|
104
|
+
@chain_id = chain_id
|
105
|
+
|
106
|
+
# the signature fields are empty for unsigned transactions.
|
107
|
+
@signature_r = fields[:r]
|
108
|
+
@signature_s = fields[:s]
|
109
|
+
|
110
|
+
# last but not least, set the type.
|
111
|
+
@type = TYPE_LEGACY
|
112
|
+
end
|
113
|
+
|
114
|
+
# overloads the constructor for decoding raw transactions and creating unsigned copies
|
115
|
+
konstructor :decode, :unsigned_copy
|
116
|
+
|
117
|
+
# Decodes a raw transaction hex into an {Eth::Tx::Legacy}
|
118
|
+
# transaction object.
|
119
|
+
#
|
120
|
+
# @param hex [String] the raw transaction hex-string.
|
121
|
+
# @return [Eth::Tx::Legacy] transaction object.
|
122
|
+
# @raise [ParameterError] if transaction misses fields.
|
123
|
+
def decode(hex)
|
124
|
+
bin = Util.hex_to_bin hex
|
125
|
+
tx = Rlp.decode bin
|
126
|
+
|
127
|
+
# decoded transactions always have 9 fields, even if they are empty or zero
|
128
|
+
raise ParameterError, "Transaction missing fields!" if tx.size < 9
|
129
|
+
|
130
|
+
# populate the 9 fields
|
131
|
+
nonce = Util.deserialize_big_endian_to_int tx[0]
|
132
|
+
gas_price = Util.deserialize_big_endian_to_int tx[1]
|
133
|
+
gas_limit = Util.deserialize_big_endian_to_int tx[2]
|
134
|
+
to = Util.bin_to_hex tx[3]
|
135
|
+
value = Util.deserialize_big_endian_to_int tx[4]
|
136
|
+
data = tx[5]
|
137
|
+
v = Util.bin_to_hex tx[6]
|
138
|
+
r = Util.bin_to_hex tx[7]
|
139
|
+
s = Util.bin_to_hex tx[8]
|
140
|
+
|
141
|
+
# try to recover the chain id from v
|
142
|
+
chain_id = Chain.to_chain_id Util.deserialize_big_endian_to_int tx[6]
|
143
|
+
|
144
|
+
# populate class attributes
|
145
|
+
@signer_nonce = nonce.to_i
|
146
|
+
@gas_price = gas_price.to_i
|
147
|
+
@gas_limit = gas_limit.to_i
|
148
|
+
@destination = to.to_s
|
149
|
+
@amount = value.to_i
|
150
|
+
@payload = data
|
151
|
+
@chain_id = chain_id
|
152
|
+
|
153
|
+
# allows us to force-setting a signature if the transaction is signed already
|
154
|
+
_set_signature(v, r, s)
|
155
|
+
|
156
|
+
unless chain_id.nil?
|
157
|
+
|
158
|
+
# recover sender address
|
159
|
+
public_key = Signature.recover(unsigned_hash, "#{r.rjust(64, "0")}#{s.rjust(64, "0")}#{v}", chain_id)
|
160
|
+
address = Util.public_key_to_address(public_key).to_s
|
161
|
+
@sender = Tx.sanitize_address address
|
162
|
+
else
|
163
|
+
|
164
|
+
# keep the 'from' field blank
|
165
|
+
@sender = Tx.sanitize_address nil
|
166
|
+
end
|
167
|
+
|
168
|
+
# last but not least, set the type.
|
169
|
+
@type = TYPE_LEGACY
|
170
|
+
end
|
171
|
+
|
172
|
+
# Creates an unsigned copy of a transaction.
|
173
|
+
#
|
174
|
+
# @param tx [Eth::Tx::Legacy] an legacy transaction object.
|
175
|
+
# @return [Eth::Tx::Legacy] an unsigned transaction object.
|
176
|
+
# @raise [TransactionTypeError] if transaction type does not match.
|
177
|
+
def unsigned_copy(tx)
|
178
|
+
|
179
|
+
# not checking transaction validity unless it's of a different class
|
180
|
+
raise TransactionTypeError, "Cannot copy transaction of different type!" unless tx.instance_of? Tx::Legacy
|
181
|
+
|
182
|
+
# populate class attributes
|
183
|
+
@signer_nonce = tx.signer_nonce
|
184
|
+
@gas_price = tx.gas_price
|
185
|
+
@gas_limit = tx.gas_limit
|
186
|
+
@destination = tx.destination
|
187
|
+
@amount = tx.amount
|
188
|
+
@payload = tx.payload
|
189
|
+
@chain_id = tx.chain_id
|
190
|
+
|
191
|
+
# force-set signature to unsigned
|
192
|
+
_set_signature(tx.chain_id, 0, 0)
|
193
|
+
|
194
|
+
# keep the 'from' field blank
|
195
|
+
@sender = Tx.sanitize_address nil
|
196
|
+
|
197
|
+
# last but not least, set the type.
|
198
|
+
@type = TYPE_LEGACY
|
199
|
+
end
|
200
|
+
|
201
|
+
# Sign the transaction with a given key.
|
202
|
+
#
|
203
|
+
# @param key [Eth::Key] the key-pair to use for signing.
|
204
|
+
# @return [String] a transaction hash.
|
205
|
+
# @raise [Signature::SignatureError] if transaction is already signed.
|
206
|
+
# @raise [Signature::SignatureError] if sender address does not match signing key.
|
207
|
+
def sign(key)
|
208
|
+
if Tx.is_signed? self
|
209
|
+
raise Signature::SignatureError, "Transaction is already signed!"
|
210
|
+
end
|
211
|
+
|
212
|
+
# ensure the sender address matches the given key
|
213
|
+
unless @sender.nil? or sender.empty?
|
214
|
+
signer_address = Tx.sanitize_address key.address.to_s
|
215
|
+
from_address = Tx.sanitize_address @sender
|
216
|
+
raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address
|
217
|
+
end
|
218
|
+
|
219
|
+
# sign a keccak hash of the unsigned, encoded transaction
|
220
|
+
signature = key.sign(unsigned_hash, @chain_id)
|
221
|
+
r, s, v = Signature.dissect signature
|
222
|
+
@signature_v = v
|
223
|
+
@signature_r = r
|
224
|
+
@signature_s = s
|
225
|
+
return hash
|
226
|
+
end
|
227
|
+
|
228
|
+
# Encodes a raw transaction object.
|
229
|
+
#
|
230
|
+
# @return [String] a raw, RLP-encoded legacy transaction.
|
231
|
+
# @raise [Signature::SignatureError] if the transaction is not yet signed.
|
232
|
+
def encoded
|
233
|
+
unless Tx.is_signed? self
|
234
|
+
raise Signature::SignatureError, "Transaction is not signed!"
|
235
|
+
end
|
236
|
+
tx_data = []
|
237
|
+
tx_data.push Util.serialize_int_to_big_endian @signer_nonce
|
238
|
+
tx_data.push Util.serialize_int_to_big_endian @gas_price
|
239
|
+
tx_data.push Util.serialize_int_to_big_endian @gas_limit
|
240
|
+
tx_data.push Util.hex_to_bin @destination
|
241
|
+
tx_data.push Util.serialize_int_to_big_endian @amount
|
242
|
+
tx_data.push Rlp::Sedes.binary.serialize @payload
|
243
|
+
tx_data.push Util.serialize_int_to_big_endian @signature_v
|
244
|
+
tx_data.push Util.serialize_int_to_big_endian @signature_r
|
245
|
+
tx_data.push Util.serialize_int_to_big_endian @signature_s
|
246
|
+
Rlp.encode tx_data
|
247
|
+
end
|
248
|
+
|
249
|
+
# Gets the encoded, raw transaction hex.
|
250
|
+
#
|
251
|
+
# @return [String] the raw transaction hex.
|
252
|
+
def hex
|
253
|
+
Util.bin_to_hex encoded
|
254
|
+
end
|
255
|
+
|
256
|
+
# Gets the transaction hash.
|
257
|
+
#
|
258
|
+
# @return [String] the transaction hash.
|
259
|
+
def hash
|
260
|
+
Util.bin_to_hex Util.keccak256 encoded
|
261
|
+
end
|
262
|
+
|
263
|
+
# Encodes the unsigned transaction object, required for signing.
|
264
|
+
#
|
265
|
+
# @return [String] an RLP-encoded, unsigned transaction.
|
266
|
+
def unsigned_encoded
|
267
|
+
tx_data = []
|
268
|
+
tx_data.push Util.serialize_int_to_big_endian @signer_nonce
|
269
|
+
tx_data.push Util.serialize_int_to_big_endian @gas_price
|
270
|
+
tx_data.push Util.serialize_int_to_big_endian @gas_limit
|
271
|
+
tx_data.push Util.hex_to_bin @destination
|
272
|
+
tx_data.push Util.serialize_int_to_big_endian @amount
|
273
|
+
tx_data.push Rlp::Sedes.binary.serialize @payload
|
274
|
+
tx_data.push Util.serialize_int_to_big_endian @chain_id
|
275
|
+
tx_data.push Util.serialize_int_to_big_endian 0
|
276
|
+
tx_data.push Util.serialize_int_to_big_endian 0
|
277
|
+
Rlp.encode tx_data
|
278
|
+
end
|
279
|
+
|
280
|
+
# Gets the sign-hash required to sign a raw transaction.
|
281
|
+
#
|
282
|
+
# @return [String] a Keccak-256 hash of an unsigned transaction.
|
283
|
+
def unsigned_hash
|
284
|
+
Util.keccak256 unsigned_encoded
|
285
|
+
end
|
286
|
+
|
287
|
+
private
|
288
|
+
|
289
|
+
# Force-sets an existing signature of a decoded transaction.
|
290
|
+
def _set_signature(v, r, s)
|
291
|
+
@signature_v = v
|
292
|
+
@signature_r = r
|
293
|
+
@signature_s = s
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
data/lib/eth/tx.rb
ADDED
@@ -0,0 +1,322 @@
|
|
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
|
+
require "konstructor"
|
16
|
+
|
17
|
+
require "eth/chain"
|
18
|
+
require "eth/tx/eip1559"
|
19
|
+
require "eth/tx/eip2930"
|
20
|
+
require "eth/tx/legacy"
|
21
|
+
require "eth/unit"
|
22
|
+
|
23
|
+
# Provides the {Eth} module.
|
24
|
+
module Eth
|
25
|
+
|
26
|
+
# Provides the `Tx` module supporting various transaction types.
|
27
|
+
module Tx
|
28
|
+
extend self
|
29
|
+
|
30
|
+
# Provides a special transaction error if transaction type is unknown.
|
31
|
+
class TransactionTypeError < TypeError; end
|
32
|
+
|
33
|
+
# Provides an decoder error if transaction cannot be decoded.
|
34
|
+
class DecoderError < StandardError; end
|
35
|
+
|
36
|
+
# Provides a parameter error if parameter types are invalid.
|
37
|
+
class ParameterError < TypeError; end
|
38
|
+
|
39
|
+
# The minimum transaction gas limit required for a value transfer.
|
40
|
+
DEFAULT_GAS_LIMIT = 21_000.freeze
|
41
|
+
|
42
|
+
# The "default" transaction gas price of 20 GWei. Do not use.
|
43
|
+
DEFAULT_GAS_PRICE = (20 * Unit::GWEI).freeze
|
44
|
+
|
45
|
+
# The calldata gas cost of a non-zero byte as per EIP-2028.
|
46
|
+
COST_NON_ZERO_BYTE = 16.freeze
|
47
|
+
|
48
|
+
# The calldata gas cost of a zero byte.
|
49
|
+
COST_ZERO_BYTE = 4.freeze
|
50
|
+
|
51
|
+
# The access list gas cost of a storage key as per EIP-2930.
|
52
|
+
COST_STORAGE_KEY = 1_900.freeze
|
53
|
+
|
54
|
+
# The access list gas cost of an address as per EIP-2930.
|
55
|
+
COST_ADDRESS = 2_400.freeze
|
56
|
+
|
57
|
+
# The maximum transaction gas limit is bound by the block gas limit.
|
58
|
+
BLOCK_GAS_LIMIT = 25_000_000.freeze
|
59
|
+
|
60
|
+
# The legacy transaction type is 0.
|
61
|
+
TYPE_LEGACY = 0x00.freeze
|
62
|
+
|
63
|
+
# The EIP-2930 transaction type is 1.
|
64
|
+
TYPE_2930 = 0x01.freeze
|
65
|
+
|
66
|
+
# The EIP-1559 transaction type is 2.
|
67
|
+
TYPE_1559 = 0x02.freeze
|
68
|
+
|
69
|
+
# The zero byte is 0x00.
|
70
|
+
ZERO_BYTE = "\x00".freeze
|
71
|
+
|
72
|
+
# Smart contract transaction gas cost
|
73
|
+
CREATE_GAS = 32_000.freeze
|
74
|
+
|
75
|
+
# Creates a new transaction of any type for given parameters and chain ID.
|
76
|
+
# Required parameters are (optional in brackets):
|
77
|
+
# - EIP-1559: chain_id, nonce, priority_fee, max_gas_fee, gas_limit(, from, to,
|
78
|
+
# value, data, access_list)
|
79
|
+
# - EIP-2930: chain_id, nonce, gas_price, gas_limit, access_list(, from, to,
|
80
|
+
# value, data)
|
81
|
+
# - Legacy: nonce, gas_price, gas_limit(, from, to, value, data)
|
82
|
+
#
|
83
|
+
# @param params [Hash] all necessary transaction fields.
|
84
|
+
# @param chain_id [Integer] the EIP-155 Chain ID (legacy transactions only).
|
85
|
+
def new(params, chain_id = Chain::ETHEREUM)
|
86
|
+
|
87
|
+
# if we deal with max gas fee parameter, attempt EIP-1559
|
88
|
+
unless params[:max_gas_fee].nil?
|
89
|
+
params[:chain_id] = chain_id if params[:chain_id].nil?
|
90
|
+
return Tx::Eip1559.new params
|
91
|
+
end
|
92
|
+
|
93
|
+
# if we deal with access list parameter, attempt EIP-2930
|
94
|
+
unless params[:access_list].nil?
|
95
|
+
params[:chain_id] = chain_id if params[:chain_id].nil?
|
96
|
+
return Tx::Eip2930.new params
|
97
|
+
end
|
98
|
+
|
99
|
+
# if nothing else, go with legacy transactions
|
100
|
+
chain_id = params[:chain_id] if !params[:chain_id].nil? and params[:chain_id] != chain_id
|
101
|
+
return Tx::Legacy.new params, chain_id
|
102
|
+
end
|
103
|
+
|
104
|
+
# Decodes a transaction hex of any known type (2, 1, or legacy).
|
105
|
+
#
|
106
|
+
# @param hex [String] the raw transaction hex-string.
|
107
|
+
# @return [Eth::Tx] transaction payload.
|
108
|
+
# @raise [TransactionTypeError] if the transaction type is unknown.
|
109
|
+
def decode(hex)
|
110
|
+
hex = Util.remove_hex_prefix hex
|
111
|
+
type = hex[0, 2].to_i(16)
|
112
|
+
case type
|
113
|
+
when TYPE_1559
|
114
|
+
|
115
|
+
# EIP-1559 transaction (type 2)
|
116
|
+
return Tx::Eip1559.decode hex
|
117
|
+
when TYPE_2930
|
118
|
+
|
119
|
+
# EIP-2930 transaction (type 1)
|
120
|
+
return Tx::Eip2930.decode hex
|
121
|
+
else
|
122
|
+
|
123
|
+
# Legacy transaction if first byte is RLP (>= 192)
|
124
|
+
if type >= 0xc0
|
125
|
+
return Tx::Legacy.decode hex
|
126
|
+
else
|
127
|
+
raise TransactionTypeError, "Cannot decode unknown transaction type #{type}!"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Creates an unsigned copy of any transaction object.
|
133
|
+
#
|
134
|
+
# @param tx [Eth::Tx] any transaction payload.
|
135
|
+
# @return [Eth::Tx] an unsigned transaction payload of the same type.
|
136
|
+
# @raise [TransactionTypeError] if the transaction type is unknown.
|
137
|
+
def unsigned_copy(tx)
|
138
|
+
case tx.type
|
139
|
+
when TYPE_1559
|
140
|
+
|
141
|
+
# EIP-1559 transaction (type 2)
|
142
|
+
return Tx::Eip1559.unsigned_copy tx
|
143
|
+
when TYPE_2930
|
144
|
+
|
145
|
+
# EIP-2930 transaction (type 1)
|
146
|
+
return Tx::Eip2930.unsigned_copy tx
|
147
|
+
when TYPE_LEGACY
|
148
|
+
|
149
|
+
# Legacy transaction ("type 0")
|
150
|
+
return Tx::Legacy.unsigned_copy tx
|
151
|
+
end
|
152
|
+
raise TransactionTypeError, "Cannot copy unknown transaction type #{tx.type}!"
|
153
|
+
end
|
154
|
+
|
155
|
+
# Estimates intrinsic gas for provided call data (EIP-2028) and
|
156
|
+
# access lists (EIP-2930).
|
157
|
+
#
|
158
|
+
# @param data [String] the call data.
|
159
|
+
# @param list [Array] the access list.
|
160
|
+
# @return [Integer] the estimated intrinsic gas cost.
|
161
|
+
def estimate_intrinsic_gas(data = "", list = [])
|
162
|
+
gas = DEFAULT_GAS_LIMIT
|
163
|
+
unless data.nil? or data.empty?
|
164
|
+
data = Util.hex_to_bin data if Util.is_hex? data
|
165
|
+
|
166
|
+
# count zero bytes
|
167
|
+
zero = data.count ZERO_BYTE
|
168
|
+
gas += zero * COST_ZERO_BYTE
|
169
|
+
|
170
|
+
# count non-zero bytes
|
171
|
+
none = data.size - zero
|
172
|
+
gas += none * COST_NON_ZERO_BYTE
|
173
|
+
end
|
174
|
+
unless list.nil? or list.empty?
|
175
|
+
list.each do |entry|
|
176
|
+
|
177
|
+
# count addresses
|
178
|
+
gas += COST_ADDRESS
|
179
|
+
|
180
|
+
entry.last.each do |key|
|
181
|
+
|
182
|
+
# count storage keys
|
183
|
+
gas += COST_STORAGE_KEY
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
return gas
|
188
|
+
end
|
189
|
+
|
190
|
+
# Validates the common transaction fields such as nonce, gas limit,
|
191
|
+
# amount, and access list.
|
192
|
+
#
|
193
|
+
# @param fields [Hash] the transaction fields.
|
194
|
+
# @return [Hash] the validated transaction fields.
|
195
|
+
# @raise [ParameterError] if nonce is an invalid integer.
|
196
|
+
# @raise [ParameterError] if gas limit is invalid.
|
197
|
+
# @raise [ParameterError] if amount is invalid.
|
198
|
+
# @raise [ParameterError] if access list is invalid.
|
199
|
+
def validate_params(fields)
|
200
|
+
if fields[:nonce].nil? or fields[:nonce] < 0
|
201
|
+
raise ParameterError, "Invalid signer nonce #{fields[:nonce]}!"
|
202
|
+
end
|
203
|
+
if fields[:gas_limit].nil? or fields[:gas_limit] < DEFAULT_GAS_LIMIT or fields[:gas_limit] > BLOCK_GAS_LIMIT
|
204
|
+
raise ParameterError, "Invalid gas limit #{fields[:gas_limit]}!"
|
205
|
+
end
|
206
|
+
unless fields[:value] >= 0
|
207
|
+
raise ParameterError, "Invalid transaction value #{fields[:value]}!"
|
208
|
+
end
|
209
|
+
unless fields[:access_list].nil? or fields[:access_list].is_a? Array
|
210
|
+
raise ParameterError, "Invalid access list #{fields[:access_list]}!"
|
211
|
+
end
|
212
|
+
return fields
|
213
|
+
end
|
214
|
+
|
215
|
+
# Validates the common type-2 transaction fields such as priority
|
216
|
+
# fee and max gas fee.
|
217
|
+
#
|
218
|
+
# @param fields [Hash] the transaction fields.
|
219
|
+
# @return [Hash] the validated transaction fields.
|
220
|
+
# @raise [ParameterError] if priority fee is invalid.
|
221
|
+
# @raise [ParameterError] if max gas fee is invalid.
|
222
|
+
def validate_eip1559_params(fields)
|
223
|
+
if fields[:priority_fee].nil? or fields[:priority_fee] < 0
|
224
|
+
raise ParameterError, "Invalid gas priority fee #{fields[:priority_fee]}!"
|
225
|
+
end
|
226
|
+
if fields[:max_gas_fee].nil? or fields[:max_gas_fee] < 0
|
227
|
+
raise ParameterError, "Invalid max gas fee #{fields[:max_gas_fee]}!"
|
228
|
+
end
|
229
|
+
return fields
|
230
|
+
end
|
231
|
+
|
232
|
+
# Validates the common legacy transaction fields such as gas price.
|
233
|
+
#
|
234
|
+
# @param fields [Hash] the transaction fields.
|
235
|
+
# @return [Hash] the validated transaction fields.
|
236
|
+
# @raise [ParameterError] if gas price is invalid.
|
237
|
+
def validate_legacy_params(fields)
|
238
|
+
if fields[:gas_price].nil? or fields[:gas_price] < 0
|
239
|
+
raise ParameterError, "Invalid gas price #{fields[:gas_price]}!"
|
240
|
+
end
|
241
|
+
return fields
|
242
|
+
end
|
243
|
+
|
244
|
+
# Populates the transaction chain id field with a serializable default
|
245
|
+
# value (1) in case it is undefined.
|
246
|
+
#
|
247
|
+
# @param id [Integer] the transaction chain id.
|
248
|
+
# @return [Integer] the sanitized transaction chain id.
|
249
|
+
def sanitize_chain(id)
|
250
|
+
id = Chain::ETHEREUM if id.nil?
|
251
|
+
return id
|
252
|
+
end
|
253
|
+
|
254
|
+
# Populates the transaction destination address with a serializable
|
255
|
+
# empty value in case it is undefined; also ensures the address is
|
256
|
+
# checksummed but not prefixed for consistency.
|
257
|
+
#
|
258
|
+
# @param addr [String] the transaction destination address.
|
259
|
+
# @return [String] the sanitized transaction destination address.
|
260
|
+
def sanitize_address(addr)
|
261
|
+
addr = "" if addr.nil?
|
262
|
+
if addr.is_a? String and !addr.empty?
|
263
|
+
addr = Address.new(addr).to_s
|
264
|
+
addr = Util.remove_hex_prefix addr
|
265
|
+
end
|
266
|
+
return addr
|
267
|
+
end
|
268
|
+
|
269
|
+
# Populates the transaction value field with a serializable empty value
|
270
|
+
# in case it is undefined.
|
271
|
+
#
|
272
|
+
# @param val [Integer] the transaction value.
|
273
|
+
# @return [Integer] the sanitized transaction value.
|
274
|
+
def sanitize_amount(val)
|
275
|
+
val = 0 if val.nil?
|
276
|
+
return val
|
277
|
+
end
|
278
|
+
|
279
|
+
# Populates the transaction payload field with a serializable empty value
|
280
|
+
# in case it is undefined; also ensures the data is binary not hex.
|
281
|
+
#
|
282
|
+
# @param data [String] the transaction payload data.
|
283
|
+
# @return [String] the sanitized transaction payload data.
|
284
|
+
def sanitize_data(data)
|
285
|
+
data = "" if data.nil?
|
286
|
+
|
287
|
+
# ensure payload to be binary if it's hex, otherwise we'll treat it raw
|
288
|
+
data = Util.hex_to_bin data if Util.is_hex? data
|
289
|
+
return data
|
290
|
+
end
|
291
|
+
|
292
|
+
# Populates the transaction access list field with a serializable empty
|
293
|
+
# array in case it is undefined; also ensures the nested data is binary
|
294
|
+
# not hex.
|
295
|
+
#
|
296
|
+
# @param list [Array] the transaction access list.
|
297
|
+
# @return [Array] the sanitized transaction access list.
|
298
|
+
def sanitize_list(list)
|
299
|
+
list = [] if list.nil?
|
300
|
+
list.each_with_index do |value, index|
|
301
|
+
if value.is_a? Array
|
302
|
+
|
303
|
+
# recursively check the entire array
|
304
|
+
list[index] = sanitize_list value
|
305
|
+
elsif Util.is_hex? value
|
306
|
+
|
307
|
+
# only modify if we find a hex value
|
308
|
+
list[index] = Util.hex_to_bin value
|
309
|
+
end
|
310
|
+
end
|
311
|
+
return list
|
312
|
+
end
|
313
|
+
|
314
|
+
# Allows to check wether a transaction is signed already.
|
315
|
+
#
|
316
|
+
# @return [Bool] true if transaction is already signed.
|
317
|
+
def is_signed?(tx)
|
318
|
+
!tx.signature_r.nil? and tx.signature_r != 0 and
|
319
|
+
!tx.signature_s.nil? and tx.signature_s != 0
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
data/lib/eth/unit.rb
ADDED
@@ -0,0 +1,49 @@
|
|
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
|
+
require "bigdecimal"
|
16
|
+
|
17
|
+
# Provides the {Eth} module.
|
18
|
+
module Eth
|
19
|
+
|
20
|
+
# Provides constants for common Ethereum units.
|
21
|
+
module Unit
|
22
|
+
extend self
|
23
|
+
|
24
|
+
# Ethereum unit 1 wei := 0.000000000000000001 Ether.
|
25
|
+
WEI = BigDecimal("1e0").freeze
|
26
|
+
|
27
|
+
# Ethereum unit 1 babbage := 0.000000000000001 Ether or 1_000 wei.
|
28
|
+
BABBAGE = BigDecimal("1e3").freeze
|
29
|
+
|
30
|
+
# Ethereum unit 1 lovelace := 0.000000000001 Ether or 1_000_000 wei.
|
31
|
+
LOVELACE = BigDecimal("1e6").freeze
|
32
|
+
|
33
|
+
# Ethereum unit 1 shannon := 0.000000001 Ether or 1_000_000_000 wei.
|
34
|
+
SHANNON = BigDecimal("1e9").freeze
|
35
|
+
|
36
|
+
# Ethereum unit 1 szabo := 0.000_001 Ether or 1_000_000_000_000 wei.
|
37
|
+
SZABO = BigDecimal("1e12").freeze
|
38
|
+
|
39
|
+
# Ethereum unit 1 finney := 0.001 Ether or 1_000_000_000_000_000 wei.
|
40
|
+
FINNEY = BigDecimal("1e15").freeze
|
41
|
+
|
42
|
+
# Ethereum unit 1 Ether := 1_000_000_000_000_000_000 wei.
|
43
|
+
ETHER = BigDecimal("1e18").freeze
|
44
|
+
|
45
|
+
# Ethereum unit 1 Gwei := 0.000000001 Ether or 1_000_000_000 wei.
|
46
|
+
# Same as shannon, but more commonly used (billion wei).
|
47
|
+
GWEI = SHANNON.freeze
|
48
|
+
end
|
49
|
+
end
|