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
138fn 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}