class Eth::Tx::Eip2930
implement EIP-1559 but still want to utilize EIP-2718 envelopes.
Provides legacy support for transactions on blockchains that do not
def _set_signature(recovery_id, r, s)
def _set_signature(recovery_id, r, s) @signature_y_parity = recovery_id @signature_r = r @signature_s = s end
def decode(hex)
-
(DecoderError)
- if transaction decoding fails. -
(ParameterError)
- if transaction is missing fields. -
(TransactionTypeError)
- if transaction type is invalid.
Returns:
-
(Eth::Tx::Eip2930)
- transaction payload.
Parameters:
-
hex
(String
) -- the raw transaction hex-string.
def decode(hex) hex = Util.remove_hex_prefix hex type = hex[0, 2] raise TransactionTypeError, "Invalid transaction type #{type}!" if type.to_i(16) != TYPE_2930 bin = Util.hex_to_bin hex[2..] tx = RLP.decode(bin) # decoded transactions always have 8 + 3 fields, even if they are empty or zero raise ParameterError, "Transaction missing fields!" if tx.size < 8 # populate the 8 payload fields chain_id = Util.deserialize_big_endian_to_int tx[0] nonce = Util.deserialize_big_endian_to_int tx[1] gas_price = Util.deserialize_big_endian_to_int tx[2] gas_limit = Util.deserialize_big_endian_to_int tx[3] to = Util.bin_to_hex tx[4] value = Util.deserialize_big_endian_to_int tx[5] data = tx[6] access_list = tx[7] # populate class attributes @chain_id = chain_id.to_i @signer_nonce = nonce.to_i @gas_price = gas_price.to_i @gas_limit = gas_limit.to_i @destination = to.to_s @amount = value.to_i @payload = data @access_list = access_list # populate the 3 signature fields if tx.size == 8 _set_signature(nil, 0, 0) elsif tx.size == 11 recovery_id = Util.bin_to_hex(tx[8]).to_i(16) r = Util.bin_to_hex tx[9] s = Util.bin_to_hex tx[10] # allows us to force-setting a signature if the transaction is signed already _set_signature(recovery_id, r, s) else raise_error DecoderError, "Cannot decode EIP-2930 payload!" end # last but not least, set the type. @type = TYPE_2930 # recover sender address v = Chain.to_v recovery_id, chain_id public_key = Signature.recover(unsigned_hash, "#{r}#{s}#{v.to_s(16)}", chain_id) address = Util.public_key_to_address(public_key).to_s @sender = Tx.sanitize_address address end
def encoded
-
(SignatureError)
- if the transaction is not yet signed.
Returns:
-
(String)
- a raw, RLP-encoded EIP-2930 type transaction object.
def encoded unless Tx.is_signed? self raise Signature::SignatureError, "Transaction is not signed!" end tx_data = [] tx_data.push Util.serialize_int_to_big_endian @chain_id tx_data.push Util.serialize_int_to_big_endian @signer_nonce tx_data.push Util.serialize_int_to_big_endian @gas_price tx_data.push Util.serialize_int_to_big_endian @gas_limit tx_data.push Util.hex_to_bin @destination tx_data.push Util.serialize_int_to_big_endian @amount tx_data.push @payload tx_data.push @access_list tx_data.push Util.serialize_int_to_big_endian @signature_y_parity tx_data.push Util.hex_to_bin @signature_r tx_data.push Util.hex_to_bin @signature_s tx_encoded = RLP.encode tx_data # create an EIP-2718 envelope with EIP-2930 type payload tx_type = Util.serialize_int_to_big_endian @type return "#{tx_type}#{tx_encoded}" end
def hash
-
(String)
- the transaction hash.
def hash Util.bin_to_hex Util.keccak256 encoded end
def hex
-
(String)
- the raw transaction hex.
def hex Util.bin_to_hex encoded end
def initialize(params)
(**params)
-
:access_list
(Array
) -- an optional access list. -
:data
(String
) -- the transaction data payload. -
:value
(Integer
) -- the transaction value. -
:to
(Eth::Address
) -- the reciever address. -
:from
(Eth::Address
) -- the sender address. -
:gas_limit
(Integer
) -- the gas limit. -
:gas_price
(Integer
) -- the gas price. -
:nonce
(Integer
) -- the signer nonce.
Parameters:
-
params
(Hash
) -- all necessary transaction fields.
def initialize(params) fields = { recovery_id: nil, r: 0, s: 0 }.merge params # populate optional fields with serializable empty values fields[:chain_id] = Tx.sanitize_chain fields[:chain_id] fields[:from] = Tx.sanitize_address fields[:from] fields[:to] = Tx.sanitize_address fields[:to] fields[:value] = Tx.sanitize_amount fields[:value] fields[:data] = Tx.sanitize_data fields[:data] # ensure sane values for all mandatory fields fields = Tx.validate_legacy_params fields fields[:access_list] = Tx.sanitize_list fields[:access_list] # ensure gas limit is not too low minimum_cost = Tx.estimate_intrinsic_gas fields[:data], fields[:access_list] raise ParameterError, "Transaction gas limit is too low, try #{minimum_cost}!" if fields[:gas_limit].to_i < minimum_cost # populate class attributes @signer_nonce = fields[:nonce].to_i @gas_price = fields[:gas_price].to_i @gas_limit = fields[:gas_limit].to_i @sender = fields[:from].to_s @destination = fields[:to].to_s @amount = fields[:value].to_i @payload = fields[:data] @access_list = fields[:access_list] # the signature v is set to the chain id for unsigned transactions @signature_y_parity = fields[:recovery_id] @chain_id = fields[:chain_id] # the signature fields are empty for unsigned transactions. @signature_r = fields[:r] @signature_s = fields[:s] # last but not least, set the type. @type = TYPE_2930 end
def sign(key)
-
(SignatureError)
- if sender address does not match signing key. -
(SignatureError)
- if transaction is already signed.
Returns:
-
(String)
- a transaction hash.
Parameters:
-
key
(Eth::Key
) -- the key-pair to use for signing.
def sign(key) if Tx.is_signed? self raise Signature::SignatureError, "Transaction is already signed!" end # ensure the sender address matches the given key unless @sender.nil? or sender.empty? signer_address = Tx.sanitize_address key.address.to_s from_address = Tx.sanitize_address @sender raise Signature::SignatureError, "Signer does not match sender" unless signer_address == from_address end # sign a keccak hash of the unsigned, encoded transaction signature = key.sign(unsigned_hash, @chain_id) r, s, v = Signature.dissect signature recovery_id = Chain.to_recovery_id v.to_i(16), @chain_id @signature_y_parity = recovery_id @signature_r = r @signature_s = s return hash end
def unsigned_copy(tx)
-
(TransactionTypeError)
- if transaction type does not match.
Returns:
-
(Eth::Tx::Eip2930)
- an unsigned EIP-2930 transaction payload.
Parameters:
-
tx
(Eth::Tx::Eip2930
) -- an EIP-2930 transaction payload.
def unsigned_copy(tx) # not checking transaction validity unless it's of a different class raise TransactionTypeError, "Cannot copy transaction of different payload type!" unless tx.instance_of? Tx::Eip2930 # populate class attributes @signer_nonce = tx.signer_nonce @gas_price = tx.gas_price @gas_limit = tx.gas_limit @destination = tx.destination @amount = tx.amount @payload = tx.payload @access_list = tx.access_list @chain_id = tx.chain_id # force-set signature to unsigned _set_signature(nil, 0, 0) # keep the 'from' field blank @sender = Tx.sanitize_address nil # last but not least, set the type. @type = TYPE_2930 end
def unsigned_encoded
-
(String)
- an RLP-encoded, unsigned, enveloped EIP-2930 transaction.
def unsigned_encoded tx_data = [] tx_data.push Util.serialize_int_to_big_endian @chain_id tx_data.push Util.serialize_int_to_big_endian @signer_nonce tx_data.push Util.serialize_int_to_big_endian @gas_price tx_data.push Util.serialize_int_to_big_endian @gas_limit tx_data.push Util.hex_to_bin @destination tx_data.push Util.serialize_int_to_big_endian @amount tx_data.push @payload tx_data.push @access_list tx_encoded = RLP.encode tx_data # create an EIP-2718 envelope with EIP-2930 type payload (unsigned) tx_type = Util.serialize_int_to_big_endian @type return "#{tx_type}#{tx_encoded}" end
def unsigned_hash
-
(String)
- a Keccak-256 hash of an unsigned transaction.
def unsigned_hash Util.keccak256 unsigned_encoded end