feat: eip-7702 (#9214)

Co-authored-by: Matthew Smith <m@lattejed.com>
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
Co-authored-by: joshieDo <93316087+joshieDo@users.noreply.github.com>
Co-authored-by: Dan Cline <6798349+Rjected@users.noreply.github.com>
This commit is contained in:
Oliver
2024-07-11 07:45:47 +02:00
committed by GitHub
parent fc3d0eb9d7
commit fc4c037e60
25 changed files with 877 additions and 72 deletions

View File

@ -386,7 +386,7 @@ revm-primitives = { version = "6.0.0", features = [
revm-inspectors = "0.4"
# eth
alloy-chains = "0.1.15"
alloy-chains = "0.1.18"
alloy-primitives = "0.7.2"
alloy-dyn-abi = "0.7.2"
alloy-sol-types = "0.7.2"

View File

@ -948,6 +948,13 @@ impl ChainSpecBuilder {
self
}
/// Enable Prague at genesis.
pub fn prague_activated(mut self) -> Self {
self = self.cancun_activated();
self.hardforks.insert(EthereumHardfork::Prague, ForkCondition::Timestamp(0));
self
}
/// Enable Bedrock at genesis
#[cfg(feature = "optimism")]
pub fn bedrock_activated(mut self) -> Self {

View File

@ -342,6 +342,9 @@ pub struct AnnouncedTxTypesMetrics {
/// Histogram for tracking frequency of EIP-4844 transaction type
pub(crate) eip4844: Histogram,
/// Histogram for tracking frequency of EIP-7702 transaction type
pub(crate) eip7702: Histogram,
}
#[derive(Debug, Default)]
@ -350,6 +353,7 @@ pub struct TxTypesCounter {
pub(crate) eip2930: usize,
pub(crate) eip1559: usize,
pub(crate) eip4844: usize,
pub(crate) eip7702: usize,
}
impl TxTypesCounter {
@ -368,6 +372,9 @@ impl TxTypesCounter {
TxType::Eip4844 => {
self.eip4844 += 1;
}
TxType::Eip7702 => {
self.eip7702 += 1;
}
_ => {}
}
}
@ -381,5 +388,6 @@ impl AnnouncedTxTypesMetrics {
self.eip2930.record(tx_types_counter.eip2930 as f64);
self.eip1559.record(tx_types_counter.eip1559 as f64);
self.eip4844.record(tx_types_counter.eip4844 as f64);
self.eip7702.record(tx_types_counter.eip7702 as f64);
}
}

View File

@ -331,6 +331,7 @@ impl FilterAnnouncement for EthMessageFilter {
}
}
// TODO(eip7702): update tests as needed
#[cfg(test)]
mod test {
use super::*;

View File

@ -181,6 +181,34 @@ impl TryFrom<alloy_rpc_types::Transaction> for Transaction {
.ok_or(ConversionError::MissingMaxFeePerBlobGas)?,
}))
}
Some(TxType::Eip7702) => {
// this is currently unsupported as it is not present in alloy due to missing rpc
// specs
Err(ConversionError::Custom("Unimplemented".to_string()))
/*
// EIP-7702
Ok(Transaction::Eip7702(TxEip7702 {
chain_id: tx.chain_id.ok_or(ConversionError::MissingChainId)?,
nonce: tx.nonce,
max_priority_fee_per_gas: tx
.max_priority_fee_per_gas
.ok_or(ConversionError::MissingMaxPriorityFeePerGas)?,
max_fee_per_gas: tx
.max_fee_per_gas
.ok_or(ConversionError::MissingMaxFeePerGas)?,
gas_limit: tx
.gas
.try_into()
.map_err(|_| ConversionError::Eip2718Error(RlpError::Overflow.into()))?,
to: tx.to.map_or(TxKind::Create, TxKind::Call),
value: tx.value,
access_list: tx.access_list.ok_or(ConversionError::MissingAccessList)?,
authorization_list: tx
.authorization_list
.ok_or(ConversionError::MissingAuthorizationList)?,
input: tx.input,
}))*/
}
#[cfg(feature = "optimism")]
Some(TxType::Deposit) => {
let fields = tx

View File

@ -68,8 +68,8 @@ pub use transaction::{
AccessList, AccessListItem, IntoRecoveredTransaction, InvalidTransactionError, Signature,
Transaction, TransactionMeta, TransactionSigned, TransactionSignedEcRecovered,
TransactionSignedNoHash, TryFromRecoveredTransaction, TxEip1559, TxEip2930, TxEip4844,
TxHashOrNumber, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID,
LEGACY_TX_TYPE_ID,
TxEip7702, TxHashOrNumber, TxLegacy, TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
};
// Re-exports

View File

@ -339,6 +339,10 @@ impl Decodable for ReceiptWithBloom {
buf.advance(1);
Self::decode_receipt(buf, TxType::Eip4844)
}
0x04 => {
buf.advance(1);
Self::decode_receipt(buf, TxType::Eip7702)
}
#[cfg(feature = "optimism")]
0x7E => {
buf.advance(1);
@ -471,6 +475,9 @@ impl<'a> ReceiptWithBloomEncoder<'a> {
TxType::Eip4844 => {
out.put_u8(0x03);
}
TxType::Eip7702 => {
out.put_u8(0x04);
}
#[cfg(feature = "optimism")]
TxType::Deposit => {
out.put_u8(0x7E);

View File

@ -1,5 +1,5 @@
use crate::{Address, Transaction, TransactionSigned, TxKind, U256};
use revm_primitives::TxEnv;
use revm_primitives::{AuthorizationList, TxEnv};
/// Implements behaviour to fill a [`TxEnv`] from another transaction.
pub trait FillTxEnv {
@ -70,6 +70,21 @@ impl FillTxEnv for TransactionSigned {
tx_env.blob_hashes.clone_from(&tx.blob_versioned_hashes);
tx_env.max_fee_per_blob_gas = Some(U256::from(tx.max_fee_per_blob_gas));
}
Transaction::Eip7702(tx) => {
tx_env.gas_limit = tx.gas_limit;
tx_env.gas_price = U256::from(tx.max_fee_per_gas);
tx_env.gas_priority_fee = Some(U256::from(tx.max_priority_fee_per_gas));
tx_env.transact_to = tx.to;
tx_env.value = tx.value;
tx_env.data = tx.input.clone();
tx_env.chain_id = Some(tx.chain_id);
tx_env.nonce = Some(tx.nonce);
tx_env.access_list = tx.access_list.0.clone();
tx_env.blob_hashes.clear();
tx_env.max_fee_per_blob_gas.take();
tx_env.authorization_list =
Some(AuthorizationList::Signed(tx.authorization_list.clone()));
}
#[cfg(feature = "optimism")]
Transaction::Deposit(tx) => {
tx_env.access_list.clear();

View File

@ -0,0 +1,388 @@
use super::access_list::AccessList;
use crate::{keccak256, Bytes, ChainId, Signature, TxKind, TxType, B256, U256};
use alloy_eips::eip7702::SignedAuthorization;
use alloy_rlp::{length_of_length, Decodable, Encodable, Header};
use bytes::BytesMut;
use reth_codecs::{main_codec, Compact};
use std::mem;
/// [EIP-7702 Set Code Transaction](https://eips.ethereum.org/EIPS/eip-7702)
///
/// Set EOA account code for one transaction
#[main_codec(no_arbitrary, add_arbitrary_tests)]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TxEip7702 {
/// Added as EIP-155: Simple replay attack protection
pub chain_id: ChainId,
/// A scalar value equal to the number of transactions sent by the sender; formally Tn.
pub nonce: u64,
/// A scalar value equal to the number of
/// Wei to be paid per unit of gas for all computation
/// costs incurred as a result of the execution of this transaction; formally Tp.
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
pub gas_limit: u64,
/// A scalar value equal to the maximum
/// amount of gas that should be used in executing
/// this transaction. This is paid up-front, before any
/// computation is done and may not be increased
/// later; formally Tg.
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasFeeCap`
pub max_fee_per_gas: u128,
/// Max Priority fee that transaction is paying
///
/// As ethereum circulation is around 120mil eth as of 2022 that is around
/// 120000000000000000000000000 wei we are safe to use u128 as its max number is:
/// 340282366920938463463374607431768211455
///
/// This is also known as `GasTipCap`
pub max_priority_fee_per_gas: u128,
/// The 160-bit address of the message calls recipient or, for a contract creation
/// transaction, ∅, used here to denote the only member of B0 ; formally Tt.
pub to: TxKind,
/// A scalar value equal to the number of Wei to
/// be transferred to the message calls recipient or,
/// in the case of contract creation, as an endowment
/// to the newly created account; formally Tv.
pub value: U256,
/// The accessList specifies a list of addresses and storage keys;
/// these addresses and storage keys are added into the `accessed_addresses`
/// and `accessed_storage_keys` global sets (introduced in EIP-2929).
/// A gas cost is charged, though at a discount relative to the cost of
/// accessing outside the list.
pub access_list: AccessList,
/// Authorizations are used to temporarily set the code of its signer to
/// the code referenced by `address`. These also include a `chain_id` (which
/// can be set to zero and not evaluated) as well as an optional `nonce`.
pub authorization_list: Vec<SignedAuthorization<alloy_primitives::Signature>>,
/// Input has two uses depending if the transaction `to` field is [`TxKind::Create`] or
/// [`TxKind::Call`].
///
/// Input as init code, or if `to` is [`TxKind::Create`]: An unlimited size byte array
/// specifying the EVM-code for the account initialisation procedure `CREATE`
///
/// Input as data, or if `to` is [`TxKind::Call`]: An unlimited size byte array specifying the
/// input data of the message call, formally Td.
pub input: Bytes,
}
impl TxEip7702 {
/// Returns the effective gas price for the given `base_fee`.
pub const fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
match base_fee {
None => self.max_fee_per_gas,
Some(base_fee) => {
// if the tip is greater than the max priority fee per gas, set it to the max
// priority fee per gas + base fee
let tip = self.max_fee_per_gas.saturating_sub(base_fee as u128);
if tip > self.max_priority_fee_per_gas {
self.max_priority_fee_per_gas + base_fee as u128
} else {
// otherwise return the max fee per gas
self.max_fee_per_gas
}
}
}
}
/// Calculates a heuristic for the in-memory size of the [`TxEip7702`] transaction.
#[inline]
pub fn size(&self) -> usize {
mem::size_of::<ChainId>() + // chain_id
mem::size_of::<u64>() + // nonce
mem::size_of::<u128>() + // gas_price
mem::size_of::<u64>() + // gas_limit
self.to.size() + // to
mem::size_of::<U256>() + // value
self.access_list.size() + // access_list
mem::size_of::<SignedAuthorization<alloy_primitives::Signature>>()
* self.authorization_list.capacity() + // authorization_list
self.input.len() // input
}
/// Decodes the inner [`TxEip7702`] fields from RLP bytes.
///
/// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following
/// RLP fields in the following order:
///
/// - `chain_id`
/// - `nonce`
/// - `gas_price`
/// - `gas_limit`
/// - `to`
/// - `value`
/// - `data` (`input`)
/// - `access_list`
/// - `authorization_list`
pub(crate) fn decode_inner(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
Ok(Self {
chain_id: Decodable::decode(buf)?,
nonce: Decodable::decode(buf)?,
max_priority_fee_per_gas: Decodable::decode(buf)?,
max_fee_per_gas: Decodable::decode(buf)?,
gas_limit: Decodable::decode(buf)?,
to: Decodable::decode(buf)?,
value: Decodable::decode(buf)?,
input: Decodable::decode(buf)?,
access_list: Decodable::decode(buf)?,
authorization_list: Decodable::decode(buf)?,
})
}
/// Outputs the length of the transaction's fields, without a RLP header.
pub(crate) fn fields_len(&self) -> usize {
self.chain_id.length() +
self.nonce.length() +
self.max_priority_fee_per_gas.length() +
self.max_fee_per_gas.length() +
self.gas_limit.length() +
self.to.length() +
self.value.length() +
self.input.0.length() +
self.access_list.length() +
self.authorization_list.length()
}
/// Encodes only the transaction's fields into the desired buffer, without a RLP header.
pub(crate) fn encode_fields(&self, out: &mut dyn bytes::BufMut) {
self.chain_id.encode(out);
self.nonce.encode(out);
self.max_priority_fee_per_gas.encode(out);
self.max_fee_per_gas.encode(out);
self.gas_limit.encode(out);
self.to.encode(out);
self.value.encode(out);
self.input.0.encode(out);
self.access_list.encode(out);
self.authorization_list.encode(out);
}
/// Inner encoding function that is used for both rlp [`Encodable`] trait and for calculating
/// hash that for eip2718 does not require rlp header
///
/// This encodes the transaction as:
/// `rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, destination,
/// value, data, access_list, authorization_list, signature_y_parity, signature_r,
/// signature_s])`
pub(crate) fn encode_with_signature(
&self,
signature: &Signature,
out: &mut dyn bytes::BufMut,
with_header: bool,
) {
let payload_length = self.fields_len() + signature.payload_len();
if with_header {
Header {
list: false,
payload_length: 1 + length_of_length(payload_length) + payload_length,
}
.encode(out);
}
out.put_u8(self.tx_type() as u8);
let header = Header { list: true, payload_length };
header.encode(out);
self.encode_fields(out);
signature.encode(out);
}
/// Output the length of the RLP signed transaction encoding, _without_ a RLP string header.
pub(crate) fn payload_len_with_signature_without_header(&self, signature: &Signature) -> usize {
let payload_length = self.fields_len() + signature.payload_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Output the length of the RLP signed transaction encoding. This encodes with a RLP header.
pub(crate) fn payload_len_with_signature(&self, signature: &Signature) -> usize {
let len = self.payload_len_with_signature_without_header(signature);
length_of_length(len) + len
}
/// Get transaction type
pub(crate) const fn tx_type(&self) -> TxType {
TxType::Eip7702
}
/// Encodes the EIP-7702 transaction in RLP for signing.
///
/// This encodes the transaction as:
/// `tx_type || rlp(chain_id, nonce, gas_price, gas_limit, to, value, input, access_list,
/// authorization_list)`
///
/// Note that there is no rlp header before the transaction type byte.
pub(crate) fn encode_for_signing(&self, out: &mut dyn bytes::BufMut) {
out.put_u8(self.tx_type() as u8);
Header { list: true, payload_length: self.fields_len() }.encode(out);
self.encode_fields(out);
}
/// Outputs the length of the signature RLP encoding for the transaction.
pub(crate) fn payload_len_for_signature(&self) -> usize {
let payload_length = self.fields_len();
// 'transaction type byte length' + 'header length' + 'payload length'
1 + length_of_length(payload_length) + payload_length
}
/// Outputs the signature hash of the transaction by first encoding without a signature, then
/// hashing.
pub(crate) fn signature_hash(&self) -> B256 {
let mut buf = BytesMut::with_capacity(self.payload_len_for_signature());
self.encode_for_signing(&mut buf);
keccak256(&buf)
}
}
// TODO(onbjerg): This is temporary until we upstream `Arbitrary` to EIP-7702 types and `Signature`
// in alloy
#[cfg(any(test, feature = "arbitrary"))]
impl<'a> arbitrary::Arbitrary<'a> for TxEip7702 {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
use arbitrary::Arbitrary;
#[derive(Arbitrary)]
struct ArbitrarySignedAuth {
chain_id: ChainId,
address: alloy_primitives::Address,
nonce: Option<u64>,
parity: bool,
r: U256,
s: U256,
}
let iter = u.arbitrary_iter::<ArbitrarySignedAuth>()?;
let mut authorization_list = Vec::new();
for auth in iter {
let auth = auth?;
let sig = alloy_primitives::Signature::from_rs_and_parity(
auth.r,
auth.s,
alloy_primitives::Parity::Parity(auth.parity),
)
.unwrap_or_else(|_| {
// Give a default one if the randomly generated one failed
alloy_primitives::Signature::from_rs_and_parity(
alloy_primitives::b256!(
"1fd474b1f9404c0c5df43b7620119ffbc3a1c3f942c73b6e14e9f55255ed9b1d"
)
.into(),
alloy_primitives::b256!(
"29aca24813279a901ec13b5f7bb53385fa1fc627b946592221417ff74a49600d"
)
.into(),
false,
)
.unwrap()
});
authorization_list.push(
alloy_eips::eip7702::Authorization {
chain_id: auth.chain_id,
address: auth.address,
nonce: auth.nonce.into(),
}
.into_signed(sig),
);
}
Ok(Self {
chain_id: Arbitrary::arbitrary(u)?,
nonce: Arbitrary::arbitrary(u)?,
gas_limit: Arbitrary::arbitrary(u)?,
max_fee_per_gas: Arbitrary::arbitrary(u)?,
max_priority_fee_per_gas: Arbitrary::arbitrary(u)?,
to: Arbitrary::arbitrary(u)?,
value: Arbitrary::arbitrary(u)?,
access_list: Arbitrary::arbitrary(u)?,
authorization_list,
input: Arbitrary::arbitrary(u)?,
})
}
}
// TODO(onbjerg): This is temporary until we upstream `Hash` for EIP-7702 types in alloy
impl std::hash::Hash for TxEip7702 {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.chain_id.hash(state);
self.nonce.hash(state);
self.gas_limit.hash(state);
self.max_fee_per_gas.hash(state);
self.max_priority_fee_per_gas.hash(state);
self.to.hash(state);
self.value.hash(state);
self.access_list.hash(state);
for auth in &self.authorization_list {
auth.signature_hash().hash(state);
}
self.input.hash(state);
}
}
#[cfg(test)]
mod tests {
use super::TxEip7702;
use crate::{
transaction::{signature::Signature, TxKind},
Address, Bytes, Transaction, TransactionSigned, U256,
};
use alloy_rlp::{Decodable, Encodable};
#[test]
fn test_decode_create() {
// tests that a contract creation tx encodes and decodes properly
let request = Transaction::Eip7702(TxEip7702 {
chain_id: 1u64,
nonce: 0,
max_fee_per_gas: 0x4a817c800,
max_priority_fee_per_gas: 0x3b9aca00,
gas_limit: 2,
to: TxKind::Create,
value: U256::ZERO,
input: Bytes::from(vec![1, 2]),
access_list: Default::default(),
authorization_list: Default::default(),
});
let signature = Signature { odd_y_parity: true, r: U256::default(), s: U256::default() };
let tx = TransactionSigned::from_transaction_and_signature(request, signature);
let mut encoded = Vec::new();
tx.encode(&mut encoded);
assert_eq!(encoded.len(), tx.length());
let decoded = TransactionSigned::decode(&mut &*encoded).unwrap();
assert_eq!(decoded, tx);
}
#[test]
fn test_decode_call() {
let request = Transaction::Eip7702(TxEip7702 {
chain_id: 1u64,
nonce: 0,
max_fee_per_gas: 0x4a817c800,
max_priority_fee_per_gas: 0x3b9aca00,
gas_limit: 2,
to: Address::default().into(),
value: U256::ZERO,
input: Bytes::from(vec![1, 2]),
access_list: Default::default(),
authorization_list: Default::default(),
});
let signature = Signature { odd_y_parity: true, r: U256::default(), s: U256::default() };
let tx = TransactionSigned::from_transaction_and_signature(request, signature);
let mut encoded = Vec::new();
tx.encode(&mut encoded);
assert_eq!(encoded.len(), tx.length());
let decoded = TransactionSigned::decode(&mut &*encoded).unwrap();
assert_eq!(decoded, tx);
}
}

View File

@ -29,6 +29,9 @@ pub enum InvalidTransactionError {
/// The transaction requires EIP-4844 which is not enabled currently.
#[error("EIP-4844 transactions are disabled")]
Eip4844Disabled,
/// The transaction requires EIP-7702 which is not enabled currently.
#[error("EIP-7702 transactions are disabled")]
Eip7702Disabled,
/// Thrown if a transaction is not supported in the current network configuration.
#[error("transaction type not supported")]
TxTypeNotSupported,

View File

@ -4,6 +4,7 @@
use crate::compression::{TRANSACTION_COMPRESSOR, TRANSACTION_DECOMPRESSOR};
use crate::{keccak256, Address, BlockHashOrNumber, Bytes, TxHash, TxKind, B256, U256};
use alloy_eips::eip7702::SignedAuthorization;
use alloy_rlp::{
Decodable, Encodable, Error as RlpError, Header, EMPTY_LIST_CODE, EMPTY_STRING_CODE,
};
@ -19,6 +20,7 @@ pub use access_list::{AccessList, AccessListItem};
pub use eip1559::TxEip1559;
pub use eip2930::TxEip2930;
pub use eip4844::TxEip4844;
pub use eip7702::TxEip7702;
pub use error::{
InvalidTransactionError, TransactionConversionError, TryFromRecoveredTransactionError,
@ -35,7 +37,8 @@ pub use sidecar::{BlobTransaction, BlobTransactionSidecar};
pub use compat::FillTxEnv;
pub use signature::{extract_chain_id, Signature};
pub use tx_type::{
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
TxType, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
LEGACY_TX_TYPE_ID,
};
pub use variant::TransactionSignedVariant;
@ -44,6 +47,7 @@ mod compat;
mod eip1559;
mod eip2930;
mod eip4844;
mod eip7702;
mod error;
mod legacy;
mod meta;
@ -122,6 +126,12 @@ pub enum Transaction {
/// EIP-4844, also known as proto-danksharding, implements the framework and logic of
/// danksharding, introducing new transaction formats and verification rules.
Eip4844(TxEip4844),
/// EOA Set Code Transactions ([EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)), type `0x4`.
///
/// EOA Set Code Transactions give the ability to temporarily set contract code for an
/// EOA for a single transaction. This allows for temporarily adding smart contract
/// functionality to the EOA.
Eip7702(TxEip7702),
/// Optimism deposit transaction.
#[cfg(feature = "optimism")]
Deposit(TxDeposit),
@ -138,6 +148,7 @@ impl Transaction {
Self::Eip2930(tx) => tx.signature_hash(),
Self::Eip1559(tx) => tx.signature_hash(),
Self::Eip4844(tx) => tx.signature_hash(),
Self::Eip7702(tx) => tx.signature_hash(),
#[cfg(feature = "optimism")]
Self::Deposit(_) => B256::ZERO,
}
@ -149,7 +160,8 @@ impl Transaction {
Self::Legacy(TxLegacy { chain_id, .. }) => *chain_id,
Self::Eip2930(TxEip2930 { chain_id, .. }) |
Self::Eip1559(TxEip1559 { chain_id, .. }) |
Self::Eip4844(TxEip4844 { chain_id, .. }) => Some(*chain_id),
Self::Eip4844(TxEip4844 { chain_id, .. }) |
Self::Eip7702(TxEip7702 { chain_id, .. }) => Some(*chain_id),
#[cfg(feature = "optimism")]
Self::Deposit(_) => None,
}
@ -161,7 +173,8 @@ impl Transaction {
Self::Legacy(TxLegacy { chain_id: ref mut c, .. }) => *c = Some(chain_id),
Self::Eip2930(TxEip2930 { chain_id: ref mut c, .. }) |
Self::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) |
Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) => *c = chain_id,
Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) |
Self::Eip7702(TxEip7702 { chain_id: ref mut c, .. }) => *c = chain_id,
#[cfg(feature = "optimism")]
Self::Deposit(_) => { /* noop */ }
}
@ -173,7 +186,8 @@ impl Transaction {
match self {
Self::Legacy(TxLegacy { to, .. }) |
Self::Eip2930(TxEip2930 { to, .. }) |
Self::Eip1559(TxEip1559 { to, .. }) => *to,
Self::Eip1559(TxEip1559 { to, .. }) |
Self::Eip7702(TxEip7702 { to, .. }) => *to,
Self::Eip4844(TxEip4844 { to, .. }) => TxKind::Call(*to),
#[cfg(feature = "optimism")]
Self::Deposit(TxDeposit { to, .. }) => *to,
@ -195,6 +209,7 @@ impl Transaction {
Self::Eip2930(access_list_tx) => access_list_tx.tx_type(),
Self::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.tx_type(),
Self::Eip4844(blob_tx) => blob_tx.tx_type(),
Self::Eip7702(set_code_tx) => set_code_tx.tx_type(),
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => deposit_tx.tx_type(),
}
@ -206,7 +221,8 @@ impl Transaction {
Self::Legacy(TxLegacy { value, .. }) |
Self::Eip2930(TxEip2930 { value, .. }) |
Self::Eip1559(TxEip1559 { value, .. }) |
Self::Eip4844(TxEip4844 { value, .. }) => value,
Self::Eip4844(TxEip4844 { value, .. }) |
Self::Eip7702(TxEip7702 { value, .. }) => value,
#[cfg(feature = "optimism")]
Self::Deposit(TxDeposit { value, .. }) => value,
}
@ -218,7 +234,8 @@ impl Transaction {
Self::Legacy(TxLegacy { nonce, .. }) |
Self::Eip2930(TxEip2930 { nonce, .. }) |
Self::Eip1559(TxEip1559 { nonce, .. }) |
Self::Eip4844(TxEip4844 { nonce, .. }) => *nonce,
Self::Eip4844(TxEip4844 { nonce, .. }) |
Self::Eip7702(TxEip7702 { nonce, .. }) => *nonce,
// Deposit transactions do not have nonces.
#[cfg(feature = "optimism")]
Self::Deposit(_) => 0,
@ -234,18 +251,32 @@ impl Transaction {
Self::Eip2930(tx) => Some(&tx.access_list),
Self::Eip1559(tx) => Some(&tx.access_list),
Self::Eip4844(tx) => Some(&tx.access_list),
Self::Eip7702(tx) => Some(&tx.access_list),
#[cfg(feature = "optimism")]
Self::Deposit(_) => None,
}
}
/// Returns the [`SignedAuthorization`] list of the transaction.
///
/// Returns `None` if this transaction is not EIP-7702.
pub fn authorization_list(
&self,
) -> Option<&[SignedAuthorization<alloy_primitives::Signature>]> {
match self {
Self::Eip7702(tx) => Some(&tx.authorization_list),
_ => None,
}
}
/// Get the gas limit of the transaction.
pub const fn gas_limit(&self) -> u64 {
match self {
Self::Legacy(TxLegacy { gas_limit, .. }) |
Self::Eip2930(TxEip2930 { gas_limit, .. }) |
Self::Eip1559(TxEip1559 { gas_limit, .. }) |
Self::Eip4844(TxEip4844 { gas_limit, .. }) => *gas_limit,
Self::Eip4844(TxEip4844 { gas_limit, .. }) |
Self::Eip7702(TxEip7702 { gas_limit, .. }) => *gas_limit,
#[cfg(feature = "optimism")]
Self::Deposit(TxDeposit { gas_limit, .. }) => *gas_limit,
}
@ -255,7 +286,7 @@ impl Transaction {
pub const fn is_dynamic_fee(&self) -> bool {
match self {
Self::Legacy(_) | Self::Eip2930(_) => false,
Self::Eip1559(_) | Self::Eip4844(_) => true,
Self::Eip1559(_) | Self::Eip4844(_) | Self::Eip7702(_) => true,
#[cfg(feature = "optimism")]
Self::Deposit(_) => false,
}
@ -269,7 +300,8 @@ impl Transaction {
Self::Legacy(TxLegacy { gas_price, .. }) |
Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price,
Self::Eip1559(TxEip1559 { max_fee_per_gas, .. }) |
Self::Eip4844(TxEip4844 { max_fee_per_gas, .. }) => *max_fee_per_gas,
Self::Eip4844(TxEip4844 { max_fee_per_gas, .. }) |
Self::Eip7702(TxEip7702 { max_fee_per_gas, .. }) => *max_fee_per_gas,
// Deposit transactions buy their L2 gas on L1 and, as such, the L2 gas is not
// refundable.
#[cfg(feature = "optimism")]
@ -285,7 +317,8 @@ impl Transaction {
match self {
Self::Legacy(_) | Self::Eip2930(_) => None,
Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) |
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => {
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) |
Self::Eip7702(TxEip7702 { max_priority_fee_per_gas, .. }) => {
Some(*max_priority_fee_per_gas)
}
#[cfg(feature = "optimism")]
@ -293,13 +326,13 @@ impl Transaction {
}
}
/// Blob versioned hashes for eip4844 transaction, for legacy,eip1559 and eip2930 transactions
/// this is `None`
/// Blob versioned hashes for eip4844 transaction, for legacy, eip1559, eip2930 and eip7702
/// transactions this is `None`
///
/// This is also commonly referred to as the "blob versioned hashes" (`BlobVersionedHashes`).
pub fn blob_versioned_hashes(&self) -> Option<Vec<B256>> {
match self {
Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) => None,
Self::Legacy(_) | Self::Eip2930(_) | Self::Eip1559(_) | Self::Eip7702(_) => None,
Self::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => {
Some(blob_versioned_hashes.to_vec())
}
@ -341,7 +374,8 @@ impl Transaction {
Self::Legacy(TxLegacy { gas_price, .. }) |
Self::Eip2930(TxEip2930 { gas_price, .. }) => *gas_price,
Self::Eip1559(TxEip1559 { max_priority_fee_per_gas, .. }) |
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) => *max_priority_fee_per_gas,
Self::Eip4844(TxEip4844 { max_priority_fee_per_gas, .. }) |
Self::Eip7702(TxEip7702 { max_priority_fee_per_gas, .. }) => *max_priority_fee_per_gas,
#[cfg(feature = "optimism")]
Self::Deposit(_) => 0,
}
@ -356,6 +390,7 @@ impl Transaction {
Self::Eip2930(tx) => tx.gas_price,
Self::Eip1559(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
Self::Eip4844(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
Self::Eip7702(dynamic_tx) => dynamic_tx.effective_gas_price(base_fee),
#[cfg(feature = "optimism")]
Self::Deposit(_) => 0,
}
@ -398,7 +433,8 @@ impl Transaction {
Self::Legacy(TxLegacy { input, .. }) |
Self::Eip2930(TxEip2930 { input, .. }) |
Self::Eip1559(TxEip1559 { input, .. }) |
Self::Eip4844(TxEip4844 { input, .. }) => input,
Self::Eip4844(TxEip4844 { input, .. }) |
Self::Eip7702(TxEip7702 { input, .. }) => input,
#[cfg(feature = "optimism")]
Self::Deposit(TxDeposit { input, .. }) => input,
}
@ -466,6 +502,9 @@ impl Transaction {
dynamic_fee_tx.encode_with_signature(signature, out, with_header)
}
Self::Eip4844(blob_tx) => blob_tx.encode_with_signature(signature, out, with_header),
Self::Eip7702(set_code_tx) => {
set_code_tx.encode_with_signature(signature, out, with_header)
}
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => deposit_tx.encode(out, with_header),
}
@ -478,6 +517,7 @@ impl Transaction {
Self::Eip2930(tx) => tx.gas_limit = gas_limit,
Self::Eip1559(tx) => tx.gas_limit = gas_limit,
Self::Eip4844(tx) => tx.gas_limit = gas_limit,
Self::Eip7702(tx) => tx.gas_limit = gas_limit,
#[cfg(feature = "optimism")]
Self::Deposit(tx) => tx.gas_limit = gas_limit,
}
@ -490,6 +530,7 @@ impl Transaction {
Self::Eip2930(tx) => tx.nonce = nonce,
Self::Eip1559(tx) => tx.nonce = nonce,
Self::Eip4844(tx) => tx.nonce = nonce,
Self::Eip7702(tx) => tx.nonce = nonce,
#[cfg(feature = "optimism")]
Self::Deposit(_) => { /* noop */ }
}
@ -502,6 +543,7 @@ impl Transaction {
Self::Eip2930(tx) => tx.value = value,
Self::Eip1559(tx) => tx.value = value,
Self::Eip4844(tx) => tx.value = value,
Self::Eip7702(tx) => tx.value = value,
#[cfg(feature = "optimism")]
Self::Deposit(tx) => tx.value = value,
}
@ -514,6 +556,7 @@ impl Transaction {
Self::Eip2930(tx) => tx.input = input,
Self::Eip1559(tx) => tx.input = input,
Self::Eip4844(tx) => tx.input = input,
Self::Eip7702(tx) => tx.input = input,
#[cfg(feature = "optimism")]
Self::Deposit(tx) => tx.input = input,
}
@ -527,6 +570,7 @@ impl Transaction {
Self::Eip2930(tx) => tx.size(),
Self::Eip1559(tx) => tx.size(),
Self::Eip4844(tx) => tx.size(),
Self::Eip7702(tx) => tx.size(),
#[cfg(feature = "optimism")]
Self::Deposit(tx) => tx.size(),
}
@ -556,6 +600,12 @@ impl Transaction {
matches!(self, Self::Eip4844(_))
}
/// Returns true if the transaction is an EIP-7702 transaction.
#[inline]
pub const fn is_eip7702(&self) -> bool {
matches!(self, Self::Eip7702(_))
}
/// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction.
pub const fn as_legacy(&self) -> Option<&TxLegacy> {
match self {
@ -587,6 +637,14 @@ impl Transaction {
_ => None,
}
}
/// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
match self {
Self::Eip7702(tx) => Some(tx),
_ => None,
}
}
}
impl From<TxLegacy> for Transaction {
@ -613,6 +671,12 @@ impl From<TxEip4844> for Transaction {
}
}
impl From<TxEip7702> for Transaction {
fn from(tx: TxEip7702) -> Self {
Self::Eip7702(tx)
}
}
impl Compact for Transaction {
// Serializes the TxType to the buffer if necessary, returning 2 bits of the type as an
// identifier instead of the length.
@ -634,6 +698,9 @@ impl Compact for Transaction {
Self::Eip4844(tx) => {
tx.to_compact(buf);
}
Self::Eip7702(tx) => {
tx.to_compact(buf);
}
#[cfg(feature = "optimism")]
Self::Deposit(tx) => {
tx.to_compact(buf);
@ -676,6 +743,10 @@ impl Compact for Transaction {
let (tx, buf) = TxEip4844::from_compact(buf, buf.len());
(Self::Eip4844(tx), buf)
}
4 => {
let (tx, buf) = TxEip7702::from_compact(buf, buf.len());
(Self::Eip7702(tx), buf)
}
#[cfg(feature = "optimism")]
126 => {
let (tx, buf) = TxDeposit::from_compact(buf, buf.len());
@ -712,6 +783,9 @@ impl Encodable for Transaction {
Self::Eip4844(blob_tx) => {
blob_tx.encode_for_signing(out);
}
Self::Eip7702(set_code_tx) => {
set_code_tx.encode_for_signing(out);
}
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => {
deposit_tx.encode(out, true);
@ -725,6 +799,7 @@ impl Encodable for Transaction {
Self::Eip2930(access_list_tx) => access_list_tx.payload_len_for_signature(),
Self::Eip1559(dynamic_fee_tx) => dynamic_fee_tx.payload_len_for_signature(),
Self::Eip4844(blob_tx) => blob_tx.payload_len_for_signature(),
Self::Eip7702(set_code_tx) => set_code_tx.payload_len_for_signature(),
#[cfg(feature = "optimism")]
Self::Deposit(deposit_tx) => deposit_tx.payload_len(),
}
@ -1161,6 +1236,9 @@ impl TransactionSigned {
dynamic_fee_tx.payload_len_with_signature(&self.signature)
}
Transaction::Eip4844(blob_tx) => blob_tx.payload_len_with_signature(&self.signature),
Transaction::Eip7702(set_code_tx) => {
set_code_tx.payload_len_with_signature(&self.signature)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len(),
}
@ -1250,7 +1328,7 @@ impl TransactionSigned {
Ok(signed)
}
/// Decodes en enveloped EIP-2718 typed transaction.
/// Decodes an enveloped EIP-2718 typed transaction.
///
/// This should _only_ be used internally in general transaction decoding methods,
/// which have already ensured that the input is a typed transaction with the following format:
@ -1287,6 +1365,7 @@ impl TransactionSigned {
TxType::Eip2930 => Transaction::Eip2930(TxEip2930::decode_inner(data)?),
TxType::Eip1559 => Transaction::Eip1559(TxEip1559::decode_inner(data)?),
TxType::Eip4844 => Transaction::Eip4844(TxEip4844::decode_inner(data)?),
TxType::Eip7702 => Transaction::Eip7702(TxEip7702::decode_inner(data)?),
#[cfg(feature = "optimism")]
TxType::Deposit => Transaction::Deposit(TxDeposit::decode_inner(data)?),
TxType::Legacy => return Err(RlpError::Custom("unexpected legacy tx type")),
@ -1363,6 +1442,9 @@ impl TransactionSigned {
Transaction::Eip4844(blob_tx) => {
blob_tx.payload_len_with_signature_without_header(&self.signature)
}
Transaction::Eip7702(set_code_tx) => {
set_code_tx.payload_len_with_signature_without_header(&self.signature)
}
#[cfg(feature = "optimism")]
Transaction::Deposit(deposit_tx) => deposit_tx.payload_len_without_header(),
}

View File

@ -1,7 +1,7 @@
//! Defines the types for blob transactions, legacy, and other EIP-2718 transactions included in a
//! response to `GetPooledTransactions`.
use super::error::TransactionConversionError;
use super::{error::TransactionConversionError, TxEip7702};
use crate::{
Address, BlobTransaction, BlobTransactionSidecar, Bytes, Signature, Transaction,
TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, TxEip4844, TxHash,
@ -48,6 +48,15 @@ pub enum PooledTransactionsElement {
/// The hash of the transaction
hash: TxHash,
},
/// An EIP-7702 typed transaction
Eip7702 {
/// The inner transaction
transaction: TxEip7702,
/// The signature
signature: Signature,
/// The hash of the transaction
hash: TxHash,
},
/// A blob transaction, which includes the transaction, blob data, commitments, and proofs.
BlobTransaction(BlobTransaction),
}
@ -69,6 +78,9 @@ impl PooledTransactionsElement {
TransactionSigned { transaction: Transaction::Eip1559(tx), signature, hash } => {
Ok(Self::Eip1559 { transaction: tx, signature, hash })
}
TransactionSigned { transaction: Transaction::Eip7702(tx), signature, hash } => {
Ok(Self::Eip7702 { transaction: tx, signature, hash })
}
// Not supported because missing blob sidecar
tx @ TransactionSigned { transaction: Transaction::Eip4844(_), .. } => Err(tx),
#[cfg(feature = "optimism")]
@ -105,6 +117,7 @@ impl PooledTransactionsElement {
Self::Legacy { transaction, .. } => transaction.signature_hash(),
Self::Eip2930 { transaction, .. } => transaction.signature_hash(),
Self::Eip1559 { transaction, .. } => transaction.signature_hash(),
Self::Eip7702 { transaction, .. } => transaction.signature_hash(),
Self::BlobTransaction(blob_tx) => blob_tx.transaction.signature_hash(),
}
}
@ -112,9 +125,10 @@ impl PooledTransactionsElement {
/// Reference to transaction hash. Used to identify transaction.
pub const fn hash(&self) -> &TxHash {
match self {
Self::Legacy { hash, .. } | Self::Eip2930 { hash, .. } | Self::Eip1559 { hash, .. } => {
hash
}
Self::Legacy { hash, .. } |
Self::Eip2930 { hash, .. } |
Self::Eip1559 { hash, .. } |
Self::Eip7702 { hash, .. } => hash,
Self::BlobTransaction(tx) => &tx.hash,
}
}
@ -124,7 +138,8 @@ impl PooledTransactionsElement {
match self {
Self::Legacy { signature, .. } |
Self::Eip2930 { signature, .. } |
Self::Eip1559 { signature, .. } => signature,
Self::Eip1559 { signature, .. } |
Self::Eip7702 { signature, .. } => signature,
Self::BlobTransaction(blob_tx) => &blob_tx.signature,
}
}
@ -135,6 +150,7 @@ impl PooledTransactionsElement {
Self::Legacy { transaction, .. } => transaction.nonce,
Self::Eip2930 { transaction, .. } => transaction.nonce,
Self::Eip1559 { transaction, .. } => transaction.nonce,
Self::Eip7702 { transaction, .. } => transaction.nonce,
Self::BlobTransaction(blob_tx) => blob_tx.transaction.nonce,
}
}
@ -238,6 +254,11 @@ impl PooledTransactionsElement {
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
Transaction::Eip7702(tx) => {Ok(Self::Eip7702 {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
})},
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement"))
}
@ -267,6 +288,11 @@ impl PooledTransactionsElement {
signature,
hash,
},
Self::Eip7702 { transaction, signature, hash } => TransactionSigned {
transaction: Transaction::Eip7702(transaction),
signature,
hash,
},
Self::BlobTransaction(blob_tx) => blob_tx.into_parts().0,
}
}
@ -286,6 +312,10 @@ impl PooledTransactionsElement {
// method computes the payload len without a RLP header
transaction.payload_len_with_signature_without_header(signature)
}
Self::Eip7702 { transaction, signature, .. } => {
// method computes the payload len without a RLP header
transaction.payload_len_with_signature_without_header(signature)
}
Self::BlobTransaction(blob_tx) => {
// the encoding does not use a header, so we set `with_header` to false
blob_tx.payload_len_with_type(false)
@ -316,6 +346,7 @@ impl PooledTransactionsElement {
// - EIP-2930: TxEip2930::encode_with_signature
// - EIP-1559: TxEip1559::encode_with_signature
// - EIP-4844: BlobTransaction::encode_with_type_inner
// - EIP-7702: TxEip7702::encode_with_signature
match self {
Self::Legacy { transaction, signature, .. } => {
transaction.encode_with_signature(signature, out)
@ -326,6 +357,9 @@ impl PooledTransactionsElement {
Self::Eip1559 { transaction, signature, .. } => {
transaction.encode_with_signature(signature, out, false)
}
Self::Eip7702 { transaction, signature, .. } => {
transaction.encode_with_signature(signature, out, false)
}
Self::BlobTransaction(blob_tx) => {
// The inner encoding is used with `with_header` set to true, making the final
// encoding:
@ -373,6 +407,14 @@ impl PooledTransactionsElement {
}
}
/// Returns the [`TxEip7702`] variant if the transaction is an EIP-7702 transaction.
pub const fn as_eip7702(&self) -> Option<&TxEip7702> {
match self {
Self::Eip7702 { transaction, .. } => Some(transaction),
_ => None,
}
}
/// Returns the blob gas used for all blobs of the EIP-4844 transaction if it is an EIP-4844
/// transaction.
///
@ -402,6 +444,7 @@ impl PooledTransactionsElement {
match self {
Self::Legacy { .. } | Self::Eip2930 { .. } => None,
Self::Eip1559 { transaction, .. } => Some(transaction.max_priority_fee_per_gas),
Self::Eip7702 { transaction, .. } => Some(transaction.max_priority_fee_per_gas),
Self::BlobTransaction(tx) => Some(tx.transaction.max_priority_fee_per_gas),
}
}
@ -414,6 +457,7 @@ impl PooledTransactionsElement {
Self::Legacy { transaction, .. } => transaction.gas_price,
Self::Eip2930 { transaction, .. } => transaction.gas_price,
Self::Eip1559 { transaction, .. } => transaction.max_fee_per_gas,
Self::Eip7702 { transaction, .. } => transaction.max_fee_per_gas,
Self::BlobTransaction(tx) => tx.transaction.max_fee_per_gas,
}
}
@ -433,6 +477,7 @@ impl Encodable for PooledTransactionsElement {
// - EIP-2930: TxEip2930::encode_with_signature
// - EIP-1559: TxEip1559::encode_with_signature
// - EIP-4844: BlobTransaction::encode_with_type_inner
// - EIP-7702: TxEip7702::encode_with_signature
match self {
Self::Legacy { transaction, signature, .. } => {
transaction.encode_with_signature(signature, out)
@ -445,6 +490,10 @@ impl Encodable for PooledTransactionsElement {
// encodes with string header
transaction.encode_with_signature(signature, out, true)
}
Self::Eip7702 { transaction, signature, .. } => {
// encodes with string header
transaction.encode_with_signature(signature, out, true)
}
Self::BlobTransaction(blob_tx) => {
// The inner encoding is used with `with_header` set to true, making the final
// encoding:
@ -468,6 +517,10 @@ impl Encodable for PooledTransactionsElement {
// method computes the payload len with a RLP header
transaction.payload_len_with_signature(signature)
}
Self::Eip7702 { transaction, signature, .. } => {
// method computes the payload len with a RLP header
transaction.payload_len_with_signature(signature)
}
Self::BlobTransaction(blob_tx) => {
// the encoding uses a header, so we set `with_header` to true
blob_tx.payload_len_with_type(true)
@ -573,6 +626,11 @@ impl Decodable for PooledTransactionsElement {
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
Transaction::Eip7702(tx) => Ok(Self::Eip7702 {
transaction: tx,
signature: typed_tx.signature,
hash: typed_tx.hash,
}),
#[cfg(feature = "optimism")]
Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement"))
}

View File

@ -25,6 +25,9 @@ pub struct Signature {
/// The S field of the signature; the point on the curve.
pub s: U256,
/// yParity: Signature Y parity; formally Ty
///
/// WARNING: if it's deprecated in favor of `alloy_primitives::Signature` be sure that parity
/// storage deser matches.
pub odd_y_parity: bool,
}

View File

@ -17,6 +17,9 @@ pub const EIP1559_TX_TYPE_ID: u8 = 2;
/// Identifier for [`TxEip4844`](crate::TxEip4844) transaction.
pub const EIP4844_TX_TYPE_ID: u8 = 3;
/// Identifier for [`TxEip7702`](crate::TxEip7702) transaction.
pub const EIP7702_TX_TYPE_ID: u8 = 4;
/// Identifier for [`TxDeposit`](crate::TxDeposit) transaction.
#[cfg(feature = "optimism")]
pub const DEPOSIT_TX_TYPE_ID: u8 = 126;
@ -42,6 +45,8 @@ pub enum TxType {
Eip1559 = 2_isize,
/// Shard Blob Transactions - EIP-4844
Eip4844 = 3_isize,
/// EOA Contract Code Transactions - EIP-7702
Eip7702 = 4_isize,
/// Optimism Deposit transaction.
#[cfg(feature = "optimism")]
Deposit = 126_isize,
@ -49,13 +54,13 @@ pub enum TxType {
impl TxType {
/// The max type reserved by an EIP.
pub const MAX_RESERVED_EIP: Self = Self::Eip4844;
pub const MAX_RESERVED_EIP: Self = Self::Eip7702;
/// Check if the transaction type has an access list.
pub const fn has_access_list(&self) -> bool {
match self {
Self::Legacy => false,
Self::Eip2930 | Self::Eip1559 | Self::Eip4844 => true,
Self::Eip2930 | Self::Eip1559 | Self::Eip4844 | Self::Eip7702 => true,
#[cfg(feature = "optimism")]
Self::Deposit => false,
}
@ -69,6 +74,7 @@ impl From<TxType> for u8 {
TxType::Eip2930 => EIP2930_TX_TYPE_ID,
TxType::Eip1559 => EIP1559_TX_TYPE_ID,
TxType::Eip4844 => EIP4844_TX_TYPE_ID,
TxType::Eip7702 => EIP7702_TX_TYPE_ID,
#[cfg(feature = "optimism")]
TxType::Deposit => DEPOSIT_TX_TYPE_ID,
}
@ -98,6 +104,8 @@ impl TryFrom<u8> for TxType {
return Ok(Self::Eip1559)
} else if value == Self::Eip4844 {
return Ok(Self::Eip4844)
} else if value == Self::Eip7702 {
return Ok(Self::Eip7702)
}
Err("invalid tx type")
@ -137,6 +145,10 @@ impl Compact for TxType {
buf.put_u8(self as u8);
3
}
Self::Eip7702 => {
buf.put_u8(self as u8);
3
}
#[cfg(feature = "optimism")]
Self::Deposit => {
buf.put_u8(self as u8);
@ -158,6 +170,7 @@ impl Compact for TxType {
let extended_identifier = buf.get_u8();
match extended_identifier {
EIP4844_TX_TYPE_ID => Self::Eip4844,
EIP7702_TX_TYPE_ID => Self::Eip7702,
#[cfg(feature = "optimism")]
DEPOSIT_TX_TYPE_ID => Self::Deposit,
_ => panic!("Unsupported TxType identifier: {extended_identifier}"),
@ -222,12 +235,15 @@ mod tests {
// Test for EIP4844 transaction
assert_eq!(TxType::try_from(U64::from(3)).unwrap(), TxType::Eip4844);
// Test for EIP7702 transaction
assert_eq!(TxType::try_from(U64::from(4)).unwrap(), TxType::Eip7702);
// Test for Deposit transaction
#[cfg(feature = "optimism")]
assert_eq!(TxType::try_from(U64::from(126)).unwrap(), TxType::Deposit);
// For transactions with unsupported values
assert!(TxType::try_from(U64::from(4)).is_err());
assert!(TxType::try_from(U64::from(5)).is_err());
}
#[test]
@ -237,6 +253,7 @@ mod tests {
(TxType::Eip2930, 1, vec![]),
(TxType::Eip1559, 2, vec![]),
(TxType::Eip4844, 3, vec![EIP4844_TX_TYPE_ID]),
(TxType::Eip7702, 3, vec![EIP7702_TX_TYPE_ID]),
#[cfg(feature = "optimism")]
(TxType::Deposit, 3, vec![DEPOSIT_TX_TYPE_ID]),
];
@ -259,6 +276,7 @@ mod tests {
(TxType::Eip2930, 1, vec![]),
(TxType::Eip1559, 2, vec![]),
(TxType::Eip4844, 3, vec![EIP4844_TX_TYPE_ID]),
(TxType::Eip7702, 3, vec![EIP7702_TX_TYPE_ID]),
#[cfg(feature = "optimism")]
(TxType::Deposit, 3, vec![DEPOSIT_TX_TYPE_ID]),
];
@ -291,6 +309,10 @@ mod tests {
let tx_type = TxType::decode(&mut &[3u8][..]).unwrap();
assert_eq!(tx_type, TxType::Eip4844);
// Test for EIP7702 transaction
let tx_type = TxType::decode(&mut &[4u8][..]).unwrap();
assert_eq!(tx_type, TxType::Eip7702);
// Test random byte not in range
let buf = [rand::thread_rng().gen_range(4..=u8::MAX)];
println!("{buf:?}");

View File

@ -54,4 +54,4 @@ optimism = [
"revm/optimism",
"reth-provider/optimism",
"reth-rpc-eth-types/optimism"
]
]

View File

@ -807,6 +807,7 @@ pub trait Call: LoadState + SpawnBlocking {
chain_id,
blob_versioned_hashes,
max_fee_per_blob_gas,
// authorization_list,
..
} = request;
@ -838,7 +839,9 @@ pub trait Call: LoadState + SpawnBlocking {
// EIP-4844 fields
blob_hashes: blob_versioned_hashes.unwrap_or_default(),
max_fee_per_blob_gas,
// EIP-7702 fields
authorization_list: None,
// authorization_list: TODO
#[cfg(feature = "optimism")]
optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() },
};

View File

@ -498,6 +498,7 @@ impl From<reth_primitives::InvalidTransactionError> for RpcInvalidTransactionErr
InvalidTransactionError::Eip2930Disabled |
InvalidTransactionError::Eip1559Disabled |
InvalidTransactionError::Eip4844Disabled |
InvalidTransactionError::Eip7702Disabled |
InvalidTransactionError::TxTypeNotSupported => Self::TxTypeNotSupported,
InvalidTransactionError::GasUintOverflow => Self::GasUintOverflow,
InvalidTransactionError::GasTooLow => Self::GasTooLow,

View File

@ -73,6 +73,7 @@ fn fill(
let chain_id = signed_tx.chain_id();
let blob_versioned_hashes = signed_tx.blob_versioned_hashes();
let access_list = signed_tx.access_list().cloned();
let _authorization_list = signed_tx.authorization_list();
let signature =
from_primitive_signature(*signed_tx.signature(), signed_tx.tx_type(), signed_tx.chain_id());
@ -124,6 +125,7 @@ pub fn transaction_to_call_request(tx: TransactionSignedEcRecovered) -> Transact
let chain_id = tx.transaction.chain_id();
let access_list = tx.transaction.access_list().cloned();
let max_fee_per_blob_gas = tx.transaction.max_fee_per_blob_gas();
let _authorization_list = tx.transaction.authorization_list();
let blob_versioned_hashes = tx.transaction.blob_versioned_hashes();
let tx_type = tx.transaction.tx_type();

View File

@ -31,6 +31,7 @@ alloy-eips = { workspace = true, default-features = false, features = [
"serde",
] }
alloy-primitives = { workspace = true, features = ["arbitrary", "serde"] }
alloy-consensus = { workspace = true, features = ["arbitrary"] }
test-fuzz.workspace = true
serde_json.workspace = true

View File

@ -0,0 +1,96 @@
use crate::Compact;
use alloy_eips::eip7702::{Authorization as AlloyAuthorization, SignedAuthorization};
use alloy_primitives::{Address, ChainId, U256};
use bytes::Buf;
use reth_codecs_derive::main_codec;
/// Authorization acts as bridge which simplifies Compact implementation for AlloyAuthorization.
///
/// Notice: Make sure this struct is 1:1 with `alloy_eips::eip7702::Authorization`
#[main_codec]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
struct Authorization {
chain_id: ChainId,
address: Address,
nonce: Option<u64>,
}
impl Compact for AlloyAuthorization {
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
let authorization =
Authorization { chain_id: self.chain_id, address: self.address, nonce: self.nonce() };
authorization.to_compact(buf)
}
fn from_compact(buf: &[u8], len: usize) -> (Self, &[u8]) {
let (authorization, buf) = Authorization::from_compact(buf, len);
let alloy_authorization = Self {
chain_id: authorization.chain_id,
address: authorization.address,
nonce: authorization.nonce.into(),
};
(alloy_authorization, buf)
}
}
impl Compact for SignedAuthorization<alloy_primitives::Signature> {
fn to_compact<B>(self, buf: &mut B) -> usize
where
B: bytes::BufMut + AsMut<[u8]>,
{
let (auth, signature) = self.into_parts();
let (v, r, s) = (signature.v(), signature.r(), signature.s());
buf.put_u8(v.y_parity_byte());
buf.put_slice(r.as_le_slice());
buf.put_slice(s.as_le_slice());
// to_compact doesn't write the len to buffer.
// By placing it as last, we don't need to store it either.
1 + 32 + 32 + auth.to_compact(buf)
}
fn from_compact(mut buf: &[u8], len: usize) -> (Self, &[u8]) {
let y = alloy_primitives::Parity::Parity(buf.get_u8() == 1);
let r = U256::from_le_slice(&buf[0..32]);
buf.advance(32);
let s = U256::from_le_slice(&buf[0..32]);
buf.advance(32);
let signature = alloy_primitives::Signature::from_rs_and_parity(r, s, y)
.expect("invalid authorization signature");
let (auth, buf) = AlloyAuthorization::from_compact(buf, len);
(auth.into_signed(signature), buf)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{address, b256};
#[test]
fn test_roundtrip_compact_authorization_list_item() {
let authorization = AlloyAuthorization {
chain_id: 1,
address: address!("dac17f958d2ee523a2206206994597c13d831ec7"),
nonce: None.into(),
}
.into_signed(
alloy_primitives::Signature::from_rs_and_parity(
b256!("1fd474b1f9404c0c5df43b7620119ffbc3a1c3f942c73b6e14e9f55255ed9b1d").into(),
b256!("29aca24813279a901ec13b5f7bb53385fa1fc627b946592221417ff74a49600d").into(),
false,
)
.unwrap(),
);
let mut compacted_authorization = Vec::<u8>::new();
let len = authorization.clone().to_compact(&mut compacted_authorization);
let (decoded_authorization, _) =
SignedAuthorization::from_compact(&compacted_authorization, len);
assert_eq!(authorization, decoded_authorization);
}
}

View File

@ -1,4 +1,5 @@
mod access_list;
mod authorization_list;
mod genesis_account;
mod log;
mod request;

View File

@ -241,7 +241,8 @@ impl InvalidPoolTransactionError {
}
InvalidTransactionError::Eip2930Disabled |
InvalidTransactionError::Eip1559Disabled |
InvalidTransactionError::Eip4844Disabled => {
InvalidTransactionError::Eip4844Disabled |
InvalidTransactionError::Eip7702Disabled => {
// settings
false
}

View File

@ -749,6 +749,10 @@ impl EthPoolTransaction for MockTransaction {
_ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
}
}
fn authorization_count(&self) -> usize {
0
}
}
impl TryFromRecoveredTransaction for MockTransaction {

View File

@ -14,7 +14,7 @@ use reth_primitives::{
BlobTransactionSidecar, BlobTransactionValidationError, FromRecoveredPooledTransaction,
IntoRecoveredTransaction, PooledTransactionsElement, PooledTransactionsElementEcRecovered,
SealedBlock, Transaction, TransactionSignedEcRecovered, TryFromRecoveredTransaction, TxHash,
TxKind, B256, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, U256,
TxKind, B256, EIP1559_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, U256,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@ -842,6 +842,11 @@ pub trait PoolTransaction:
self.tx_type() == EIP4844_TX_TYPE_ID
}
/// Returns true if the transaction is an EIP-7702 transaction.
fn is_eip7702(&self) -> bool {
self.tx_type() == EIP7702_TX_TYPE_ID
}
/// Returns the length of the rlp encoded transaction object
///
/// Note: Implementations should cache this value.
@ -866,6 +871,9 @@ pub trait EthPoolTransaction: PoolTransaction {
blob: &BlobTransactionSidecar,
settings: &KzgSettings,
) -> Result<(), BlobTransactionValidationError>;
/// Returns the number of authorizations this transaction has.
fn authorization_count(&self) -> usize;
}
/// The default [`PoolTransaction`] for the [Pool](crate::Pool) for Ethereum.
@ -938,6 +946,9 @@ impl EthPooledTransaction {
blob_sidecar = EthBlobTransactionSidecar::Missing;
U256::from(t.max_fee_per_gas).saturating_mul(U256::from(t.gas_limit))
}
Transaction::Eip7702(t) => {
U256::from(t.max_fee_per_gas).saturating_mul(U256::from(t.gas_limit))
}
_ => U256::ZERO,
};
let mut cost = transaction.value();
@ -1024,6 +1035,7 @@ impl PoolTransaction for EthPooledTransaction {
Transaction::Eip2930(tx) => tx.gas_price,
Transaction::Eip1559(tx) => tx.max_fee_per_gas,
Transaction::Eip4844(tx) => tx.max_fee_per_gas,
Transaction::Eip7702(tx) => tx.max_fee_per_gas,
_ => 0,
}
}
@ -1041,6 +1053,7 @@ impl PoolTransaction for EthPooledTransaction {
Transaction::Legacy(_) | Transaction::Eip2930(_) => None,
Transaction::Eip1559(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::Eip4844(tx) => Some(tx.max_priority_fee_per_gas),
Transaction::Eip7702(tx) => Some(tx.max_priority_fee_per_gas),
_ => None,
}
}
@ -1120,6 +1133,13 @@ impl EthPoolTransaction for EthPooledTransaction {
_ => Err(BlobTransactionValidationError::NotBlobTransaction(self.tx_type())),
}
}
fn authorization_count(&self) -> usize {
match &self.transaction.transaction {
Transaction::Eip7702(tx) => tx.authorization_list.len(),
_ => 0,
}
}
}
impl TryFromRecoveredTransaction for EthPooledTransaction {
@ -1130,7 +1150,7 @@ impl TryFromRecoveredTransaction for EthPooledTransaction {
) -> Result<Self, Self::Error> {
// ensure we can handle the transaction type and its format
match tx.tx_type() as u8 {
0..=EIP1559_TX_TYPE_ID => {
0..=EIP1559_TX_TYPE_ID | EIP7702_TX_TYPE_ID => {
// supported
}
EIP4844_TX_TYPE_ID => {

View File

@ -12,14 +12,14 @@ use crate::{
use reth_chainspec::{ChainSpec, EthereumHardforks};
use reth_primitives::{
constants::{eip4844::MAX_BLOBS_PER_BLOCK, ETHEREUM_BLOCK_GAS_LIMIT},
GotExpected, InvalidTransactionError, SealedBlock, TxKind, EIP1559_TX_TYPE_ID,
EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
GotExpected, InvalidTransactionError, SealedBlock, EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID,
EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID, LEGACY_TX_TYPE_ID,
};
use reth_provider::{AccountReader, BlockReaderIdExt, StateProviderFactory};
use reth_tasks::TaskSpawner;
use revm::{
interpreter::gas::validate_initial_tx_gas,
primitives::{AccessListItem, EnvKzgSettings, SpecId},
primitives::{EnvKzgSettings, SpecId},
};
use std::{
marker::PhantomData,
@ -119,6 +119,8 @@ pub(crate) struct EthTransactionValidatorInner<Client, T> {
eip1559: bool,
/// Fork indicator whether we are using EIP-4844 blob transactions.
eip4844: bool,
/// Fork indicator whether we are using EIP-7702 type transactions.
eip7702: bool,
/// The current max gas limit
block_gas_limit: u64,
/// Minimum priority fee to enforce for acceptance into the pool.
@ -185,6 +187,15 @@ where
)
}
}
EIP7702_TX_TYPE_ID => {
// Reject EIP-7702 transactions.
if !self.eip7702 {
return TransactionValidationOutcome::Invalid(
transaction,
InvalidTransactionError::Eip7702Disabled.into(),
)
}
}
_ => {
return TransactionValidationOutcome::Invalid(
@ -255,9 +266,17 @@ where
}
}
// intrinsic gas checks
let is_shanghai = self.fork_tracker.is_shanghai_activated();
if let Err(err) = ensure_intrinsic_gas(&transaction, is_shanghai) {
if transaction.is_eip7702() {
// Cancun fork is required for 7702 txs
if !self.fork_tracker.is_prague_activated() {
return TransactionValidationOutcome::Invalid(
transaction,
InvalidTransactionError::TxTypeNotSupported.into(),
)
}
}
if let Err(err) = ensure_intrinsic_gas(&transaction, &self.fork_tracker) {
return TransactionValidationOutcome::Invalid(transaction, err)
}
@ -407,6 +426,10 @@ where
if self.chain_spec.is_shanghai_active_at_timestamp(new_tip_block.timestamp) {
self.fork_tracker.shanghai.store(true, std::sync::atomic::Ordering::Relaxed);
}
if self.chain_spec.is_prague_active_at_timestamp(new_tip_block.timestamp) {
self.fork_tracker.prague.store(true, std::sync::atomic::Ordering::Relaxed);
}
}
}
@ -418,12 +441,16 @@ pub struct EthTransactionValidatorBuilder {
shanghai: bool,
/// Fork indicator whether we are in the Cancun hardfork.
cancun: bool,
/// Fork indicator whether we are in the Cancun hardfork.
prague: bool,
/// Whether using EIP-2718 type transactions is allowed
eip2718: bool,
/// Whether using EIP-1559 type transactions is allowed
eip1559: bool,
/// Whether using EIP-4844 type transactions is allowed
eip4844: bool,
/// Whether using EIP-7702 type transactions is allowed
eip7702: bool,
/// The current max gas limit
block_gas_limit: u64,
/// Minimum priority fee to enforce for acceptance into the pool.
@ -464,12 +491,16 @@ impl EthTransactionValidatorBuilder {
eip2718: true,
eip1559: true,
eip4844: true,
eip7702: true,
// shanghai is activated by default
shanghai: true,
// cancun is activated by default
cancun: true,
// prague not yet activated
prague: false,
}
}
@ -504,6 +535,17 @@ impl EthTransactionValidatorBuilder {
self
}
/// Disables the Prague fork.
pub const fn no_prague(self) -> Self {
self.set_prague(false)
}
/// Set the Prague fork.
pub const fn set_prague(mut self, prague: bool) -> Self {
self.prague = prague;
self
}
/// Disables the support for EIP-2718 transactions.
pub const fn no_eip2718(self) -> Self {
self.set_eip2718(false)
@ -591,9 +633,11 @@ impl EthTransactionValidatorBuilder {
chain_spec,
shanghai,
cancun,
prague,
eip2718,
eip1559,
eip4844,
eip7702,
block_gas_limit,
minimum_priority_fee,
kzg_settings,
@ -602,8 +646,11 @@ impl EthTransactionValidatorBuilder {
..
} = self;
let fork_tracker =
ForkTracker { shanghai: AtomicBool::new(shanghai), cancun: AtomicBool::new(cancun) };
let fork_tracker = ForkTracker {
shanghai: AtomicBool::new(shanghai),
cancun: AtomicBool::new(cancun),
prague: AtomicBool::new(prague),
};
let inner = EthTransactionValidatorInner {
chain_spec,
@ -612,6 +659,7 @@ impl EthTransactionValidatorBuilder {
eip1559,
fork_tracker,
eip4844,
eip7702,
block_gas_limit,
minimum_priority_fee,
blob_store: Box::new(blob_store),
@ -670,23 +718,30 @@ impl EthTransactionValidatorBuilder {
/// Keeps track of whether certain forks are activated
#[derive(Debug)]
pub(crate) struct ForkTracker {
pub struct ForkTracker {
/// Tracks if shanghai is activated at the block's timestamp.
pub(crate) shanghai: AtomicBool,
pub shanghai: AtomicBool,
/// Tracks if cancun is activated at the block's timestamp.
pub(crate) cancun: AtomicBool,
pub cancun: AtomicBool,
/// Tracks if prague is activated at the block's timestamp.
pub prague: AtomicBool,
}
impl ForkTracker {
/// Returns `true` if Shanghai fork is activated.
pub(crate) fn is_shanghai_activated(&self) -> bool {
pub fn is_shanghai_activated(&self) -> bool {
self.shanghai.load(std::sync::atomic::Ordering::Relaxed)
}
/// Returns `true` if Cancun fork is activated.
pub(crate) fn is_cancun_activated(&self) -> bool {
pub fn is_cancun_activated(&self) -> bool {
self.cancun.load(std::sync::atomic::Ordering::Relaxed)
}
/// Returns `true` if Prague fork is activated.
pub fn is_prague_activated(&self) -> bool {
self.prague.load(std::sync::atomic::Ordering::Relaxed)
}
}
/// Ensure that the code size is not greater than `max_init_code_size`.
@ -707,39 +762,34 @@ pub fn ensure_max_init_code_size<T: PoolTransaction>(
/// Ensures that gas limit of the transaction exceeds the intrinsic gas of the transaction.
///
/// See also [`calculate_intrinsic_gas_after_merge`]
pub fn ensure_intrinsic_gas<T: PoolTransaction>(
/// Caution: This only checks past the Merge hardfork.
pub fn ensure_intrinsic_gas<T: EthPoolTransaction>(
transaction: &T,
is_shanghai: bool,
fork_tracker: &ForkTracker,
) -> Result<(), InvalidPoolTransactionError> {
if transaction.gas_limit() <
calculate_intrinsic_gas_after_merge(
transaction.input(),
&transaction.kind(),
transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]),
is_shanghai,
)
{
let spec_id = if fork_tracker.is_prague_activated() {
SpecId::PRAGUE
} else if fork_tracker.is_shanghai_activated() {
SpecId::SHANGHAI
} else {
SpecId::MERGE
};
let gas_after_merge = validate_initial_tx_gas(
spec_id,
transaction.input(),
transaction.kind().is_create(),
transaction.access_list().map(|list| list.0.as_slice()).unwrap_or(&[]),
transaction.authorization_count() as u64,
);
if transaction.gas_limit() < gas_after_merge {
Err(InvalidPoolTransactionError::IntrinsicGasTooLow)
} else {
Ok(())
}
}
/// Calculates the Intrinsic Gas usage for a Transaction
///
/// Caution: This only checks past the Merge hardfork.
#[inline]
pub fn calculate_intrinsic_gas_after_merge(
input: &[u8],
kind: &TxKind,
access_list: &[AccessListItem],
is_shanghai: bool,
) -> u64 {
let spec_id = if is_shanghai { SpecId::SHANGHAI } else { SpecId::MERGE };
validate_initial_tx_gas(spec_id, input, kind.is_create(), access_list, 0)
}
#[cfg(test)]
mod tests {
use super::*;
@ -764,10 +814,14 @@ mod tests {
#[tokio::test]
async fn validate_transaction() {
let transaction = get_transaction();
let mut fork_tracker =
ForkTracker { shanghai: false.into(), cancun: false.into(), prague: false.into() };
let res = ensure_intrinsic_gas(&transaction, false);
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
assert!(res.is_ok());
let res = ensure_intrinsic_gas(&transaction, true);
fork_tracker.shanghai = true.into();
let res = ensure_intrinsic_gas(&transaction, &fork_tracker);
assert!(res.is_ok());
let provider = MockEthProvider::default();