Skip to main content

anvil/eth/backend/executor/
optimism.rs

1//! OP-stack receipt construction for the Anvil block executor.
2
3use super::AnvilBlockExecutor;
4use alloy_consensus::{Eip658Value, Receipt, ReceiptWithBloom};
5use alloy_primitives::Log;
6use foundry_evm::hardfork::FoundryHardfork;
7use foundry_primitives::FoundryReceiptEnvelope;
8use op_alloy_consensus::{OpDepositReceipt, OpDepositReceiptWithBloom};
9use revm::context_interface::result::ExecutionResult;
10
11#[cfg(feature = "base")]
12use foundry_evm::hardforks::BaseUpgrade;
13
14#[cfg(feature = "optimism")]
15use alloy_consensus::Transaction;
16#[cfg(feature = "optimism")]
17use alloy_eips::Encodable2718;
18#[cfg(feature = "optimism")]
19use foundry_evm::hardfork::OpHardfork;
20#[cfg(feature = "optimism")]
21use foundry_primitives::FoundryTxEnvelope;
22#[cfg(feature = "optimism")]
23use op_revm::{L1BlockInfo, estimate_tx_compressed_size};
24#[cfg(feature = "optimism")]
25use revm::Database;
26
27impl<E> AnvilBlockExecutor<E> {
28    /// Configures OP-specific block accounting without changing the shared constructor.
29    #[cfg(feature = "optimism")]
30    pub(crate) fn set_optimism_hardfork(&mut self, hardfork: FoundryHardfork) {
31        self.optimism_jovian = OpHardfork::from(hardfork) >= OpHardfork::Jovian;
32        self.deposit_hardfork = Some(hardfork);
33    }
34
35    /// Records the Base upgrade that gates deposit receipt metadata.
36    #[cfg(feature = "base")]
37    pub(crate) const fn set_base_upgrade(&mut self, upgrade: BaseUpgrade) {
38        self.deposit_hardfork = Some(FoundryHardfork::Base(upgrade));
39    }
40}
41
42/// Returns the blob gas accounted for by an OP transaction under the active hardfork.
43#[cfg(feature = "optimism")]
44pub(crate) fn blob_gas_used<DB: Database>(
45    db: &mut DB,
46    tx: &FoundryTxEnvelope,
47    jovian: bool,
48) -> Result<u64, alloy_evm::block::BlockExecutionError> {
49    if !jovian || tx.is_deposit() {
50        return Ok(tx.blob_gas_used().unwrap_or_default());
51    }
52
53    let encoded = estimate_tx_compressed_size(tx.encoded_2718().as_ref()).saturating_div(1_000_000);
54    let scalar = L1BlockInfo::fetch_da_footprint_gas_scalar(db)
55        .map_err(alloy_evm::block::BlockExecutionError::other)?;
56    Ok(encoded.saturating_mul(u64::from(scalar)))
57}
58
59/// Builds a mined OP deposit receipt and derives its fork-specific metadata.
60///
61/// `depositor_nonce` is the sender nonce before the deposit executed. Without a configured
62/// hardfork the receipt carries both fields, matching the network's default configuration.
63pub(crate) fn build_mined_deposit_receipt<H>(
64    result: ExecutionResult<H>,
65    hardfork: Option<FoundryHardfork>,
66    depositor_nonce: u64,
67    cumulative_gas_used: u64,
68) -> FoundryReceiptEnvelope {
69    let (deposit_nonce, deposit_receipt_version) = match hardfork {
70        Some(hardfork) => deposit_metadata(hardfork, depositor_nonce),
71        None => (Some(depositor_nonce), Some(1)),
72    };
73    build_deposit_receipt(result, cumulative_gas_used, deposit_nonce, deposit_receipt_version)
74}
75
76/// Builds an RPC-simulated OP deposit receipt and derives its fork-specific metadata.
77pub(crate) fn build_simulated_deposit_receipt<H>(
78    hardfork: FoundryHardfork,
79    caller_nonce: u64,
80    result: &ExecutionResult<H>,
81    logs: Vec<Log>,
82    cumulative_gas_used: u64,
83) -> FoundryReceiptEnvelope {
84    let (deposit_nonce, deposit_receipt_version) = deposit_metadata(hardfork, caller_nonce);
85    let receipt =
86        Receipt { status: Eip658Value::Eip658(result.is_success()), cumulative_gas_used, logs }
87            .with_bloom();
88    wrap_deposit_receipt(receipt, deposit_nonce, deposit_receipt_version)
89}
90
91/// Resolves the deposit nonce and receipt version active at `hardfork`.
92///
93/// Base is an OP-stack chain, so it gates the same two fields on its own upgrade names.
94fn deposit_metadata(hardfork: FoundryHardfork, caller_nonce: u64) -> (Option<u64>, Option<u64>) {
95    #[cfg(feature = "base")]
96    if matches!(hardfork, FoundryHardfork::Base(_)) {
97        let upgrade = BaseUpgrade::from(hardfork);
98        return (
99            (upgrade >= BaseUpgrade::Regolith).then_some(caller_nonce),
100            (upgrade >= BaseUpgrade::Canyon).then_some(1),
101        );
102    }
103    #[cfg(feature = "optimism")]
104    {
105        let hardfork = OpHardfork::from(hardfork);
106        (
107            (hardfork >= OpHardfork::Regolith).then_some(caller_nonce),
108            (hardfork >= OpHardfork::Canyon).then_some(1),
109        )
110    }
111    #[cfg(not(feature = "optimism"))]
112    (None, None)
113}
114
115fn build_deposit_receipt<H>(
116    result: ExecutionResult<H>,
117    cumulative_gas_used: u64,
118    deposit_nonce: Option<u64>,
119    deposit_receipt_version: Option<u64>,
120) -> FoundryReceiptEnvelope {
121    let receipt = Receipt {
122        status: Eip658Value::Eip658(result.is_success()),
123        cumulative_gas_used,
124        logs: result.into_logs(),
125    }
126    .with_bloom();
127    wrap_deposit_receipt(receipt, deposit_nonce, deposit_receipt_version)
128}
129
130fn wrap_deposit_receipt(
131    receipt: ReceiptWithBloom<Receipt>,
132    deposit_nonce: Option<u64>,
133    deposit_receipt_version: Option<u64>,
134) -> FoundryReceiptEnvelope {
135    FoundryReceiptEnvelope::Deposit(OpDepositReceiptWithBloom {
136        receipt: OpDepositReceipt {
137            inner: receipt.receipt,
138            deposit_nonce,
139            deposit_receipt_version,
140        },
141        logs_bloom: receipt.logs_bloom,
142    })
143}