Skip to main content

anvil/eth/backend/mem/
base.rs

1//! Base block preparation and transaction execution for the in-memory backend.
2
3use super::Backend;
4use crate::eth::{
5    backend::{
6        db::{AnvilCacheDB, Db},
7        executor::{BlockExecutionKind, ExecutedPoolTransactions},
8        time::{PendingBlockTimestamp, TimeManager},
9    },
10    error::BlockchainError,
11    pool::transactions::PoolTransaction,
12};
13use alloy_eips::{Decodable2718, Encodable2718};
14use alloy_evm::{Database, EthEvmFactory, Evm, EvmEnv, EvmFactory};
15use alloy_network::Network;
16use alloy_primitives::{Address, B256, TxKind, U256};
17use anvil_core::eth::transaction::PendingTransaction;
18use base_common_chains::ChainConfig;
19use base_common_consensus::Predeploys;
20use base_common_evm::{
21    BaseContext, BaseEvmFactory, BaseHaltReason, BaseSpecId, BaseTime, BaseTransaction,
22    BaseUpgrade, L1BlockInfo,
23};
24use base_common_genesis::RollupConfig;
25use base_common_rpc_types::EIP8130_PRE_ZENITH_RPC_ERROR;
26use base_consensus_upgrades::Jovian;
27use base_protocol::{BaseTimeUpdateTx, L1BlockInfoJovian, L1BlockInfoTx};
28use foundry_evm::{backend::DatabaseError, hardfork::FoundryHardfork};
29use foundry_primitives::FoundryTxEnvelope;
30use revm::{
31    DatabaseRef, Inspector,
32    context::{
33        TxEnv,
34        result::{ExecutionResult, HaltReason, Output, ResultAndState},
35    },
36    database::EmptyDB,
37    database_interface::WrapDatabaseRef,
38};
39use std::sync::Arc;
40
41impl<N: Network> Backend<N> {
42    /// Base path of [`Backend::transact_call_with_inspector_ref`].
43    pub(super) fn transact_base_with_inspector_ref<'db, I, DB>(
44        &self,
45        db: &'db DB,
46        evm_env: &EvmEnv,
47        inspector: &mut I,
48        tx: BaseTransaction<TxEnv>,
49    ) -> Result<ResultAndState<HaltReason>, BlockchainError>
50    where
51        DB: DatabaseRef + ?Sized,
52        I: Inspector<BaseContext<WrapDatabaseRef<&'db DB>>>,
53        WrapDatabaseRef<&'db DB>: Database<Error = DatabaseError>,
54    {
55        let upgrade = self.base_upgrade_at_timestamp(evm_env.block_env.timestamp.saturating_to());
56        if tx.eip8130.is_some() && upgrade < BaseUpgrade::Zenith {
57            return Err(BlockchainError::InvalidTransactionRequest(
58                EIP8130_PRE_ZENITH_RPC_ERROR.to_string(),
59            ));
60        }
61        let base_env = EvmEnv::new(
62            evm_env.cfg_env.clone().with_spec_and_mainnet_gas_params(BaseSpecId::new(upgrade)),
63            evm_env.block_env.clone(),
64        );
65        let activation_admin = self.base_activation_admin().or_else(|| {
66            ChainConfig::activation_admin_address_for_upgrade_by_chain_id(
67                base_env.cfg_env.chain_id,
68                upgrade,
69            )
70        });
71        let factory = BaseEvmFactory::new(activation_admin);
72        let mut evm = factory.create_evm_with_inspector(WrapDatabaseRef(db), base_env, inspector);
73        evm.ctx_mut().cfg.tx_chain_id_check = true;
74        self.inject_precompiles(evm.precompiles_mut(), evm_env);
75        let result = Evm::transact_raw(&mut evm, tx)?;
76        Ok(ResultAndState {
77            result: result.result.map_haltreason(|halt| match halt {
78                BaseHaltReason::Base(eth) => eth,
79                BaseHaltReason::FailedDeposit => HaltReason::PrecompileError,
80            }),
81            state: result.state,
82        })
83    }
84
85    /// Rejects a fork whose protocol contracts would reject every Denim block's system deposits.
86    ///
87    /// The deposits run through the block executor against the fork state, so startup and resets
88    /// fail with a clear error instead of producing a node that cannot mine.
89    pub(super) fn ensure_fork_accepts_system_transactions(
90        &self,
91        db: &dyn Db,
92        parent_env: &EvmEnv,
93        parent_hash: B256,
94        hardfork: FoundryHardfork,
95    ) -> Result<(), DatabaseError> {
96        let mut evm_env = parent_env.clone();
97        evm_env.block_env.number = evm_env.block_env.number.saturating_add(U256::from(1));
98        let transactions = system_transactions(
99            db,
100            evm_env.block_env.number.saturating_to(),
101            evm_env.block_env.timestamp.saturating_to(),
102            parent_hash,
103            true,
104        )?;
105        let mut candidate_db = AnvilCacheDB::new(db, *evm_env.spec_id());
106        self.execute_with_block_executor(
107            &mut candidate_db,
108            &evm_env,
109            parent_hash,
110            hardfork,
111            Some(B256::ZERO),
112            BlockExecutionKind::Complete,
113            &transactions,
114            &self.pool_tx_gas_config(&evm_env),
115            &self.inspector_tx_config(),
116            &|_, _| Ok(()),
117        )
118        .and_then(|(executed, _)| validate_system_transactions(&transactions, &executed))
119        .map_err(|err| {
120            DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
121                "Denim system deposits fail on this fork; its L1Block likely predates Jovian (fork \
122                 at or after the Jovian upgrade, or use an earlier --hardfork): {err}"
123            )))
124        })
125    }
126}
127
128/// Advances the persisted BaseTime phase, independently of the absolute block number.
129fn next_millis_part(db: &dyn DatabaseRef<Error = DatabaseError>) -> Result<u16, DatabaseError> {
130    let parent = BaseTime::fetch_timestamp_millis_part(&mut WrapDatabaseRef(db))?;
131    if !BaseTimeUpdateTx::is_valid_timestamp_millis_part(parent) {
132        return Err(DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
133            "invalid parent BaseTime millisecond component: {parent}"
134        ))));
135    }
136    Ok(parent + BaseTimeUpdateTx::BLOCK_INTERVAL_MILLIS)
137}
138
139/// Prepares the whole-second carry and explicit time controls for a Denim child block.
140pub(super) fn prepare_block_timestamp(
141    db: &dyn DatabaseRef<Error = DatabaseError>,
142    time: &TimeManager,
143    parent_timestamp: u64,
144) -> Result<PendingBlockTimestamp, DatabaseError> {
145    let carry = u64::from(next_millis_part(db)? >= 1_000);
146    Ok(time.prepare_next_timestamp_with_increment(carry, parent_timestamp.saturating_add(carry)))
147}
148
149/// Builds the L1-info and BaseTime deposits from the candidate's parent database.
150///
151/// Fork continuation retains the L1 origin and fee configuration, advancing only its sequence.
152/// Standalone blocks use a deterministic synthetic origin because Anvil does not derive from L1.
153pub(super) fn system_transactions(
154    db: &dyn DatabaseRef<Error = DatabaseError>,
155    block_number: u64,
156    timestamp: u64,
157    parent_hash: B256,
158    fork: bool,
159) -> Result<Vec<Arc<PoolTransaction<FoundryTxEnvelope>>>, DatabaseError> {
160    let mut state = WrapDatabaseRef(db);
161    let fees = L1BlockInfo::try_fetch(
162        &mut state,
163        U256::from(block_number),
164        BaseSpecId::new(BaseUpgrade::Denim),
165    )?;
166    let origin = db.storage_ref(Predeploys::L1_BLOCK_INFO, U256::ZERO)?.to_be_bytes::<32>();
167    let sequence_slot =
168        db.storage_ref(Predeploys::L1_BLOCK_INFO, U256::from(3))?.to_be_bytes::<32>();
169    let sequence = u64::from_be_bytes(sequence_slot[24..32].try_into().unwrap());
170    let batcher = db.storage_ref(Predeploys::L1_BLOCK_INFO, U256::from(4))?;
171    let (number, origin_timestamp, origin_hash, sequence) = if fork {
172        (
173            u64::from_be_bytes(origin[24..32].try_into().unwrap()),
174            u64::from_be_bytes(origin[16..24].try_into().unwrap()),
175            B256::from(db.storage_ref(Predeploys::L1_BLOCK_INFO, U256::from(2))?),
176            sequence.checked_add(1).ok_or_else(|| {
177                DatabaseError::AnyRequest(Arc::new(eyre::eyre!("L1-info sequence number overflow")))
178            })?,
179        )
180    } else {
181        (block_number, timestamp, parent_hash, block_number)
182    };
183    let l1_info = L1BlockInfoTx::Jovian(L1BlockInfoJovian::new(
184        number,
185        origin_timestamp,
186        fees.l1_base_fee.saturating_to(),
187        origin_hash,
188        sequence,
189        Address::from_word(B256::from(batcher)),
190        fees.l1_blob_base_fee.unwrap_or_default().saturating_to(),
191        fees.l1_blob_base_fee_scalar.unwrap_or_default().saturating_to(),
192        fees.l1_base_fee_scalar.saturating_to(),
193        fees.operator_fee_scalar.unwrap_or_default().saturating_to(),
194        fees.operator_fee_constant.unwrap_or_default().saturating_to(),
195        fees.da_footprint_gas_scalar.unwrap_or_default(),
196    ));
197    let mut upgrades = RollupConfig::default();
198    upgrades.set_upgrade_activation_timestamp(BaseUpgrade::Regolith, 0);
199    let l1_info = l1_info.into_deposit_tx(&upgrades, timestamp);
200    let base_time = BaseTimeUpdateTx::new(next_millis_part(db)? % 1_000)
201        .expect("the parent BaseTime phase was validated")
202        .into_deposit_tx(block_number);
203    Ok([l1_info, base_time]
204        .into_iter()
205        .map(|deposit| {
206            let envelope = FoundryTxEnvelope::decode_2718(&mut deposit.encoded_2718().as_slice())
207                .expect("Base and OP deposits share the canonical encoding");
208            let pending = PendingTransaction::new(envelope).expect("deposit sender is explicit");
209            Arc::new(PoolTransaction::new(pending))
210        })
211        .collect())
212}
213
214/// Installs the canonical L1Block runtime when a local chain has no deployment.
215///
216/// Execute the pinned upgrade's constructor to obtain its runtime instead of carrying a second
217/// bytecode artifact. Existing fork deployments and their storage are left intact.
218pub(super) fn ensure_l1_block_predeploy(db: &mut dyn Db) -> Result<(), DatabaseError> {
219    if db.basic(Predeploys::L1_BLOCK_INFO)?.is_some_and(|info| !info.is_empty_code_hash()) {
220        return Ok(());
221    }
222    let mut evm = EthEvmFactory::default().create_evm(EmptyDB::default(), EvmEnv::default());
223    let result = evm
224        .transact_raw(TxEnv {
225            kind: TxKind::Create,
226            gas_limit: 1_000_000,
227            data: Jovian::l1_block_deployment_bytecode(),
228            ..Default::default()
229        })
230        .map_err(|err| DatabaseError::AnyRequest(Arc::new(eyre::eyre!(err))))?;
231    let ExecutionResult::Success { output: Output::Create(code, _), .. } = result.result else {
232        return Err(DatabaseError::AnyRequest(Arc::new(eyre::eyre!(
233            "failed to initialize the L1Block runtime"
234        ))));
235    };
236    db.set_code(Predeploys::L1_BLOCK_INFO, code)
237}
238
239/// Rejects a candidate whose mandatory prefix was skipped or reverted during execution.
240pub(super) fn validate_system_transactions(
241    expected: &[Arc<PoolTransaction<FoundryTxEnvelope>>],
242    executed: &ExecutedPoolTransactions<FoundryTxEnvelope>,
243) -> Result<(), BlockchainError> {
244    for (index, transaction) in expected.iter().enumerate() {
245        if executed.included.get(index).is_none_or(|included| included.hash() != transaction.hash())
246            || executed.tx_info.get(index).is_none_or(|info| !info.exit.is_ok())
247        {
248            return Err(BlockchainError::Internal(format!(
249                "required Base system deposit at transaction index {index} failed"
250            )));
251        }
252    }
253    Ok(())
254}