Skip to main content

foundry_evm_core/evm/
base.rs

1use crate::{
2    FoundryChain, FoundryContextExt, FoundryInspectorExt,
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
74impl FoundryEvmFactory for BaseEvmFactory {
75    type Chain = L1BlockInfo;
76    type FoundryContext<'db> = BaseContext<&'db mut dyn DatabaseExt<Self>>;
77
78    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> = BaseRevmEvm<'db, I>;
79
80    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
81        &self,
82        db: &'db mut dyn DatabaseExt<Self>,
83        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
84        inspector: I,
85    ) -> Self::FoundryEvm<'db, I> {
86        let upgrade = evm_env.cfg_env.spec.upgrade();
87        // Nested EVMs use the same fork database, so execution chain-ID overrides cannot
88        // change the source-chain registry configuration when rebuilding their precompiles.
89        let activation_admin = self.activation_admin_address().or_else(|| {
90            ChainConfig::activation_admin_address_for_upgrade_by_chain_id(
91                db.active_fork_source_chain_id().unwrap_or(evm_env.cfg_env.chain_id),
92                upgrade,
93            )
94        });
95        let factory = self.with_activation_admin_address(activation_admin);
96        let mut base_evm = factory.create_evm_with_inspector(db, evm_env, inspector);
97        base_evm.ctx_mut().cfg.tx_chain_id_check = true;
98        // Preserve existing code, including sentinels already present on forks.
99        let sentinel = Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB));
100        let sentinel_hash = sentinel.hash_slow();
101        let journal = base_evm.ctx_mut().journal_mut();
102        for address in base_code_sentinel_addresses(upgrade) {
103            if let Ok(account) = journal.load_account_with_code(address)
104                && account.info.code.as_ref().is_none_or(|code| code.is_empty())
105            {
106                journal.set_code_with_hash(address, sentinel.clone(), sentinel_hash);
107            }
108        }
109        base_evm
110    }
111
112    fn create_nested_evm_with_inspector<'db, I>(
113        &self,
114        db: &'db mut dyn DatabaseExt<Self>,
115        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
116        inspector: I,
117    ) -> NestedEvmFor<'db, Self>
118    where
119        I: FoundryInspectorExt<Self::FoundryContext<'db>> + 'db,
120    {
121        Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector))
122    }
123}
124
125fn map_base_error(error: EVMError<DatabaseError, BaseTransactionError>) -> EVMError<DatabaseError> {
126    match error {
127        EVMError::Database(db) => EVMError::Database(db),
128        EVMError::Header(header) => EVMError::Header(header),
129        EVMError::Custom(message) => EVMError::Custom(message),
130        EVMError::Transaction(transaction) => {
131            EVMError::Custom(format!("base transaction error: {transaction}"))
132        }
133        EVMError::CustomAny(error) => EVMError::CustomAny(error),
134    }
135}
136
137impl<'db, I: FoundryInspectorExt<BaseContext<&'db mut dyn DatabaseExt<BaseEvmFactory>>>> NestedEvm
138    for BaseRevmEvm<'db, I>
139{
140    type Spec = BaseSpecId;
141    type Block = BlockEnv;
142    type Tx = BaseTransaction<TxEnv>;
143    type Chain = L1BlockInfo;
144    type Journal = Journal<&'db mut dyn DatabaseExt<BaseEvmFactory>>;
145
146    fn journal_inner_mut(&mut self) -> &mut JournaledState {
147        &mut self.ctx_mut().journaled_state.inner
148    }
149
150    fn tx_mut(&mut self) -> &mut Self::Tx {
151        self.ctx_mut().tx_mut()
152    }
153
154    fn chain_mut(&mut self) -> &mut Self::Chain {
155        &mut self.ctx_mut().chain
156    }
157
158    fn journal_mut(&mut self) -> &mut Self::Journal {
159        &mut self.ctx_mut().journaled_state
160    }
161
162    fn precompiles_mut(&mut self) -> &mut PrecompilesMap {
163        Evm::precompiles_mut(self)
164    }
165
166    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
167        run_inspected_frame(self, BaseEvmHandler::<I>::new(), frame).map_err(map_base_error)
168    }
169
170    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState<HaltReason>> {
171        let ResultAndState { result, state } =
172            Evm::transact_raw(self, tx).map_err(map_base_error)?;
173        let result = result.map_haltreason(|halt| match halt {
174            BaseHaltReason::Base(eth) => eth,
175            BaseHaltReason::FailedDeposit => HaltReason::PrecompileError,
176        });
177        Ok(ResultAndState::new(result, state))
178    }
179
180    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
181        self.ctx_ref().evm_clone()
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use crate::{
189        backend::{Backend, CowBackend},
190        evm::with_inherited_evm,
191        opts::EvmOpts,
192    };
193    use alloy_sol_types::SolCall;
194    use base_common_precompiles::{
195        ActivationRegistryStorage, B20FactoryStorage, IActivationRegistry, NonceManagerStorage,
196        PolicyRegistryStorage, TxContextStorage,
197    };
198    use foundry_config::Config;
199    use foundry_evm_networks::NetworkConfigs;
200    use revm::{
201        ExecuteEvm, context::CfgEnv, inspector::NoOpInspector, primitives::TxKind,
202        state::AccountInfo,
203    };
204
205    fn base_env(chain_id: u64, upgrade: BaseUpgrade) -> EvmEnv<BaseSpecId, BlockEnv> {
206        let mut cfg = CfgEnv::new_with_spec(BaseSpecId::new(upgrade));
207        cfg.chain_id = chain_id;
208        EvmEnv::new(cfg, BlockEnv::default())
209    }
210
211    #[test]
212    fn failed_deposit_maps_to_stop() {
213        assert_eq!(
214            BaseHaltReason::FailedDeposit.into_instruction_result(),
215            InstructionResult::Stop
216        );
217    }
218
219    #[test]
220    fn constructor_resolves_activation_admin_and_preserves_override() {
221        let chain_admin =
222            ChainConfig::activation_admin_address_for_upgrade_by_chain_id(8453, BaseUpgrade::Beryl)
223                .unwrap();
224        let custom_admin = Address::repeat_byte(0xaa);
225        for (override_admin, expected_admin) in
226            [(None, chain_admin), (Some(custom_admin), custom_admin)]
227        {
228            let mut db = Backend::<BaseEvmNetwork>::spawn(None).unwrap();
229            let mut evm = BaseEvmFactory::new(override_admin).create_foundry_evm_with_inspector(
230                &mut db,
231                base_env(8453, BaseUpgrade::Beryl),
232                NoOpInspector,
233            );
234            let tx = BaseTransaction::builder()
235                .base(
236                    TxEnv::builder()
237                        .chain_id(Some(8453))
238                        .kind(TxKind::Call(ActivationRegistryStorage::ADDRESS))
239                        .data(Bytes::from(IActivationRegistry::adminCall {}.abi_encode()))
240                        .gas_limit(100_000),
241                )
242                .build_fill();
243            let result = evm.transact_one(tx).unwrap();
244            let admin =
245                IActivationRegistry::adminCall::abi_decode_returns(result.output().unwrap())
246                    .unwrap();
247            assert_eq!(admin, expected_admin);
248        }
249    }
250
251    #[tokio::test(flavor = "multi_thread")]
252    async fn fork_activation_admin_uses_source_chain_in_nested_deployment() {
253        let (_api, handle) =
254            anvil::spawn(anvil::NodeConfig::test().with_chain_id(Some(8453u64))).await;
255        let caller = handle.genesis_accounts().next().unwrap();
256        let expected =
257            ChainConfig::activation_admin_address_for_upgrade_by_chain_id(8453, BaseUpgrade::Beryl)
258                .unwrap();
259        for chain_id in [31337, 84532] {
260            let mut opts = EvmOpts {
261                fork_url: Some(handle.http_endpoint()),
262                networks: NetworkConfigs::with_base(),
263                ..Default::default()
264            };
265            opts.env.chain_id = Some(chain_id);
266            let fork = opts.get_fork(&Config::default(), 8453, Some(0)).unwrap();
267            let backend = Backend::<BaseEvmNetwork>::spawn(Some(fork)).unwrap();
268            assert_eq!(backend.active_fork_source_chain_id(), Some(8453));
269            let mut db = CowBackend::new_borrowed(&backend);
270            let env = base_env(chain_id, BaseUpgrade::Beryl);
271            let mut evm = BaseEvmFactory::default().create_foundry_evm_with_inspector(
272                &mut db,
273                env,
274                NoOpInspector,
275            );
276            let tx = BaseTransaction::builder()
277                .base(
278                    TxEnv::builder()
279                        .caller(caller)
280                        .chain_id(Some(chain_id))
281                        .kind(TxKind::Call(ActivationRegistryStorage::ADDRESS))
282                        .data(Bytes::from(IActivationRegistry::adminCall {}.abi_encode()))
283                        .gas_limit(100_000),
284                )
285                .build_fill();
286            let result = evm.transact_one(tx).unwrap();
287            assert_eq!(
288                IActivationRegistry::adminCall::abi_decode_returns(result.output().unwrap())
289                    .unwrap(),
290                expected
291            );
292            // Deploy initcode that calls admin() and returns it as the deployed code.
293            let mut initcode = vec![0x63];
294            initcode.extend(IActivationRegistry::adminCall::SELECTOR);
295            initcode
296                .extend([0x60, 0xe0, 0x1b, 0x5f, 0x52, 0x60, 0x20, 0x5f, 0x60, 0x04, 0x5f, 0x73]);
297            initcode.extend(ActivationRegistryStorage::ADDRESS.as_slice());
298            initcode.extend([0x5a, 0xfa, 0x50, 0x60, 0x20, 0x5f, 0xf3]);
299            with_inherited_evm::<BaseEvmFactory, _>(evm.ctx_mut(), NoOpInspector, &mut |nested| {
300                let result = nested
301                    .transact_raw(
302                        BaseTransaction::builder()
303                            .base(
304                                TxEnv::builder()
305                                    .caller(caller)
306                                    .chain_id(Some(chain_id))
307                                    .nonce(1)
308                                    .kind(TxKind::Create)
309                                    .data(Bytes::from(initcode.clone()))
310                                    .gas_limit(200_000),
311                            )
312                            .build_fill(),
313                    )
314                    .unwrap();
315                assert!(result.result.is_success(), "{:?}", result.result);
316                assert_eq!(
317                    IActivationRegistry::adminCall::abi_decode_returns(
318                        result.result.output().unwrap()
319                    )
320                    .unwrap(),
321                    expected
322                );
323                Ok(())
324            })
325            .unwrap();
326        }
327    }
328
329    #[test]
330    fn constructor_plants_only_active_registry_sentinels() {
331        for upgrade in [BaseUpgrade::Azul, BaseUpgrade::Beryl] {
332            let mut db = Backend::<BaseEvmNetwork>::spawn(None).unwrap();
333            let mut evm = BaseEvmFactory::default().create_foundry_evm_with_inspector(
334                &mut db,
335                base_env(8453, upgrade),
336                NoOpInspector,
337            );
338            for (address, registry) in [
339                (ActivationRegistryStorage::ADDRESS, true),
340                (PolicyRegistryStorage::ADDRESS, true),
341                (B20FactoryStorage::ADDRESS, false),
342                (NonceManagerStorage::ADDRESS, false),
343                (TxContextStorage::ADDRESS, false),
344            ] {
345                let expected = if upgrade == BaseUpgrade::Beryl && registry {
346                    Bytecode::new_legacy(Bytes::from_static(SYSTEM_PRECOMPILE_STUB))
347                } else {
348                    Bytecode::default()
349                };
350                let account = evm.ctx_mut().journal_mut().load_account_with_code(address).unwrap();
351                assert_eq!(
352                    account.info.code.as_ref().unwrap(),
353                    &expected,
354                    "{upgrade:?}: {address}"
355                );
356                assert_eq!(account.info.code_hash, expected.hash_slow());
357            }
358        }
359    }
360
361    #[test]
362    fn constructor_preserves_existing_registry_code() {
363        let address = ActivationRegistryStorage::ADDRESS;
364        let code = Bytecode::new_legacy(Bytes::from_static(&[0x60, 0x00, 0x00]));
365        let code_hash = code.hash_slow();
366        let mut db = Backend::<BaseEvmNetwork>::spawn(None).unwrap();
367        db.insert_account_info(
368            address,
369            AccountInfo { code_hash, code: Some(code.clone()), ..Default::default() },
370        );
371        let mut evm = BaseEvmFactory::default().create_foundry_evm_with_inspector(
372            &mut db,
373            base_env(8453, BaseUpgrade::Beryl),
374            NoOpInspector,
375        );
376        let account = evm.ctx_mut().journal_mut().load_account_with_code(address).unwrap();
377        assert_eq!(account.info.code.as_ref().unwrap(), &code);
378        assert_eq!(account.info.code_hash, code_hash);
379    }
380}