Skip to main content

foundry_evm_core/evm/
mod.rs

1use std::{fmt::Debug, ops::Deref};
2
3use crate::{
4    FoundryBlock, FoundryChain, FoundryContextExt, FoundryInspectorExt, FoundryJournal,
5    FoundryTransaction, FromAnyRpcTransaction,
6    backend::{DatabaseExt, JournaledState},
7};
8use alloy_consensus::{SignableTransaction, Signed, transaction::SignerRecoverable};
9use alloy_evm::{
10    EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, precompiles::PrecompilesMap,
11};
12use alloy_network::{Ethereum, Network};
13use alloy_primitives::{Address, Signature, U256};
14use alloy_rlp::Decodable;
15use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt};
16use foundry_config::ExecutionSpec;
17use foundry_fork_db::{DatabaseError, ForkBlockEnv};
18use revm::{
19    Database,
20    context::{
21        ContextTr, JournalTr,
22        result::{EVMError, HaltReason, ResultAndState},
23    },
24    handler::FrameResult,
25    inspector::NoOpInspector,
26    interpreter::{
27        CallInput, CallInputs, CallScheme, CallValue, CreateInputs, FrameInput, InstructionResult,
28    },
29    primitives::hardfork::SpecId,
30};
31use serde::{Deserialize, Serialize};
32use tempo_alloy::TempoNetwork;
33use tempo_evm::evm::TempoEvmFactory;
34use tempo_revm::TempoHaltReason;
35
36pub mod eth;
37#[cfg(feature = "monad")]
38pub mod monad;
39#[cfg(feature = "optimism")]
40pub mod op;
41pub mod tempo;
42
43mod block_context;
44pub use block_context::*;
45
46pub use eth::*;
47#[cfg(feature = "monad")]
48pub use monad::*;
49#[cfg(feature = "optimism")]
50pub use op::*;
51pub use tempo::*;
52
53/// Foundry's compatibility trait associating a [`Network`] with a [`FoundryEvmFactory`].
54pub trait FoundryEvmNetwork: Copy + Debug + Default + 'static {
55    type Network: Network<
56            TxEnvelope: Decodable
57                            + SignerRecoverable
58                            + From<Signed<<Self::Network as Network>::UnsignedTx>>
59                            + for<'d> Deserialize<'d>
60                            + Serialize
61                            + UIfmt,
62            UnsignedTx: SignableTransaction<Signature>,
63            TransactionRequest: FoundryTransactionBuilder<Self::Network>
64                                    + for<'d> Deserialize<'d>
65                                    + Serialize,
66            ReceiptResponse: FoundryReceiptResponse,
67        >;
68    type EvmFactory: FoundryEvmFactory<Tx: FromRecoveredTx<<Self::Network as Network>::TxEnvelope>>;
69}
70
71#[derive(Clone, Copy, Debug, Default)]
72pub struct EthEvmNetwork;
73impl FoundryEvmNetwork for EthEvmNetwork {
74    type Network = Ethereum;
75    type EvmFactory = EthEvmFactory;
76}
77
78#[derive(Clone, Copy, Debug, Default)]
79pub struct TempoEvmNetwork;
80impl FoundryEvmNetwork for TempoEvmNetwork {
81    type Network = TempoNetwork;
82    type EvmFactory = TempoEvmFactory;
83}
84
85#[derive(Clone, Copy, Debug, Default)]
86#[cfg(feature = "monad")]
87pub struct MonadEvmNetwork;
88#[cfg(feature = "monad")]
89impl FoundryEvmNetwork for MonadEvmNetwork {
90    type Network = Ethereum;
91    type EvmFactory = alloy_monad_evm::MonadEvmFactory;
92}
93
94/// Convenience type aliases for accessing associated types through [`FoundryEvmNetwork`].
95pub type EvmFactoryFor<FEN> = <FEN as FoundryEvmNetwork>::EvmFactory;
96pub type FoundryContextFor<'db, FEN> =
97    <EvmFactoryFor<FEN> as FoundryEvmFactory>::FoundryContext<'db>;
98pub type TxEnvFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::Tx;
99pub type HaltReasonFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::HaltReason;
100pub type SpecFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::Spec;
101pub type BlockEnvFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::BlockEnv;
102pub type PrecompilesFor<FEN> = <EvmFactoryFor<FEN> as EvmFactory>::Precompiles;
103pub type EvmEnvFor<FEN> = EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>;
104pub type NetworkFor<FEN> = <FEN as FoundryEvmNetwork>::Network;
105pub type TxEnvelopeFor<FEN> = <NetworkFor<FEN> as Network>::TxEnvelope;
106pub type TransactionRequestFor<FEN> = <NetworkFor<FEN> as Network>::TransactionRequest;
107pub type TransactionResponseFor<FEN> = <NetworkFor<FEN> as Network>::TransactionResponse;
108pub type BlockResponseFor<FEN> = <NetworkFor<FEN> as Network>::BlockResponse;
109
110pub type ChainFor<FEN> = <EvmFactoryFor<FEN> as FoundryEvmFactory>::Chain;
111
112/// Boxed nested EVM produced by a Foundry EVM factory.
113pub type NestedEvmFor<'db, F> = Box<
114    dyn NestedEvm<
115            Spec = <F as EvmFactory>::Spec,
116            Block = <F as EvmFactory>::BlockEnv,
117            Tx = <F as EvmFactory>::Tx,
118            Chain = <F as FoundryEvmFactory>::Chain,
119            Journal = <<F as FoundryEvmFactory>::FoundryContext<'db> as ContextTr>::Journal,
120        > + 'db,
121>;
122
123pub trait FoundryEvmFactory:
124    EvmFactory<
125        Spec: Into<SpecId> + ExecutionSpec + Default + Copy + Unpin + Send + 'static,
126        BlockEnv: FoundryBlock + ForkBlockEnv + Default + Unpin,
127        Tx: Clone + Debug + FoundryTransaction + FromAnyRpcTransaction + Default + Send + Sync,
128        HaltReason: IntoInstructionResult,
129        Precompiles = PrecompilesMap,
130    > + Clone
131    + Debug
132    + Default
133    + 'static
134{
135    /// Chain type for EVM's context created by this factory.
136    type Chain: FoundryChain<Self::Tx>;
137
138    /// Foundry Context abstraction
139    type FoundryContext<'db>: FoundryContextExt<
140            Block = Self::BlockEnv,
141            Tx = Self::Tx,
142            Spec = Self::Spec,
143            Chain = Self::Chain,
144            Journal: FoundryJournal,
145            Db: DatabaseExt<Self>,
146        >
147    where
148        Self: 'db;
149
150    /// The Foundry-wrapped EVM type produced by this factory.
151    type FoundryEvm<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>: Evm<
152            DB = &'db mut dyn DatabaseExt<Self>,
153            Tx = Self::Tx,
154            BlockEnv = Self::BlockEnv,
155            Spec = Self::Spec,
156            HaltReason = Self::HaltReason,
157        > + Deref<Target = Self::FoundryContext<'db>>
158    where
159        Self: 'db;
160
161    /// Creates a Foundry-wrapped EVM with the given inspector.
162    fn create_foundry_evm_with_inspector<'db, I: FoundryInspectorExt<Self::FoundryContext<'db>>>(
163        &self,
164        db: &'db mut dyn DatabaseExt<Self>,
165        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
166        chain_context: Self::Chain,
167        inspector: I,
168    ) -> Self::FoundryEvm<'db, I>;
169
170    /// Tries to execute a canonical system transaction on a regular Alloy EVM during replay.
171    ///
172    /// Returning `Ok(None)` means the transaction was not recognized. Implementations must not
173    /// mutate the EVM, its database, or inspector before returning `Ok(None)`, because callers may
174    /// fall back to ordinary execution using the same EVM instance.
175    #[cfg(feature = "monad")]
176    fn try_transact_system_replay<DB, I>(
177        &self,
178        _evm: &mut Self::Evm<DB, I>,
179        _tx: &Self::Tx,
180    ) -> eyre::Result<Option<ResultAndState<Self::HaltReason>>>
181    where
182        DB: alloy_evm::Database,
183        I: revm::inspector::Inspector<Self::Context<DB>>,
184    {
185        Ok(None)
186    }
187
188    /// Creates an uninspected EVM with explicit transaction-position context.
189    fn create_evm_with_context<DB: alloy_evm::Database>(
190        &self,
191        db: DB,
192        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
193        chain_context: Self::Chain,
194    ) -> Self::Evm<DB, NoOpInspector>;
195
196    /// Creates a Foundry-wrapped EVM with a dynamic inspector, returning a boxed [`NestedEvm`].
197    ///
198    /// This helper exists because `&mut dyn FoundryInspectorExt<FoundryContext>` cannot satisfy
199    /// the generic `I: FoundryInspectorExt<Self::FoundryContext<'db>>` bound when the context
200    /// type is only known through an associated type.  Each concrete factory implements this
201    /// directly, side-stepping the higher-kinded lifetime issue.
202    fn create_foundry_nested_evm<'db>(
203        &self,
204        db: &'db mut dyn DatabaseExt<Self>,
205        evm_env: EvmEnv<Self::Spec, Self::BlockEnv>,
206        chain_context: Self::Chain,
207        inspector: &'db mut dyn FoundryInspectorExt<Self::FoundryContext<'db>>,
208    ) -> NestedEvmFor<'db, Self>;
209}
210
211/// Object-safe trait exposing the operations that cheatcode nested EVM closures need.
212///
213/// This abstracts over the concrete EVM type (`FoundryEvm`, future `TempoEvm`, etc.)
214/// so that cheatcode impls can build and run nested EVMs without knowing the concrete type.
215pub trait NestedEvm {
216    /// The spec type.
217    type Spec;
218    /// The block environment type.
219    type Block;
220    /// The transaction environment type.
221    type Tx: FoundryTransaction;
222    /// Chain context identifying the active transaction position.
223    type Chain: FoundryChain<Self::Tx>;
224    /// The Journal type, which may own Monad's reserve-balance-tracker state.
225    type Journal: FoundryJournal;
226    /// Returns a mutable reference to the journal inner state (`JournaledState`).
227    fn journal_inner_mut(&mut self) -> &mut JournaledState;
228
229    /// Returns a mutable reference to the transaction environment.
230    fn tx_mut(&mut self) -> &mut Self::Tx;
231
232    /// Returns a mutable reference to the chain-position context.
233    fn chain_mut(&mut self) -> &mut Self::Chain;
234
235    /// Returns a mutable reference to the Journal.
236    fn journal_mut(&mut self) -> &mut Self::Journal;
237
238    /// Runs a single execution frame (create or call) through the EVM handler loop.
239    fn run_execution(&mut self, frame: FrameInput) -> Result<FrameResult, EVMError<DatabaseError>>;
240
241    /// Executes a full transaction with the given tx env.
242    fn transact_raw(&mut self, tx: Self::Tx) -> eyre::Result<ResultAndState<HaltReason>>;
243
244    fn to_evm_env(&self) -> EvmEnv<Self::Spec, Self::Block>;
245}
246
247/// Closure type used by `CheatcodesExecutor` methods that run nested EVM operations.
248pub type NestedEvmClosure<'a, F> = &'a mut dyn for<'j> FnMut(
249    &mut dyn NestedEvm<
250        Spec = <F as EvmFactory>::Spec,
251        Block = <F as EvmFactory>::BlockEnv,
252        Tx = <F as EvmFactory>::Tx,
253        Chain = <F as FoundryEvmFactory>::Chain,
254        Journal = <<F as FoundryEvmFactory>::FoundryContext<'j> as ContextTr>::Journal,
255    >,
256)
257    -> Result<(), EVMError<DatabaseError>>;
258
259/// Nested EVM closure for a Foundry EVM network.
260pub type NestedEvmClosureFor<'a, FEN> = NestedEvmClosure<'a, EvmFactoryFor<FEN>>;
261
262/// Clones the current context (env + journal), passes the database, cloned env,
263/// and cloned journal inner to the callback. The callback builds whatever EVM it
264/// needs, runs its operations, and returns `(result, modified_env, modified_journal)`.
265/// Modified state is written back after the callback returns.
266pub fn with_cloned_context<CTX: FoundryContextExt>(
267    ecx: &mut CTX,
268    f: impl FnOnce(
269        &mut CTX::Db,
270        EvmEnv<CTX::Spec, CTX::Block>,
271        JournaledState,
272    )
273        -> Result<(EvmEnv<CTX::Spec, CTX::Block>, JournaledState), EVMError<DatabaseError>>,
274) -> Result<(), EVMError<DatabaseError>> {
275    let evm_env = ecx.evm_clone();
276    let (db, journal_inner) = ecx.db_journal_inner_mut();
277    let journal_inner = journal_inner.clone();
278
279    let (sub_evm_env, sub_inner) = f(db, evm_env, journal_inner)?;
280
281    // Write back modified state. The db borrow was released when f returned.
282    ecx.set_journal_inner(sub_inner);
283    ecx.set_evm(sub_evm_env);
284
285    Ok(())
286}
287
288/// Get the call inputs for the CREATE2 factory.
289pub fn get_create2_factory_call_inputs<T: JournalTr>(
290    salt: U256,
291    inputs: &CreateInputs,
292    deployer: Address,
293    journal: &mut T,
294) -> Result<CallInputs, <T::Database as Database>::Error> {
295    let calldata = [&salt.to_be_bytes::<32>()[..], &inputs.init_code()[..]].concat();
296    let account = journal.load_account_with_code(deployer)?;
297    Ok(CallInputs {
298        caller: inputs.caller(),
299        bytecode_address: deployer,
300        known_bytecode: (account.info.code_hash, account.info.code.clone().unwrap_or_default()),
301        target_address: deployer,
302        scheme: CallScheme::Call,
303        value: CallValue::Transfer(inputs.value()),
304        input: CallInput::Bytes(calldata.into()),
305        gas_limit: inputs.gas_limit(),
306        reservoir: inputs.reservoir(),
307        is_static: false,
308        return_memory_offset: 0..0,
309        charged_new_account_state_gas: false,
310    })
311}
312
313/// Converts a network-specific halt reason into an [`InstructionResult`].
314pub trait IntoInstructionResult {
315    fn into_instruction_result(self) -> InstructionResult;
316}
317
318impl IntoInstructionResult for HaltReason {
319    fn into_instruction_result(self) -> InstructionResult {
320        self.into()
321    }
322}
323
324impl IntoInstructionResult for TempoHaltReason {
325    fn into_instruction_result(self) -> InstructionResult {
326        match self {
327            Self::Ethereum(eth) => eth.into(),
328            _ => InstructionResult::PrecompileError,
329        }
330    }
331}