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