Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
270 changes: 18 additions & 252 deletions crates/anvil/core/src/eth/transaction/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Transaction related types
use alloy_consensus::{
Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TxEip1559, TxEip2930,
TxEnvelope, TxLegacy, TxReceipt, Typed2718,
Receipt, ReceiptEnvelope, ReceiptWithBloom, Signed, Transaction, TransactionEnvelope,
TxEip1559, TxEip2930, TxEnvelope, TxLegacy, TxReceipt, Typed2718,
constants::{
EIP1559_TX_TYPE_ID, EIP2930_TX_TYPE_ID, EIP4844_TX_TYPE_ID, EIP7702_TX_TYPE_ID,
LEGACY_TX_TYPE_ID,
Expand All @@ -14,7 +14,7 @@ use alloy_consensus::{

use alloy_eips::eip2718::{Decodable2718, Eip2718Error, Encodable2718};
use alloy_network::{AnyReceiptEnvelope, AnyRpcTransaction, AnyTransactionReceipt, AnyTxEnvelope};
use alloy_primitives::{Address, B256, Bloom, Bytes, Signature, TxHash, TxKind, U64, U256};
use alloy_primitives::{Address, B256, Bloom, Bytes, TxHash, TxKind, U64, U256};
use alloy_rlp::{Decodable, Encodable, Header};
use alloy_rpc_types::{
AccessList, ConversionError, Transaction as RpcTransaction, TransactionReceipt,
Expand Down Expand Up @@ -581,19 +581,30 @@ impl PendingTransaction {
}

/// Container type for signed, typed transactions.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, TransactionEnvelope)]
#[envelope(
alloy_consensus = alloy_consensus,
tx_type_name = TxType,
serde_cfg(all()),
)]
pub enum TypedTransaction {
/// Legacy transaction type
#[envelope(ty = 0)]
Legacy(Signed<TxLegacy>),
/// EIP-2930 transaction
#[envelope(ty = 1)]
EIP2930(Signed<TxEip2930>),
/// EIP-1559 transaction
#[envelope(ty = 2)]
EIP1559(Signed<TxEip1559>),
/// EIP-4844 transaction
#[envelope(ty = 3)]
EIP4844(Signed<TxEip4844Variant>),
/// EIP-7702 transaction
#[envelope(ty = 4)]
EIP7702(Signed<TxEip7702>),
Comment on lines 581 to 596
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be

#[envelope(flatten)]
Ethereum(TxEnvelope)

but not sure if worth it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow this seems clean, i'll try this 👍

/// op-stack deposit transaction
#[envelope(ty = 126)]
Deposit(TxDeposit),
}

Expand Down Expand Up @@ -647,73 +658,12 @@ impl TypedTransaction {
}
}

/// Returns true if the transaction uses dynamic fees: EIP1559, EIP4844 or EIP7702
pub fn is_dynamic_fee(&self) -> bool {
matches!(self, Self::EIP1559(_) | Self::EIP4844(_) | Self::EIP7702(_))
}

pub fn gas_price(&self) -> u128 {
match self {
Self::Legacy(tx) => tx.tx().gas_price,
Self::EIP2930(tx) => tx.tx().gas_price,
Self::EIP1559(tx) => tx.tx().max_fee_per_gas,
Self::EIP4844(tx) => tx.tx().tx().max_fee_per_gas,
Self::EIP7702(tx) => tx.tx().max_fee_per_gas,
Self::Deposit(_) => 0,
}
}

pub fn gas_limit(&self) -> u64 {
match self {
Self::Legacy(tx) => tx.tx().gas_limit,
Self::EIP2930(tx) => tx.tx().gas_limit,
Self::EIP1559(tx) => tx.tx().gas_limit,
Self::EIP4844(tx) => tx.tx().tx().gas_limit,
Self::EIP7702(tx) => tx.tx().gas_limit,
Self::Deposit(tx) => tx.gas_limit,
}
}

pub fn value(&self) -> U256 {
U256::from(match self {
Self::Legacy(tx) => tx.tx().value,
Self::EIP2930(tx) => tx.tx().value,
Self::EIP1559(tx) => tx.tx().value,
Self::EIP4844(tx) => tx.tx().tx().value,
Self::EIP7702(tx) => tx.tx().value,
Self::Deposit(tx) => tx.value,
})
}

pub fn data(&self) -> &Bytes {
match self {
Self::Legacy(tx) => &tx.tx().input,
Self::EIP2930(tx) => &tx.tx().input,
Self::EIP1559(tx) => &tx.tx().input,
Self::EIP4844(tx) => &tx.tx().tx().input,
Self::EIP7702(tx) => &tx.tx().input,
Self::Deposit(tx) => &tx.input,
}
}

/// Returns the transaction type
pub fn r#type(&self) -> Option<u8> {
match self {
Self::Legacy(_) => None,
Self::EIP2930(_) => Some(1),
Self::EIP1559(_) => Some(2),
Self::EIP4844(_) => Some(3),
Self::EIP7702(_) => Some(4),
Self::Deposit(_) => Some(0x7E),
}
}

