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, 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,
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
46impl IntoInstructionResult for OpHaltReason {
47    fn into_instruction_result(self) -> InstructionResult {
48        match self {
49            Self::Base(eth) => eth.into(),
50            Self::FailedDeposit => InstructionResult::Stop,
51        }
52    }
53}
54
55impl FoundryEvmFactory for OpEvmFactory {
56    type Chain = L1BlockInfo;
57    type FoundryContext<'db> = OpEvmContext<&'db mut dyn DatabaseExt<Self>>;
58
59    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>> =
60        OpEvm<&'db mut dyn DatabaseExt<Self>, I, Self::Precompiles>;
61
62    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
63        &self,
64        db: &'db mut dyn DatabaseExt<Self>,
65        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
66        inspector: I,
67    ) -> Self::FoundryEvm<'db, I> {
68        let mut op_evm = Self::default().create_evm_with_inspector(db, evm_env, inspector);
69        op_evm.cfg.tx_chain_id_check = true;
70        op_evm
71    }
72
73    fn create_nested_evm_with_inspector<'db, I>(
74        &self,
75        db: &'db mut dyn DatabaseExt<Self>,
76        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
77        inspector: I,
78    ) -> NestedEvmFor<'db, Self>
79    where
80        I: FoundryInspectorExt<Self::FoundryContext<'db>> + 'db,
81    {
82        Box::new(self.create_foundry_evm_with_inspector(db, evm_env, inspector).into_inner())
83    }
84}
85
86impl<'db, I: FoundryInspectorExt<OpEvmContext<&'db mut dyn DatabaseExt<OpEvmFactory>>>> NestedEvm
87    for OpRevmEvm<'db, I>
88{
89    type Spec = OpSpecId;
90    type Block = BlockEnv;
91    type Tx = OpTx;
92    type Chain = L1BlockInfo;
93    type Journal = Journal<&'db mut dyn DatabaseExt<OpEvmFactory>>;
94
95    fn tx_mut(&mut self) -> &mut Self::Tx {
96        self.ctx_mut().tx_mut()
97    }
98
99    fn journal_inner_mut(&mut self) -> &mut JournaledState {
100        &mut self.ctx().journaled_state.inner
101    }
102
103    fn chain_mut(&mut self) -> &mut Self::Chain {
104        &mut self.ctx_mut().chain
105    }
106
107    fn precompiles_mut(&mut self) -> &mut alloy_evm::precompiles::PrecompilesMap {
108        &mut self.0.precompiles
109    }
110
111    fn journal_mut(&mut self) -> &mut Self::Journal {
112        &mut self.ctx_mut().journaled_state
113    }
114
115    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>> {
116        run_inspected_frame(self, OpEvmHandler::<I>::new(), frame).map_err(map_op_error)
117    }
118
119    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState<HaltReason>> {
120        self.ctx().set_tx(tx);
121
122        let mut handler = OpEvmHandler::<I>::new();
123        let result = handler.inspect_run(self).map_err(map_op_error)?;
124
125        let result = result.map_haltreason(|h| match h {
126            OpHaltReason::Base(eth) => eth,
127            _ => HaltReason::PrecompileError,
128        });
129
130        Ok(ResultAndState::new(result, self.ctx_ref().journaled_state.inner.state.clone()))
131    }
132
133    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block> {
134        self.ctx_ref().evm_clone()
135    }
136}
137
138/// Maps an OP [`EVMError`] to the common `EVMError<DatabaseError>` used by [`NestedEvm`].
139fn map_op_error(e: EVMError<DatabaseError, OpTransactionError>) -> EVMError<DatabaseError> {
140    match e {
141        EVMError::Database(db) => EVMError::Database(db),
142        EVMError::Header(h) => EVMError::Header(h),
143        EVMError::Custom(s) => EVMError::Custom(s),
144        EVMError::Transaction(t) => EVMError::Custom(format!("op transaction error: {t}")),
145        EVMError::CustomAny(custom_any_error) => EVMError::CustomAny(custom_any_error),
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::{backend::Backend, evm::EvmEnvFor};
153    use alloy_primitives::{Address, TxKind, U256};
154    use op_revm::constants::L1_FEE_RECIPIENT;
155    use revm::{
156        context::{ContextTr, TxEnv},
157        inspector::NoOpInspector,
158        state::AccountInfo,
159    };
160
161    #[test]
162    fn constructors_allow_restoring_l1_block_context() {
163        let factory = OpEvmFactory::default();
164        let mut db = Backend::<OpEvmNetwork>::spawn(None).unwrap();
165        let chain = L1BlockInfo {
166            l2_block: Some(U256::from(42)),
167            l1_base_fee: U256::from(123),
168            tx_l1_cost: Some(U256::from(456)),
169            ..Default::default()
170        };
171        {
172            let mut evm = factory.create_foundry_evm_with_inspector(
173                &mut db,
174                EvmEnvFor::<OpEvmNetwork>::default(),
175                NoOpInspector,
176            );
177            *evm.chain_mut() = chain.clone();
178            assert_eq!(evm.chain().l2_block, chain.l2_block);
179            assert_eq!(evm.chain().l1_base_fee, chain.l1_base_fee);
180            assert_eq!(evm.chain().tx_l1_cost, chain.tx_l1_cost);
181        }
182        let mut evm = factory.create_nested_evm_with_inspector(
183            &mut db,
184            EvmEnvFor::<OpEvmNetwork>::default(),
185            NoOpInspector,
186        );
187        *evm.chain_mut() = chain.clone();
188        assert_eq!(evm.chain_mut().l2_block, chain.l2_block);
189        assert_eq!(evm.chain_mut().l1_base_fee, chain.l1_base_fee);
190        assert_eq!(evm.chain_mut().tx_l1_cost, chain.tx_l1_cost);
191    }
192
193    #[test]
194    fn nested_execution_uses_restored_l1_block_context() {
195        let caller = Address::with_last_byte(0x42);
196        let recipient = Address::with_last_byte(0x43);
197        let l1_cost = U256::from(456);
198        let mut db = Backend::<OpEvmNetwork>::spawn(None).unwrap();
199        db.insert_account_info(caller, AccountInfo { balance: U256::MAX, ..Default::default() });
200
201        let mut evm_env = EvmEnvFor::<OpEvmNetwork>::default();
202        evm_env.cfg_env.spec = OpSpecId::REGOLITH;
203        evm_env.cfg_env.disable_fee_charge = false;
204        let mut evm = OpEvmFactory::default().create_nested_evm(&mut db, evm_env);
205        *evm.chain_mut() = L1BlockInfo {
206            l2_block: Some(U256::ZERO),
207            tx_l1_cost: Some(l1_cost),
208            ..Default::default()
209        };
210        let tx = OpTx(op_revm::OpTransaction {
211            base: TxEnv {
212                caller,
213                gas_limit: 21_000,
214                kind: TxKind::Call(recipient),
215                ..Default::default()
216            },
217            enveloped_tx: Some(Default::default()),
218            deposit: Default::default(),
219        });
220
221        let result = evm.transact_raw(tx).unwrap();
222
223        assert!(result.result.is_success());
224        assert_eq!(result.state[&L1_FEE_RECIPIENT].info.balance, l1_cost);
225    }
226}