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;
18use 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#[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
59pub 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 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 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 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}