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;
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 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 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 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 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}