1use alloy_evm::{EvmEnv, EvmFactory, precompiles::PrecompilesMap};
2use alloy_op_evm::{OpEvm, OpEvmContext, OpEvmFactory, OpTx};
3use foundry_fork_db::DatabaseError;
4use op_alloy_network::Optimism;
5use op_revm::{
6 L1BlockInfo, OpEvm as RevmEvm, OpHaltReason, OpSpecId, OpTransactionError, handler::OpHandler,
7};
8use revm::{
9 context::{
10 BlockEnv, ContextTr, Journal,
11 result::{EVMError, HaltReason, ResultAndState},
12 },
13 handler::{EthFrame, EvmTr, FrameResult, instructions::EthInstructions},
14 inspector::InspectorHandler,
15 interpreter::{FrameInput, InstructionResult, interpreter::EthInterpreter},
16};
17
18use crate::{
19 FoundryChain, FoundryContextExt, FoundryInspectorExt, FoundryTransaction,
20 backend::{DatabaseExt, JournaledState},
21 evm::{
22 FoundryEvmFactory, FoundryEvmNetwork, IntoInstructionResult, NestedEvm, NestedEvmFor,
23 run_inspected_frame,
24 },
25};
26
27#[derive(Clone, Copy, Debug, Default)]
28pub struct OpEvmNetwork;
29impl FoundryEvmNetwork for OpEvmNetwork {
30 type Network = Optimism;
31 type EvmFactory = OpEvmFactory;
32}
33
34type OpEvmHandler<'db, I> =
35 OpHandler<OpRevmEvm<'db, I>, EVMError<DatabaseError, OpTransactionError>, EthFrame>;
36
37pub type OpRevmEvm<'db, I> = RevmEvm<
38 OpEvmContext<&'db mut dyn DatabaseExt<OpEvmFactory>>,
39 I,
40 EthInstructions<EthInterpreter, OpEvmContext<&'db mut dyn DatabaseExt<OpEvmFactory>>>,
41 PrecompilesMap,
42>;
43
44impl FoundryChain<OpTx> for L1BlockInfo {
45 fn clear_transaction_fee_cache(&mut self) {
46 self.clear_tx_l1_cost();
47 }
48}
49
50impl IntoInstructionResult for OpHaltReason {
51 fn into_instruction_result(self) -> InstructionResult {
52 match self {
53 Self::Base(eth) => eth.into(),
54 Self::FailedDeposit => InstructionResult::Stop,
55 }
56 }
57}
58
59impl FoundryEvmFactory for OpEvmFactory {
60 type Chain = L1BlockInfo;
61 type FoundryContext<'db> = OpEvmContext<&'db mut dyn DatabaseExt<Self>>;
62
63 type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> =
64 OpEvm<&'db mut dyn DatabaseExt<Self>, I, Self::Precompiles>;
65
66 fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
67 &self,
68 db: &'db mut dyn DatabaseExt<Self>,
69 evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
70 inspector: I,
71 ) -> Self::FoundryEvm<'db, I> {
72 let mut op_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector);
73 op_evm.cfg.tx_chain_id_check = true;
74 op_evm
75 }
76
77 fn create_nested_evm_with_inspector<'db, I>(
78 &self,
79 db: &'db mut dyn DatabaseExt<Self>,
80 evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
81 inspector: I,
82 ) -> NestedEvmFor<'db, Self>
83 where
84 I: FoundryInspectorExt<Self::FoundryContext<'db>> + 'db,
85 {
86 Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner())
87 }
88}
89
90impl<'db, I: FoundryInspectorExt<OpEvmContext<&'db mut dyn DatabaseExt<OpEvmFactory>>>> NestedEvm
91 for OpRevmEvm<'db, I>
92{
93 type Spec = OpSpecId;
94 type Block = BlockEnv;
95 type Tx = OpTx;
96 type Chain = L1BlockInfo;
97 type Journal = Journal<&'db mut dyn DatabaseExt<OpEvmFactory>>;
98
99 fn tx_mut(&mut self) -> &mut Self::Tx {
100 self.ctx_mut().tx_mut()
101 }
102
103 fn journal_inner_mut(&mut self) -> &mut JournaledState {
104 &mut self.ctx().journaled_state.inner
105 }
106
107 fn chain_mut(&mut self) -> &mut Self::Chain {
108 &mut self.ctx_mut().chain
109 }
110
111 fn precompiles_mut(&mut self) -> &mut alloy_evm::precompiles::PrecompilesMap {
112 &mut self.0.precompiles
113 }
114
115 fn journal_mut(&mut self) -> &mut Self::Journal {
116 &mut self.ctx_mut().journaled_state
117 }
118
119 fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
120 run_inspected_frame(self, OpEvmHandler::<I>::new(), frame).map_err(map_op_error)
121 }
122
123 fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState<HaltReason>> {
124 if self.ctx().cfg().disable_fee_charge
125 && tx.enveloped_tx().is_some_and(|enveloped| enveloped.is_empty())
126 {
127 self.ctx_mut().chain_mut().clear_transaction_fee_cache();
128 }
129 self.ctx().set_tx(tx);
130
131 let mut handler = OpEvmHandler::<I>::new();
132 let result = handler.inspect_run(self).map_err(map_op_error)?;
133
134 let result = result.map_haltreason(|h| match h {
135 OpHaltReason::Base(eth) => eth,
136 _ => HaltReason::PrecompileError,
137 });
138
139 Ok(ResultAndState::new(result, self.ctx_ref().journaled_state.inner.state.clone()))
140 }
141
142 fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
143 self.ctx_ref().evm_clone()
144 }
145}
146
147fn map_op_error(e: EVMError<DatabaseError, OpTransactionError>) -> EVMError<DatabaseError> {
149 match e {
150 EVMError::Database(db) => EVMError::Database(db),
151 EVMError::Header(h) => EVMError::Header(h),
152 EVMError::Custom(s) => EVMError::Custom(s),
153 EVMError::Transaction(t) => EVMError::Custom(format!("op transaction error: {t}")),
154 EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::backend::Backend;
162 use alloy_primitives::{Address, Bytes, TxKind, U256};
163 use op_revm::constants::L1_FEE_RECIPIENT;
164 use revm::{context::TxEnv, inspector::NoOpInspector, state::AccountInfo};
165
166 #[test]
167 fn constructors_allow_restoring_l1_block_context() {
168 let factory = OpEvmFactory::default();
169 let mut db = Backend::<OpEvmNetwork>::spawn(None).unwrap();
170 let chain = L1BlockInfo {
171 l2_block: Some(U256::from(42)),
172 l1_base_fee: U256::from(123),
173 tx_l1_cost: Some(U256::from(456)),
174 ..Default::default()
175 };
176 {
177 let mut evm = factory.create_foundry_evm_with_inspector(
178 &mut db,
179 EvmEnv::<OpSpecId>::default(),
180 NoOpInspector,
181 );
182 *evm.chain_mut() = chain.clone();
183 assert_eq!(evm.chain().l2_block, chain.l2_block);
184 assert_eq!(evm.chain().l1_base_fee, chain.l1_base_fee);
185 assert_eq!(evm.chain().tx_l1_cost, chain.tx_l1_cost);
186 }
187 let mut evm = factory.create_nested_evm_with_inspector(
188 &mut db,
189 EvmEnv::<OpSpecId>::default(),
190 NoOpInspector,
191 );
192 *evm.chain_mut() = chain.clone();
193 assert_eq!(evm.chain_mut().l2_block, chain.l2_block);
194 assert_eq!(evm.chain_mut().l1_base_fee, chain.l1_base_fee);
195 assert_eq!(evm.chain_mut().tx_l1_cost, chain.tx_l1_cost);
196 }
197
198 #[test]
199 fn nested_execution_uses_restored_l1_block_context() {
200 let caller = Address::with_last_byte(0x42);
201 let recipient = Address::with_last_byte(0x43);
202 let l1_cost = U256::from(456);
203 let mut db = Backend::<OpEvmNetwork>::spawn(None).unwrap();
204 db.insert_account_info(caller, AccountInfo { balance: U256::MAX, ..Default::default() });
205
206 let mut evm_env = EvmEnv::<OpSpecId>::default();
207 evm_env.cfg_env.spec = OpSpecId::REGOLITH;
208 evm_env.cfg_env.disable_fee_charge = false;
209 let mut evm = OpEvmFactory::default().create_nested_evm(&mut db, evm_env);
210 *evm.chain_mut() = L1BlockInfo {
211 l2_block: Some(U256::ZERO),
212 tx_l1_cost: Some(l1_cost),
213 ..Default::default()
214 };
215 let tx = OpTx(op_revm::OpTransaction {
216 base: TxEnv {
217 caller,
218 gas_limit: 21_000,
219 kind: TxKind::Call(recipient),
220 ..Default::default()
221 },
222 enveloped_tx: Some(Default::default()),
223 deposit: Default::default(),
224 });
225
226 let result = evm.transact_raw(tx).unwrap();
227
228 assert!(result.result.is_success());
229 assert_eq!(result.state[&L1_FEE_RECIPIENT].info.balance, l1_cost);
230 }
231
232 #[test]
233 fn nested_execution_skips_cached_l1_cost_when_fees_disabled() {
234 let caller = Address::with_last_byte(0x42);
235 let recipient = Address::with_last_byte(0x43);
236 let mut db = Backend::<OpEvmNetwork>::spawn(None).unwrap();
237 db.insert_account_info(caller, AccountInfo { balance: U256::MAX, ..Default::default() });
238
239 let mut evm_env = EvmEnv::<OpSpecId>::default();
240 evm_env.cfg_env.spec = OpSpecId::REGOLITH;
241 evm_env.cfg_env.disable_fee_charge = true;
242 let mut evm = OpEvmFactory::default().create_nested_evm(&mut db, evm_env);
243 *evm.chain_mut() = L1BlockInfo {
244 l2_block: Some(U256::ZERO),
245 tx_l1_cost: Some(U256::from(456)),
246 ..Default::default()
247 };
248 let tx = OpTx(op_revm::OpTransaction {
249 base: TxEnv {
250 caller,
251 gas_limit: 21_000,
252 kind: TxKind::Call(recipient),
253 ..Default::default()
254 },
255 enveloped_tx: Some(Bytes::new()),
256 deposit: Default::default(),
257 });
258
259 let result = evm.transact_raw(tx).unwrap();
260
261 assert!(result.result.is_success());
262 assert_eq!(result.state[&caller].info.balance, U256::MAX);
263 assert_eq!(result.state[&L1_FEE_RECIPIENT].info.balance, U256::ZERO);
264 }
265}