/// Max cost of the transaction
/// It is the gas limit multiplied by the gas price,
/// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob
/// gas) is also added
pub fn max_cost(&self) -> u128 {
let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price());
let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price().unwrap_or(0));

if self.is_eip4844() {
max_cost = max_cost.saturating_add(
Expand Down Expand Up @@ -744,13 +694,6 @@ impl TypedTransaction {
}
}

pub fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
Self::EIP4844(tx) => Some(tx.tx().tx().max_fee_per_blob_gas),
_ => None,
}
}

/// Returns a helper type that contains commonly used values as fields
pub fn essentials(&self) -> TransactionEssentials {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this could also be easily dropped.

Would you like it in this PR? or as a follow-up?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's do a followup

match self {
Expand Down Expand Up @@ -841,60 +784,13 @@ impl TypedTransaction {
}
}

pub fn nonce(&self) -> u64 {
match self {
Self::Legacy(t) => t.tx().nonce,
Self::EIP2930(t) => t.tx().nonce,
Self::EIP1559(t) => t.tx().nonce,
Self::EIP4844(t) => t.tx().tx().nonce,
Self::EIP7702(t) => t.tx().nonce,
Self::Deposit(_t) => 0,
}
}

pub fn chain_id(&self) -> Option<u64> {
match self {
Self::Legacy(t) => t.tx().chain_id,
Self::EIP2930(t) => Some(t.tx().chain_id),
Self::EIP1559(t) => Some(t.tx().chain_id),
Self::EIP4844(t) => Some(t.tx().tx().chain_id),
Self::EIP7702(t) => Some(t.tx().chain_id),
Self::Deposit(t) => t.chain_id(),
}
}

pub fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
match self {
Self::Legacy(tx) => Some(tx),
_ => None,
}
}

/// Returns true whether this tx is a legacy transaction
pub fn is_legacy(&self) -> bool {
matches!(self, Self::Legacy(_))
}

/// Returns true whether this tx is a EIP1559 transaction
pub fn is_eip1559(&self) -> bool {
matches!(self, Self::EIP1559(_))
}

/// Returns true whether this tx is a EIP2930 transaction
pub fn is_eip2930(&self) -> bool {
matches!(self, Self::EIP2930(_))
}

/// Returns true whether this tx is a EIP4844 transaction
pub fn is_eip4844(&self) -> bool {
matches!(self, Self::EIP4844(_))
}

/// Returns true whether this tx is a EIP7702 transaction
pub fn is_eip7702(&self) -> bool {
matches!(self, Self::EIP7702(_))
}

/// Returns the hash of the transaction.
///
/// Note: If this transaction has the Impersonated signature then this returns a modified unique
Expand Down Expand Up @@ -931,136 +827,6 @@ impl TypedTransaction {
Self::Deposit(tx) => Ok(tx.from),
}
}

/// Returns what kind of transaction this is
pub fn kind(&self) -> TxKind {
match self {
Self::Legacy(tx) => tx.tx().to,
Self::EIP2930(tx) => tx.tx().to,
Self::EIP1559(tx) => tx.tx().to,
Self::EIP4844(tx) => TxKind::Call(tx.tx().tx().to),
Self::EIP7702(tx) => TxKind::Call(tx.tx().to),
Self::Deposit(tx) => tx.to,
}
}

/// Returns the callee if this transaction is a call
pub fn to(&self) -> Option<Address> {
self.kind().to().copied()
}

/// Returns the Signature of the transaction
pub fn signature(&self) -> Signature {
match self {
Self::Legacy(tx) => *tx.signature(),
Self::EIP2930(tx) => *tx.signature(),
Self::EIP1559(tx) => *tx.signature(),
Self::EIP4844(tx) => *tx.signature(),
Self::EIP7702(tx) => *tx.signature(),
Self::Deposit(_) => Signature::from_scalars_and_parity(
B256::with_last_byte(1),
B256::with_last_byte(1),
false,
),
}
}
}

impl Encodable for TypedTransaction {
fn encode(&self, out: &mut dyn bytes::BufMut) {
if !self.is_legacy() {
Header { list: false, payload_length: self.encode_2718_len() }.encode(out);
}

self.encode_2718(out);
}
}

