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)

Raises:
  • (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

Raises:
  • (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

Returns:
  • (String) - the transaction hash.
def hash
  Util.bin_to_hex Util.keccak256 encoded
end

def hex

Returns:
  • (String) - the raw transaction hex.
def hex
  Util.bin_to_hex encoded
end

def initialize(params)

Options Hash: (**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)

Raises:
  • (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)

Raises:
  • (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

Returns:
  • (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

Returns:
  • (String) - a Keccak-256 hash of an unsigned transaction.
def unsigned_hash
  Util.keccak256 unsigned_encoded
end