Skip to main content

foundry_cheatcodes/
inspector.rs

1//! Cheatcode EVM inspector.
2
3#[cfg(feature = "monad")]
4use crate::monad::{apply_monad_cheatcode as apply_monad_cheatcode_call, is_monad_cheatcode_call};
5use crate::{
6    Cheatcode, CheatsConfig, CheatsCtxt, Error, Result,
7    Vm::{self, AccountAccess},
8    evm::{
9        DealRecord, GasRecord, RecordAccess, journaled_account,
10        mock::{MockCallDataContext, MockCallReturnData},
11        prank::Prank,
12    },
13    inspector::utils::CommonCreateInput,
14    script::{Broadcast, Wallets},
15    test::{
16        assume::AssumeNoRevert,
17        expect::{
18            self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedCreate,
19            ExpectedEmitTracker, ExpectedRevert, ExpectedRevertKind,
20        },
21        revert_handlers,
22    },
23    utils::IgnoredTraces,
24};
25use alloy_consensus::BlobTransactionSidecarVariant;
26use alloy_network::{Ethereum, Network, TransactionBuilder};
27use alloy_primitives::{
28    Address, B256, Bytes, Log, TxKind, U256, hex,
29    map::{AddressHashMap, HashMap, HashSet},
30};
31use alloy_rpc_types::AccessList;
32use alloy_signer_local::PrivateKeySigner;
33use alloy_sol_types::{SolCall, SolInterface, SolValue};
34use foundry_common::{
35    FoundryTransactionBuilder, SELECTOR_LEN, TransactionMaybeSigned,
36    mapping_slots::{
37        MappingSlots, PendingMappingHash, capture_hash as capture_mapping_hash,
38        record_hash as record_mapping_hash, step as mapping_step,
39    },
40};
41use foundry_evm_core::{
42    Breakpoints, EvmEnv, FoundryTransaction, InspectorExt,
43    abi::Vm::stopExpectSafeMemoryCall,
44    backend::{ContextUpdate, DatabaseError, DatabaseExt, LocalForkId, RevertDiagnostic},
45    constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME},
46    env::FoundryContextExt,
47    evm::{
48        BlockEnvFor, ChainContextFor, EthEvmNetwork, FoundryContextFor, FoundryEvmFactory,
49        FoundryEvmNetwork, NestedEvmClosureFor, SpecFor, TransactionRequestFor,
50        TransactionStateFor, TxEnvFor, with_cloned_context,
51    },
52};
53use foundry_evm_traces::{
54    TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier,
55};
56use foundry_wallets::wallet_multi::MultiWallet;
57use itertools::Itertools;
58use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner};
59use rand::Rng;
60use revm::{
61    Inspector, JournalEntry,
62    bytecode::opcode as op,
63    context::{Cfg, ContextTr, Host, JournalTr, Transaction, TransactionType, result::EVMError},
64    context_interface::{CreateScheme, transaction::SignedAuthorization},
65    handler::FrameResult,
66    interpreter::{
67        CallInput, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome,
68        FrameInput, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
69        interpreter_types::{Jumps, LoopControl, MemoryTr, ReturnData},
70        return_ok,
71    },
72};
73use serde_json::Value;
74use std::{
75    cmp::max,
76    collections::{BTreeMap, VecDeque},
77    fmt::Debug,
78    fs::File,
79    io::BufReader,
80    ops::Range,
81    path::PathBuf,
82    sync::{Arc, OnceLock},
83};
84
85mod utils;
86
87pub mod analysis;
88pub use analysis::CheatcodeAnalysis;
89
90/// Helper trait for running nested EVM operations from inside cheatcode implementations.
91pub trait CheatcodesExecutor<FEN: FoundryEvmNetwork> {
92    /// Runs a closure with a nested EVM built from the current context.
93    /// The inspector is assembled internally — never exposed to the caller.
94    fn with_nested_evm(
95        &mut self,
96        cheats: &mut Cheatcodes<FEN>,
97        ecx: &mut FoundryContextFor<'_, FEN>,
98        f: NestedEvmClosureFor<'_, FEN>,
99    ) -> Result<(), EVMError<DatabaseError>>;
100
101    /// Replays a historical transaction on the database. Inspector is assembled internally.
102    fn transact_on_db(
103        &mut self,
104        cheats: &mut Cheatcodes<FEN>,
105        ecx: &mut FoundryContextFor<'_, FEN>,
106        fork_id: Option<U256>,
107        transaction: B256,
108    ) -> eyre::Result<ContextUpdate<ChainContextFor<FEN>>>;
109
110    /// Executes a `TransactionRequest` on the database. Inspector is assembled internally.
111    fn transact_from_tx_on_db(
112        &mut self,
113        cheats: &mut Cheatcodes<FEN>,
114        ecx: &mut FoundryContextFor<'_, FEN>,
115        tx: TxEnvFor<FEN>,
116    ) -> eyre::Result<()>;
117
118    /// Runs a closure with a fresh nested EVM built from a raw database and environment.
119    /// Unlike `with_nested_evm`, this does NOT clone from `ecx` and does NOT write back.
120    /// The caller is responsible for state merging. Used by `executeTransactionCall`.
121    /// Returns the final EVM environment after the closure runs (consumed without cloning).
122    #[allow(clippy::type_complexity)]
123    fn with_fresh_nested_evm(
124        &mut self,
125        cheats: &mut Cheatcodes<FEN>,
126        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
127        evm_env: EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>,
128        chain_context: ChainContextFor<FEN>,
129        f: NestedEvmClosureFor<'_, FEN>,
130    ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>>;
131
132    /// Simulates `console.log` invocation.
133    fn console_log(&mut self, msg: &str);
134
135    /// Returns a mutable reference to the tracing inspector if it is available.
136    fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
137        None
138    }
139
140    /// Marks that the next EVM frame is an "inner context" so that isolation mode does not
141    /// trigger a nested `transact_inner`. `original_origin` is stored for the existing
142    /// inner-context adjustment logic that restores `tx.origin`.
143    fn set_in_inner_context(&mut self, _enabled: bool, _original_origin: Option<Address>) {}
144}
145
146/// Builds a sub-EVM from the current context and executes the given CREATE frame.
147pub(crate) fn exec_create<FEN: FoundryEvmNetwork>(
148    executor: &mut dyn CheatcodesExecutor<FEN>,
149    inputs: CreateInputs,
150    ccx: &mut CheatsCtxt<'_, '_, FEN>,
151) -> std::result::Result<CreateOutcome, EVMError<DatabaseError>> {
152    let mut inputs = Some(inputs);
153    let mut outcome = None;
154    executor.with_nested_evm(ccx.state, ccx.ecx, &mut |evm| {
155        let inputs = inputs.take().unwrap();
156        evm.journal_inner_mut().depth += 1;
157
158        let frame = FrameInput::Create(Box::new(inputs));
159
160        let result = match evm.run_execution(frame)? {
161            FrameResult::Call(_) => unreachable!(),
162            FrameResult::Create(create) => create,
163        };
164
165        evm.journal_inner_mut().depth -= 1;
166
167        outcome = Some(result);
168        Ok(())
169    })?;
170    Ok(outcome.unwrap())
171}
172
173/// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an
174/// inspector.
175#[derive(Debug, Default, Clone, Copy)]
176struct TransparentCheatcodesExecutor;
177
178impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for TransparentCheatcodesExecutor {
179    fn with_nested_evm(
180        &mut self,
181        cheats: &mut Cheatcodes<FEN>,
182        ecx: &mut FoundryContextFor<'_, FEN>,
183        f: NestedEvmClosureFor<'_, FEN>,
184    ) -> Result<(), EVMError<DatabaseError>> {
185        let factory = FEN::EvmFactory::default();
186        let chain_context = factory.capture_chain_context(ecx);
187        let state = factory.capture_transaction_state(ecx);
188        let mut nested_chain_context = None;
189        let mut transaction_state = None;
190        with_cloned_context(ecx, |db, evm_env, journaled_state| {
191            let mut evm = factory.create_foundry_nested_evm(db, evm_env, chain_context, cheats);
192            *evm.journal_inner_mut() = journaled_state;
193            evm.restore_transaction_state(state);
194            f(&mut *evm)?;
195            nested_chain_context = Some(evm.capture_chain_context());
196            transaction_state = Some(evm.capture_transaction_state());
197            let sub_inner = evm.journal_inner_mut().clone();
198            let sub_evm_env = evm.to_evm_env();
199            Ok((sub_evm_env, sub_inner))
200        })?;
201        factory.apply_context_transition(
202            ecx,
203            Some(&nested_chain_context.expect("nested EVM chain context was captured")),
204        );
205        factory.restore_transaction_state(
206            ecx,
207            transaction_state.expect("nested EVM state was captured"),
208        );
209        Ok(())
210    }
211
212    fn with_fresh_nested_evm(
213        &mut self,
214        cheats: &mut Cheatcodes<FEN>,
215        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
216        evm_env: EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>,
217        chain_context: ChainContextFor<FEN>,
218        f: NestedEvmClosureFor<'_, FEN>,
219    ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>> {
220        let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(
221            db,
222            evm_env,
223            chain_context,
224            cheats,
225        );
226        f(&mut *evm)?;
227        Ok(evm.to_evm_env())
228    }
229
230    fn transact_on_db(
231        &mut self,
232        cheats: &mut Cheatcodes<FEN>,
233        ecx: &mut FoundryContextFor<'_, FEN>,
234        fork_id: Option<U256>,
235        transaction: B256,
236    ) -> eyre::Result<ContextUpdate<ChainContextFor<FEN>>> {
237        let evm_env = ecx.evm_clone();
238        let outer_tx_env = ecx.tx_clone();
239        let (db, inner) = ecx.db_journal_inner_mut();
240        db.transact(fork_id, transaction, evm_env, &outer_tx_env, inner, cheats)
241    }
242
243    fn transact_from_tx_on_db(
244        &mut self,
245        cheats: &mut Cheatcodes<FEN>,
246        ecx: &mut FoundryContextFor<'_, FEN>,
247        tx: TxEnvFor<FEN>,
248    ) -> eyre::Result<()> {
249        let evm_env = ecx.evm_clone();
250        let (db, inner) = ecx.db_journal_inner_mut();
251        db.transact_from_tx(tx, evm_env, inner, cheats)
252    }
253
254    fn console_log(&mut self, _msg: &str) {}
255}
256
257macro_rules! try_or_return {
258    ($e:expr) => {
259        match $e {
260            Ok(v) => v,
261            Err(_) => return,
262        }
263    };
264}
265
266/// Contains additional, test specific resources that should be kept for the duration of the test
267#[derive(Debug, Default)]
268pub struct TestContext {
269    /// Buffered readers for files opened for reading (path => BufReader mapping)
270    pub opened_read_files: HashMap<PathBuf, BufReader<File>>,
271}
272
273/// Every time we clone `Context`, we want it to be empty
274impl Clone for TestContext {
275    fn clone(&self) -> Self {
276        Default::default()
277    }
278}
279
280impl TestContext {
281    /// Clears the context.
282    pub fn clear(&mut self) {
283        self.opened_read_files.clear();
284    }
285}
286
287/// Helps collecting transactions from different forks.
288#[derive(Clone, Debug)]
289pub struct BroadcastableTransaction<N: Network = Ethereum> {
290    /// The optional RPC URL.
291    pub rpc: Option<String>,
292    /// The transaction to broadcast.
293    pub transaction: TransactionMaybeSigned<N>,
294}
295
296#[derive(Clone, Debug, Copy)]
297pub struct RecordDebugStepInfo {
298    /// The debug trace node index when the recording starts.
299    pub start_node_idx: usize,
300    /// The original tracer config when the recording starts.
301    pub original_tracer_config: TracingInspectorConfig,
302}
303
304/// Environment overrides applied at the opcode level.
305///
306/// In isolation mode (and inside the synthetic transactions used by
307/// `--gas-report` / `--isolate`) the transaction environment is zeroed for
308/// fee-accounting purposes, so cheatcodes that mutate the env (e.g.
309/// `vm.fee`, `vm.txGasPrice`, `vm.blobhashes`) cannot rely on those
310/// mutations being visible to contracts via the `BASEFEE`, `GASPRICE` and
311/// `BLOBHASH` opcodes. These overrides are applied in `step_end` to fix
312/// the value that was just pushed onto the stack.
313///
314/// # Semantics when invoked from inside the synthetic isolation transaction
315///
316/// `vm.fee` / `vm.txGasPrice` / `vm.blobhashes` consult
317/// [`Cheatcodes::in_isolation_context`]; when set, they only update these
318/// overrides (so `tx.gas_price = 0` continues to apply to fee accounting and
319/// EIP-4844 inner-tx validation does not reject the synthetic call) and
320/// leave the real env untouched. After the inner transaction returns, the
321/// outer env is restored from the cached snapshot taken before
322/// `transact_inner`, which means:
323///
324/// - the override **does** persist for subsequent `BASEFEE`, `GASPRICE` and `BLOBHASH` reads (this
325///   hook fires in `step_end` regardless of isolation),
326/// - `vm.getBlobhashes()` also consults these overrides, so it returns the correct value.
327/// - but the real `block.basefee` / `tx.gas_price` / `tx.blob_hashes` do **not** reflect the
328///   cheatcode value, so other non-opcode env consumers will not see it.
329///
330/// Calling these cheatcodes outside isolation behaves as before (real env
331/// is also mutated and the override mirrors it).
332#[derive(Clone, Debug, Default)]
333pub struct EnvOverrides {
334    /// Override for the `BASEFEE` opcode (set via `vm.fee`).
335    pub basefee: Option<u64>,
336    /// Override for the `GASPRICE` opcode (set via `vm.txGasPrice`).
337    pub gas_price: Option<u128>,
338    /// Override for the `BLOBHASH` opcode (set via `vm.blobhashes`).
339    pub blob_hashes: Option<Vec<B256>>,
340    /// `tx.gas_price` captured at snapshot time when no gas_price override was
341    /// active. `sync_tx_after_env_override_restore` uses this to restore the
342    /// real pre-override value (not hardcoded 0) on revert.
343    pub pre_override_gas_price: Option<u128>,
344    /// `tx.tx_type` captured at snapshot time when no blob_hashes override was
345    /// active. Prevents tx_type being stuck at EIP4844 after reverting from a
346    /// blobhashes-set state.
347    pub pre_override_tx_type: Option<u8>,
348    /// `tx.blob_hashes` captured at snapshot time when no blob_hashes override
349    /// was active.
350    pub pre_override_blob_hashes: Option<Vec<B256>>,
351    /// The opcode about to run (captured in `step`, consumed in `step_end`),
352    /// used to know what was just executed when `step_end` fires — at that
353    /// point `interpreter.bytecode.opcode()` already points at the *next*
354    /// instruction.
355    pending_opcode: Option<u8>,
356    /// Pending index for the `BLOBHASH` opcode, captured in `step` (where
357    /// the index is still on top of the stack) for use in `step_end` (after
358    /// the opcode has consumed it and pushed the looked-up hash).
359    pending_blobhash_index: Option<u64>,
360}
361
362impl EnvOverrides {
363    /// Whether any override is set.
364    #[inline]
365    pub const fn is_any_set(&self) -> bool {
366        self.basefee.is_some() || self.gas_price.is_some() || self.blob_hashes.is_some()
367    }
368}
369
370/// A callback registered for a storage access hook.
371#[derive(Clone, Copy, Debug, PartialEq, Eq)]
372pub struct StorageHook {
373    /// Contract that receives the callback.
374    pub callback_target: Address,
375    /// Callback function selector.
376    pub callback_selector: [u8; 4],
377}
378
379#[derive(Clone, Debug)]
380enum PendingStorageHook {
381    Load {
382        account: Address,
383        slot: U256,
384        hook: StorageHook,
385    },
386    Store {
387        account: Address,
388        slot: U256,
389        old_value: U256,
390        mapping: Option<(B256, Vec<B256>)>,
391        hook: StorageHook,
392    },
393}
394
395#[derive(Clone, Debug)]
396struct ActiveStorageHook {
397    parent_depth: usize,
398    callback_target: Address,
399    callback_input: Bytes,
400    saved_gas: Gas,
401    saved_return_data: Bytes,
402    saved_stack_item: Option<U256>,
403    journal_start: usize,
404    inspector_state: StorageHookInspectorState,
405    outcome: Option<(InstructionResult, Bytes)>,
406}
407
408#[derive(Clone, Debug)]
409struct StorageHookInspectorState {
410    accesses: RecordAccess,
411    recording_accesses: bool,
412    mapping_slots: Option<AddressHashMap<MappingSlots>>,
413    recorded_logs: Option<Vec<Vm::Log>>,
414    mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
415    mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
416    expected_revert: Option<ExpectedRevert>,
417    assume_no_revert: Option<AssumeNoRevert>,
418    expected_calls: ExpectedCallTracker,
419    expected_emits: ExpectedEmitTracker,
420    expected_creates: Vec<ExpectedCreate>,
421}
422
423/// Holds gas metering state.
424#[derive(Clone, Debug, Default)]
425pub struct GasMetering {
426    /// True if gas metering is paused.
427    pub paused: bool,
428    /// True if gas metering was resumed or reset during the test.
429    /// Used to reconcile gas when frame ends (if spent less than refunded).
430    pub touched: bool,
431    /// True if gas metering should be reset to frame limit.
432    pub reset: bool,
433    /// Stores paused gas frames.
434    pub paused_frames: Vec<Gas>,
435
436    /// The group and name of the active snapshot.
437    pub active_gas_snapshot: Option<(String, String)>,
438
439    /// Cache of the amount of gas used in previous call.
440    /// This is used by the `lastCallGas` cheatcode.
441    pub last_call_gas: Option<crate::Vm::Gas>,
442
443    /// Cache of the amount of gas used in previous call or create frame.
444    /// This is used by the `lastFrameGas` cheatcode.
445    pub last_frame_gas: Option<crate::Vm::Gas>,
446
447    /// True if gas recording is enabled.
448    pub recording: bool,
449    /// The gas used in the last frame.
450    pub last_gas_used: u64,
451    /// Gas records for the active snapshots.
452    pub gas_records: Vec<GasRecord>,
453}
454
455impl GasMetering {
456    /// Start the gas recording.
457    pub const fn start(&mut self) {
458        self.recording = true;
459    }
460
461    /// Stop the gas recording.
462    pub const fn stop(&mut self) {
463        self.recording = false;
464    }
465
466    /// Resume paused gas metering.
467    pub fn resume(&mut self) {
468        if self.paused {
469            self.paused = false;
470            self.touched = true;
471        }
472        self.paused_frames.clear();
473    }
474
475    /// Reset gas to limit.
476    pub fn reset(&mut self) {
477        self.paused = false;
478        self.touched = true;
479        self.reset = true;
480        self.paused_frames.clear();
481    }
482}
483
484/// Holds data about arbitrary storage.
485#[derive(Clone, Debug, Default)]
486pub struct ArbitraryStorage {
487    /// Mapping of arbitrary storage addresses to generated values (slot, arbitrary value).
488    /// (SLOADs return random value if storage slot wasn't accessed).
489    /// Changed values are recorded and used to copy storage to different addresses.
490    values: HashMap<Address, HashMap<U256, U256>>,
491    /// Mapping of address with storage copied to arbitrary storage address source.
492    copies: HashMap<Address, Address>,
493    /// Address with storage slots that should be overwritten even if previously set.
494    overwrites: HashSet<Address>,
495    /// Storage slots explicitly written with `vm.store`, grouped by address.
496    explicit_slots: HashMap<Address, HashSet<U256>>,
497}
498
499impl ArbitraryStorage {
500    /// Marks an address with arbitrary storage.
501    pub fn mark_arbitrary(&mut self, address: &Address, overwrite: bool) {
502        self.values.insert(*address, HashMap::default());
503        self.explicit_slots.remove(address);
504        if overwrite {
505            self.overwrites.insert(*address);
506        } else {
507            self.overwrites.remove(address);
508        }
509    }
510
511    /// Maps an address that copies storage with the arbitrary storage address.
512    pub fn mark_copy(&mut self, from: &Address, to: &Address) {
513        if self.values.contains_key(from) {
514            self.copies.insert(*to, *from);
515            if let Some(slots) = self.explicit_slots.get(from).cloned() {
516                self.explicit_slots.insert(*to, slots);
517            } else {
518                self.explicit_slots.remove(to);
519            }
520        }
521    }
522
523    /// Marks a slot as explicitly written if the address has arbitrary or copied storage.
524    fn mark_explicit(&mut self, address: Address, slot: U256) {
525        if self.values.contains_key(&address) || self.copies.contains_key(&address) {
526            self.explicit_slots.entry(address).or_default().insert(slot);
527        }
528    }
529
530    /// Returns whether a slot was explicitly written for the given address.
531    fn is_explicit(&self, address: Address, slot: U256) -> bool {
532        self.explicit_slots.get(&address).is_some_and(|slots| slots.contains(&slot))
533    }
534
535    /// Returns addresses explicitly marked with arbitrary storage.
536    fn targets(&self) -> impl Iterator<Item = Address> + '_ {
537        self.values.keys().copied()
538    }
539
540    /// Returns addresses explicitly marked with arbitrary storage and whether nonzero slots are
541    /// overwritten.
542    fn target_overwrite_modes(&self) -> impl Iterator<Item = (Address, bool)> + '_ {
543        self.values.keys().map(|address| (*address, self.overwrites.contains(address)))
544    }
545
546    /// Returns addresses that copy storage from arbitrary-storage targets.
547    fn copied_targets(&self) -> impl Iterator<Item = Address> + '_ {
548        self.copies.keys().copied()
549    }
550
551    /// Returns copied arbitrary-storage targets and their source address.
552    fn copied_target_sources(&self) -> impl Iterator<Item = (Address, Address)> + '_ {
553        self.copies.iter().map(|(target, source)| (*target, *source))
554    }
555
556    /// Caches a concrete value for a slot on an arbitrary-storage address or copied target.
557    fn cache_value(&mut self, address: Address, slot: U256, data: U256) {
558        if let Some(values) = self.values.get_mut(&address) {
559            values.insert(slot, data);
560            return;
561        }
562
563        let Some(source) = self.copies.get(&address).copied() else {
564            return;
565        };
566        if let Some(values) = self.values.get_mut(&source) {
567            values.insert(slot, data);
568        }
569    }
570
571    /// Returns a cached arbitrary value for a slot.
572    fn cached_value(&self, address: Address, slot: U256) -> Option<U256> {
573        self.values.get(&address).and_then(|values| values.get(&slot)).copied()
574    }
575
576    /// Saves arbitrary storage value for a given address:
577    /// - store value in changed values cache.
578    /// - update account's storage with given value.
579    pub fn save<CTX: ContextTr>(
580        &mut self,
581        ecx: &mut CTX,
582        address: Address,
583        slot: U256,
584        data: U256,
585    ) {
586        self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data);
587        if ecx.journal_mut().load_account(address).is_ok() {
588            ecx.journal_mut()
589                .sstore(address, slot, data)
590                .expect("could not set arbitrary storage value");
591        }
592    }
593
594    /// Copies arbitrary storage value from source address to the given target address:
595    /// - if a value is present in arbitrary values cache, then update target storage and return
596    ///   existing value.
597    /// - if no value was yet generated for given slot, then save new value in cache and update both
598    ///   source and target storages.
599    pub fn copy<CTX: ContextTr>(
600        &mut self,
601        ecx: &mut CTX,
602        target: Address,
603        slot: U256,
604        new_value: U256,
605    ) -> U256 {
606        let source = self.copies.get(&target).expect("missing arbitrary copy target entry");
607        let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage");
608        let value = match storage_cache.get(&slot) {
609            Some(value) => *value,
610            None => {
611                storage_cache.insert(slot, new_value);
612                // Update source storage with new value.
613                if ecx.journal_mut().load_account(*source).is_ok() {
614                    ecx.journal_mut()
615                        .sstore(*source, slot, new_value)
616                        .expect("could not copy arbitrary storage value");
617                }
618                new_value
619            }
620        };
621        // Update target storage with new value.
622        if ecx.journal_mut().load_account(target).is_ok() {
623            ecx.journal_mut().sstore(target, slot, value).expect("could not set storage");
624        }
625        value
626    }
627}
628
629/// List of transactions that can be broadcasted.
630pub type BroadcastableTransactions<N> = VecDeque<BroadcastableTransaction<N>>;
631
632#[derive(Clone, Copy, Debug, PartialEq, Eq)]
633enum CreatedAccountsFrameKind {
634    Call,
635    Create,
636}
637
638#[derive(Clone, Copy, Debug)]
639struct CreatedAccountsFrame {
640    kind: CreatedAccountsFrameKind,
641    depth: usize,
642    checkpoint: usize,
643}
644
645#[derive(Clone, Copy, Debug)]
646struct CreatedAccountChange {
647    fork_id: Option<LocalForkId>,
648    address: Address,
649    creation: usize,
650    previous: Option<usize>,
651    committed: bool,
652}
653
654#[derive(Clone, Debug)]
655struct CreatedAccountsSnapshot {
656    fork_id: Option<LocalForkId>,
657    bindings: AddressHashMap<usize>,
658}
659
660/// An EVM inspector that handles calls to various cheatcodes, each with their own behavior.
661///
662/// Cheatcodes can be called by contracts during execution to modify the VM environment, such as
663/// mocking addresses, signatures and altering call reverts.
664///
665/// Executing cheatcodes can be very powerful. Most cheatcodes are limited to evm internals, but
666/// there are also cheatcodes like `ffi` which can execute arbitrary commands or `writeFile` and
667/// `readFile` which can manipulate files of the filesystem. Therefore, several restrictions are
668/// implemented for these cheatcodes:
669/// - `ffi`, and file cheatcodes are _always_ opt-in (via foundry config) and never enabled by
670///   default: all respective cheatcode handlers implement the appropriate checks
671/// - File cheatcodes require explicit permissions which paths are allowed for which operation, see
672///   `Config.fs_permission`
673/// - Only permitted accounts are allowed to execute cheatcodes in forking mode, this ensures no
674///   contract deployed on the live network is able to execute cheatcodes by simply calling the
675///   cheatcode address: by default, the caller, test contract and newly deployed contracts are
676///   allowed to execute cheatcodes
677#[derive(Clone, Debug)]
678pub struct Cheatcodes<FEN: FoundryEvmNetwork = EthEvmNetwork> {
679    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities
680    pub analysis: Option<CheatcodeAnalysis>,
681
682    /// The block environment
683    ///
684    /// Used in the cheatcode handler to overwrite the block environment separately from the
685    /// execution block environment.
686    pub block: Option<BlockEnvFor<FEN>>,
687
688    /// The active fork block override updated by a fork-switching cheatcode.
689    ///
690    /// This persists fork changes made through a copy-on-write backend between invariant calls.
691    pub fork_block_number_override: Option<u64>,
692
693    /// Currently active EIP-7702 delegations that will be consumed when building the next
694    /// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during
695    /// transaction construction.
696    pub active_delegations: Vec<SignedAuthorization>,
697
698    /// The active EIP-4844 blob that will be attached to the next call.
699    pub active_blob_sidecar: Option<BlobTransactionSidecarVariant>,
700
701    /// The gas price.
702    ///
703    /// Used in the cheatcode handler to overwrite the gas price separately from the gas price
704    /// in the execution environment.
705    pub gas_price: Option<u128>,
706
707    /// Address labels
708    pub labels: AddressHashMap<String>,
709
710    /// Prank information, mapped to the call depth where pranks were added.
711    pub pranks: BTreeMap<usize, Prank>,
712
713    /// Expected revert information
714    pub expected_revert: Option<ExpectedRevert>,
715
716    /// Assume next call can revert and discard fuzz run if it does.
717    pub assume_no_revert: Option<AssumeNoRevert>,
718
719    /// Additional diagnostic for reverts
720    pub fork_revert_diagnostic: Option<RevertDiagnostic>,
721
722    /// Recorded storage reads and writes
723    pub accesses: RecordAccess,
724
725    /// Whether storage access recording is currently active
726    pub recording_accesses: bool,
727
728    /// Recorded account accesses (calls, creates) organized by relative call depth, where the
729    /// topmost vector corresponds to accesses at the depth at which account access recording
730    /// began. Each vector in the matrix represents a list of accesses at a specific call
731    /// depth. Once that call context has ended, the last vector is removed from the matrix and
732    /// merged into the previous vector.
733    pub recorded_account_diffs_stack: Option<Vec<Vec<AccountAccess>>>,
734
735    /// Account accesses performed by the test runner before user code can start recording.
736    pending_account_diffs: Option<Arc<[AccountAccess]>>,
737
738    /// Completed account accesses prepended to the active user recording session.
739    recorded_account_diffs_prefix: Option<Arc<[AccountAccess]>>,
740
741    /// Successfully created accounts in execution order.
742    created_accounts: Vec<Address>,
743
744    /// The creation currently represented by each address on each fork.
745    created_account_bindings: HashMap<(Option<LocalForkId>, Address), usize>,
746
747    /// Revertible changes to creation bindings made by EVM create frames.
748    created_account_changes: Vec<CreatedAccountChange>,
749
750    /// Creation-list checkpoints for frames observed by this inspector.
751    created_accounts_frames: Vec<CreatedAccountsFrame>,
752
753    /// Creation lists captured by state snapshots.
754    created_accounts_snapshots: HashMap<U256, CreatedAccountsSnapshot>,
755
756    /// The information of the debug step recording.
757    pub record_debug_steps_info: Option<RecordDebugStepInfo>,
758
759    /// Recorded logs
760    pub recorded_logs: Option<Vec<crate::Vm::Log>>,
761
762    /// Mocked calls
763    // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext`
764    pub mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
765
766    /// Mocked functions. Maps target address to be mocked to pair of (calldata, mock address).
767    pub mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
768
769    /// Expected calls
770    pub expected_calls: ExpectedCallTracker,
771    /// Expected emits
772    pub expected_emits: ExpectedEmitTracker,
773    /// Expected creates
774    pub expected_creates: Vec<ExpectedCreate>,
775
776    /// Map of context depths to memory offset ranges that may be written to within the call depth.
777    pub allowed_mem_writes: HashMap<u64, Vec<Range<u64>>>,
778
779    /// Current broadcasting information
780    pub broadcast: Option<Broadcast>,
781
782    /// Scripting based transactions
783    pub broadcastable_transactions: BroadcastableTransactions<FEN::Network>,
784
785    /// Current EIP-2930 access lists.
786    pub access_list: Option<AccessList>,
787
788    /// Additional, user configurable context this Inspector has access to when inspecting a call.
789    pub config: Arc<CheatsConfig>,
790
791    /// Test-scoped context holding data that needs to be reset every test run
792    pub test_context: TestContext,
793
794    /// Whether to commit FS changes such as file creations, writes and deletes.
795    /// Used to prevent duplicate changes file executing non-committing calls.
796    pub fs_commit: bool,
797
798    /// Serialized JSON values.
799    // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic.
800    pub serialized_jsons: BTreeMap<String, BTreeMap<String, Value>>,
801
802    /// All recorded ETH `deal`s.
803    pub eth_deals: Vec<DealRecord>,
804
805    /// Gas metering state.
806    pub gas_metering: GasMetering,
807
808    /// Contains gas snapshots made over the course of a test suite.
809    // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic.
810    pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>,
811
812    /// Mapping slots.
813    pub mapping_slots: Option<AddressHashMap<MappingSlots>>,
814
815    /// The current program counter.
816    pub pc: usize,
817    /// Breakpoints supplied by the `breakpoint` cheatcode.
818    /// `char -> (address, pc)`
819    pub breakpoints: Breakpoints,
820
821    /// Whether the next contract creation should be intercepted to return its initcode.
822    pub intercept_next_create_call: bool,
823
824    /// Optional cheatcodes `TestRunner`. Used for generating random values from uint and int
825    /// strategies.
826    test_runner: Option<TestRunner>,
827
828    /// Ignored traces.
829    pub ignored_traces: IgnoredTraces,
830
831    /// Addresses with arbitrary storage.
832    pub arbitrary_storage: Option<ArbitraryStorage>,
833
834    /// SLOAD callbacks keyed by effective storage address.
835    storage_load_hooks: AddressHashMap<StorageHook>,
836    /// SSTORE callbacks keyed by effective storage address.
837    storage_store_hooks: AddressHashMap<StorageHook>,
838    /// Mapping SSTORE callbacks keyed by effective storage address and root slot.
839    mapping_storage_store_hooks: AddressHashMap<HashMap<B256, StorageHook>>,
840    /// Execution-local provenance used only by mapping storage hooks.
841    storage_hook_mapping_slots: AddressHashMap<MappingSlots>,
842    /// A 64-byte Keccak operation awaiting successful completion.
843    pending_mapping_hash: Option<PendingMappingHash>,
844    /// Whether any storage hook map contains a callback.
845    storage_hooks_registered: bool,
846    /// Matching storage access captured before the opcode executes.
847    pending_storage_hook: Option<PendingStorageHook>,
848    /// Synthetic callback frame currently executing or awaiting parent cleanup.
849    active_storage_hook: Option<ActiveStorageHook>,
850
851    /// Deprecated cheatcodes mapped to the reason. Used to report warnings on test results.
852    pub deprecated: HashMap<&'static str, Option<&'static str>>,
853    /// Unlocked wallets used in scripts and testing of scripts.
854    pub wallets: Option<Wallets>,
855    /// Parsed secp256k1 private-key signers for repeated `vm.addr` / `vm.sign` calls.
856    pub private_key_signers: HashMap<U256, PrivateKeySigner>,
857    /// Signatures identifier for decoding events and functions
858    signatures_identifier: OnceLock<Option<SignaturesIdentifier>>,
859    /// Used to determine whether the broadcasted call has dynamic gas limit.
860    pub dynamic_gas_limit: bool,
861    // Custom execution evm version.
862    pub execution_evm_version: Option<SpecFor<FEN>>,
863
864    /// Opcode-level environment overrides for `BASEFEE`, `GASPRICE` and
865    /// `BLOBHASH`. Set by `vm.fee`, `vm.txGasPrice`, `vm.blobhashes` and
866    /// applied in [`Inspector::step_end`].
867    ///
868    /// Needed because in isolation mode the synthetic inner transaction
869    /// zeroes the corresponding tx/block env fields for fee-accounting.
870    ///
871    /// Keyed by active fork ID (`None` -> local) so that multi-fork tests do not bleed overrides
872    /// across forks when `vm.selectFork` / `vm.createSelectFork` switches the active fork.
873    pub env_overrides: HashMap<Option<LocalForkId>, EnvOverrides>,
874
875    /// Per-state-snapshot copies of [`Self::env_overrides`], captured by
876    /// `vm.snapshotState` and restored by `vm.revertToState[AndDelete]`.
877    ///
878    /// `env_overrides` lives on the cheatcode inspector rather than in
879    /// `EvmEnv`, so the backend's snapshot/revert mechanism does not see
880    /// it. Without this, an override set after a snapshot would survive a
881    /// `revertToState`, and the BASEFEE/GASPRICE/BLOBHASH opcodes (which
882    /// the override layer rewrites in `step_end`) would keep returning
883    /// the post-snapshot value even though `EvmEnv` was rolled back.
884    pub env_overrides_snapshots: HashMap<U256, HashMap<Option<LocalForkId>, EnvOverrides>>,
885
886    /// Per-state-snapshot copies of [`Self::fork_block_number_override`].
887    pub fork_block_number_override_snapshots: HashMap<U256, Option<u64>>,
888
889    /// Transaction-position context and family-owned execution state captured atomically alongside
890    /// state snapshots.
891    pub context_snapshots: HashMap<U256, (ChainContextFor<FEN>, TransactionStateFor<FEN>)>,
892
893    /// Whether we are currently executing inside an isolation context, i.e.
894    /// the synthetic inner transaction wrapped by
895    /// `InspectorStackRefMut::transact_inner` (used by `--gas-report` and
896    /// `--isolate`).
897    ///
898    /// Toggled by the inspector stack around the inner `transact_raw`
899    /// call. Cheatcodes that mutate the tx/block env consult this flag and
900    /// route the change through `EnvOverrides` instead of the actual env
901    /// when `true`, so they don't fight with the fee-accounting zeroing.
902    pub in_isolation_context: bool,
903}
904
905// This is not derived because calling this in `fn new` with `..Default::default()` creates a second
906// `CheatsConfig` which is unused, and inside it `ProjectPathsConfig` is relatively expensive to
907// create.
908impl Default for Cheatcodes {
909    fn default() -> Self {
910        Self::new(Arc::default())
911    }
912}
913
914impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
915    /// Creates a new `Cheatcodes` with the given settings.
916    pub fn new(config: Arc<CheatsConfig>) -> Self {
917        Self {
918            analysis: None,
919            fs_commit: true,
920            labels: config.labels.clone(),
921            config,
922            block: Default::default(),
923            fork_block_number_override: Default::default(),
924            active_delegations: Default::default(),
925            active_blob_sidecar: Default::default(),
926            gas_price: Default::default(),
927            pranks: Default::default(),
928            expected_revert: Default::default(),
929            assume_no_revert: Default::default(),
930            fork_revert_diagnostic: Default::default(),
931            accesses: Default::default(),
932            recording_accesses: Default::default(),
933            recorded_account_diffs_stack: Default::default(),
934            pending_account_diffs: Default::default(),
935            recorded_account_diffs_prefix: Default::default(),
936            created_accounts: Default::default(),
937            created_account_bindings: Default::default(),
938            created_account_changes: Default::default(),
939            created_accounts_frames: Default::default(),
940            created_accounts_snapshots: Default::default(),
941            recorded_logs: Default::default(),
942            record_debug_steps_info: Default::default(),
943            mocked_calls: Default::default(),
944            mocked_functions: Default::default(),
945            expected_calls: Default::default(),
946            expected_emits: Default::default(),
947            expected_creates: Default::default(),
948            allowed_mem_writes: Default::default(),
949            broadcast: Default::default(),
950            broadcastable_transactions: Default::default(),
951            access_list: Default::default(),
952            test_context: Default::default(),
953            serialized_jsons: Default::default(),
954            eth_deals: Default::default(),
955            gas_metering: Default::default(),
956            gas_snapshots: Default::default(),
957            mapping_slots: Default::default(),
958            pc: Default::default(),
959            breakpoints: Default::default(),
960            intercept_next_create_call: Default::default(),
961            test_runner: Default::default(),
962            ignored_traces: Default::default(),
963            arbitrary_storage: Default::default(),
964            storage_load_hooks: Default::default(),
965            storage_store_hooks: Default::default(),
966            mapping_storage_store_hooks: Default::default(),
967            storage_hook_mapping_slots: Default::default(),
968            pending_mapping_hash: Default::default(),
969            storage_hooks_registered: Default::default(),
970            pending_storage_hook: Default::default(),
971            active_storage_hook: Default::default(),
972            deprecated: Default::default(),
973            wallets: Default::default(),
974            private_key_signers: Default::default(),
975            signatures_identifier: Default::default(),
976            dynamic_gas_limit: Default::default(),
977            execution_evm_version: None,
978            env_overrides: Default::default(),
979            env_overrides_snapshots: Default::default(),
980            fork_block_number_override_snapshots: Default::default(),
981            context_snapshots: Default::default(),
982            in_isolation_context: false,
983        }
984    }
985
986    /// Enables cheatcode analysis capabilities by providing a solar compiler instance.
987    pub fn set_analysis(&mut self, analysis: CheatcodeAnalysis) {
988        self.analysis = Some(analysis);
989    }
990
991    /// Starts an internal account diff recording session for test runner setup.
992    pub fn start_internal_state_diff_recording(&mut self) -> bool {
993        if self.recorded_account_diffs_stack.is_some()
994            || self.recorded_account_diffs_prefix.is_some()
995        {
996            return false;
997        }
998        self.recorded_account_diffs_stack = Some(Default::default());
999        true
1000    }
1001
1002    /// Stops an internal account diff recording session without leaving recording enabled.
1003    pub fn stop_internal_state_diff_recording(&mut self) -> Vec<AccountAccess> {
1004        self.recorded_account_diffs_stack.take().unwrap_or_default().into_iter().flatten().collect()
1005    }
1006
1007    /// Makes account accesses captured by the test runner available to the next recording session.
1008    pub fn set_pending_account_diffs(&mut self, accesses: Vec<AccountAccess>) {
1009        self.pending_account_diffs = (!accesses.is_empty()).then(|| Arc::from(accesses));
1010    }
1011
1012    /// Starts a user account diff recording session, including pending test runner accesses.
1013    pub fn start_state_diff_recording(&mut self) {
1014        self.recorded_account_diffs_prefix = self.pending_account_diffs.take();
1015        self.recorded_account_diffs_stack = Some(Default::default());
1016    }
1017
1018    /// Returns completed and active account accesses in execution order.
1019    pub fn recorded_account_diffs(&self) -> impl Iterator<Item = &AccountAccess> {
1020        self.recorded_account_diffs_prefix
1021            .iter()
1022            .flat_map(|prefix| prefix.iter())
1023            .chain(self.recorded_account_diffs_stack.iter().flatten().flatten())
1024    }
1025
1026    /// Takes completed account accesses from the active user recording session.
1027    pub fn take_recorded_account_diffs_prefix(&mut self) -> Vec<AccountAccess> {
1028        self.recorded_account_diffs_prefix
1029            .take()
1030            .map(|prefix| prefix.as_ref().to_vec())
1031            .unwrap_or_default()
1032    }
1033
1034    /// Returns the current creation bound to each address on the given fork.
1035    pub(crate) fn created_account_bindings(
1036        &self,
1037        fork_id: Option<LocalForkId>,
1038    ) -> AddressHashMap<usize> {
1039        self.created_account_bindings
1040            .iter()
1041            .filter_map(|(&(event_fork_id, address), &creation)| {
1042                (event_fork_id == fork_id).then_some((address, creation))
1043            })
1044            .collect()
1045    }
1046
1047    /// Returns successfully created accounts bound to the given fork in creation order.
1048    pub(crate) fn created_accounts(&self, fork_id: Option<LocalForkId>) -> Vec<Address> {
1049        let bindings = self.created_account_bindings(fork_id);
1050        self.created_accounts
1051            .iter()
1052            .enumerate()
1053            .filter_map(|(index, &address)| {
1054                (bindings.get(&address) == Some(&index)).then_some(address)
1055            })
1056            .collect()
1057    }
1058
1059    /// Records a successfully created account.
1060    pub(crate) fn record_created_account(
1061        &mut self,
1062        fork_id: Option<LocalForkId>,
1063        address: Address,
1064    ) {
1065        let creation = self.created_accounts.len();
1066        self.created_accounts.push(address);
1067        let previous = self.created_account_bindings.insert((fork_id, address), creation);
1068        self.created_account_changes.push(CreatedAccountChange {
1069            fork_id,
1070            address,
1071            creation,
1072            previous,
1073            committed: false,
1074        });
1075    }
1076
1077    /// Keeps creation bindings saved with an outgoing fork across later frame reverts.
1078    pub(crate) fn commit_created_account_changes(&mut self, fork_id: Option<LocalForkId>) {
1079        for change in &mut self.created_account_changes {
1080            if change.fork_id == fork_id {
1081                change.committed = true;
1082            }
1083        }
1084    }
1085
1086    /// Records shared pre-fork creations on a newly selected fork without replacing local ones.
1087    pub(crate) fn record_initial_created_accounts(
1088        &mut self,
1089        fork_id: Option<LocalForkId>,
1090        accounts: impl IntoIterator<Item = (Address, usize)>,
1091    ) {
1092        for (address, creation) in accounts {
1093            self.created_account_bindings.entry((fork_id, address)).or_insert(creation);
1094        }
1095    }
1096
1097    /// Records creations propagated to a fork with persistent account state.
1098    pub(crate) fn record_propagated_accounts(
1099        &mut self,
1100        fork_id: Option<LocalForkId>,
1101        accounts: impl IntoIterator<Item = (Address, usize)>,
1102    ) {
1103        self.created_account_bindings
1104            .extend(accounts.into_iter().map(|(address, creation)| ((fork_id, address), creation)));
1105    }
1106
1107    /// Captures creation ordering alongside a state snapshot.
1108    pub(crate) fn snapshot_created_accounts(
1109        &mut self,
1110        snapshot_id: U256,
1111        fork_id: Option<LocalForkId>,
1112    ) {
1113        let bindings = self.created_account_bindings(fork_id);
1114        self.created_accounts_snapshots
1115            .insert(snapshot_id, CreatedAccountsSnapshot { fork_id, bindings });
1116    }
1117
1118    /// Restores creation ordering from a state snapshot.
1119    pub(crate) fn revert_created_accounts(&mut self, snapshot_id: U256, remove: bool) {
1120        let snapshot = if remove {
1121            self.created_accounts_snapshots.remove(&snapshot_id)
1122        } else {
1123            self.created_accounts_snapshots.get(&snapshot_id).cloned()
1124        };
1125        if let Some(snapshot) = snapshot {
1126            self.created_account_bindings.retain(|(fork_id, _), _| *fork_id != snapshot.fork_id);
1127            self.created_account_bindings.extend(
1128                snapshot
1129                    .bindings
1130                    .into_iter()
1131                    .map(|(address, creation)| ((snapshot.fork_id, address), creation)),
1132            );
1133        }
1134    }
1135
1136    /// Deletes one captured creation-order snapshot.
1137    pub(crate) fn delete_created_accounts_snapshot(&mut self, snapshot_id: U256) {
1138        self.created_accounts_snapshots.remove(&snapshot_id);
1139    }
1140
1141    /// Deletes all captured creation-order snapshots.
1142    pub(crate) fn clear_created_accounts_snapshots(&mut self) {
1143        self.created_accounts_snapshots.clear();
1144    }
1145
1146    fn start_created_accounts_frame(
1147        &mut self,
1148        reset: bool,
1149        kind: CreatedAccountsFrameKind,
1150        depth: usize,
1151    ) {
1152        if reset {
1153            self.created_accounts.clear();
1154            self.created_account_bindings.clear();
1155            self.created_account_changes.clear();
1156            self.created_accounts_frames.clear();
1157            // Earlier snapshots contain no creations from the new root transaction.
1158            for snapshot in self.created_accounts_snapshots.values_mut() {
1159                snapshot.bindings.clear();
1160            }
1161        }
1162        self.created_accounts_frames.push(CreatedAccountsFrame {
1163            kind,
1164            depth,
1165            checkpoint: self.created_account_changes.len(),
1166        });
1167    }
1168
1169    fn finish_created_accounts_frame(
1170        &mut self,
1171        success: bool,
1172        kind: CreatedAccountsFrameKind,
1173        depth: usize,
1174    ) {
1175        let Some(frame) = self
1176            .created_accounts_frames
1177            .last()
1178            .copied()
1179            .filter(|frame| frame.kind == kind && frame.depth == depth)
1180        else {
1181            return;
1182        };
1183        let checkpoint = frame.checkpoint;
1184        self.created_accounts_frames.pop();
1185        if !success {
1186            while self.created_account_changes.len() > checkpoint {
1187                let change = self.created_account_changes.pop().expect("length checked");
1188                if change.committed {
1189                    continue;
1190                }
1191                let key = (change.fork_id, change.address);
1192                if self.created_account_bindings.get(&key) != Some(&change.creation) {
1193                    continue;
1194                }
1195                if let Some(previous) = change.previous {
1196                    self.created_account_bindings.insert(key, previous);
1197                } else {
1198                    self.created_account_bindings.remove(&key);
1199                }
1200            }
1201        }
1202    }
1203
1204    /// Returns the env overrides for the given fork (`None` = no-fork / local).
1205    pub fn env_overrides_for(&self, fork_id: Option<U256>) -> Option<&EnvOverrides> {
1206        self.env_overrides.get(&fork_id).filter(|o| o.is_any_set())
1207    }
1208
1209    /// Returns a mutable reference to the env overrides for the given fork, inserting a
1210    /// default entry if absent.
1211    pub fn env_overrides_for_mut(&mut self, fork_id: Option<U256>) -> &mut EnvOverrides {
1212        self.env_overrides.entry(fork_id).or_default()
1213    }
1214
1215    /// Returns the configured prank at given depth or the first prank configured at a lower depth.
1216    /// For example, if pranks configured for depth 1, 3 and 5, the prank for depth 4 is the one
1217    /// configured at depth 3.
1218    pub fn get_prank(&self, depth: usize) -> Option<&Prank> {
1219        self.pranks.range(..=depth).last().map(|(_, prank)| prank)
1220    }
1221
1222    /// Returns the configured wallets if available, else creates a new instance.
1223    pub fn wallets(&mut self) -> &Wallets {
1224        self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None))
1225    }
1226
1227    /// Sets the unlocked wallets.
1228    pub fn set_wallets(&mut self, wallets: Wallets) {
1229        self.wallets = Some(wallets);
1230    }
1231
1232    /// Adds a delegation to the active delegations list.
1233    pub fn add_delegation(&mut self, authorization: SignedAuthorization) {
1234        self.active_delegations.push(authorization);
1235    }
1236
1237    /// Returns the signatures identifier.
1238    pub fn signatures_identifier(&self) -> Option<&SignaturesIdentifier> {
1239        self.signatures_identifier
1240            .get_or_init(|| {
1241                if let Some(artifacts) = &self.config.available_artifacts {
1242                    return SignaturesIdentifier::new_offline_with_abis(
1243                        artifacts.values().map(|contract| &contract.abi),
1244                    )
1245                    .ok();
1246                }
1247                SignaturesIdentifier::new(true).ok()
1248            })
1249            .as_ref()
1250    }
1251
1252    /// Decodes the input data and applies the cheatcode.
1253    fn apply_cheatcode(
1254        &mut self,
1255        ecx: &mut FoundryContextFor<'_, FEN>,
1256        call: &CallInputs,
1257        executor: &mut dyn CheatcodesExecutor<FEN>,
1258    ) -> Result {
1259        // decode the cheatcode call
1260        let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| {
1261            if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e {
1262                let msg = format!(
1263                    "unknown cheatcode with selector {selector}; \
1264                     you may have a mismatch between the `Vm` interface (likely in `forge-std`) \
1265                     and the `forge` version"
1266                );
1267                return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg));
1268            }
1269            e
1270        })?;
1271
1272        let caller = call.caller;
1273
1274        // ensure the caller is allowed to execute cheatcodes,
1275        // but only if the backend is in forking mode
1276        ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
1277
1278        apply_dispatch(
1279            &decoded,
1280            &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller },
1281            executor,
1282        )
1283    }
1284
1285    /// Decodes the input data and applies Monad-specific cheatcodes.
1286    #[cfg(feature = "monad")]
1287    fn apply_monad_cheatcode(
1288        &mut self,
1289        ecx: &mut FoundryContextFor<'_, FEN>,
1290        call: &CallInputs,
1291    ) -> Result {
1292        let input = call.input.bytes(ecx);
1293        let caller = call.caller;
1294
1295        // ensure the caller is allowed to execute cheatcodes,
1296        // but only if the backend is in forking mode
1297        ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
1298
1299        apply_monad_cheatcode_call(
1300            &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller },
1301            &input,
1302        )
1303    }
1304
1305    /// Grants cheat code access for new contracts if the caller also has
1306    /// cheatcode access or the new contract is created in top most call.
1307    ///
1308    /// There may be cheatcodes in the constructor of the new contract, in order to allow them
1309    /// automatically we need to determine the new address.
1310    fn allow_cheatcodes_on_create(
1311        &self,
1312        ecx: &mut FoundryContextFor<FEN>,
1313        caller: Address,
1314        created_address: Address,
1315    ) {
1316        if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) {
1317            ecx.db_mut().allow_cheatcode_access(created_address);
1318        }
1319    }
1320
1321    /// Apply EIP-2930 access list.
1322    ///
1323    /// If the transaction type is [TransactionType::Legacy] we need to upgrade it to
1324    /// [TransactionType::Eip2930] in order to use access lists. Other transaction types support
1325    /// access lists themselves.
1326    fn apply_accesslist(&mut self, ecx: &mut FoundryContextFor<FEN>) {
1327        if let Some(access_list) = &self.access_list {
1328            ecx.tx_mut().set_access_list(access_list.clone());
1329
1330            if ecx.tx().tx_type() == TransactionType::Legacy as u8 {
1331                ecx.tx_mut().set_tx_type(TransactionType::Eip2930 as u8);
1332            }
1333        }
1334    }
1335
1336    /// Called when there was a revert.
1337    ///
1338    /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's
1339    /// revert would run into issues.
1340    pub fn on_revert(&mut self, ecx: &mut FoundryContextFor<FEN>) {
1341        trace!(deals=?self.eth_deals.len(), "rolling back deals");
1342
1343        // Delay revert clean up until expected revert is handled, if set.
1344        if self.expected_revert.is_some() {
1345            return;
1346        }
1347
1348        // we only want to apply cleanup top level
1349        if ecx.journal().depth() > 0 {
1350            return;
1351        }
1352
1353        // Roll back all previously applied deals
1354        // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine
1355        // which rolls back any transfers.
1356        while let Some(record) = self.eth_deals.pop() {
1357            if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) {
1358                acc.info.balance = record.old_balance;
1359            }
1360        }
1361    }
1362
1363    pub fn call_with_executor(
1364        &mut self,
1365        ecx: &mut FoundryContextFor<'_, FEN>,
1366        call: &mut CallInputs,
1367        executor: &mut dyn CheatcodesExecutor<FEN>,
1368    ) -> Option<CallOutcome> {
1369        // Apply custom execution evm version.
1370        if let Some(spec_id) = self.execution_evm_version {
1371            ecx.set_spec_and_gas_params(spec_id);
1372        }
1373
1374        let gas = Gas::new(call.gas_limit);
1375        let curr_depth = ecx.journal().depth();
1376        self.start_created_accounts_frame(
1377            curr_depth == 0,
1378            CreatedAccountsFrameKind::Call,
1379            curr_depth,
1380        );
1381
1382        // At the root call to test function or script `run()`/`setUp()` functions, we are
1383        // decreasing sender nonce to ensure that it matches on-chain nonce once we start
1384        // broadcasting.
1385        if curr_depth == 0 {
1386            let sender = ecx.tx().caller();
1387            let account = match super::evm::journaled_account(ecx, sender) {
1388                Ok(account) => account,
1389                Err(err) => {
1390                    return Some(CallOutcome {
1391                        result: InterpreterResult {
1392                            result: InstructionResult::Revert,
1393                            output: err.abi_encode().into(),
1394                            gas,
1395                        },
1396                        memory_offset: call.return_memory_offset.clone(),
1397                        was_precompile_called: false,
1398                        precompile_call_logs: vec![],
1399                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1400                    });
1401                }
1402            };
1403            let prev = account.info.nonce;
1404            account.info.nonce = prev.saturating_sub(1);
1405
1406            trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
1407        }
1408
1409        if call.target_address == CHEATCODE_ADDRESS {
1410            return match self.apply_cheatcode(ecx, call, executor) {
1411                Ok(retdata) => Some(CallOutcome {
1412                    result: InterpreterResult {
1413                        result: InstructionResult::Return,
1414                        output: retdata.into(),
1415                        gas,
1416                    },
1417                    memory_offset: call.return_memory_offset.clone(),
1418                    was_precompile_called: true,
1419                    precompile_call_logs: vec![],
1420                    charged_new_account_state_gas: call.charged_new_account_state_gas,
1421                }),
1422                Err(err) => Some(CallOutcome {
1423                    result: InterpreterResult {
1424                        result: InstructionResult::Revert,
1425                        output: err.abi_encode().into(),
1426                        gas,
1427                    },
1428                    memory_offset: call.return_memory_offset.clone(),
1429                    was_precompile_called: false,
1430                    precompile_call_logs: vec![],
1431                    charged_new_account_state_gas: call.charged_new_account_state_gas,
1432                }),
1433            };
1434        }
1435
1436        #[cfg(feature = "monad")]
1437        if is_monad_cheatcode_call::<FEN>(call.target_address) {
1438            let checkpoint = ecx.journal_mut().checkpoint();
1439            return match self.apply_monad_cheatcode(ecx, call) {
1440                Ok(retdata) => {
1441                    ecx.journal_mut().checkpoint_commit();
1442                    Some(CallOutcome {
1443                        result: InterpreterResult {
1444                            result: InstructionResult::Return,
1445                            output: retdata.into(),
1446                            gas,
1447                        },
1448                        memory_offset: call.return_memory_offset.clone(),
1449                        was_precompile_called: true,
1450                        precompile_call_logs: vec![],
1451                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1452                    })
1453                }
1454                Err(err) => {
1455                    ecx.journal_mut().checkpoint_revert(checkpoint);
1456                    Some(CallOutcome {
1457                        result: InterpreterResult {
1458                            result: InstructionResult::Revert,
1459                            output: err.abi_encode().into(),
1460                            gas,
1461                        },
1462                        memory_offset: call.return_memory_offset.clone(),
1463                        was_precompile_called: false,
1464                        precompile_call_logs: vec![],
1465                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1466                    })
1467                }
1468            };
1469        }
1470
1471        if call.target_address == HARDHAT_CONSOLE_ADDRESS {
1472            return None;
1473        }
1474
1475        // `expectRevert`: track max call depth. This is also done in `initialize_interp`, but
1476        // precompile calls don't create an interpreter frame so we must also track it here.
1477        // The callee executes at `curr_depth + 1`.
1478        if let Some(expected) = &mut self.expected_revert {
1479            expected.max_depth = max(curr_depth + 1, expected.max_depth);
1480        }
1481
1482        // Handle expected calls
1483
1484        // Grab the different calldatas expected.
1485        if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&call.bytecode_address)
1486        {
1487            let input = call.input.as_bytes(ecx);
1488            let value = call.transfer_value();
1489
1490            // Match every partial/full calldata
1491            for ((calldata, expected_scheme), (expected, actual_count)) in expected_calls_for_target
1492            {
1493                // Increment actual times seen if...
1494                // The calldata is at most, as big as this call's input, and
1495                if calldata.len() <= input.len() &&
1496                    // Both calldata match, taking the length of the assumed smaller one (which will have at least the selector), and
1497                    input.get(..calldata.len()) == Some(calldata.as_ref()) &&
1498                    // The value matches, if provided
1499                    expected.value.is_none_or(|expected_value| Some(expected_value) == value) &&
1500                    // The gas matches, if provided
1501                    expected.gas.is_none_or(|gas| gas == call.gas_limit) &&
1502                    // The minimum gas matches, if provided
1503                    expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit) &&
1504                    // The call scheme matches, if provided
1505                    expected_scheme.is_none_or(|scheme| scheme == call.scheme)
1506                {
1507                    *actual_count += 1;
1508                }
1509            }
1510        }
1511
1512        // Apply our prank
1513        if let Some(prank) = &self.get_prank(curr_depth) {
1514            // Apply delegate call, `call.caller`` will not equal `prank.prank_caller`
1515            if prank.delegate_call
1516                && curr_depth == prank.depth
1517                && call.scheme == CallScheme::DelegateCall
1518            {
1519                call.target_address = prank.new_caller;
1520                call.caller = prank.new_caller;
1521                if let Some(new_origin) = prank.new_origin {
1522                    ecx.tx_mut().set_caller(new_origin);
1523                }
1524            }
1525
1526            if curr_depth >= prank.depth && call.caller == prank.prank_caller {
1527                // At the target depth we set `msg.sender`
1528                let prank_applied = if curr_depth == prank.depth {
1529                    // Ensure new caller is loaded and touched
1530                    let _ = journaled_account(ecx, prank.new_caller);
1531                    call.caller = prank.new_caller;
1532                    true
1533                } else {
1534                    false
1535                };
1536
1537                // At the target depth, or deeper, we set `tx.origin`
1538                let prank_applied = if let Some(new_origin) = prank.new_origin {
1539                    ecx.tx_mut().set_caller(new_origin);
1540                    true
1541                } else {
1542                    prank_applied
1543                };
1544
1545                // If prank applied for first time, then update
1546                if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
1547                    self.pranks.insert(curr_depth, applied_prank);
1548                }
1549            }
1550        }
1551
1552        // Handle mocked calls
1553        if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
1554            let input = call.input.bytes(ecx);
1555            let value = call.transfer_value();
1556            let ctx = MockCallDataContext { calldata: input.clone(), value };
1557
1558            if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
1559                Some(queue) => Some(queue),
1560                None => mocks
1561                    .iter_mut()
1562                    .find(|(mock, _)| {
1563                        input.get(..mock.calldata.len()) == Some(&mock.calldata[..])
1564                            && mock.value.is_none_or(|mock_value| Some(mock_value) == value)
1565                    })
1566                    .map(|(_, v)| v),
1567            } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned())
1568            {
1569                if let Some(value) = call.transfer_value() {
1570                    let checkpoint = ecx.journal_mut().checkpoint();
1571                    match ecx.journal_mut().transfer_loaded(
1572                        call.transfer_from(),
1573                        call.transfer_to(),
1574                        value,
1575                    ) {
1576                        None => {
1577                            if return_data.ret_type.is_ok() {
1578                                ecx.journal_mut().checkpoint_commit();
1579                            } else {
1580                                ecx.journal_mut().checkpoint_revert(checkpoint);
1581                            }
1582                        }
1583                        Some(err) => {
1584                            ecx.journal_mut().checkpoint_revert(checkpoint);
1585                            return Some(CallOutcome {
1586                                result: InterpreterResult {
1587                                    result: err.into(),
1588                                    output: Bytes::new(),
1589                                    gas,
1590                                },
1591                                memory_offset: call.return_memory_offset.clone(),
1592                                was_precompile_called: false,
1593                                precompile_call_logs: vec![],
1594                                charged_new_account_state_gas: call.charged_new_account_state_gas,
1595                            });
1596                        }
1597                    }
1598                }
1599
1600                // If the mocked calls stack has a single element in it, don't empty it
1601                if return_data_queue.len() > 1 {
1602                    return_data_queue.pop_front();
1603                }
1604
1605                return Some(CallOutcome {
1606                    result: InterpreterResult {
1607                        result: return_data.ret_type,
1608                        output: return_data.data,
1609                        gas,
1610                    },
1611                    memory_offset: call.return_memory_offset.clone(),
1612                    was_precompile_called: true,
1613                    precompile_call_logs: vec![],
1614                    charged_new_account_state_gas: call.charged_new_account_state_gas,
1615                });
1616            }
1617        }
1618
1619        // Apply EIP-2930 access list
1620        self.apply_accesslist(ecx);
1621
1622        // Apply our broadcast
1623        if let Some(broadcast) = &self.broadcast {
1624            // Additional check as transfers in forge scripts seem to be estimated at 2300
1625            // by revm leading to "Intrinsic gas too low" failure when simulated on chain.
1626            let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
1627            self.dynamic_gas_limit = false;
1628
1629            // We only apply a broadcast *to a specific depth*.
1630            //
1631            // We do this because any subsequent contract calls *must* exist on chain and
1632            // we only want to grab *this* call, not internal ones
1633            if curr_depth == broadcast.depth && call.caller == broadcast.original_caller {
1634                // At the target depth we set `msg.sender` & tx.origin.
1635                // We are simulating the caller as being an EOA, so *both* must be set to the
1636                // broadcast.origin.
1637                ecx.tx_mut().set_caller(broadcast.new_origin);
1638
1639                call.caller = broadcast.new_origin;
1640                // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here
1641                // because we only need the from, to, value, and data. We can later change this
1642                // into 1559, in the cli package, relatively easily once we
1643                // know the target chain supports EIP-1559.
1644                if !call.is_static {
1645                    if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
1646                        return Some(CallOutcome {
1647                            result: InterpreterResult {
1648                                result: InstructionResult::Revert,
1649                                output: Error::encode(err),
1650                                gas,
1651                            },
1652                            memory_offset: call.return_memory_offset.clone(),
1653                            was_precompile_called: false,
1654                            precompile_call_logs: vec![],
1655                            charged_new_account_state_gas: call.charged_new_account_state_gas,
1656                        });
1657                    }
1658
1659                    let input = call.input.bytes(ecx);
1660                    let chain_id = ecx.cfg().chain_id();
1661                    let rpc = ecx.db().active_fork_url();
1662                    let account =
1663                        ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap();
1664
1665                    let mut tx_req = TransactionRequestFor::<FEN>::default()
1666                        .with_from(broadcast.new_origin)
1667                        .with_to(call.target_address)
1668                        .with_value(call.transfer_value().unwrap_or_default())
1669                        .with_input(input)
1670                        .with_nonce(account.info.nonce)
1671                        .with_chain_id(chain_id);
1672                    if is_fixed_gas_limit {
1673                        tx_req.set_gas_limit(call.gas_limit)
1674                    }
1675
1676                    let active_delegations = std::mem::take(&mut self.active_delegations);
1677                    // Set active blob sidecar, if any.
1678                    if let Some(blob_sidecar) = self.active_blob_sidecar.take() {
1679                        // Ensure blob and delegation are not set for the same tx.
1680                        if !active_delegations.is_empty() {
1681                            let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible";
1682                            return Some(CallOutcome {
1683                                result: InterpreterResult {
1684                                    result: InstructionResult::Revert,
1685                                    output: Error::encode(msg),
1686                                    gas,
1687                                },
1688                                memory_offset: call.return_memory_offset.clone(),
1689                                was_precompile_called: false,
1690                                precompile_call_logs: vec![],
1691                                charged_new_account_state_gas: call.charged_new_account_state_gas,
1692                            });
1693                        }
1694                        tx_req.set_blob_sidecar(blob_sidecar);
1695                    }
1696
1697                    // Apply active EIP-7702 delegations, if any.
1698                    if !active_delegations.is_empty() {
1699                        for auth in &active_delegations {
1700                            let Ok(authority) = auth.recover_authority() else {
1701                                continue;
1702                            };
1703                            if authority == broadcast.new_origin {
1704                                // Increment nonce of broadcasting account to reflect signed
1705                                // authorization.
1706                                account.info.nonce += 1;
1707                            }
1708                        }
1709                        tx_req.set_authorization_list(active_delegations);
1710                    }
1711                    if let Some(fee_token) = self.config.fee_token {
1712                        tx_req.set_fee_token(fee_token);
1713                    }
1714                    self.broadcastable_transactions.push_back(BroadcastableTransaction {
1715                        rpc,
1716                        transaction: TransactionMaybeSigned::new(tx_req),
1717                    });
1718                    debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call");
1719
1720                    // Explicitly increment nonce if calls are not isolated.
1721                    if !self.config.evm_opts.isolate {
1722                        let prev = account.info.nonce;
1723                        account.info.nonce += 1;
1724                        debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce");
1725                    }
1726                } else if broadcast.single_call {
1727                    let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead";
1728                    return Some(CallOutcome {
1729                        result: InterpreterResult {
1730                            result: InstructionResult::Revert,
1731                            output: Error::encode(msg),
1732                            gas,
1733                        },
1734                        memory_offset: call.return_memory_offset.clone(),
1735                        was_precompile_called: false,
1736                        precompile_call_logs: vec![],
1737                        charged_new_account_state_gas: call.charged_new_account_state_gas,
1738                    });
1739                }
1740            }
1741        }
1742
1743        // Record called accounts if `startStateDiffRecording` has been called
1744        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1745            // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero
1746            // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code
1747            let (initialized, old_balance, old_nonce) =
1748                if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) {
1749                    (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce)
1750                } else {
1751                    (false, U256::ZERO, 0)
1752                };
1753
1754            let kind = match call.scheme {
1755                CallScheme::Call => crate::Vm::AccountAccessKind::Call,
1756                CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode,
1757                CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall,
1758                CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall,
1759            };
1760
1761            // Record this call by pushing it to a new pending vector; all subsequent calls at
1762            // that depth will be pushed to the same vector. When the call ends, the
1763            // RecordedAccountAccess (and all subsequent RecordedAccountAccesses) will be
1764            // updated with the revert status of this call, since the EVM does not mark accounts
1765            // as "warm" if the call from which they were accessed is reverted
1766            recorded_account_diffs_stack.push(vec![AccountAccess {
1767                chainInfo: crate::Vm::ChainInfo {
1768                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
1769                    chainId: U256::from(ecx.cfg().chain_id()),
1770                },
1771                accessor: call.caller,
1772                account: call.bytecode_address,
1773                kind,
1774                initialized,
1775                oldBalance: old_balance,
1776                newBalance: U256::ZERO, // updated on call_end
1777                oldNonce: old_nonce,
1778                newNonce: 0, // updated on call_end
1779                value: call.call_value(),
1780                data: call.input.bytes(ecx),
1781                reverted: false,
1782                deployedCode: Bytes::new(),
1783                storageAccesses: vec![], // updated on step
1784                depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1785            }]);
1786        }
1787
1788        None
1789    }
1790
1791    pub fn rng(&mut self) -> &mut impl Rng {
1792        self.test_runner().rng()
1793    }
1794
1795    pub fn test_runner(&mut self) -> &mut TestRunner {
1796        self.test_runner.get_or_insert_with(|| match self.config.seed {
1797            Some(seed) => TestRunner::new_with_rng(
1798                proptest::test_runner::Config::default(),
1799                TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1800            ),
1801            None => TestRunner::new(proptest::test_runner::Config::default()),
1802        })
1803    }
1804
1805    pub fn set_seed(&mut self, seed: U256) {
1806        self.test_runner = Some(TestRunner::new_with_rng(
1807            proptest::test_runner::Config::default(),
1808            TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1809        ));
1810    }
1811
1812    /// Returns existing or set a default `ArbitraryStorage` option.
1813    /// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage.
1814    pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage {
1815        self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default)
1816    }
1817
1818    /// Returns addresses explicitly marked with arbitrary storage.
1819    pub fn arbitrary_storage_targets(&self) -> impl Iterator<Item = Address> + '_ {
1820        self.arbitrary_storage.as_ref().into_iter().flat_map(ArbitraryStorage::targets)
1821    }
1822
1823    /// Returns addresses explicitly marked with arbitrary storage and whether nonzero slots are
1824    /// overwritten.
1825    pub fn arbitrary_storage_target_overwrite_modes(
1826        &self,
1827    ) -> impl Iterator<Item = (Address, bool)> + '_ {
1828        self.arbitrary_storage
1829            .as_ref()
1830            .into_iter()
1831            .flat_map(ArbitraryStorage::target_overwrite_modes)
1832    }
1833
1834    /// Returns addresses that copy storage from arbitrary-storage targets.
1835    pub fn arbitrary_storage_copied_targets(&self) -> impl Iterator<Item = Address> + '_ {
1836        self.arbitrary_storage.as_ref().into_iter().flat_map(ArbitraryStorage::copied_targets)
1837    }
1838
1839    /// Returns copied arbitrary-storage targets and their source address.
1840    pub fn arbitrary_storage_copied_target_sources(
1841        &self,
1842    ) -> impl Iterator<Item = (Address, Address)> + '_ {
1843        self.arbitrary_storage
1844            .as_ref()
1845            .into_iter()
1846            .flat_map(ArbitraryStorage::copied_target_sources)
1847    }
1848
1849    /// Caches a concrete replay value for a slot on an arbitrary-storage address or copied target.
1850    pub fn cache_arbitrary_storage_value(&mut self, address: Address, slot: U256, value: U256) {
1851        if let Some(storage) = &mut self.arbitrary_storage {
1852            storage.cache_value(address, slot, value);
1853        }
1854    }
1855
1856    /// Marks a slot as explicitly written with `vm.store`.
1857    pub fn mark_arbitrary_storage_slot_explicit(&mut self, address: Address, slot: U256) {
1858        if let Some(storage) = &mut self.arbitrary_storage {
1859            storage.mark_explicit(address, slot);
1860        }
1861    }
1862
1863    /// Returns whether a slot was explicitly written with `vm.store`.
1864    pub fn is_arbitrary_storage_slot_explicit(&self, address: Address, slot: U256) -> bool {
1865        self.arbitrary_storage.as_ref().is_some_and(|storage| storage.is_explicit(address, slot))
1866    }
1867
1868    /// Returns a cached arbitrary-storage replay value for a slot.
1869    pub fn cached_arbitrary_storage_value(&self, address: Address, slot: U256) -> Option<U256> {
1870        self.arbitrary_storage.as_ref().and_then(|storage| storage.cached_value(address, slot))
1871    }
1872
1873    /// Whether the given address has arbitrary storage.
1874    pub fn has_arbitrary_storage(&self, address: &Address) -> bool {
1875        match &self.arbitrary_storage {
1876            Some(storage) => storage.values.contains_key(address),
1877            None => false,
1878        }
1879    }
1880
1881    /// Whether the given slot of address with arbitrary storage should be overwritten.
1882    /// True if address is marked as and overwrite and if no value was previously generated for
1883    /// given slot.
1884    pub fn should_overwrite_arbitrary_storage(
1885        &self,
1886        address: &Address,
1887        storage_slot: U256,
1888    ) -> bool {
1889        match &self.arbitrary_storage {
1890            Some(storage) => {
1891                storage.overwrites.contains(address)
1892                    && storage
1893                        .values
1894                        .get(address)
1895                        .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot))
1896                        .is_none()
1897            }
1898            None => false,
1899        }
1900    }
1901
1902    /// Whether the given address is a copy of an address with arbitrary storage.
1903    pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool {
1904        match &self.arbitrary_storage {
1905            Some(storage) => storage.copies.contains_key(address),
1906            None => false,
1907        }
1908    }
1909
1910    /// Registers an SLOAD callback, replacing the existing callback for `target`.
1911    pub fn register_storage_load_hook(
1912        &mut self,
1913        target: Address,
1914        callback_target: Address,
1915        callback_selector: [u8; 4],
1916    ) {
1917        self.storage_load_hooks.insert(target, StorageHook { callback_target, callback_selector });
1918        self.storage_hooks_registered = true;
1919    }
1920
1921    /// Registers an SSTORE callback, replacing the existing callback for `target`.
1922    pub fn register_storage_store_hook(
1923        &mut self,
1924        target: Address,
1925        callback_target: Address,
1926        callback_selector: [u8; 4],
1927    ) {
1928        self.storage_store_hooks.insert(target, StorageHook { callback_target, callback_selector });
1929        self.storage_hooks_registered = true;
1930    }
1931
1932    /// Registers a mapping SSTORE callback. Returns false when a raw hook conflicts.
1933    pub fn register_mapping_storage_store_hook(
1934        &mut self,
1935        target: Address,
1936        root_slot: B256,
1937        callback_target: Address,
1938        callback_selector: [u8; 4],
1939    ) -> bool {
1940        if self.storage_store_hooks.contains_key(&target) {
1941            return false;
1942        }
1943        self.storage_hook_mapping_slots.remove(&target);
1944        self.mapping_storage_store_hooks
1945            .entry(target)
1946            .or_default()
1947            .insert(root_slot, StorageHook { callback_target, callback_selector });
1948        self.storage_hooks_registered = true;
1949        true
1950    }
1951
1952    /// Returns registered mapping SSTORE callbacks.
1953    pub fn mapping_storage_store_hooks(
1954        &self,
1955    ) -> impl Iterator<Item = (Address, B256, StorageHook)> + '_ {
1956        self.mapping_storage_store_hooks
1957            .iter()
1958            .flat_map(|(target, hooks)| hooks.iter().map(|(root, hook)| (*target, *root, *hook)))
1959    }
1960
1961    /// Returns whether mapping hooks conflict with a raw store hook.
1962    pub fn has_mapping_storage_store_hooks(&self, target: Address) -> bool {
1963        self.mapping_storage_store_hooks.get(&target).is_some_and(|hooks| !hooks.is_empty())
1964    }
1965
1966    /// Returns registered SLOAD callbacks.
1967    pub fn storage_load_hooks(&self) -> impl Iterator<Item = (Address, StorageHook)> + '_ {
1968        self.storage_load_hooks.iter().map(|(target, hook)| (*target, *hook))
1969    }
1970
1971    /// Returns registered SSTORE callbacks.
1972    pub fn storage_store_hooks(&self) -> impl Iterator<Item = (Address, StorageHook)> + '_ {
1973        self.storage_store_hooks.iter().map(|(target, hook)| (*target, *hook))
1974    }
1975
1976    /// Returns whether any storage callback is registered.
1977    #[inline]
1978    pub const fn has_storage_hooks(&self) -> bool {
1979        self.storage_hooks_registered
1980    }
1981
1982    /// Clears execution-local mapping provenance while preserving hook registrations.
1983    pub fn clear_storage_hook_mapping_slots(&mut self) {
1984        self.storage_hook_mapping_slots.clear();
1985    }
1986
1987    /// Returns whether a synthetic storage-hook callback or one of its child calls is executing.
1988    #[inline]
1989    pub const fn is_storage_hook_active(&self) -> bool {
1990        self.active_storage_hook.is_some()
1991    }
1992
1993    /// Returns whether `call` is the synthetic callback for the active storage hook.
1994    pub fn is_storage_hook_callback(
1995        &self,
1996        ecx: &FoundryContextFor<'_, FEN>,
1997        call: &CallInputs,
1998    ) -> bool {
1999        self.active_storage_hook.as_ref().is_some_and(|active| {
2000            active.outcome.is_none()
2001                && ecx.journal().depth() == active.parent_depth
2002                && call.caller == CHEATCODE_ADDRESS
2003                && call.target_address == active.callback_target
2004                && call.input.bytes(ecx) == active.callback_input
2005        })
2006    }
2007
2008    fn finish_storage_hook_call(
2009        &mut self,
2010        ecx: &FoundryContextFor<'_, FEN>,
2011        call: &CallInputs,
2012        outcome: &CallOutcome,
2013    ) -> bool {
2014        let Some(active) = self.active_storage_hook.as_mut() else { return false };
2015        if active.outcome.is_some()
2016            || ecx.journal().depth() != active.parent_depth
2017            || call.caller != CHEATCODE_ADDRESS
2018            || call.target_address != active.callback_target
2019            || call.input.bytes(ecx) != active.callback_input
2020        {
2021            return false;
2022        }
2023        active.outcome = Some((outcome.result.result, outcome.result.output.clone()));
2024        true
2025    }
2026
2027    #[inline(always)]
2028    pub fn has_step_hooks(&self) -> bool {
2029        self.broadcast.is_some()
2030            || self.gas_metering.paused
2031            || self.gas_metering.reset
2032            || self.recording_accesses
2033            || self.recorded_account_diffs_stack.is_some()
2034            || !self.allowed_mem_writes.is_empty()
2035            || self.mapping_slots.is_some()
2036            || self.gas_metering.recording
2037            || self.has_active_env_overrides()
2038            || self.has_storage_hooks()
2039    }
2040
2041    #[inline(always)]
2042    pub fn has_step_end_hooks(&self) -> bool {
2043        self.gas_metering.paused
2044            || self.gas_metering.touched
2045            || self.arbitrary_storage.is_some()
2046            || self.mapping_slots.is_some()
2047            || self.has_active_env_overrides()
2048            || self.has_storage_hooks()
2049    }
2050
2051    #[inline(always)]
2052    pub fn has_log_hooks(&self) -> bool {
2053        !self.expected_emits.is_empty() || self.recorded_logs.is_some()
2054    }
2055
2056    #[inline(always)]
2057    pub fn has_recording_accesses_only_step_hook(&self) -> bool {
2058        self.recording_accesses
2059            && self.broadcast.is_none()
2060            && !self.gas_metering.paused
2061            && !self.gas_metering.reset
2062            && self.recorded_account_diffs_stack.is_none()
2063            && self.allowed_mem_writes.is_empty()
2064            && self.mapping_slots.is_none()
2065            && !self.has_storage_hooks()
2066            && !self.gas_metering.recording
2067            && !self.has_active_env_overrides()
2068    }
2069
2070    #[inline(always)]
2071    fn has_active_env_overrides(&self) -> bool {
2072        self.env_overrides.values().any(EnvOverrides::is_any_set)
2073    }
2074
2075    /// Returns struct definitions from the analysis, if available.
2076    pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> {
2077        self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok())
2078    }
2079}
2080
2081impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for Cheatcodes<FEN> {
2082    fn initialize_interp(
2083        &mut self,
2084        interpreter: &mut Interpreter,
2085        ecx: &mut FoundryContextFor<'_, FEN>,
2086    ) {
2087        // When the first interpreter is initialized we've circumvented the balance and gas checks,
2088        // so we apply our actual block data with the correct fees and all.
2089        if let Some(block) = self.block.take() {
2090            ecx.set_block(block);
2091        }
2092        if let Some(gas_price) = self.gas_price.take() {
2093            ecx.tx_mut().set_gas_price(gas_price);
2094        }
2095
2096        // Record gas for current frame.
2097        if self.gas_metering.paused {
2098            self.gas_metering.paused_frames.push(interpreter.gas);
2099        }
2100
2101        // `expectRevert`: track the max call depth during `expectRevert`
2102        if let Some(expected) = &mut self.expected_revert {
2103            expected.max_depth = max(ecx.journal().depth(), expected.max_depth);
2104        }
2105    }
2106
2107    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2108        self.pc = interpreter.bytecode.pc();
2109
2110        if !self.has_step_hooks() {
2111            return;
2112        }
2113
2114        if self.finish_storage_hook_callback(interpreter, ecx) {
2115            return;
2116        }
2117
2118        if self.broadcast.is_some() {
2119            self.set_gas_limit_type(interpreter);
2120        }
2121
2122        // `pauseGasMetering`: pause / resume interpreter gas.
2123        if self.gas_metering.paused {
2124            self.meter_gas(interpreter);
2125        }
2126
2127        // `resetGasMetering`: reset interpreter gas.
2128        if self.gas_metering.reset {
2129            self.meter_gas_reset(interpreter);
2130        }
2131
2132        // `record`: record storage reads and writes.
2133        if self.recording_accesses {
2134            self.record_accesses(interpreter);
2135        }
2136
2137        // `startStateDiffRecording`: record granular ordered storage accesses.
2138        if self.recorded_account_diffs_stack.is_some() {
2139            self.record_state_diffs(interpreter, ecx);
2140        }
2141
2142        // `expectSafeMemory`: check if the current opcode is allowed to interact with memory.
2143        if !self.allowed_mem_writes.is_empty() {
2144            self.check_mem_opcodes(
2145                interpreter,
2146                ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
2147            );
2148        }
2149
2150        if self.mapping_slots.is_some() || !self.mapping_storage_store_hooks.is_empty() {
2151            // `startMappingRecording`: record SSTORE.
2152            if let Some(mapping_slots) = &mut self.mapping_slots {
2153                mapping_step(mapping_slots, interpreter);
2154            }
2155
2156            let account = interpreter.input.target_address;
2157            let mapping_hook_active = self.active_storage_hook.is_none()
2158                && self
2159                    .mapping_storage_store_hooks
2160                    .get(&account)
2161                    .is_some_and(|hooks| !hooks.is_empty());
2162            if mapping_hook_active {
2163                mapping_step(&mut self.storage_hook_mapping_slots, interpreter);
2164            }
2165            self.pending_mapping_hash = if self.mapping_slots.is_some() || mapping_hook_active {
2166                capture_mapping_hash(interpreter)
2167            } else {
2168                None
2169            };
2170        }
2171
2172        // `snapshotGas*`: take a snapshot of the current gas.
2173        if self.gas_metering.recording {
2174            self.meter_gas_record(interpreter, ecx);
2175        }
2176
2177        // Capture the opcode for `step_end` to use, since by the time
2178        // `step_end` runs the PC has already advanced past it. Also peek the
2179        // BLOBHASH index now (still on top of stack before execution) so we
2180        // can look up the override later.
2181        if !self.env_overrides.is_empty() {
2182            let fork_id = ecx.db().active_fork_id();
2183            if let Some(env_overrides) =
2184                self.env_overrides.get_mut(&fork_id).filter(|o| o.is_any_set())
2185            {
2186                // Always clear stale pending state first so a leftover value from
2187                // a prior step (e.g. when `peek` failed, or when an override
2188                // wasn't actually used) cannot leak into the next opcode.
2189                env_overrides.pending_opcode = None;
2190                env_overrides.pending_blobhash_index = None;
2191
2192                let opcode = interpreter.bytecode.opcode();
2193                match opcode {
2194                    op::BASEFEE | op::GASPRICE => {
2195                        env_overrides.pending_opcode = Some(opcode);
2196                    }
2197                    op::BLOBHASH => {
2198                        env_overrides.pending_opcode = Some(opcode);
2199                        env_overrides.pending_blobhash_index =
2200                            interpreter.stack.peek(0).ok().and_then(|index| index.try_into().ok());
2201                    }
2202                    _ => {}
2203                }
2204            }
2205        }
2206
2207        if self.active_storage_hook.is_none() {
2208            self.capture_storage_hook(interpreter, ecx);
2209        }
2210    }
2211
2212    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2213        if !self.has_step_end_hooks() {
2214            return;
2215        }
2216
2217        if self.gas_metering.paused {
2218            self.meter_gas_end(interpreter);
2219        }
2220
2221        if self.gas_metering.touched {
2222            self.meter_gas_check(interpreter);
2223        }
2224
2225        // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage.
2226        if self.arbitrary_storage.is_some() {
2227            self.arbitrary_storage_end(interpreter, ecx);
2228        }
2229
2230        if let Some(pending) = self.pending_mapping_hash.take()
2231            && interpreter
2232                .bytecode
2233                .action
2234                .as_ref()
2235                .and_then(InterpreterAction::instruction_result)
2236                .is_none()
2237        {
2238            if let Some(mapping_slots) = &mut self.mapping_slots {
2239                record_mapping_hash(mapping_slots, interpreter, pending);
2240            }
2241            if self
2242                .mapping_storage_store_hooks
2243                .get(&pending.address)
2244                .is_some_and(|hooks| !hooks.is_empty())
2245                && self.active_storage_hook.is_none()
2246            {
2247                record_mapping_hash(&mut self.storage_hook_mapping_slots, interpreter, pending);
2248            }
2249        }
2250
2251        if self.active_storage_hook.is_none() {
2252            self.invoke_pending_storage_hook(interpreter, ecx);
2253        }
2254
2255        // Apply opcode-level env overrides (basefee/gasprice/blobhash). Needed
2256        // in isolation mode where the actual tx/block env is zeroed for
2257        // fee-accounting; in non-isolation mode the override and the real env
2258        // are kept in sync by the cheatcode handlers, so this is a no-op fixup.
2259        //
2260        // We must only rewrite the stack if the opcode actually completed
2261        // successfully and pushed its result; otherwise (stack underflow on
2262        // BLOBHASH, OOG before push, etc.) the stack is in an error state and
2263        // a blind `pop()+push()` would corrupt the failing frame.
2264        if !self.env_overrides.is_empty() {
2265            let fork_id = ecx.db().active_fork_id();
2266            if self.env_overrides.get(&fork_id).is_some_and(|o| o.is_any_set()) {
2267                // Mirrors the pattern used by `meter_gas_record`: when `action` is
2268                // `Some` with an `instruction_result`, the opcode has set a
2269                // non-continue result (halt/revert/error) — i.e. it didn't push
2270                // its normal result. `None` means "still running", which is the
2271                // success path for a stack-only opcode in `step_end`.
2272                let opcode_failed = interpreter
2273                    .bytecode
2274                    .action
2275                    .as_ref()
2276                    .and_then(|a| a.instruction_result())
2277                    .is_some();
2278                if opcode_failed {
2279                    if let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) {
2280                        env_overrides.pending_opcode = None;
2281                        env_overrides.pending_blobhash_index = None;
2282                    }
2283                } else {
2284                    self.apply_env_overrides(interpreter, fork_id);
2285                }
2286            }
2287        }
2288    }
2289
2290    fn log(&mut self, _ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
2291        if !self.expected_emits.is_empty()
2292            && let Some(err) = expect::handle_expect_emit(self, &log, None)
2293        {
2294            // Because we do not have access to the interpreter here, we cannot fail the test
2295            // immediately. In most cases the failure will still be caught on `call_end`.
2296            // In the rare case it is not, we log the error here.
2297            let _ = sh_err!("{err:?}");
2298        }
2299
2300        // `recordLogs`
2301        record_logs(&mut self.recorded_logs, &log);
2302    }
2303
2304    fn log_full(
2305        &mut self,
2306        interpreter: &mut Interpreter,
2307        _ecx: &mut FoundryContextFor<'_, FEN>,
2308        log: Log,
2309    ) {
2310        if !self.expected_emits.is_empty() {
2311            expect::handle_expect_emit(self, &log, Some(interpreter));
2312        }
2313
2314        // `recordLogs`
2315        record_logs(&mut self.recorded_logs, &log);
2316    }
2317
2318    fn call(
2319        &mut self,
2320        ecx: &mut FoundryContextFor<'_, FEN>,
2321        inputs: &mut CallInputs,
2322    ) -> Option<CallOutcome> {
2323        if self.is_storage_hook_callback(ecx, inputs) {
2324            return None;
2325        }
2326        Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor)
2327    }
2328
2329    fn call_end(
2330        &mut self,
2331        ecx: &mut FoundryContextFor<'_, FEN>,
2332        call: &CallInputs,
2333        outcome: &mut CallOutcome,
2334    ) {
2335        if self.finish_storage_hook_call(ecx, call, outcome) {
2336            return;
2337        }
2338
2339        let cheatcode_call = call.target_address == CHEATCODE_ADDRESS
2340            || call.target_address == HARDHAT_CONSOLE_ADDRESS;
2341        #[cfg(feature = "monad")]
2342        let cheatcode_call = cheatcode_call || is_monad_cheatcode_call::<FEN>(call.target_address);
2343        let curr_depth = ecx.journal().depth();
2344
2345        self.finish_created_accounts_frame(
2346            outcome.result.is_ok(),
2347            CreatedAccountsFrameKind::Call,
2348            curr_depth,
2349        );
2350
2351        // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do
2352        // it for cheatcode calls because they are not applied for cheatcodes in the `call` hook.
2353        // This should be placed before the revert handling, because we might exit early there
2354        if !cheatcode_call {
2355            // Clean up pranks
2356            if let Some(prank) = &self.get_prank(curr_depth)
2357                && curr_depth == prank.depth
2358            {
2359                ecx.tx_mut().set_caller(prank.prank_origin);
2360
2361                // Clean single-call prank once we have returned to the original depth
2362                if prank.single_call {
2363                    self.pranks.remove(&curr_depth);
2364                }
2365            }
2366
2367            // Clean up broadcast
2368            if let Some(broadcast) = &self.broadcast
2369                && curr_depth == broadcast.depth
2370            {
2371                ecx.tx_mut().set_caller(broadcast.original_origin);
2372
2373                // Clean single-call broadcast once we have returned to the original depth
2374                if broadcast.single_call {
2375                    let _ = self.broadcast.take();
2376                }
2377            }
2378        }
2379
2380        // Handle assume no revert cheatcode.
2381        if let Some(assume_no_revert) = &mut self.assume_no_revert {
2382            // Record current reverter address before processing the expect revert if call reverted,
2383            // expect revert is set with expected reverter address and no actual reverter set yet.
2384            if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() {
2385                assume_no_revert.reverted_by = Some(call.target_address);
2386            }
2387
2388            // allow multiple cheatcode calls at the same depth
2389            let curr_depth = ecx.journal().depth();
2390            if curr_depth <= assume_no_revert.depth && !cheatcode_call {
2391                // Discard run if we're at the same depth as cheatcode, call reverted, and no
2392                // specific reason was supplied
2393                if outcome.result.is_revert() {
2394                    let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap();
2395                    return match revert_handlers::handle_assume_no_revert(
2396                        &assume_no_revert,
2397                        outcome.result.result,
2398                        &outcome.result.output,
2399                        &self.config.available_artifacts,
2400                    ) {
2401                        // if result is Ok, it was an anticipated revert; return an "assume" error
2402                        // to reject this run
2403                        Ok(_) => {
2404                            outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into();
2405                        }
2406                        // if result is Error, it was an unanticipated revert; should revert
2407                        // normally
2408                        Err(error) => {
2409                            trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
2410                            outcome.result.result = InstructionResult::Revert;
2411                            outcome.result.output = error.abi_encode().into();
2412                        }
2413                    };
2414                }
2415                // Call didn't revert, reset `assume_no_revert` state.
2416                self.assume_no_revert = None;
2417            }
2418        }
2419
2420        // Handle expected reverts.
2421        if let Some(expected_revert) = &mut self.expected_revert {
2422            // Record current reverter address and call scheme before processing the expect revert
2423            // if call reverted.
2424            let call_failed = !matches!(outcome.result.result, return_ok!());
2425            if call_failed {
2426                // Record current reverter address if expect revert is set with expected reverter
2427                // address and no actual reverter was set yet or if we're expecting more than one
2428                // revert.
2429                if expected_revert.reverter.is_some()
2430                    && (expected_revert.reverted_by.is_none() || expected_revert.count > 1)
2431                {
2432                    expected_revert.reverted_by = Some(call.target_address);
2433                }
2434            }
2435
2436            let curr_depth = ecx.journal().depth();
2437            if curr_depth <= expected_revert.depth {
2438                // Decide whether this `call_end` should consume the pending `expectRevert`.
2439                // With `internal_expect_revert` enabled, a same-depth revert can satisfy it, but
2440                // we must not consume it for external calls that succeed (e.g. calls to
2441                // non-contract addresses that return `Stop` before Solidity's own revert).
2442                let internal = self.config.internal_expect_revert;
2443                let went_deeper = expected_revert.max_depth > expected_revert.depth;
2444                let needs_processing = match expected_revert.kind {
2445                    ExpectedRevertKind::Default => (|| {
2446                        // Cheatcode reverts propagate up; let the outer frame catch them.
2447                        if cheatcode_call {
2448                            return false;
2449                        }
2450                        // Any failure satisfies the expectation.
2451                        if call_failed {
2452                            return true;
2453                        }
2454                        // Traditional expectRevert: succeeded external call went deeper.
2455                        if !internal && went_deeper {
2456                            return true;
2457                        }
2458                        // Test function returned: catch dangling expectations.
2459                        if curr_depth == 0 {
2460                            return true;
2461                        }
2462                        // Same-depth success with internal mode off is an error; with it on,
2463                        // keep waiting for the actual revert.
2464                        !internal
2465                    })(),
2466                    // `pending_processing == true` means we're in the `call_end` hook for
2467                    // `vm.expectCheatcodeRevert` and shouldn't expect a revert here.
2468                    ExpectedRevertKind::Cheatcode { pending_processing } => {
2469                        cheatcode_call && !pending_processing
2470                    }
2471                };
2472
2473                if needs_processing {
2474                    let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
2475                    let clear_last_frame_gas =
2476                        matches!(expected_revert.kind, ExpectedRevertKind::Default);
2477                    return match revert_handlers::handle_expect_revert(
2478                        cheatcode_call,
2479                        false,
2480                        self.config.internal_expect_revert,
2481                        &expected_revert,
2482                        outcome.result.result,
2483                        outcome.result.output.clone(),
2484                        &self.config.available_artifacts,
2485                    ) {
2486                        Err(error) => {
2487                            trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
2488                            outcome.result.result = InstructionResult::Revert;
2489                            outcome.result.output = error.abi_encode().into();
2490                        }
2491                        Ok((_, retdata)) => {
2492                            expected_revert.actual_count += 1;
2493                            if expected_revert.actual_count < expected_revert.count {
2494                                self.expected_revert = Some(expected_revert);
2495                            }
2496                            if clear_last_frame_gas {
2497                                self.gas_metering.last_frame_gas = None;
2498                            }
2499                            outcome.result.result = InstructionResult::Return;
2500                            outcome.result.output = retdata;
2501                        }
2502                    };
2503                }
2504
2505                // Flip `pending_processing` flag for cheatcode revert expectations, marking that
2506                // we've exited the `expectCheatcodeRevert` call scope
2507                if let ExpectedRevertKind::Cheatcode { pending_processing } =
2508                    &mut self.expected_revert.as_mut().unwrap().kind
2509                {
2510                    *pending_processing = false;
2511                }
2512            }
2513        }
2514
2515        // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode
2516        // invocations
2517        if cheatcode_call {
2518            return;
2519        }
2520
2521        // Record the gas usage of the call, this allows the `lastFrameGas` cheatcode to
2522        // retrieve the gas usage of the last call or create.
2523        let gas = outcome.result.gas;
2524        let frame_gas = crate::Vm::Gas {
2525            gasLimit: gas.limit(),
2526            gasTotalUsed: gas.total_gas_spent(),
2527            gasMemoryUsed: 0,
2528            gasRefunded: gas.refunded(),
2529            gasRemaining: gas.remaining(),
2530        };
2531        self.gas_metering.last_call_gas = Some(frame_gas.clone());
2532        self.gas_metering.last_frame_gas = Some(frame_gas);
2533
2534        // If `startStateDiffRecording` has been called, update the `reverted` status of the
2535        // previous call depth's recorded accesses, if any
2536        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2537            // The root call cannot be recorded.
2538            if ecx.journal().depth() > 0
2539                && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop()
2540            {
2541                // Update the reverted status of all deeper calls if this call reverted, in
2542                // accordance with EVM behavior
2543                if outcome.result.is_revert() {
2544                    for element in &mut *last_recorded_depth {
2545                        element.reverted = true;
2546                        for storage_access in &mut element.storageAccesses {
2547                            storage_access.reverted = true;
2548                        }
2549                    }
2550                }
2551
2552                if let Some(call_access) = last_recorded_depth.first_mut() {
2553                    // Assert that we're at the correct depth before recording post-call state
2554                    // changes. Depending on the depth the cheat was
2555                    // called at, there may not be any pending
2556                    // calls to update if execution has percolated up to a higher depth.
2557                    let curr_depth = ecx.journal().depth();
2558                    if call_access.depth == curr_depth as u64
2559                        && let Ok(acc) = ecx.journal_mut().load_account(call.target_address)
2560                    {
2561                        debug_assert!(access_is_call(call_access.kind));
2562                        call_access.newBalance = acc.data.info.balance;
2563                        call_access.newNonce = acc.data.info.nonce;
2564                    }
2565                    // Merge the last depth's AccountAccesses into the AccountAccesses at the
2566                    // current depth, or push them back onto the pending
2567                    // vector if higher depths were not recorded. This
2568                    // preserves ordering of accesses.
2569                    if let Some(last) = recorded_account_diffs_stack.last_mut() {
2570                        last.extend(last_recorded_depth);
2571                    } else {
2572                        recorded_account_diffs_stack.push(last_recorded_depth);
2573                    }
2574                }
2575            }
2576        }
2577
2578        // this will ensure we don't have false positives when trying to diagnose reverts in fork
2579        // mode
2580        let diag = self.fork_revert_diagnostic.take();
2581
2582        // If the call already reverted, preserve that primary failure and skip post-call
2583        // expect* validation so it cannot overwrite the original revert.
2584        if outcome.result.is_revert() {
2585            // if there's a revert and a previous call was diagnosed as fork related revert then we
2586            // can return a better error here
2587            if let Some(err) = diag {
2588                outcome.result.output = Error::encode(err.to_error_msg(&self.labels));
2589            }
2590            return;
2591        }
2592
2593        // At the end of the call,
2594        // we need to check if we've found all the emits.
2595        // We know we've found all the expected emits in the right order
2596        // if the queue is fully matched.
2597        // If it's not fully matched, then either:
2598        // 1. Not enough events were emitted (we'll know this because the amount of times we
2599        // inspected events will be less than the size of the queue) 2. The wrong events
2600        // were emitted (The inspected events should match the size of the queue, but still some
2601        // events will not be matched)
2602
2603        // First, check that we're at the call depth where the emits were declared from.
2604        let should_check_emits = self
2605            .expected_emits
2606            .iter()
2607            .any(|(expected, _)| {
2608                let curr_depth = ecx.journal().depth();
2609                expected.depth == curr_depth
2610            }) &&
2611            // Ignore staticcalls
2612            !call.is_static;
2613        if should_check_emits {
2614            let expected_counts = self
2615                .expected_emits
2616                .iter()
2617                .filter_map(|(expected, count_map)| {
2618                    let count = match expected.address {
2619                        Some(emitter) => match count_map.get(&emitter) {
2620                            Some(log_count) => expected
2621                                .log
2622                                .as_ref()
2623                                .map(|l| log_count.count(l))
2624                                .unwrap_or_else(|| log_count.count_unchecked()),
2625                            None => 0,
2626                        },
2627                        None => match &expected.log {
2628                            Some(log) => count_map.values().map(|logs| logs.count(log)).sum(),
2629                            None => count_map.values().map(|logs| logs.count_unchecked()).sum(),
2630                        },
2631                    };
2632
2633                    (count != expected.count).then_some((expected, count))
2634                })
2635                .collect::<Vec<_>>();
2636
2637            // Revert if not all emits expected were matched.
2638            if let Some((expected, _)) = self
2639                .expected_emits
2640                .iter()
2641                .find(|(expected, _)| !expected.found && expected.count > 0)
2642            {
2643                outcome.result.result = InstructionResult::Revert;
2644                let mismatch_error = expected.mismatch_error.clone();
2645                let expected_log = expected.log.clone();
2646                let checks = expected.checks;
2647                let anonymous = expected.anonymous;
2648                let error_msg = mismatch_error
2649                    .as_ref()
2650                    .map(|mismatch| {
2651                        mismatch.to_error_msg(self, checks, expected_log.as_ref(), anonymous)
2652                    })
2653                    .unwrap_or_else(|| "log != expected log".to_string());
2654                outcome.result.output = error_msg.abi_encode().into();
2655                return;
2656            }
2657
2658            if !expected_counts.is_empty() {
2659                let msg = if outcome.result.is_ok() {
2660                    let (expected, count) = expected_counts.first().unwrap();
2661                    format!("log emitted {count} times, expected {}", expected.count)
2662                } else {
2663                    "expected an emit, but the call reverted instead. \
2664                     ensure you're testing the happy path when using `expectEmit`"
2665                        .to_string()
2666                };
2667
2668                outcome.result.result = InstructionResult::Revert;
2669                outcome.result.output = Error::encode(msg);
2670                return;
2671            }
2672
2673            // All emits were found, we're good.
2674            // Clear the queue, as we expect the user to declare more events for the next call
2675            // if they wanna match further events.
2676            self.expected_emits.clear()
2677        }
2678
2679        // try to diagnose reverts in multi-fork mode where a call is made to an address that does
2680        // not exist
2681        if let TxKind::Call(test_contract) = ecx.tx().kind() {
2682            // if a call to a different contract than the original test contract returned with
2683            // `Stop` we check if the contract actually exists on the active fork
2684            if ecx.db().is_forked_mode()
2685                && outcome.result.result == InstructionResult::Stop
2686                && call.target_address != test_contract
2687            {
2688                self.fork_revert_diagnostic =
2689                    ecx.db().diagnose_revert(call.target_address, ecx.journal().evm_state());
2690            }
2691        }
2692
2693        // If the depth is 0, then this is the root call terminating
2694        if ecx.journal().depth() == 0 {
2695            // If we already have a revert, we shouldn't run the below logic as it can obfuscate an
2696            // earlier error that happened first with unrelated information about
2697            // another error when using cheatcodes.
2698            if outcome.result.is_revert() {
2699                return;
2700            }
2701
2702            // If there's not a revert, we can continue on to run the last logic for expect*
2703            // cheatcodes.
2704
2705            // Match expected calls
2706            for (address, calldatas) in &self.expected_calls {
2707                // Loop over each address, and for each address, loop over each calldata it expects.
2708                for ((calldata, scheme), (expected, actual_count)) in calldatas {
2709                    // Grab the values we expect to see
2710                    let ExpectedCallData { gas, min_gas, value, count, call_type } = expected;
2711
2712                    let failed = match call_type {
2713                        // If the cheatcode was called with a `count` argument,
2714                        // we must check that the EVM performed a CALL with this calldata exactly
2715                        // `count` times.
2716                        ExpectedCallType::Count => *count != *actual_count,
2717                        // If the cheatcode was called without a `count` argument,
2718                        // we must check that the EVM performed a CALL with this calldata at least
2719                        // `count` times. The amount of times to check was
2720                        // the amount of time the cheatcode was called.
2721                        ExpectedCallType::NonCount => *count > *actual_count,
2722                    };
2723                    if failed {
2724                        let expected_values = [
2725                            Some(format!("data {}", hex::encode_prefixed(calldata))),
2726                            value.as_ref().map(|v| format!("value {v}")),
2727                            gas.map(|g| format!("gas {g}")),
2728                            min_gas.map(|g| format!("minimum gas {g}")),
2729                            scheme.map(|scheme| format!("call type {scheme:?}")),
2730                        ]
2731                        .into_iter()
2732                        .flatten()
2733                        .join(", ");
2734                        let but = if outcome.result.is_ok() {
2735                            let s = if *actual_count == 1 { "" } else { "s" };
2736                            format!("was called {actual_count} time{s}")
2737                        } else {
2738                            "the call reverted instead; \
2739                             ensure you're testing the happy path when using `expectCall`"
2740                                .to_string()
2741                        };
2742                        let s = if *count == 1 { "" } else { "s" };
2743                        let msg = format!(
2744                            "expected call to {address} with {expected_values} \
2745                             to be called {count} time{s}, but {but}"
2746                        );
2747                        outcome.result.result = InstructionResult::Revert;
2748                        outcome.result.output = Error::encode(msg);
2749
2750                        return;
2751                    }
2752                }
2753            }
2754
2755            // Check if we have any leftover expected emits
2756            // First, if any emits were found at the root call, then we its ok and we remove them.
2757            // For count=0 expectations, NOT being found is success, so mark them as found
2758            for (expected, _) in &mut self.expected_emits {
2759                if expected.count == 0 && !expected.found {
2760                    expected.found = true;
2761                }
2762            }
2763            self.expected_emits.retain(|(expected, _)| !expected.found);
2764            // If not empty, we got mismatched emits
2765            if !self.expected_emits.is_empty() {
2766                let msg = if outcome.result.is_ok() {
2767                    "expected an emit, but no logs were emitted afterwards. \
2768                     you might have mismatched events or not enough events were emitted"
2769                } else {
2770                    "expected an emit, but the call reverted instead. \
2771                     ensure you're testing the happy path when using `expectEmit`"
2772                };
2773                outcome.result.result = InstructionResult::Revert;
2774                outcome.result.output = Error::encode(msg);
2775                return;
2776            }
2777
2778            // Check for leftover expected creates
2779            if let Some(expected_create) = self.expected_creates.first() {
2780                let msg = format!(
2781                    "expected {} call by address {} for bytecode {} but not found",
2782                    expected_create.create_scheme,
2783                    hex::encode_prefixed(expected_create.deployer),
2784                    hex::encode_prefixed(&expected_create.bytecode),
2785                );
2786                outcome.result.result = InstructionResult::Revert;
2787                outcome.result.output = Error::encode(msg);
2788            }
2789        }
2790    }
2791
2792    fn create(
2793        &mut self,
2794        ecx: &mut FoundryContextFor<'_, FEN>,
2795        mut input: &mut CreateInputs,
2796    ) -> Option<CreateOutcome> {
2797        // Apply custom execution evm version.
2798        if let Some(spec_id) = self.execution_evm_version {
2799            ecx.set_spec_and_gas_params(spec_id);
2800        }
2801
2802        let gas = Gas::new(input.gas_limit());
2803        let curr_depth = ecx.journal().depth();
2804        self.start_created_accounts_frame(
2805            curr_depth == 0,
2806            CreatedAccountsFrameKind::Create,
2807            curr_depth,
2808        );
2809
2810        // Check if we should intercept this create
2811        if self.intercept_next_create_call {
2812            // Reset the flag
2813            self.intercept_next_create_call = false;
2814
2815            // Get initcode from the input
2816            let output = input.init_code();
2817
2818            // Return a revert with the initcode as error data
2819            return Some(CreateOutcome {
2820                result: InterpreterResult { result: InstructionResult::Revert, output, gas },
2821                address: None,
2822                charged_create_state_gas: input.charged_create_state_gas(),
2823            });
2824        }
2825
2826        // Apply our prank
2827        if let Some(prank) = &self.get_prank(curr_depth)
2828            && curr_depth >= prank.depth
2829            && input.caller() == prank.prank_caller
2830        {
2831            // At the target depth we set `msg.sender`
2832            let prank_applied = if curr_depth == prank.depth {
2833                // Ensure new caller is loaded and touched
2834                let _ = journaled_account(ecx, prank.new_caller);
2835                input.set_caller(prank.new_caller);
2836                true
2837            } else {
2838                false
2839            };
2840
2841            // At the target depth, or deeper, we set `tx.origin`
2842            let prank_applied = if let Some(new_origin) = prank.new_origin {
2843                ecx.tx_mut().set_caller(new_origin);
2844                true
2845            } else {
2846                prank_applied
2847            };
2848
2849            // If prank applied for first time, then update
2850            if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
2851                self.pranks.insert(curr_depth, applied_prank);
2852            }
2853        }
2854
2855        // Apply EIP-2930 access list
2856        self.apply_accesslist(ecx);
2857
2858        // Apply our broadcast
2859        if let Some(broadcast) = &mut self.broadcast
2860            && curr_depth >= broadcast.depth
2861            && input.caller() == broadcast.original_caller
2862        {
2863            if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
2864                return Some(CreateOutcome {
2865                    result: InterpreterResult {
2866                        result: InstructionResult::Revert,
2867                        output: Error::encode(err),
2868                        gas,
2869                    },
2870                    address: None,
2871                    charged_create_state_gas: input.charged_create_state_gas(),
2872                });
2873            }
2874
2875            ecx.tx_mut().set_caller(broadcast.new_origin);
2876
2877            if curr_depth == broadcast.depth || broadcast.deploy_from_code {
2878                // Reset deploy from code flag for upcoming calls;
2879                broadcast.deploy_from_code = false;
2880
2881                input.set_caller(broadcast.new_origin);
2882
2883                let rpc = ecx.db().active_fork_url();
2884                let account = &ecx.journal().evm_state()[&broadcast.new_origin];
2885                let mut tx_req = TransactionRequestFor::<FEN>::default()
2886                    .with_from(broadcast.new_origin)
2887                    .with_kind(TxKind::Create)
2888                    .with_value(input.value())
2889                    .with_input(input.init_code())
2890                    .with_nonce(account.info.nonce);
2891                if let Some(fee_token) = self.config.fee_token {
2892                    tx_req.set_fee_token(fee_token);
2893                }
2894                self.broadcastable_transactions.push_back(BroadcastableTransaction {
2895                    rpc,
2896                    transaction: TransactionMaybeSigned::new(tx_req),
2897                });
2898
2899                input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create));
2900            }
2901        }
2902
2903        // Allow cheatcodes from the address of the new contract
2904        let address = input.allow_cheatcodes(self, ecx);
2905
2906        self.record_created_account(ecx.db().active_fork_id(), address);
2907
2908        // If `recordAccountAccesses` has been called, record the create
2909        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2910            recorded_account_diffs_stack.push(vec![AccountAccess {
2911                chainInfo: crate::Vm::ChainInfo {
2912                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
2913                    chainId: U256::from(ecx.cfg().chain_id()),
2914                },
2915                accessor: input.caller(),
2916                account: address,
2917                kind: crate::Vm::AccountAccessKind::Create,
2918                initialized: true,
2919                oldBalance: U256::ZERO, // updated on create_end
2920                newBalance: U256::ZERO, // updated on create_end
2921                oldNonce: 0,            // new contract starts with nonce 0
2922                newNonce: 1,            // updated on create_end (contracts start with nonce 1)
2923                value: input.value(),
2924                data: input.init_code(),
2925                reverted: false,
2926                deployedCode: Bytes::new(), // updated on create_end
2927                storageAccesses: vec![],    // updated on create_end
2928                depth: curr_depth as u64,
2929            }]);
2930        }
2931
2932        None
2933    }
2934
2935    fn create_end(
2936        &mut self,
2937        ecx: &mut FoundryContextFor<'_, FEN>,
2938        call: &CreateInputs,
2939        outcome: &mut CreateOutcome,
2940    ) {
2941        let call = Some(call);
2942        let curr_depth = ecx.journal().depth();
2943
2944        self.finish_created_accounts_frame(
2945            outcome.result.is_ok(),
2946            CreatedAccountsFrameKind::Create,
2947            curr_depth,
2948        );
2949
2950        // Clean up pranks
2951        if let Some(prank) = &self.get_prank(curr_depth)
2952            && curr_depth == prank.depth
2953        {
2954            ecx.tx_mut().set_caller(prank.prank_origin);
2955
2956            // Clean single-call prank once we have returned to the original depth
2957            if prank.single_call {
2958                std::mem::take(&mut self.pranks);
2959            }
2960        }
2961
2962        // Clean up broadcasts
2963        if let Some(broadcast) = &self.broadcast
2964            && curr_depth == broadcast.depth
2965        {
2966            ecx.tx_mut().set_caller(broadcast.original_origin);
2967
2968            // Clean single-call broadcast once we have returned to the original depth
2969            if broadcast.single_call {
2970                std::mem::take(&mut self.broadcast);
2971            }
2972        }
2973
2974        // Handle expected reverts.
2975        if let Some(expected_revert) = &mut self.expected_revert {
2976            // Record the would-be deployed address as the reverter, picking the innermost
2977            // reverting CREATE: this hook runs at every depth, the deepest frame fires
2978            // first, and the `is_none()` lock pins it. For `count > 1` the lock is
2979            // released after each successful iteration (see below) so each iteration
2980            // independently records its own innermost CREATE.
2981            //
2982            // This intentionally differs from `call_end` for `count > 1`, where
2983            // legacy nested CALL handling reports the outermost call per iteration.
2984            //
2985            // `outcome.address` is `None` for pre-frame rejection (depth/balance/nonce);
2986            // in that case the surrounding `call_end` records the caller as the reverter.
2987            if outcome.result.is_revert()
2988                && expected_revert.reverter.is_some()
2989                && expected_revert.reverted_by.is_none()
2990                && let Some(addr) = outcome.address
2991            {
2992                expected_revert.reverted_by = Some(addr);
2993            }
2994
2995            if curr_depth <= expected_revert.depth
2996                && matches!(expected_revert.kind, ExpectedRevertKind::Default)
2997            {
2998                let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
2999                return match revert_handlers::handle_expect_revert(
3000                    false,
3001                    true,
3002                    self.config.internal_expect_revert,
3003                    &expected_revert,
3004                    outcome.result.result,
3005                    outcome.result.output.clone(),
3006                    &self.config.available_artifacts,
3007                ) {
3008                    Ok((address, retdata)) => {
3009                        expected_revert.actual_count += 1;
3010                        if expected_revert.actual_count < expected_revert.count {
3011                            // Reset so the next iteration's innermost CREATE wins again.
3012                            expected_revert.reverted_by = None;
3013                            self.expected_revert = Some(expected_revert.clone());
3014                        }
3015
3016                        outcome.result.result = InstructionResult::Return;
3017                        outcome.result.output = retdata;
3018                        outcome.address = address;
3019                        self.gas_metering.last_frame_gas = None;
3020                    }
3021                    Err(err) => {
3022                        outcome.result.result = InstructionResult::Revert;
3023                        outcome.result.output = err.abi_encode().into();
3024                    }
3025                };
3026            }
3027        }
3028
3029        if curr_depth > 0 {
3030            // Record the gas usage of the create frame, this allows the `lastFrameGas` cheatcode to
3031            // retrieve the gas usage of the last call or create.
3032            let gas = outcome.result.gas;
3033            self.gas_metering.last_frame_gas = Some(crate::Vm::Gas {
3034                gasLimit: gas.limit(),
3035                gasTotalUsed: gas.total_gas_spent(),
3036                gasMemoryUsed: 0,
3037                gasRefunded: gas.refunded(),
3038                gasRemaining: gas.remaining(),
3039            });
3040        }
3041
3042        // If `startStateDiffRecording` has been called, update the `reverted` status of the
3043        // previous call depth's recorded accesses, if any.
3044        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack
3045            && let Some(mut last_depth) = recorded_account_diffs_stack.pop()
3046        {
3047            // Update the reverted status of all deeper calls if this call reverted, in
3048            // accordance with EVM behavior.
3049            if outcome.result.is_revert() {
3050                for element in &mut *last_depth {
3051                    element.reverted = true;
3052                    for storage_access in &mut element.storageAccesses {
3053                        storage_access.reverted = true;
3054                    }
3055                }
3056            }
3057
3058            if let Some(create_access) = last_depth.first_mut() {
3059                // Update post-create state only if recording began before this frame.
3060                if create_access.depth == curr_depth as u64 {
3061                    debug_assert_eq!(
3062                        create_access.kind as u8,
3063                        crate::Vm::AccountAccessKind::Create as u8
3064                    );
3065                    if let Some(address) = outcome.address
3066                        && let Ok(created_acc) = ecx.journal_mut().load_account(address)
3067                    {
3068                        create_access.newBalance = created_acc.data.info.balance;
3069                        create_access.newNonce = created_acc.data.info.nonce;
3070                        create_access.deployedCode =
3071                            created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
3072                    }
3073                }
3074            }
3075            // Merge the last depth's AccountAccesses into the AccountAccesses at the
3076            // current depth, or push them back onto the pending
3077            // vector if higher depths were not recorded. This
3078            // preserves ordering of accesses.
3079            if let Some(last) = recorded_account_diffs_stack.last_mut() {
3080                last.append(&mut last_depth);
3081            } else {
3082                recorded_account_diffs_stack.push(last_depth);
3083            }
3084        }
3085
3086        // Match the create against expected_creates
3087        if !self.expected_creates.is_empty()
3088            && let (Some(address), Some(call)) = (outcome.address, call)
3089            && let Ok(created_acc) = ecx.journal_mut().load_account(address)
3090        {
3091            let bytecode = created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
3092            if let Some((index, _)) =
3093                self.expected_creates.iter().find_position(|expected_create| {
3094                    expected_create.deployer == call.caller()
3095                        && expected_create.create_scheme.eq(call.scheme().into())
3096                        && expected_create.bytecode == bytecode
3097                })
3098            {
3099                self.expected_creates.swap_remove(index);
3100            }
3101        }
3102    }
3103}
3104
3105impl<FEN: FoundryEvmNetwork> InspectorExt for Cheatcodes<FEN> {
3106    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
3107        let target_depth = if let Some(prank) = &self.get_prank(depth) {
3108            prank.depth
3109        } else if let Some(broadcast) = &self.broadcast {
3110            broadcast.depth
3111        } else {
3112            1
3113        };
3114
3115        if depth != target_depth {
3116            return false;
3117        }
3118
3119        match inputs.scheme() {
3120            CreateScheme::Create2 { .. } => {
3121                self.broadcast.is_some() || self.config.always_use_create_2_factory
3122            }
3123            CreateScheme::Create => self.config.batch_rewrite_creates && self.broadcast.is_some(),
3124            _ => false,
3125        }
3126    }
3127
3128    fn create2_deployer(&self) -> Address {
3129        self.config.evm_opts.create2_deployer
3130    }
3131}
3132
3133impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
3134    #[cold]
3135    fn meter_gas(&mut self, interpreter: &mut Interpreter) {
3136        if let Some(paused_gas) = self.gas_metering.paused_frames.last() {
3137            // Keep gas constant if paused.
3138            // Make sure we record the memory changes so that memory expansion is not paused.
3139            let memory = *interpreter.gas.memory();
3140            interpreter.gas = *paused_gas;
3141            interpreter.gas.memory_mut().words_num = memory.words_num;
3142            interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost;
3143        } else {
3144            // Record frame paused gas.
3145            self.gas_metering.paused_frames.push(interpreter.gas);
3146        }
3147    }
3148
3149    #[cold]
3150    fn meter_gas_record(
3151        &mut self,
3152        interpreter: &mut Interpreter,
3153        ecx: &mut FoundryContextFor<'_, FEN>,
3154    ) {
3155        if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() {
3156            self.gas_metering.gas_records.iter_mut().for_each(|record| {
3157                let curr_depth = ecx.journal().depth();
3158                if curr_depth == record.depth {
3159                    // Skip the first opcode of the first call frame as it includes the gas cost of
3160                    // creating the snapshot.
3161                    if self.gas_metering.last_gas_used != 0 {
3162                        let gas_diff = interpreter
3163                            .gas
3164                            .total_gas_spent()
3165                            .saturating_sub(self.gas_metering.last_gas_used);
3166                        record.gas_used = record.gas_used.saturating_add(gas_diff);
3167                    }
3168
3169                    // Update `last_gas_used` to the current spent gas for the next iteration to
3170                    // compare against.
3171                    self.gas_metering.last_gas_used = interpreter.gas.total_gas_spent();
3172                }
3173            });
3174        }
3175    }
3176
3177    #[cold]
3178    fn meter_gas_end(&mut self, interpreter: &mut Interpreter) {
3179        // Remove recorded gas if we exit frame.
3180        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
3181            && will_exit(interpreter_action)
3182        {
3183            self.gas_metering.paused_frames.pop();
3184        }
3185    }
3186
3187    #[cold]
3188    const fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) {
3189        let mut gas = Gas::new(interpreter.gas.limit());
3190        gas.memory_mut().words_num = interpreter.gas.memory().words_num;
3191        gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost;
3192        interpreter.gas = gas;
3193        self.gas_metering.reset = false;
3194    }
3195
3196    #[cold]
3197    fn meter_gas_check(&mut self, interpreter: &mut Interpreter) {
3198        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
3199            && will_exit(interpreter_action)
3200        {
3201            // Reset gas if spent is less than refunded.
3202            // This can happen if gas was paused / resumed or reset.
3203            // https://github.com/foundry-rs/foundry/issues/4370
3204            if interpreter.gas.total_gas_spent()
3205                < u64::try_from(interpreter.gas.refunded()).unwrap_or_default()
3206            {
3207                interpreter.gas = Gas::new(interpreter.gas.limit());
3208            }
3209        }
3210    }
3211
3212    /// Applies opcode-level overrides for `BASEFEE`, `GASPRICE` and `BLOBHASH`.
3213    ///
3214    /// Called from `step_end` *after* the opcode has executed and only when the
3215    /// opcode succeeded (the caller checks `instruction_result`). The opcode
3216    /// pushed its (possibly zeroed) result onto the stack; we replace the top
3217    /// of stack with the cheatcode-set override. This is what makes `vm.fee`,
3218    /// `vm.txGasPrice` and `vm.blobhashes` visible to called contracts under
3219    /// `--isolate` / `--gas-report`, where the inner transaction zeroes the
3220    /// real fee fields for fee-accounting purposes.
3221    ///
3222    /// We can't read the just-executed opcode from `interpreter.bytecode.opcode()`
3223    /// here because the PC has already advanced; instead `step` stashes it in
3224    /// `env_overrides.pending_opcode` for us.
3225    #[cold]
3226    fn apply_env_overrides(&mut self, interpreter: &mut Interpreter, fork_id: Option<U256>) {
3227        let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) else { return };
3228        let Some(opcode) = env_overrides.pending_opcode.take() else { return };
3229        match opcode {
3230            op::BASEFEE => {
3231                if let Some(basefee) = env_overrides.basefee {
3232                    // BASEFEE pushed one value; replace it.
3233                    Self::replace_top_of_stack(interpreter, U256::from(basefee));
3234                }
3235            }
3236            op::GASPRICE => {
3237                if let Some(gas_price) = env_overrides.gas_price {
3238                    // GASPRICE pushed one value; replace it.
3239                    Self::replace_top_of_stack(interpreter, U256::from(gas_price));
3240                }
3241            }
3242            op::BLOBHASH => {
3243                let blob_hashes = env_overrides.blob_hashes.clone();
3244                let blobhash_index = env_overrides.pending_blobhash_index.take();
3245                if let Some(ref blob_hashes) = blob_hashes
3246                    && let Some(index) = blobhash_index
3247                {
3248                    // BLOBHASH popped the index and pushed the hash; replace
3249                    // the hash with our override (zero for out-of-range, per EIP-4844).
3250                    let hash = blob_hashes.get(index as usize).copied().unwrap_or_default();
3251                    Self::replace_top_of_stack(interpreter, hash.into());
3252                }
3253            }
3254            _ => {}
3255        }
3256    }
3257
3258    /// Replaces the top of the interpreter stack with `value`.
3259    ///
3260    /// The caller must only invoke this after a successful opcode that pushed
3261    /// a value onto the stack; the `pop()` is therefore expected to succeed.
3262    /// If it does not (e.g. because of a bug in the caller's success gating)
3263    /// we bail out instead of pushing on top of an unexpected stack, which
3264    /// would silently grow the stack and corrupt the frame.
3265    fn replace_top_of_stack(interpreter: &mut Interpreter, value: U256) {
3266        if interpreter.stack.pop().is_err() {
3267            debug_assert!(false, "env override expected opcode result on stack");
3268            return;
3269        }
3270        let _ = interpreter.stack.push(value);
3271    }
3272
3273    /// Generates or copies arbitrary values for storage slots.
3274    /// Invoked in inspector `step_end` (when the current opcode is not executed), if current opcode
3275    /// to execute is `SLOAD` and storage slot is cold.
3276    /// Ensures that in next step (when `SLOAD` opcode is executed) an arbitrary value is returned:
3277    /// - copies the existing arbitrary storage value (or the new generated one if no value in
3278    ///   cache) from mapped source address to the target address.
3279    /// - generates arbitrary value and saves it in target address storage.
3280    #[cold]
3281    fn arbitrary_storage_end(
3282        &mut self,
3283        interpreter: &mut Interpreter,
3284        ecx: &mut FoundryContextFor<'_, FEN>,
3285    ) {
3286        let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD {
3287            (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address)
3288        } else {
3289            return;
3290        };
3291
3292        if self.is_arbitrary_storage_slot_explicit(target_address, key) {
3293            return;
3294        }
3295
3296        let Some(value) = ecx.sload(target_address, key) else {
3297            return;
3298        };
3299
3300        if (value.is_cold && value.data.is_zero())
3301            || self.should_overwrite_arbitrary_storage(&target_address, key)
3302        {
3303            if self.has_arbitrary_storage(&target_address) {
3304                let arbitrary_value = self
3305                    .cached_arbitrary_storage_value(target_address, key)
3306                    .unwrap_or_else(|| self.rng().random());
3307                self.arbitrary_storage.as_mut().unwrap().save(
3308                    ecx,
3309                    target_address,
3310                    key,
3311                    arbitrary_value,
3312                );
3313            } else if self.is_arbitrary_storage_copy(&target_address) {
3314                let arbitrary_value = self.rng().random();
3315                self.arbitrary_storage.as_mut().unwrap().copy(
3316                    ecx,
3317                    target_address,
3318                    key,
3319                    arbitrary_value,
3320                );
3321            }
3322        }
3323    }
3324
3325    /// Restores parent interpreter state after a synthetic storage-hook callback.
3326    ///
3327    /// Returns whether a failed callback was propagated to the parent frame.
3328    #[inline]
3329    pub fn finish_storage_hook_callback(
3330        &mut self,
3331        interpreter: &mut Interpreter,
3332        ecx: &mut FoundryContextFor<'_, FEN>,
3333    ) -> bool {
3334        let Some(active) = self.active_storage_hook.as_ref() else { return false };
3335        let Some((result, output)) = active.outcome.clone() else { return false };
3336
3337        let active = self.active_storage_hook.take().expect("active storage hook exists");
3338        Self::restore_storage_hook_access(ecx, active.journal_start);
3339        self.restore_storage_hook_inspector_state(active.inspector_state);
3340        let _ = interpreter.stack.pop();
3341        if let Some(item) = active.saved_stack_item {
3342            let result = interpreter.stack.push(item);
3343            debug_assert!(result, "reserved storage-hook stack slot must be available");
3344        }
3345        interpreter.gas = active.saved_gas;
3346        interpreter.return_data.set_buffer(active.saved_return_data);
3347
3348        if result.is_ok() {
3349            false
3350        } else {
3351            interpreter.bytecode.set_action(InterpreterAction::new_return(
3352                InstructionResult::Revert,
3353                output,
3354                interpreter.gas,
3355            ));
3356            true
3357        }
3358    }
3359
3360    fn take_storage_hook_inspector_state(&mut self) -> StorageHookInspectorState {
3361        StorageHookInspectorState {
3362            accesses: std::mem::take(&mut self.accesses),
3363            recording_accesses: std::mem::replace(&mut self.recording_accesses, false),
3364            mapping_slots: self.mapping_slots.take(),
3365            recorded_logs: self.recorded_logs.take(),
3366            mocked_calls: std::mem::take(&mut self.mocked_calls),
3367            mocked_functions: std::mem::take(&mut self.mocked_functions),
3368            expected_revert: self.expected_revert.take(),
3369            assume_no_revert: self.assume_no_revert.take(),
3370            expected_calls: std::mem::take(&mut self.expected_calls),
3371            expected_emits: std::mem::take(&mut self.expected_emits),
3372            expected_creates: std::mem::take(&mut self.expected_creates),
3373        }
3374    }
3375
3376    fn restore_storage_hook_inspector_state(&mut self, state: StorageHookInspectorState) {
3377        self.accesses = state.accesses;
3378        self.recording_accesses = state.recording_accesses;
3379        self.mapping_slots = state.mapping_slots;
3380        self.recorded_logs = state.recorded_logs;
3381        self.mocked_calls = state.mocked_calls;
3382        self.mocked_functions = state.mocked_functions;
3383        self.expected_revert = state.expected_revert;
3384        self.assume_no_revert = state.assume_no_revert;
3385        self.expected_calls = state.expected_calls;
3386        self.expected_emits = state.expected_emits;
3387        self.expected_creates = state.expected_creates;
3388    }
3389
3390    fn restore_storage_hook_access(ecx: &mut FoundryContextFor<'_, FEN>, journal_start: usize) {
3391        let (_, journal) = ecx.db_journal_inner_mut();
3392        let entries =
3393            journal.journal.drain(journal_start.min(journal.journal.len())..).collect_vec();
3394        for entry in entries {
3395            match entry {
3396                JournalEntry::AccountWarmed { address } => {
3397                    journal.state.get_mut(&address).expect("warmed account exists").mark_cold();
3398                }
3399                JournalEntry::StorageWarmed { address, key } => {
3400                    // TODO(@mablr): Preserve the EIP-2200 `original_value` when bumping the REVM
3401                    // family to 42. REVM 41's `mark_cold` resets it for a slot first warmed and
3402                    // modified by the callback.
3403                    journal
3404                        .state
3405                        .get_mut(&address)
3406                        .expect("warmed account exists")
3407                        .storage
3408                        .get_mut(&key)
3409                        .expect("warmed storage slot exists")
3410                        .mark_cold();
3411                }
3412                entry => journal.journal.push(entry),
3413            }
3414        }
3415    }
3416
3417    fn capture_storage_hook(
3418        &mut self,
3419        interpreter: &Interpreter,
3420        ecx: &mut FoundryContextFor<'_, FEN>,
3421    ) {
3422        self.pending_storage_hook = None;
3423        if self.active_storage_hook.is_some() {
3424            return;
3425        }
3426        let account = interpreter.input.target_address;
3427        match interpreter.bytecode.opcode() {
3428            op::SLOAD => {
3429                let slot = try_or_return!(interpreter.stack.peek(0));
3430                let Some(hook) = self.storage_load_hooks.get(&account).copied() else { return };
3431                self.pending_storage_hook = Some(PendingStorageHook::Load { account, slot, hook });
3432            }
3433            op::SSTORE => {
3434                let slot = try_or_return!(interpreter.stack.peek(0));
3435                let (hook, mapping) = if let Some(hook) = self.storage_store_hooks.get(&account) {
3436                    (*hook, None)
3437                } else {
3438                    let Some(provenance) = self
3439                        .storage_hook_mapping_slots
3440                        .get(&account)
3441                        .and_then(|slots| slots.resolve(slot.into()))
3442                    else {
3443                        return;
3444                    };
3445                    let Some(hook) = self
3446                        .mapping_storage_store_hooks
3447                        .get(&account)
3448                        .and_then(|hooks| hooks.get(&provenance.root_slot))
3449                        .copied()
3450                    else {
3451                        return;
3452                    };
3453                    (hook, Some((provenance.root_slot, provenance.keys)))
3454                };
3455                let checkpoint = ecx.journal_mut().checkpoint();
3456                let old_value =
3457                    ecx.sload(account, slot).map(|value| value.data).unwrap_or_default();
3458                ecx.journal_mut().checkpoint_revert(checkpoint);
3459                self.pending_storage_hook =
3460                    Some(PendingStorageHook::Store { account, slot, old_value, mapping, hook });
3461            }
3462            _ => {}
3463        }
3464    }
3465
3466    fn invoke_pending_storage_hook(
3467        &mut self,
3468        interpreter: &mut Interpreter,
3469        ecx: &mut FoundryContextFor<'_, FEN>,
3470    ) {
3471        let Some(pending) = self.pending_storage_hook.take() else { return };
3472        if interpreter
3473            .bytecode
3474            .action
3475            .as_ref()
3476            .and_then(InterpreterAction::instruction_result)
3477            .is_some()
3478        {
3479            return;
3480        }
3481
3482        let (hook, input, saved_stack_item) = match pending {
3483            PendingStorageHook::Load { account, slot, hook } => {
3484                let value = try_or_return!(interpreter.stack.peek(0));
3485                let mut input = Vec::with_capacity(4 + 32 * 3);
3486                input.extend_from_slice(&hook.callback_selector);
3487                input.extend_from_slice(account.into_word().as_slice());
3488                input.extend_from_slice(&slot.to_be_bytes::<32>());
3489                input.extend_from_slice(&value.to_be_bytes::<32>());
3490                (hook, Bytes::from(input), Some(value))
3491            }
3492            PendingStorageHook::Store { account, slot, old_value, mapping, hook } => {
3493                let new_value =
3494                    ecx.sload(account, slot).map(|value| value.data).unwrap_or_default();
3495                let mut input = Vec::with_capacity(4 + 32 * 4);
3496                input.extend_from_slice(&hook.callback_selector);
3497                input.extend_from_slice(account.into_word().as_slice());
3498                input.extend_from_slice(&slot.to_be_bytes::<32>());
3499                if let Some((root, keys)) = mapping {
3500                    input.extend_from_slice(root.as_slice());
3501                    input.extend_from_slice(&U256::from(32 * 6).to_be_bytes::<32>());
3502                    input.extend_from_slice(&old_value.to_be_bytes::<32>());
3503                    input.extend_from_slice(&new_value.to_be_bytes::<32>());
3504                    input.extend_from_slice(&U256::from(keys.len()).to_be_bytes::<32>());
3505                    for key in keys {
3506                        input.extend_from_slice(key.as_slice());
3507                    }
3508                } else {
3509                    input.extend_from_slice(&old_value.to_be_bytes::<32>());
3510                    input.extend_from_slice(&new_value.to_be_bytes::<32>());
3511                }
3512                (hook, Bytes::from(input), None)
3513            }
3514        };
3515
3516        let journal_start = ecx.db_journal_inner_mut().1.journal.len();
3517        let account = match ecx.journal_mut().load_account_with_code(hook.callback_target) {
3518            Ok(account) => account,
3519            Err(err) => {
3520                interpreter.bytecode.set_action(InterpreterAction::new_return(
3521                    InstructionResult::Revert,
3522                    Error::encode(err),
3523                    interpreter.gas,
3524                ));
3525                return;
3526            }
3527        };
3528        let known_bytecode =
3529            (account.info.code_hash, account.info.code.clone().unwrap_or_default());
3530        let saved_gas = interpreter.gas;
3531        let saved_return_data = Bytes::copy_from_slice(interpreter.return_data.buffer());
3532        let gas_limit = interpreter.gas.remaining();
3533        let parent_depth = ecx.journal().depth();
3534        if saved_stack_item.is_some() {
3535            let result = interpreter.stack.pop();
3536            debug_assert!(result.is_ok(), "captured SLOAD result must be on the stack");
3537        }
3538        let inspector_state = self.take_storage_hook_inspector_state();
3539
3540        self.active_storage_hook = Some(ActiveStorageHook {
3541            parent_depth,
3542            callback_target: hook.callback_target,
3543            callback_input: input.clone(),
3544            saved_gas,
3545            saved_return_data,
3546            saved_stack_item,
3547            journal_start,
3548            inspector_state,
3549            outcome: None,
3550        });
3551        interpreter.bytecode.set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
3552            CallInputs {
3553                input: CallInput::Bytes(input),
3554                return_memory_offset: 0..0,
3555                gas_limit,
3556                reservoir: 0,
3557                bytecode_address: hook.callback_target,
3558                known_bytecode,
3559                target_address: hook.callback_target,
3560                caller: CHEATCODE_ADDRESS,
3561                value: CallValue::Transfer(U256::ZERO),
3562                scheme: CallScheme::Call,
3563                is_static: false,
3564                charged_new_account_state_gas: false,
3565            },
3566        ))));
3567    }
3568
3569    /// Records storage slots reads and writes.
3570    #[cold]
3571    fn record_accesses(&mut self, interpreter: &mut Interpreter) {
3572        let access = &mut self.accesses;
3573        match interpreter.bytecode.opcode() {
3574            op::SLOAD => {
3575                let key = try_or_return!(interpreter.stack.peek(0));
3576                access.record_read(interpreter.input.target_address, key);
3577            }
3578            op::SSTORE => {
3579                let key = try_or_return!(interpreter.stack.peek(0));
3580                access.record_write(interpreter.input.target_address, key);
3581            }
3582            _ => {}
3583        }
3584    }
3585
3586    #[cold]
3587    fn record_state_diffs(
3588        &mut self,
3589        interpreter: &mut Interpreter,
3590        ecx: &mut FoundryContextFor<'_, FEN>,
3591    ) {
3592        let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return };
3593        match interpreter.bytecode.opcode() {
3594            op::SELFDESTRUCT => {
3595                // Ensure that we're not selfdestructing a context recording was initiated on
3596                let Some(last) = account_accesses.last_mut() else { return };
3597
3598                // get previous balance, nonce and initialized status of the target account
3599                let target = try_or_return!(interpreter.stack.peek(0));
3600                let target = Address::from_word(B256::from(target));
3601                let (initialized, old_balance, old_nonce) = ecx
3602                    .journal_mut()
3603                    .load_account(target)
3604                    .map(|account| {
3605                        (
3606                            account.data.info.exists(),
3607                            account.data.info.balance,
3608                            account.data.info.nonce,
3609                        )
3610                    })
3611                    .unwrap_or_default();
3612
3613                // load balance of this account
3614                let value = ecx
3615                    .balance(interpreter.input.target_address)
3616                    .map(|b| b.data)
3617                    .unwrap_or(U256::ZERO);
3618
3619                // register access for the target account
3620                last.push(crate::Vm::AccountAccess {
3621                    chainInfo: crate::Vm::ChainInfo {
3622                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
3623                        chainId: U256::from(ecx.cfg().chain_id()),
3624                    },
3625                    accessor: interpreter.input.target_address,
3626                    account: target,
3627                    kind: crate::Vm::AccountAccessKind::SelfDestruct,
3628                    initialized,
3629                    oldBalance: old_balance,
3630                    newBalance: old_balance + value,
3631                    oldNonce: old_nonce,
3632                    newNonce: old_nonce, // nonce doesn't change on selfdestruct
3633                    value,
3634                    data: Bytes::new(),
3635                    reverted: false,
3636                    deployedCode: Bytes::new(),
3637                    storageAccesses: vec![],
3638                    depth: ecx
3639                        .journal()
3640                        .depth()
3641                        .try_into()
3642                        .expect("journaled state depth exceeds u64"),
3643                });
3644            }
3645
3646            op::SLOAD => {
3647                let Some(last) = account_accesses.last_mut() else { return };
3648
3649                let key = try_or_return!(interpreter.stack.peek(0));
3650                let address = interpreter.input.target_address;
3651
3652                // Try to include present value for informational purposes, otherwise assume
3653                // it's not set (zero value). Revert the checkpoint so this read does not warm the
3654                // slot for the actual SLOAD opcode.
3655                let checkpoint = ecx.journal_mut().checkpoint();
3656                let present_value =
3657                    ecx.sload(address, key).map(|previous| previous.data).unwrap_or_default();
3658                ecx.journal_mut().checkpoint_revert(checkpoint);
3659                let access = crate::Vm::StorageAccess {
3660                    account: interpreter.input.target_address,
3661                    slot: key.into(),
3662                    isWrite: false,
3663                    previousValue: present_value.into(),
3664                    newValue: present_value.into(),
3665                    reverted: false,
3666                };
3667                let curr_depth =
3668                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3669                append_storage_access(last, access, curr_depth);
3670            }
3671            op::SSTORE => {
3672                let Some(last) = account_accesses.last_mut() else { return };
3673
3674                let key = try_or_return!(interpreter.stack.peek(0));
3675                let value = try_or_return!(interpreter.stack.peek(1));
3676                let address = interpreter.input.target_address;
3677                // Try to load the account and the slot's previous value, otherwise, assume it's
3678                // not set (zero value). Revert the checkpoint so this read does not warm the slot
3679                // for the actual SSTORE opcode.
3680                let checkpoint = ecx.journal_mut().checkpoint();
3681                let previous_value =
3682                    ecx.sload(address, key).map(|previous| previous.data).unwrap_or_default();
3683                ecx.journal_mut().checkpoint_revert(checkpoint);
3684
3685                let access = crate::Vm::StorageAccess {
3686                    account: address,
3687                    slot: key.into(),
3688                    isWrite: true,
3689                    previousValue: previous_value.into(),
3690                    newValue: value.into(),
3691                    reverted: false,
3692                };
3693                let curr_depth =
3694                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3695                append_storage_access(last, access, curr_depth);
3696            }
3697
3698            // Record account accesses via the EXT family of opcodes
3699            op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => {
3700                let kind = match interpreter.bytecode.opcode() {
3701                    op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy,
3702                    op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize,
3703                    op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash,
3704                    op::BALANCE => crate::Vm::AccountAccessKind::Balance,
3705                    _ => unreachable!(),
3706                };
3707                let address =
3708                    Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0))));
3709                let checkpoint = ecx.journal_mut().checkpoint();
3710                let (initialized, balance, nonce) = ecx
3711                    .journal_mut()
3712                    .load_account(address)
3713                    .map(|acc| (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce))
3714                    .unwrap_or_default();
3715                ecx.journal_mut().checkpoint_revert(checkpoint);
3716                let curr_depth =
3717                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3718                let account_access = crate::Vm::AccountAccess {
3719                    chainInfo: crate::Vm::ChainInfo {
3720                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
3721                        chainId: U256::from(ecx.cfg().chain_id()),
3722                    },
3723                    accessor: interpreter.input.target_address,
3724                    account: address,
3725                    kind,
3726                    initialized,
3727                    oldBalance: balance,
3728                    newBalance: balance,
3729                    oldNonce: nonce,
3730                    newNonce: nonce, // EXT* operations don't change nonce
3731                    value: U256::ZERO,
3732                    data: Bytes::new(),
3733                    reverted: false,
3734                    deployedCode: Bytes::new(),
3735                    storageAccesses: vec![],
3736                    depth: curr_depth,
3737                };
3738                // Record the EXT* call as an account access at the current depth
3739                // (future storage accesses will be recorded in a new "Resume" context)
3740                if let Some(last) = account_accesses.last_mut() {
3741                    last.push(account_access);
3742                } else {
3743                    account_accesses.push(vec![account_access]);
3744                }
3745            }
3746            _ => {}
3747        }
3748    }
3749
3750    /// Checks to see if the current opcode can either mutate directly or expand memory.
3751    ///
3752    /// If the opcode at the current program counter is a match, check if the modified memory lies
3753    /// within the allowed ranges. If not, revert and fail the test.
3754    #[cold]
3755    fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) {
3756        let Some(ranges) = self.allowed_mem_writes.get(&depth) else {
3757            return;
3758        };
3759
3760        // The `mem_opcode_match` macro is used to match the current opcode against a list of
3761        // opcodes that can mutate memory (either directly or expansion via reading). If the
3762        // opcode is a match, the memory offsets that are being written to are checked to be
3763        // within the allowed ranges. If not, the test is failed and the transaction is
3764        // reverted. For all opcodes that can mutate memory aside from MSTORE,
3765        // MSTORE8, and MLOAD, the size and destination offset are on the stack, and
3766        // the macro expands all of these cases. For MSTORE, MSTORE8, and MLOAD, the
3767        // size of the memory write is implicit, so these cases are hard-coded.
3768        macro_rules! mem_opcode_match {
3769            ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => {
3770                match interpreter.bytecode.opcode() {
3771                    ////////////////////////////////////////////////////////////////
3772                    //    OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING     //
3773                    ////////////////////////////////////////////////////////////////
3774
3775                    op::MSTORE => {
3776                        // The offset of the mstore operation is at the top of the stack.
3777                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3778
3779                        // If none of the allowed ranges contain [offset, offset + 32), memory has been
3780                        // unexpectedly mutated.
3781                        if !ranges.iter().any(|range| {
3782                            range.contains(&offset) && range.contains(&(offset + 31))
3783                        }) {
3784                            // SPECIAL CASE: When the compiler attempts to store the selector for
3785                            // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory
3786                            // pointer, which could have been updated to the exclusive upper bound during
3787                            // execution.
3788                            let value = try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>();
3789                            if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
3790                                return
3791                            }
3792
3793                            disallowed_mem_write(offset, 32, interpreter, ranges);
3794                            return
3795                        }
3796                    }
3797                    op::MSTORE8 => {
3798                        // The offset of the mstore8 operation is at the top of the stack.
3799                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3800
3801                        // If none of the allowed ranges contain the offset, memory has been
3802                        // unexpectedly mutated.
3803                        if !ranges.iter().any(|range| range.contains(&offset)) {
3804                            disallowed_mem_write(offset, 1, interpreter, ranges);
3805                            return
3806                        }
3807                    }
3808
3809                    ////////////////////////////////////////////////////////////////
3810                    //        OPERATIONS THAT CAN EXPAND MEMORY BY READING        //
3811                    ////////////////////////////////////////////////////////////////
3812
3813                    op::MLOAD => {
3814                        // The offset of the mload operation is at the top of the stack
3815                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3816
3817                        // If the offset being loaded is >= than the memory size, the
3818                        // memory is being expanded. If none of the allowed ranges contain
3819                        // [offset, offset + 32), memory has been unexpectedly mutated.
3820                        if offset >= interpreter.memory.size() as u64 && !ranges.iter().any(|range| {
3821                            range.contains(&offset) && range.contains(&(offset + 31))
3822                        }) {
3823                            disallowed_mem_write(offset, 32, interpreter, ranges);
3824                            return
3825                        }
3826                    }
3827
3828                    ////////////////////////////////////////////////////////////////
3829                    //          OPERATIONS WITH OFFSET AND SIZE ON STACK          //
3830                    ////////////////////////////////////////////////////////////////
3831
3832                    op::CALL => {
3833                        // The destination offset of the operation is the fifth element on the stack.
3834                        let dest_offset = try_or_return!(interpreter.stack.peek(5)).saturating_to::<u64>();
3835
3836                        // The size of the data that will be copied is the sixth element on the stack.
3837                        let size = try_or_return!(interpreter.stack.peek(6)).saturating_to::<u64>();
3838
3839                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
3840                        // memory outside of the expected ranges has been touched. If the opcode
3841                        // only reads from memory, this is okay as long as the memory is not expanded.
3842                        let fail_cond = !ranges.iter().any(|range| {
3843                            range.contains(&dest_offset) &&
3844                                range.contains(&(dest_offset + size.saturating_sub(1)))
3845                        });
3846
3847                        // If the failure condition is met, set the output buffer to a revert string
3848                        // that gives information about the allowed ranges and revert.
3849                        if fail_cond {
3850                            // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed.
3851                            // It allocated calldata at the current free memory pointer, and will attempt to read
3852                            // from this memory region to perform the call.
3853                            let to = Address::from_word(try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>().into());
3854                            if to == CHEATCODE_ADDRESS {
3855                                let args_offset = try_or_return!(interpreter.stack.peek(3)).saturating_to::<usize>();
3856                                let args_size = try_or_return!(interpreter.stack.peek(4)).saturating_to::<usize>();
3857                                let memory_word = interpreter.memory.slice_len(args_offset, args_size);
3858                                if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
3859                                    return
3860                                }
3861                            }
3862
3863                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
3864                            return
3865                        }
3866                    }
3867
3868                    $(op::$opcode => {
3869                        // The destination offset of the operation.
3870                        let dest_offset = try_or_return!(interpreter.stack.peek($offset_depth)).saturating_to::<u64>();
3871
3872                        // The size of the data that will be copied.
3873                        let size = try_or_return!(interpreter.stack.peek($size_depth)).saturating_to::<u64>();
3874
3875                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
3876                        // memory outside of the expected ranges has been touched. If the opcode
3877                        // only reads from memory, this is okay as long as the memory is not expanded.
3878                        let fail_cond = !ranges.iter().any(|range| {
3879                                range.contains(&dest_offset) &&
3880                                    range.contains(&(dest_offset + size.saturating_sub(1)))
3881                            }) && ($writes ||
3882                                [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| {
3883                                    offset >= interpreter.memory.size() as u64
3884                                })
3885                            );
3886
3887                        // If the failure condition is met, set the output buffer to a revert string
3888                        // that gives information about the allowed ranges and revert.
3889                        if fail_cond {
3890                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
3891                            return
3892                        }
3893                    })*
3894
3895                    _ => {}
3896                }
3897            }
3898        }
3899
3900        // Check if the current opcode can write to memory, and if so, check if the memory
3901        // being written to is registered as safe to modify.
3902        mem_opcode_match!(
3903            (CALLDATACOPY, 0, 2, true),
3904            (CODECOPY, 0, 2, true),
3905            (RETURNDATACOPY, 0, 2, true),
3906            (EXTCODECOPY, 1, 3, true),
3907            (CALLCODE, 5, 6, true),
3908            (STATICCALL, 4, 5, true),
3909            (DELEGATECALL, 4, 5, true),
3910            (KECCAK256, 0, 1, false),
3911            (LOG0, 0, 1, false),
3912            (LOG1, 0, 1, false),
3913            (LOG2, 0, 1, false),
3914            (LOG3, 0, 1, false),
3915            (LOG4, 0, 1, false),
3916            (CREATE, 1, 2, false),
3917            (CREATE2, 1, 2, false),
3918            (RETURN, 0, 1, false),
3919            (REVERT, 0, 1, false),
3920        );
3921    }
3922
3923    #[cold]
3924    fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
3925        match interpreter.bytecode.opcode() {
3926            op::CREATE2 => self.dynamic_gas_limit = true,
3927            op::CALL => {
3928                // If first element of the stack is close to current remaining gas then assume
3929                // dynamic gas limit.
3930                self.dynamic_gas_limit =
3931                    try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100
3932            }
3933            _ => self.dynamic_gas_limit = false,
3934        }
3935    }
3936}
3937
3938/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write,
3939/// and sets the return range to the revert string's location in memory.
3940///
3941/// This will set the interpreter's next action to a return with the revert string as the output.
3942/// And trigger a revert.
3943fn disallowed_mem_write(
3944    dest_offset: u64,
3945    size: u64,
3946    interpreter: &mut Interpreter,
3947    ranges: &[Range<u64>],
3948) {
3949    let revert_string = format!(
3950        "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}",
3951        dest_offset,
3952        size,
3953        ranges.iter().map(|r| format!("[0x{:02X}, 0x{:02X})", r.start, r.end)).join(" U ")
3954    );
3955
3956    interpreter.bytecode.set_action(InterpreterAction::new_return(
3957        InstructionResult::Revert,
3958        Bytes::from(revert_string.into_bytes()),
3959        interpreter.gas,
3960    ));
3961}
3962
3963/// Returns true if the kind of account access is a call.
3964const fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool {
3965    matches!(
3966        kind,
3967        crate::Vm::AccountAccessKind::Call
3968            | crate::Vm::AccountAccessKind::StaticCall
3969            | crate::Vm::AccountAccessKind::CallCode
3970            | crate::Vm::AccountAccessKind::DelegateCall
3971    )
3972}
3973
3974/// Records a log into the recorded logs vector, if it exists.
3975fn record_logs(recorded_logs: &mut Option<Vec<Vm::Log>>, log: &Log) {
3976    if let Some(storage_recorded_logs) = recorded_logs {
3977        storage_recorded_logs.push(Vm::Log {
3978            topics: log.data.topics().to_vec(),
3979            data: log.data.data.clone(),
3980            emitter: log.address,
3981        });
3982    }
3983}
3984
3985/// Appends an AccountAccess that resumes the recording of the current context.
3986fn append_storage_access(
3987    last: &mut Vec<AccountAccess>,
3988    storage_access: crate::Vm::StorageAccess,
3989    storage_depth: u64,
3990) {
3991    // Assert that there's an existing record for the current context.
3992    if !last.is_empty() && last.first().unwrap().depth < storage_depth {
3993        // Three cases to consider:
3994        // 1. If there hasn't been a context switch since the start of this context, then add the
3995        //    storage access to the current context record.
3996        // 2. If there's an existing Resume record, then add the storage access to it.
3997        // 3. Otherwise, create a new Resume record based on the current context.
3998        if last.len() == 1 {
3999            last.first_mut().unwrap().storageAccesses.push(storage_access);
4000        } else {
4001            let last_record = last.last_mut().unwrap();
4002            if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 {
4003                last_record.storageAccesses.push(storage_access);
4004            } else {
4005                let entry = last.first().unwrap();
4006                let resume_record = crate::Vm::AccountAccess {
4007                    chainInfo: crate::Vm::ChainInfo {
4008                        forkId: entry.chainInfo.forkId,
4009                        chainId: entry.chainInfo.chainId,
4010                    },
4011                    accessor: entry.accessor,
4012                    account: entry.account,
4013                    kind: crate::Vm::AccountAccessKind::Resume,
4014                    initialized: entry.initialized,
4015                    storageAccesses: vec![storage_access],
4016                    reverted: entry.reverted,
4017                    // The remaining fields are defaults
4018                    oldBalance: U256::ZERO,
4019                    newBalance: U256::ZERO,
4020                    oldNonce: 0,
4021                    newNonce: 0,
4022                    value: U256::ZERO,
4023                    data: Bytes::new(),
4024                    deployedCode: Bytes::new(),
4025                    depth: entry.depth,
4026                };
4027                last.push(resume_record);
4028            }
4029        }
4030    }
4031}
4032
4033/// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor.
4034const fn cheatcode_of<T: spec::CheatcodeDef>(_: &T) -> &'static spec::Cheatcode<'static> {
4035    T::CHEATCODE
4036}
4037
4038fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str {
4039    cheat.func.signature.split('(').next().unwrap()
4040}
4041
4042const fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str {
4043    cheat.func.id
4044}
4045
4046const fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str {
4047    cheat.func.signature
4048}
4049
4050/// Dispatches the cheatcode call to the appropriate function.
4051fn apply_dispatch<FEN: FoundryEvmNetwork>(
4052    calls: &Vm::VmCalls,
4053    ccx: &mut CheatsCtxt<'_, '_, FEN>,
4054    executor: &mut dyn CheatcodesExecutor<FEN>,
4055) -> Result {
4056    // Extract metadata for logging/deprecation via CheatcodeDef.
4057    macro_rules! get_cheatcode {
4058        ($($variant:ident),*) => {
4059            match calls {
4060                $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)*
4061            }
4062        };
4063    }
4064    let cheat = vm_calls!(get_cheatcode);
4065
4066    let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered();
4067    trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying");
4068
4069    if let spec::Status::Deprecated(replacement) = cheat.status {
4070        ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement);
4071    }
4072
4073    // Monomorphized dispatch: calls apply_full directly, no trait objects.
4074    macro_rules! dispatch {
4075        ($($variant:ident),*) => {
4076            match calls {
4077                $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)*
4078            }
4079        };
4080    }
4081    let mut result = if ccx.state.config.blocked_cheatcodes.contains(&cheat.func.selector_bytes) {
4082        Err(fmt_err!("disabled during restricted execution"))
4083    } else {
4084        vm_calls!(dispatch)
4085    };
4086
4087    // Format the error message to include the cheatcode name.
4088    if let Err(e) = &mut result
4089        && e.is_str()
4090    {
4091        let name = cheatcode_name(cheat);
4092        // Skip showing the cheatcode name for:
4093        // - assertions: too verbose, and can already be inferred from the error message
4094        // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl`
4095        if !name.contains("assert") && name != "rpcUrl" {
4096            *e = fmt_err!("vm.{name}: {e}");
4097        }
4098    }
4099
4100    trace!(
4101        target: "cheatcodes",
4102        return = %match &result {
4103            Ok(b) => hex::encode(b),
4104            Err(e) => e.to_string(),
4105        }
4106    );
4107
4108    result
4109}
4110
4111/// Helper function to check if frame execution will exit.
4112const fn will_exit(action: &InterpreterAction) -> bool {
4113    match action {
4114        InterpreterAction::Return(result) => {
4115            result.result.is_ok_or_revert() || result.result.is_halt()
4116        }
4117        _ => false,
4118    }
4119}
4120
4121#[cfg(test)]
4122mod tests {
4123    use super::*;
4124
4125    fn cheats(flag: bool, broadcast: Option<Broadcast>) -> Cheatcodes {
4126        let config = CheatsConfig { batch_rewrite_creates: flag, ..Default::default() };
4127        let mut cheats = Cheatcodes::new(Arc::new(config));
4128        cheats.broadcast = broadcast;
4129        cheats
4130    }
4131
4132    fn create_inputs() -> CreateInputs {
4133        CreateInputs::new(Address::ZERO, CreateScheme::Create, U256::ZERO, Bytes::new(), 100_000, 0)
4134    }
4135
4136    fn broadcast_at(depth: usize) -> Broadcast {
4137        Broadcast { depth, ..Default::default() }
4138    }
4139
4140    #[test]
4141    fn flag_off_with_broadcast_returns_false() {
4142        let mut cheats = cheats(false, Some(broadcast_at(1)));
4143        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
4144    }
4145
4146    #[test]
4147    fn flag_on_without_broadcast_returns_false() {
4148        let mut cheats = cheats(true, None);
4149        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
4150    }
4151
4152    #[test]
4153    fn flag_on_with_broadcast_depth_mismatch_returns_false() {
4154        let mut cheats = cheats(true, Some(broadcast_at(2)));
4155        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
4156    }
4157
4158    #[test]
4159    fn flag_on_with_broadcast_depth_match_returns_true() {
4160        let mut cheats = cheats(true, Some(broadcast_at(1)));
4161        assert!(cheats.should_use_create2_factory(1, &create_inputs()));
4162    }
4163
4164    #[test]
4165    fn default_cheatcodes_have_no_opcode_hooks() {
4166        let cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4167        assert!(!cheats.has_step_hooks());
4168        assert!(!cheats.has_step_end_hooks());
4169        assert!(!cheats.has_log_hooks());
4170    }
4171
4172    #[test]
4173    fn active_cheatcode_state_enables_opcode_hooks() {
4174        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4175
4176        cheats.recording_accesses = true;
4177        assert!(cheats.has_step_hooks());
4178        assert!(!cheats.has_step_end_hooks());
4179        assert!(cheats.has_recording_accesses_only_step_hook());
4180
4181        cheats.recording_accesses = false;
4182        cheats.gas_metering.touched = true;
4183        assert!(!cheats.has_step_hooks());
4184        assert!(cheats.has_step_end_hooks());
4185        assert!(!cheats.has_recording_accesses_only_step_hook());
4186
4187        cheats.gas_metering.touched = false;
4188        cheats.register_storage_load_hook(Address::ZERO, Address::ZERO, [0; 4]);
4189        assert!(cheats.has_step_hooks());
4190        assert!(cheats.has_step_end_hooks());
4191        assert!(!cheats.has_recording_accesses_only_step_hook());
4192    }
4193
4194    #[test]
4195    fn mixed_step_hooks_disable_record_access_fast_path() {
4196        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4197        cheats.recording_accesses = true;
4198
4199        cheats.gas_metering.reset = true;
4200        assert!(!cheats.has_recording_accesses_only_step_hook());
4201
4202        cheats.gas_metering.reset = false;
4203        cheats.env_overrides.insert(None, EnvOverrides { basefee: Some(1), ..Default::default() });
4204        assert!(!cheats.has_recording_accesses_only_step_hook());
4205    }
4206
4207    #[test]
4208    fn inactive_env_override_entries_do_not_enable_opcode_hooks() {
4209        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4210        cheats.env_overrides.insert(None, EnvOverrides::default());
4211
4212        assert!(!cheats.has_step_hooks());
4213        assert!(!cheats.has_step_end_hooks());
4214
4215        cheats.env_overrides.get_mut(&None).unwrap().basefee = Some(1);
4216        assert!(cheats.has_step_hooks());
4217        assert!(cheats.has_step_end_hooks());
4218    }
4219
4220    #[test]
4221    fn active_log_state_enables_log_hooks() {
4222        let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4223
4224        cheats.recorded_logs = Some(Default::default());
4225        assert!(cheats.has_log_hooks());
4226
4227        cheats.recorded_logs = None;
4228        cheats.expected_emits.push_back((
4229            expect::ExpectedEmit {
4230                depth: 0,
4231                log: None,
4232                checks: [false; 5],
4233                address: None,
4234                anonymous: false,
4235                found: false,
4236                count: 1,
4237                mismatch_error: None,
4238            },
4239            Default::default(),
4240        ));
4241        assert!(cheats.has_log_hooks());
4242    }
4243
4244    #[test]
4245    fn arbitrary_storage_cache_value_routes_copied_targets_to_source() {
4246        let mut storage = ArbitraryStorage::default();
4247        let source = Address::repeat_byte(0x11);
4248        let copied = Address::repeat_byte(0x22);
4249        let slot = U256::from(7);
4250
4251        storage.mark_arbitrary(&source, false);
4252        storage.mark_copy(&source, &copied);
4253        storage.cache_value(copied, slot, U256::ZERO);
4254
4255        assert_eq!(storage.cached_value(source, slot), Some(U256::ZERO));
4256    }
4257}