impl Decodable for TypedTransaction {
fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
let mut h_decode_copy = *buf;
let header = alloy_rlp::Header::decode(&mut h_decode_copy)?;

// Legacy TX
if header.list {
return Ok(TxEnvelope::decode(buf)?.into());
}

// Check byte after header
let ty = *h_decode_copy.first().ok_or(alloy_rlp::Error::Custom("empty slice"))?;

if ty != 0x7E {
Ok(TxEnvelope::decode(buf)?.into())
} else {
Ok(Self::Deposit(TxDeposit::decode_2718(buf)?))
}
}
}

impl Typed2718 for TypedTransaction {
fn ty(&self) -> u8 {
self.r#type().unwrap_or(0)
}
}

impl Encodable2718 for TypedTransaction {
fn encode_2718_len(&self) -> usize {
match self {
Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718_len(),
Self::Deposit(tx) => 1 + tx.length(),
}
}

fn encode_2718(&self, out: &mut dyn BufMut) {
match self {
Self::Legacy(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
Self::EIP2930(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
Self::EIP1559(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
Self::EIP4844(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
Self::EIP7702(tx) => TxEnvelope::from(tx.clone()).encode_2718(out),
Self::Deposit(tx) => {
tx.encode_2718(out);
}
}
}
}

impl Decodable2718 for TypedTransaction {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
if ty == 0x7E {
return Ok(Self::Deposit(TxDeposit::decode(buf)?));
}
match TxEnvelope::typed_decode(ty, buf)? {
TxEnvelope::Eip2930(tx) => Ok(Self::EIP2930(tx)),
TxEnvelope::Eip1559(tx) => Ok(Self::EIP1559(tx)),
TxEnvelope::Eip4844(tx) => Ok(Self::EIP4844(tx)),
TxEnvelope::Eip7702(tx) => Ok(Self::EIP7702(tx)),
_ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
}
}

fn fallback_decode(buf: &mut &[u8]) -> Result<Self, Eip2718Error> {
match TxEnvelope::fallback_decode(buf)? {
TxEnvelope::Legacy(tx) => Ok(Self::Legacy(tx)),
_ => Err(Eip2718Error::RlpError(alloy_rlp::Error::Custom("unexpected tx type"))),
}
}
}

impl From<TxEnvelope> for TypedTransaction {
fn from(value: TxEnvelope) -> Self {
match value {
TxEnvelope::Legacy(tx) => Self::Legacy(tx),
TxEnvelope::Eip2930(tx) => Self::EIP2930(tx),
TxEnvelope::Eip1559(tx) => Self::EIP1559(tx),
TxEnvelope::Eip4844(tx) => Self::EIP4844(tx),
TxEnvelope::Eip7702(tx) => Self::EIP7702(tx),
}
}
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -1556,7 +1322,7 @@ pub fn convert_to_anvil_receipt(receipt: AnyTransactionReceipt) -> Option<Receip
#[cfg(test)]
mod tests {
use super::*;
use alloy_primitives::{Log, LogData, b256, hex};
use alloy_primitives::{Log, LogData, Signature, b256, hex};
use std::str::FromStr;

// <https://github.com/foundry-rs/foundry/issues/10852>
Expand Down Expand Up @@ -1634,7 +1400,7 @@ mod tests {
// https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
let res = TypedTransaction::decode(&mut raw_tx.as_slice()).unwrap();
assert_eq!(res.r#type(), Some(3));
assert!(res.is_type(3));

let tx = match res {
TypedTransaction::EIP4844(tx) => tx,
Expand Down
4 changes: 2 additions & 2 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{
mem::transaction_build,
};
use alloy_consensus::{
Account, Blob,
Account, Blob, Transaction,
transaction::{Recovered, eip4844::TxEip4844Variant},
};
use alloy_dyn_abi::TypedData;
Expand Down Expand Up @@ -2783,7 +2783,7 @@ impl EthApi {
let gas_price = tx.gas_price();
let value = tx.value();
let gas = tx.gas_limit();
TxpoolInspectSummary { to, value, gas, gas_price }
TxpoolInspectSummary { to, value, gas, gas_price: gas_price.unwrap_or(0) }
}

// Note: naming differs geth vs anvil:
Expand Down
3 changes: 2 additions & 1 deletion crates/anvil/src/eth/backend/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use crate::{
mem::inspector::AnvilInspector,
};
use alloy_consensus::{
Receipt, ReceiptWithBloom, constants::EMPTY_WITHDRAWALS, proofs::calculate_receipt_root,
Receipt, ReceiptWithBloom, Transaction, constants::EMPTY_WITHDRAWALS,
proofs::calculate_receipt_root,
};
use alloy_eips::{eip7685::EMPTY_REQUESTS_HASH, eip7840::BlobParams};
use alloy_evm::{
Expand Down
Loading
Loading