Skip to main content

foundry_evm_core/evm/
base.rs

1use crate::{
2    FoundryChain, FoundryContextExt, FoundryInspectorExt, FoundryTransaction,
3    backend::{DatabaseExt, JournaledState},
4    constants::SYSTEM_PRECOMPILE_STUB,
5    evm::{
6        FoundryEvmFactory, FoundryEvmNetwork, IntoInstructionResult, NestedEvm, NestedEvmFor,
7        run_inspected_frame,
8    },
9};
10use alloy_evm::{Evm, EvmEnv, EvmFactory, precompiles::PrecompilesMap};
11use alloy_primitives::{Address, Bytes};
12use base_common_chains::ChainConfig;
13use base_common_evm::{
14    BaseContext, BaseEvm, BaseEvmFactory, BaseHaltReason, BaseHandler, BaseSpecId, BaseTransaction,
15    BaseTransactionError, BaseUpgrade, L1BlockInfo,
16};
17use base_common_network::Base;
18// Only the tests below need this crate, but Cargo forbids optional dev-dependencies, so it is an
19// optional regular dependency that the `base` feature turns on.
20use base_common_precompiles as _;
21use foundry_evm_networks::{BASE_CODE_SENTINEL_ADDRESSES, is_base_precompile_active_at};
22use foundry_fork_db::DatabaseError;
23use revm::{
24    context::{
25        BlockEnv, ContextTr, Journal, JournalTr, TxEnv,
26        result::{EVMError, HaltReason, ResultAndState},
27    },
28    handler::{EthFrame, EvmTr, FrameResult},
29    interpreter::{FrameInput, InstructionResult, interpreter::EthInterpreter},
30    state::Bytecode,
31};
32
33/// Base EVM network.
34#[derive(Clone, Copy, Debug, Default)]
35pub struct BaseEvmNetwork;
36
37impl FoundryEvmNetwork for BaseEvmNetwork {
38    type Network = Base;
39    type EvmFactory = BaseEvmFactory;
40}
41
42impl IntoInstructionResult for BaseHaltReason {
43    fn into_instruction_result(self) -> InstructionResult {
44        match self {
45            Self::Base(eth) => eth.into(),
46            Self::FailedDeposit => InstructionResult::Stop,
47        }
48    }
49}
50
51pub type BaseRevmEvm<'db, I> = BaseEvm<&'db mut dyn DatabaseExt<BaseEvmFactory>, I, PrecompilesMap>;
52
53type BaseEvmHandler<'db, I> = BaseHandler<
54    BaseRevmEvm<'db, I>,
55    EVMError<DatabaseError, BaseTransactionError>,
56    EthFrame<EthInterpreter>,
57>;
58
59/// Base precompiles installed at `upgrade` that hold state but carry no bytecode.
60///
61/// Solidity emits an `extcodesize` check for high-level calls to functions without return data,
62/// so a code-less precompile makes the *caller* revert before the precompile ever runs. Base
63/// mainnet plants a one-byte sentinel on exactly these accounts, so mirroring it keeps local
64/// execution faithful to the chain.
65pub fn base_code_sentinel_addresses(upgrade: BaseUpgrade) -> impl Iterator<Item = Address> {
66    BASE_CODE_SENTINEL_ADDRESSES
67        .iter()
68        .copied()
69        .filter(move |address| is_base_precompile_active_at(*address, upgrade))
70}
71
72impl FoundryChain<BaseTransaction<TxEnv>> for L1BlockInfo {
73    fn clear_transaction_fee_cache(&mut self) {
74        self.clear_tx_l1_cost();
75    }
76}
77
78impl FoundryEvmFactory for BaseEvmFactory {
79    type Chain = L1BlockInfo;
80    type FoundryContext<'db> = BaseContext<&'db mut dyn DatabaseExt<Self>>;
81
82    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> = BaseRevmEvm<'db, I>;
83
84    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
85        &self,
86        db: &'db mut dyn DatabaseExt<Self>,
87        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
88        inspector: I,
89    ) -> Self::FoundryEvm<'db, I> {
90        let upgrade = evm_env.cfg_env.spec.upgrade();
91        // Nested EVMs use the same fork database, so execution chain-ID overrides cannot
92        // change the source-chain registry configuration when rebuilding their precompiles.
93        let activation_admin = self.activation_admin_address().or_else(|| {
94            ChainConfig::activation_admin_address_for_upgrade_by_chain_id(
95                db.active_fork_source_chain_id().unwrap_or(evm_env.cfg_env.chain_id),
96                upgrade,
97            )
98        });
99        let factory = self.with_activation_admin_address(activation_admin);
100        let mut base_evm = factory.create_evm_with_inspector(db, evm_env, inspector);
101        base_evm.ctx_mut().cfg.tx_chain_id_check = true;
102        // Preserve existing code, including sentinels already present on forks.
103        let sentinel = Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
104        let sentinel_hash = sentinel.hash_slow();
105        let journal = base_evm.ctx_mut().journal_mut();
106        for address in base_code_sentinel_addresses(upgrade) {
107            if let Ok(account) = journal.load_account_with_code(address)
108                && account.info.code.as_ref().is_none_or(|code| code.is_empty())
109            {
110                journal.set_code_with_hash(address, sentinel.clone(), sentinel_hash);
111            }
112        }
113        base_evm
114    }
115
116    fn create_nested_evm_with_inspector<'db, I>(
117        &self,
118        db: &'db mut dyn DatabaseExt<Self>,
119        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
120        inspector: I,
121    ) -> NestedEvmFor<'db, Self>
122    where
123        I: FoundryInspectorExt<Self::FoundryContext<'db>> + 'db,
124    {
125        Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector))
126    }
127}
128
129fn map_base_error(error: EVMError<DatabaseError, BaseTransactionError>) -> EVMError<DatabaseError> {
130    match error {
131        EVMError::Database(db) => EVMError::Database(db),
132        EVMError::Header(header) => EVMError::Header(header),
133        EVMError::Custom(message) => EVMError::Custom(message),
134        EVMError::Transaction(transaction) => {
135            EVMError::Custom(format!("base transaction error: {transaction}"))
136        }
137        EVMError::CustomAny(error) => EVMError::CustomAny(error),
138    }
139}
140
141impl<'db, I: FoundryInspectorExt<BaseContext<&'db mut dyn DatabaseExt<BaseEvmFactory>>>> NestedEvm
142    for BaseRevmEvm<'db, I>
143{
144    type Spec = BaseSpecId;
145    type Block = BlockEnv;
146    type Tx = BaseTransaction<TxEnv>;
147    type Chain = L1BlockInfo;
148    type Journal = Journal<&'db mut dyn DatabaseExt<BaseEvmFactory>>;
149
150    fn journal_inner_mut(&mut self) -> &mut JournaledState {
151        &mut self.ctx_mut().journaled_state.inner
152    }
153
154    fn tx_mut(&mut self) -> &mut Self::Tx {
155        self.ctx_mut().tx_mut()
156    }
157
158    fn chain_mut(&mut self) -> &mut Self::Chain {
159        &mut self.ctx_mut().chain
160    }
161
162    fn journal_mut(&mut self) -> &mut Self::Journal {
163        &mut self.ctx_mut().journaled_state
164    }
165
166    fn precompiles_mut(&mut self) -> &mut PrecompilesMap {
167        Evm::precompiles_mut(self)
168    }
169
170    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
171        run_inspected_frame(self, BaseEvmHandler::<I>::new(), frame).map_err(map_base_error)
172    }
173
174    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState<HaltReason>> {
175        if self.ctx().cfg().disable_fee_charge
176            && tx.enveloped_tx().is_some_and(|enveloped| enveloped.is_empty())
177        {
178            self.ctx_mut().chain_mut().clear_transaction_fee_cache();
179        }
180        let ResultAndState { result, state } =
181            Evm::transact_raw(self, tx).map_err(map_base_error)?;
182        let result = result.map_haltreason(|halt| match halt {
183            BaseHaltReason::Base(eth) => eth,
184            BaseHaltReason::FailedDeposit => HaltReason::PrecompileError,
185        });
186        Ok(ResultAndState::new(result, state))
187    }
188
189    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
190        self.ctx_ref().evm_clone()
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::{
198        backend::{Backend, CowBackend},
199        evm::with_inherited_evm,
200        opts::EvmOpts,
201    };
202    use alloy_sol_types::SolCall;
203    use base_common_precompiles::{
204        ActivationRegistryStorage, B20FactoryStorage, IActivationRegistry, NonceManagerStorage,
205        PolicyRegistryStorage, TxContextStorage,
206    };
207    use foundry_config::Config;
208    use foundry_evm_networks::NetworkConfigs;
209    use revm::{
210        ExecuteEvm, context::CfgEnv, inspector::NoOpInspector, primitives::TxKind,
211        state::AccountInfo,
212    };
213
214    fn base_env(chain_id: u64, upgrade: BaseUpgrade) -> EvmEnv<BaseSpecId, BlockEnv> {
215        let mut cfg = CfgEnv::new_with_spec(BaseSpecId::new(upgrade));
216        cfg.chain_id = chain_id;
217        EvmEnv::new(cfg, BlockEnv::default())
218    }
219
220    #[test]
221    fn failed_deposit_maps_to_stop() {
222        assert_eq!(
223            BaseHaltReason::FailedDeposit.into_instruction_result(),
224            InstructionResult::Stop
225        );
226    }
227
228    #[test]
229    fn constructor_resolves_activation_admin_and_preserves_override() {
230        let chain_admin =
231            ChainConfig::activation_admin_address_for_upgrade_by_chain_id(8453, BaseUpgrade::Beryl)
232                .unwrap();
233        let custom_admin = Address::repeat_byte(0xaa);
234        for (override_admin, expected_admin) in
235            [(None, chain_admin), (Some(custom_admin), custom_admin)]
236        {
237            let mut db = Backend::<BaseEvmNetwork>::spawn(None).unwrap();
238            let mut evm = BaseEvmFactory::new(override_admin).create_foundry_evm_with_inspector(
239                &mut db,
240                base_env(8453, BaseUpgrade::Beryl),
241                NoOpInspector,
242            );
243            let tx = BaseTransaction::builder()
244                .base(
245                    TxEnv::builder()
246                        .chain_id(Some(8453))
247                        .kind(TxKind::Call(ActivationRegistryStorage::ADDRESS))
248                        .data(Bytes::from(IActivationRegistry::adminCall {}.abi_encode()))
249                        .gas_limit(100_000),
250                )
251                .build_fill();
252            let result = evm.transact_one(tx).unwrap();
253            let admin =
254                IActivationRegistry::adminCall::abi_decode_returns(result.output().unwrap())
255                    .unwrap();
256            assert_eq!(admin, expected_admin);
257        }
258    }
259
260    #[tokio::test(flavor = "multi_thread")]
261    async fn fork_activation_admin_uses_source_chain_in_nested_deployment() {
262        let (_api, handle) =
263            anvil::spawn(anvil::NodeConfig::test().with_chain_id(Some(8453u64))).await;
264        let caller = handle.genesis_accounts().next().unwrap();
265        let expected =
266            ChainConfig::activation_admin_address_for_upgrade_by_chain_id(8453, BaseUpgrade::Beryl)
267                .unwrap();
268        for chain_id in [31337, 84532] {
269            let mut opts = EvmOpts {
270                fork_url: Some(handle.http_endpoint()),
271                networks: NetworkConfigs::with_base(),
272                ..Default::default()
273            };
274            opts.env.chain_id = Some(chain_id);
275            let fork = opts.get_fork(&Config::default(), 8453, Some(0)).unwrap();
276            let backend = Backend::<BaseEvmNetwork>::spawn(Some(fork)).unwrap();
277            assert_eq!(backend.active_fork_source_chain_id(), Some(8453));
278            let mut db = CowBackend::new_borrowed(&backend);
279            let env = base_env(chain_id, BaseUpgrade::Beryl);
280            let mut evm = BaseEvmFactory::default().create_foundry_evm_with_inspector(
281                &mut db,
282                env,
283                NoOpInspector,
284            );
285            let tx = BaseTransaction::builder()
286                .base(
287                    TxEnv::builder()
288                        .caller(caller)
289                        .chain_id(Some(chain_id))
290                        .kind(TxKind::Call(ActivationRegistryStorage::ADDRESS))
291                        .data(Bytes::from(IActivationRegistry::adminCall {}.abi_encode()))
292                        .gas_limit(100_000),
293                )
294                .build_fill();
295            let result = evm.transact_one(tx).unwrap();
296            assert_eq!(
297                IActivationRegistry::adminCall::abi_decode_returns(result.output().unwrap())
298                    .unwrap(),
299                expected
300            );
301            // Deploy initcode that calls admin() and returns it as the deployed code.
302            let mut initcode = vec![0x63];
303            initcode.extend(IActivationRegistry::adminCall::SELECTOR);
304            initcode
305                .extend([0x60, 0xe0, 0x1b, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0x60, 0x04, 0x5f, 0x73]);
306            initcode.extend(ActivationRegistryStorage::ADDRESS.as_slice());
307            initcode.extend([0x5a, 0xfa, 0x50, 0x60, 0x20, 0x5f, 0xf3]);
308            with_inherited_evm::<BaseEvmFactory, _>(evm.ctx_mut(), NoOpInspector, &mut |nested| {
309                let result = nested
310                    .transact_raw(
311                        BaseTransaction::builder()
312                            .base(
313                                TxEnv::builder()
314                                    .caller(caller)
315                                    .chain_id(Some(chain_id))
316                                    .nonce(1)
317                                    .kind(TxKind::Create)
318                                    .data(Bytes::from(initcode.clone()))
319                                    .gas_limit(200_000),
320                            )
321                            .build_fill(),
322                    )
323                    .unwrap();
324                assert!(result.result.is_success(), "{:?}", result.result);
325                assert_eq!(
326                    IActivationRegistry::adminCall::abi_decode_returns(
327                        result.result.output().unwrap()
328                    )
329                    .unwrap(),
330                    expected
331                );
332                Ok(())
333            })
334            .unwrap();
335        }
336    }
337
338    #[test]
339    fn constructor_plants_only_active_registry_sentinels() {
340        for upgrade in [BaseUpgrade::Azul, BaseUpgrade::Beryl] {
341            let mut db = Backend::<BaseEvmNetwork>::spawn(None).unwrap();
342            let mut evm = BaseEvmFactory::default().create_foundry_evm_with_inspector(
343                &mut db,
344                base_env(8453, upgrade),
345                NoOpInspector,
346            );
347            for (address, registry) in [
348                (ActivationRegistryStorage::ADDRESS, true),
349                (PolicyRegistryStorage::ADDRESS, true),
350                (B20FactoryStorage::ADDRESS, false),
351                (NonceManagerStorage::ADDRESS, false),
352                (TxContextStorage::ADDRESS, false),
353            ] {
354                let expected = if upgrade == BaseUpgrade::Beryl && registry {
355                    Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB))
356                } else {
357                    Bytecode::default()
358                };
359                let account = evm.ctx_mut().journal_mut().load_account_with_code(address).unwrap();
360                assert_eq!(
361                    account.info.code.as_ref().unwrap(),
362                    &expected,
363                    "{upgrade:?}: {address}"
364                );
365                assert_eq!(account.info.code_hash(), expected.hash_slow());
366            }
367        }
368    }
369
370    #[test]
371    fn constructor_preserves_existing_registry_code() {
372        let address = ActivationRegistryStorage::ADDRESS;
373        let code = Bytecode::new_legacy(Bytes::from_static(&[0x60, 0x00, 0x00]));
374        let code_hash = code.hash_slow();
375        let mut db = Backend::<BaseEvmNetwork>::spawn(None).unwrap();
376        db.insert_account_info(address, AccountInfo::default().with_code(code.clone()));
377        let mut evm = BaseEvmFactory::default().create_foundry_evm_with_inspector(
378            &mut db,
379            base_env(8453, BaseUpgrade::Beryl),
380            NoOpInspector,
381        );
382        let account = evm.ctx_mut().journal_mut().load_account_with_code(address).unwrap();
383        assert_eq!(account.info.code.as_ref().unwrap(), &code);
384        assert_eq!(account.info.code_hash(), code_hash);
385    }
386}