Skip to main content

foundry_evm_core/evm/
op.rs

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
147/// Maps an OP [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
148fn 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}