Skip to main content

foundry_cheatcodes/
inspector.rs

1//! Cheatcode EVM inspector.
2
3use crate::{
4    Cheatcode, CheatsConfig, CheatsCtxt, Error, Result,
5    Vm::{self, AccountAccess},
6    evm::{
7        DealRecord, GasRecord, RecordAccess, journaled_account,
8        mock::{MockCallDataContext, MockCallReturnData},
9        prank::Prank,
10    },
11    inspector::utils::CommonCreateInput,
12    script::{Broadcast, Wallets},
13    test::{
14        assume::AssumeNoRevert,
15        expect::{
16            self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedCreate,
17            ExpectedEmitTracker, ExpectedRevert, ExpectedRevertKind,
18        },
19        revert_handlers,
20    },
21    utils::IgnoredTraces,
22};
23use alloy_consensus::BlobTransactionSidecarVariant;
24use alloy_network::{Ethereum, Network, TransactionBuilder};
25use alloy_primitives::{
26    Address, B256, Bytes, Log, TxKind, U256, hex,
27    map::{AddressHashMap, HashMap, HashSet},
28};
29use alloy_rpc_types::AccessList;
30use alloy_sol_types::{SolCall, SolInterface, SolValue};
31use foundry_common::{
32    FoundryTransactionBuilder, SELECTOR_LEN, TransactionMaybeSigned,
33    mapping_slots::{MappingSlots, step as mapping_step},
34};
35use foundry_evm_core::{
36    Breakpoints, EvmEnv, FoundryTransaction, InspectorExt,
37    abi::Vm::stopExpectSafeMemoryCall,
38    backend::{DatabaseError, DatabaseExt, LocalForkId, RevertDiagnostic},
39    constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME},
40    env::FoundryContextExt,
41    evm::{
42        BlockEnvFor, EthEvmNetwork, FoundryContextFor, FoundryEvmFactory, FoundryEvmNetwork,
43        NestedEvmClosure, SpecFor, TransactionRequestFor, TxEnvFor, with_cloned_context,
44    },
45};
46use foundry_evm_traces::{
47    TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier,
48};
49use foundry_wallets::wallet_multi::MultiWallet;
50use itertools::Itertools;
51use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner};
52use rand::Rng;
53use revm::{
54    Inspector,
55    bytecode::opcode as op,
56    context::{Cfg, ContextTr, Host, JournalTr, Transaction, TransactionType, result::EVMError},
57    context_interface::{CreateScheme, transaction::SignedAuthorization},
58    handler::FrameResult,
59    inspector::JournalExt,
60    interpreter::{
61        CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas,
62        InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
63        interpreter_types::{Jumps, LoopControl, MemoryTr},
64        return_ok,
65    },
66};
67use serde_json::Value;
68use std::{
69    cmp::max,
70    collections::{BTreeMap, VecDeque},
71    fmt::Debug,
72    fs::File,
73    io::BufReader,
74    ops::Range,
75    path::PathBuf,
76    sync::{Arc, OnceLock},
77};
78
79mod utils;
80
81pub mod analysis;
82pub use analysis::CheatcodeAnalysis;
83
84/// Helper trait for running nested EVM operations from inside cheatcode implementations.
85pub trait CheatcodesExecutor<FEN: FoundryEvmNetwork> {
86    /// Runs a closure with a nested EVM built from the current context.
87    /// The inspector is assembled internally — never exposed to the caller.
88    fn with_nested_evm(
89        &mut self,
90        cheats: &mut Cheatcodes<FEN>,
91        ecx: &mut FoundryContextFor<'_, FEN>,
92        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
93    ) -> Result<(), EVMError<DatabaseError>>;
94
95    /// Replays a historical transaction on the database. Inspector is assembled internally.
96    fn transact_on_db(
97        &mut self,
98        cheats: &mut Cheatcodes<FEN>,
99        ecx: &mut FoundryContextFor<'_, FEN>,
100        fork_id: Option<U256>,
101        transaction: B256,
102    ) -> eyre::Result<()>;
103
104    /// Executes a `TransactionRequest` on the database. Inspector is assembled internally.
105    fn transact_from_tx_on_db(
106        &mut self,
107        cheats: &mut Cheatcodes<FEN>,
108        ecx: &mut FoundryContextFor<'_, FEN>,
109        tx: TxEnvFor<FEN>,
110    ) -> eyre::Result<()>;
111
112    /// Runs a closure with a fresh nested EVM built from a raw database and environment.
113    /// Unlike `with_nested_evm`, this does NOT clone from `ecx` and does NOT write back.
114    /// The caller is responsible for state merging. Used by `executeTransactionCall`.
115    /// Returns the final EVM environment after the closure runs (consumed without cloning).
116    #[allow(clippy::type_complexity)]
117    fn with_fresh_nested_evm(
118        &mut self,
119        cheats: &mut Cheatcodes<FEN>,
120        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
121        evm_env: EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>,
122        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
123    ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>>;
124
125    /// Simulates `console.log` invocation.
126    fn console_log(&mut self, msg: &str);
127
128    /// Returns a mutable reference to the tracing inspector if it is available.
129    fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
130        None
131    }
132
133    /// Marks that the next EVM frame is an "inner context" so that isolation mode does not
134    /// trigger a nested `transact_inner`. `original_origin` is stored for the existing
135    /// inner-context adjustment logic that restores `tx.origin`.
136    fn set_in_inner_context(&mut self, _enabled: bool, _original_origin: Option<Address>) {}
137}
138
139/// Builds a sub-EVM from the current context and executes the given CREATE frame.
140pub(crate) fn exec_create<FEN: FoundryEvmNetwork>(
141    executor: &mut dyn CheatcodesExecutor<FEN>,
142    inputs: CreateInputs,
143    ccx: &mut CheatsCtxt<'_, '_, FEN>,
144) -> std::result::Result<CreateOutcome, EVMError<DatabaseError>> {
145    let mut inputs = Some(inputs);
146    let mut outcome = None;
147    executor.with_nested_evm(ccx.state, ccx.ecx, &mut |evm| {
148        let inputs = inputs.take().unwrap();
149        evm.journal_inner_mut().depth += 1;
150
151        let frame = FrameInput::Create(Box::new(inputs));
152
153        let result = match evm.run_execution(frame)? {
154            FrameResult::Call(_) => unreachable!(),
155            FrameResult::Create(create) => create,
156        };
157
158        evm.journal_inner_mut().depth -= 1;
159
160        outcome = Some(result);
161        Ok(())
162    })?;
163    Ok(outcome.unwrap())
164}
165
166/// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an
167/// inspector.
168#[derive(Debug, Default, Clone, Copy)]
169struct TransparentCheatcodesExecutor;
170
171impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for TransparentCheatcodesExecutor {
172    fn with_nested_evm(
173        &mut self,
174        cheats: &mut Cheatcodes<FEN>,
175        ecx: &mut FoundryContextFor<'_, FEN>,
176        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
177    ) -> Result<(), EVMError<DatabaseError>> {
178        with_cloned_context(ecx, |db, evm_env, journal_inner| {
179            let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, cheats);
180            *evm.journal_inner_mut() = journal_inner;
181            f(&mut *evm)?;
182            let sub_inner = evm.journal_inner_mut().clone();
183            let sub_evm_env = evm.to_evm_env();
184            Ok((sub_evm_env, sub_inner))
185        })
186    }
187
188    fn with_fresh_nested_evm(
189        &mut self,
190        cheats: &mut Cheatcodes<FEN>,
191        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
192        evm_env: EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>,
193        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
194    ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>> {
195        let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, cheats);
196        f(&mut *evm)?;
197        Ok(evm.to_evm_env())
198    }
199
200    fn transact_on_db(
201        &mut self,
202        cheats: &mut Cheatcodes<FEN>,
203        ecx: &mut FoundryContextFor<'_, FEN>,
204        fork_id: Option<U256>,
205        transaction: B256,
206    ) -> eyre::Result<()> {
207        let evm_env = ecx.evm_clone();
208        let (db, inner) = ecx.db_journal_inner_mut();
209        db.transact(fork_id, transaction, evm_env, inner, cheats)
210    }
211
212    fn transact_from_tx_on_db(
213        &mut self,
214        cheats: &mut Cheatcodes<FEN>,
215        ecx: &mut FoundryContextFor<'_, FEN>,
216        tx: TxEnvFor<FEN>,
217    ) -> eyre::Result<()> {
218        let evm_env = ecx.evm_clone();
219        let (db, inner) = ecx.db_journal_inner_mut();
220        db.transact_from_tx(tx, evm_env, inner, cheats)
221    }
222
223    fn console_log(&mut self, _msg: &str) {}
224}
225
226macro_rules! try_or_return {
227    ($e:expr) => {
228        match $e {
229            Ok(v) => v,
230            Err(_) => return,
231        }
232    };
233}
234
235/// Contains additional, test specific resources that should be kept for the duration of the test
236#[derive(Debug, Default)]
237pub struct TestContext {
238    /// Buffered readers for files opened for reading (path => BufReader mapping)
239    pub opened_read_files: HashMap<PathBuf, BufReader<File>>,
240}
241
242/// Every time we clone `Context`, we want it to be empty
243impl Clone for TestContext {
244    fn clone(&self) -> Self {
245        Default::default()
246    }
247}
248
249impl TestContext {
250    /// Clears the context.
251    pub fn clear(&mut self) {
252        self.opened_read_files.clear();
253    }
254}
255
256/// Helps collecting transactions from different forks.
257#[derive(Clone, Debug)]
258pub struct BroadcastableTransaction<N: Network = Ethereum> {
259    /// The optional RPC URL.
260    pub rpc: Option<String>,
261    /// The transaction to broadcast.
262    pub transaction: TransactionMaybeSigned<N>,
263}
264
265#[derive(Clone, Debug, Copy)]
266pub struct RecordDebugStepInfo {
267    /// The debug trace node index when the recording starts.
268    pub start_node_idx: usize,
269    /// The original tracer config when the recording starts.
270    pub original_tracer_config: TracingInspectorConfig,
271}
272
273/// Environment overrides applied at the opcode level.
274///
275/// In isolation mode (and inside the synthetic transactions used by
276/// `--gas-report` / `--isolate`) the transaction environment is zeroed for
277/// fee-accounting purposes, so cheatcodes that mutate the env (e.g.
278/// `vm.fee`, `vm.txGasPrice`, `vm.blobhashes`) cannot rely on those
279/// mutations being visible to contracts via the `BASEFEE`, `GASPRICE` and
280/// `BLOBHASH` opcodes. These overrides are applied in `step_end` to fix
281/// the value that was just pushed onto the stack.
282///
283/// # Semantics when invoked from inside the synthetic isolation transaction
284///
285/// `vm.fee` / `vm.txGasPrice` / `vm.blobhashes` consult
286/// [`Cheatcodes::in_isolation_context`]; when set, they only update these
287/// overrides (so `tx.gas_price = 0` continues to apply to fee accounting and
288/// EIP-4844 inner-tx validation does not reject the synthetic call) and
289/// leave the real env untouched. After the inner transaction returns, the
290/// outer env is restored from the cached snapshot taken before
291/// `transact_inner`, which means:
292///
293/// - the override **does** persist for subsequent `BASEFEE`, `GASPRICE` and `BLOBHASH` reads (this
294///   hook fires in `step_end` regardless of isolation),
295/// - `vm.getBlobhashes()` also consults these overrides, so it returns the correct value.
296/// - but the real `block.basefee` / `tx.gas_price` / `tx.blob_hashes` do **not** reflect the
297///   cheatcode value, so other non-opcode env consumers will not see it.
298///
299/// Calling these cheatcodes outside isolation behaves as before (real env
300/// is also mutated and the override mirrors it).
301#[derive(Clone, Debug, Default)]
302pub struct EnvOverrides {
303    /// Override for the `BASEFEE` opcode (set via `vm.fee`).
304    pub basefee: Option<u64>,
305    /// Override for the `GASPRICE` opcode (set via `vm.txGasPrice`).
306    pub gas_price: Option<u128>,
307    /// Override for the `BLOBHASH` opcode (set via `vm.blobhashes`).
308    pub blob_hashes: Option<Vec<B256>>,
309    /// `tx.gas_price` captured at snapshot time when no gas_price override was
310    /// active. `sync_tx_after_env_override_restore` uses this to restore the
311    /// real pre-override value (not hardcoded 0) on revert.
312    pub pre_override_gas_price: Option<u128>,
313    /// `tx.tx_type` captured at snapshot time when no blob_hashes override was
314    /// active. Prevents tx_type being stuck at EIP4844 after reverting from a
315    /// blobhashes-set state.
316    pub pre_override_tx_type: Option<u8>,
317    /// `tx.blob_hashes` captured at snapshot time when no blob_hashes override
318    /// was active.
319    pub pre_override_blob_hashes: Option<Vec<B256>>,
320    /// The opcode about to run (captured in `step`, consumed in `step_end`),
321    /// used to know what was just executed when `step_end` fires — at that
322    /// point `interpreter.bytecode.opcode()` already points at the *next*
323    /// instruction.
324    pending_opcode: Option<u8>,
325    /// Pending index for the `BLOBHASH` opcode, captured in `step` (where
326    /// the index is still on top of the stack) for use in `step_end` (after
327    /// the opcode has consumed it and pushed the looked-up hash).
328    pending_blobhash_index: Option<u64>,
329}
330
331impl EnvOverrides {
332    /// Whether any override is set.
333    #[inline]
334    pub const fn is_any_set(&self) -> bool {
335        self.basefee.is_some() || self.gas_price.is_some() || self.blob_hashes.is_some()
336    }
337}
338
339/// Holds gas metering state.
340#[derive(Clone, Debug, Default)]
341pub struct GasMetering {
342    /// True if gas metering is paused.
343    pub paused: bool,
344    /// True if gas metering was resumed or reset during the test.
345    /// Used to reconcile gas when frame ends (if spent less than refunded).
346    pub touched: bool,
347    /// True if gas metering should be reset to frame limit.
348    pub reset: bool,
349    /// Stores paused gas frames.
350    pub paused_frames: Vec<Gas>,
351
352    /// The group and name of the active snapshot.
353    pub active_gas_snapshot: Option<(String, String)>,
354
355    /// Cache of the amount of gas used in previous call.
356    /// This is used by the `lastCallGas` cheatcode.
357    pub last_call_gas: Option<crate::Vm::Gas>,
358
359    /// True if gas recording is enabled.
360    pub recording: bool,
361    /// The gas used in the last frame.
362    pub last_gas_used: u64,
363    /// Gas records for the active snapshots.
364    pub gas_records: Vec<GasRecord>,
365}
366
367impl GasMetering {
368    /// Start the gas recording.
369    pub const fn start(&mut self) {
370        self.recording = true;
371    }
372
373    /// Stop the gas recording.
374    pub const fn stop(&mut self) {
375        self.recording = false;
376    }
377
378    /// Resume paused gas metering.
379    pub fn resume(&mut self) {
380        if self.paused {
381            self.paused = false;
382            self.touched = true;
383        }
384        self.paused_frames.clear();
385    }
386
387    /// Reset gas to limit.
388    pub fn reset(&mut self) {
389        self.paused = false;
390        self.touched = true;
391        self.reset = true;
392        self.paused_frames.clear();
393    }
394}
395
396/// Holds data about arbitrary storage.
397#[derive(Clone, Debug, Default)]
398pub struct ArbitraryStorage {
399    /// Mapping of arbitrary storage addresses to generated values (slot, arbitrary value).
400    /// (SLOADs return random value if storage slot wasn't accessed).
401    /// Changed values are recorded and used to copy storage to different addresses.
402    pub values: HashMap<Address, HashMap<U256, U256>>,
403    /// Mapping of address with storage copied to arbitrary storage address source.
404    pub copies: HashMap<Address, Address>,
405    /// Address with storage slots that should be overwritten even if previously set.
406    pub overwrites: HashSet<Address>,
407}
408
409impl ArbitraryStorage {
410    /// Marks an address with arbitrary storage.
411    pub fn mark_arbitrary(&mut self, address: &Address, overwrite: bool) {
412        self.values.insert(*address, HashMap::default());
413        if overwrite {
414            self.overwrites.insert(*address);
415        } else {
416            self.overwrites.remove(address);
417        }
418    }
419
420    /// Maps an address that copies storage with the arbitrary storage address.
421    pub fn mark_copy(&mut self, from: &Address, to: &Address) {
422        if self.values.contains_key(from) {
423            self.copies.insert(*to, *from);
424        }
425    }
426
427    /// Saves arbitrary storage value for a given address:
428    /// - store value in changed values cache.
429    /// - update account's storage with given value.
430    pub fn save<CTX: ContextTr>(
431        &mut self,
432        ecx: &mut CTX,
433        address: Address,
434        slot: U256,
435        data: U256,
436    ) {
437        self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data);
438        if ecx.journal_mut().load_account(address).is_ok() {
439            ecx.journal_mut()
440                .sstore(address, slot, data)
441                .expect("could not set arbitrary storage value");
442        }
443    }
444
445    /// Copies arbitrary storage value from source address to the given target address:
446    /// - if a value is present in arbitrary values cache, then update target storage and return
447    ///   existing value.
448    /// - if no value was yet generated for given slot, then save new value in cache and update both
449    ///   source and target storages.
450    pub fn copy<CTX: ContextTr>(
451        &mut self,
452        ecx: &mut CTX,
453        target: Address,
454        slot: U256,
455        new_value: U256,
456    ) -> U256 {
457        let source = self.copies.get(&target).expect("missing arbitrary copy target entry");
458        let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage");
459        let value = match storage_cache.get(&slot) {
460            Some(value) => *value,
461            None => {
462                storage_cache.insert(slot, new_value);
463                // Update source storage with new value.
464                if ecx.journal_mut().load_account(*source).is_ok() {
465                    ecx.journal_mut()
466                        .sstore(*source, slot, new_value)
467                        .expect("could not copy arbitrary storage value");
468                }
469                new_value
470            }
471        };
472        // Update target storage with new value.
473        if ecx.journal_mut().load_account(target).is_ok() {
474            ecx.journal_mut().sstore(target, slot, value).expect("could not set storage");
475        }
476        value
477    }
478}
479
480/// List of transactions that can be broadcasted.
481pub type BroadcastableTransactions<N> = VecDeque<BroadcastableTransaction<N>>;
482
483/// An EVM inspector that handles calls to various cheatcodes, each with their own behavior.
484///
485/// Cheatcodes can be called by contracts during execution to modify the VM environment, such as
486/// mocking addresses, signatures and altering call reverts.
487///
488/// Executing cheatcodes can be very powerful. Most cheatcodes are limited to evm internals, but
489/// there are also cheatcodes like `ffi` which can execute arbitrary commands or `writeFile` and
490/// `readFile` which can manipulate files of the filesystem. Therefore, several restrictions are
491/// implemented for these cheatcodes:
492/// - `ffi`, and file cheatcodes are _always_ opt-in (via foundry config) and never enabled by
493///   default: all respective cheatcode handlers implement the appropriate checks
494/// - File cheatcodes require explicit permissions which paths are allowed for which operation, see
495///   `Config.fs_permission`
496/// - Only permitted accounts are allowed to execute cheatcodes in forking mode, this ensures no
497///   contract deployed on the live network is able to execute cheatcodes by simply calling the
498///   cheatcode address: by default, the caller, test contract and newly deployed contracts are
499///   allowed to execute cheatcodes
500#[derive(Clone, Debug)]
501pub struct Cheatcodes<FEN: FoundryEvmNetwork = EthEvmNetwork> {
502    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities
503    pub analysis: Option<CheatcodeAnalysis>,
504
505    /// The block environment
506    ///
507    /// Used in the cheatcode handler to overwrite the block environment separately from the
508    /// execution block environment.
509    pub block: Option<BlockEnvFor<FEN>>,
510
511    /// Currently active EIP-7702 delegations that will be consumed when building the next
512    /// transaction. Set by `vm.attachDelegation()` and consumed via `.take()` during
513    /// transaction construction.
514    pub active_delegations: Vec<SignedAuthorization>,
515
516    /// The active EIP-4844 blob that will be attached to the next call.
517    pub active_blob_sidecar: Option<BlobTransactionSidecarVariant>,
518
519    /// The gas price.
520    ///
521    /// Used in the cheatcode handler to overwrite the gas price separately from the gas price
522    /// in the execution environment.
523    pub gas_price: Option<u128>,
524
525    /// Address labels
526    pub labels: AddressHashMap<String>,
527
528    /// Prank information, mapped to the call depth where pranks were added.
529    pub pranks: BTreeMap<usize, Prank>,
530
531    /// Expected revert information
532    pub expected_revert: Option<ExpectedRevert>,
533
534    /// Assume next call can revert and discard fuzz run if it does.
535    pub assume_no_revert: Option<AssumeNoRevert>,
536
537    /// Additional diagnostic for reverts
538    pub fork_revert_diagnostic: Option<RevertDiagnostic>,
539
540    /// Recorded storage reads and writes
541    pub accesses: RecordAccess,
542
543    /// Whether storage access recording is currently active
544    pub recording_accesses: bool,
545
546    /// Recorded account accesses (calls, creates) organized by relative call depth, where the
547    /// topmost vector corresponds to accesses at the depth at which account access recording
548    /// began. Each vector in the matrix represents a list of accesses at a specific call
549    /// depth. Once that call context has ended, the last vector is removed from the matrix and
550    /// merged into the previous vector.
551    pub recorded_account_diffs_stack: Option<Vec<Vec<AccountAccess>>>,
552
553    /// The information of the debug step recording.
554    pub record_debug_steps_info: Option<RecordDebugStepInfo>,
555
556    /// Recorded logs
557    pub recorded_logs: Option<Vec<crate::Vm::Log>>,
558
559    /// Mocked calls
560    // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext`
561    pub mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
562
563    /// Mocked functions. Maps target address to be mocked to pair of (calldata, mock address).
564    pub mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
565
566    /// Expected calls
567    pub expected_calls: ExpectedCallTracker,
568    /// Expected emits
569    pub expected_emits: ExpectedEmitTracker,
570    /// Expected creates
571    pub expected_creates: Vec<ExpectedCreate>,
572
573    /// Map of context depths to memory offset ranges that may be written to within the call depth.
574    pub allowed_mem_writes: HashMap<u64, Vec<Range<u64>>>,
575
576    /// Current broadcasting information
577    pub broadcast: Option<Broadcast>,
578
579    /// Scripting based transactions
580    pub broadcastable_transactions: BroadcastableTransactions<FEN::Network>,
581
582    /// Current EIP-2930 access lists.
583    pub access_list: Option<AccessList>,
584
585    /// Additional, user configurable context this Inspector has access to when inspecting a call.
586    pub config: Arc<CheatsConfig>,
587
588    /// Test-scoped context holding data that needs to be reset every test run
589    pub test_context: TestContext,
590
591    /// Whether to commit FS changes such as file creations, writes and deletes.
592    /// Used to prevent duplicate changes file executing non-committing calls.
593    pub fs_commit: bool,
594
595    /// Serialized JSON values.
596    // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic.
597    pub serialized_jsons: BTreeMap<String, BTreeMap<String, Value>>,
598
599    /// All recorded ETH `deal`s.
600    pub eth_deals: Vec<DealRecord>,
601
602    /// Gas metering state.
603    pub gas_metering: GasMetering,
604
605    /// Contains gas snapshots made over the course of a test suite.
606    // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic.
607    pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>,
608
609    /// Mapping slots.
610    pub mapping_slots: Option<AddressHashMap<MappingSlots>>,
611
612    /// The current program counter.
613    pub pc: usize,
614    /// Breakpoints supplied by the `breakpoint` cheatcode.
615    /// `char -> (address, pc)`
616    pub breakpoints: Breakpoints,
617
618    /// Whether the next contract creation should be intercepted to return its initcode.
619    pub intercept_next_create_call: bool,
620
621    /// Optional cheatcodes `TestRunner`. Used for generating random values from uint and int
622    /// strategies.
623    test_runner: Option<TestRunner>,
624
625    /// Ignored traces.
626    pub ignored_traces: IgnoredTraces,
627
628    /// Addresses with arbitrary storage.
629    pub arbitrary_storage: Option<ArbitraryStorage>,
630
631    /// Deprecated cheatcodes mapped to the reason. Used to report warnings on test results.
632    pub deprecated: HashMap<&'static str, Option<&'static str>>,
633    /// Unlocked wallets used in scripts and testing of scripts.
634    pub wallets: Option<Wallets>,
635    /// Signatures identifier for decoding events and functions
636    signatures_identifier: OnceLock<Option<SignaturesIdentifier>>,
637    /// Used to determine whether the broadcasted call has dynamic gas limit.
638    pub dynamic_gas_limit: bool,
639    // Custom execution evm version.
640    pub execution_evm_version: Option<SpecFor<FEN>>,
641
642    /// Opcode-level environment overrides for `BASEFEE`, `GASPRICE` and
643    /// `BLOBHASH`. Set by `vm.fee`, `vm.txGasPrice`, `vm.blobhashes` and
644    /// applied in [`Inspector::step_end`].
645    ///
646    /// Needed because in isolation mode the synthetic inner transaction
647    /// zeroes the corresponding tx/block env fields for fee-accounting.
648    ///
649    /// Keyed by active fork ID (`None` -> local) so that multi-fork tests do not bleed overrides
650    /// across forks when `vm.selectFork` / `vm.createSelectFork` switches the active fork.
651    pub env_overrides: HashMap<Option<LocalForkId>, EnvOverrides>,
652
653    /// Per-state-snapshot copies of [`Self::env_overrides`], captured by
654    /// `vm.snapshotState` and restored by `vm.revertToState[AndDelete]`.
655    ///
656    /// `env_overrides` lives on the cheatcode inspector rather than in
657    /// `EvmEnv`, so the backend's snapshot/revert mechanism does not see
658    /// it. Without this, an override set after a snapshot would survive a
659    /// `revertToState`, and the BASEFEE/GASPRICE/BLOBHASH opcodes (which
660    /// the override layer rewrites in `step_end`) would keep returning
661    /// the post-snapshot value even though `EvmEnv` was rolled back.
662    pub env_overrides_snapshots: HashMap<U256, HashMap<Option<LocalForkId>, EnvOverrides>>,
663
664    /// Whether we are currently executing inside an isolation context, i.e.
665    /// the synthetic inner transaction wrapped by
666    /// `InspectorStackRefMut::transact_inner` (used by `--gas-report` and
667    /// `--isolate`).
668    ///
669    /// Toggled by the inspector stack around the inner `transact_raw`
670    /// call. Cheatcodes that mutate the tx/block env consult this flag and
671    /// route the change through `EnvOverrides` instead of the actual env
672    /// when `true`, so they don't fight with the fee-accounting zeroing.
673    pub in_isolation_context: bool,
674}
675
676// This is not derived because calling this in `fn new` with `..Default::default()` creates a second
677// `CheatsConfig` which is unused, and inside it `ProjectPathsConfig` is relatively expensive to
678// create.
679impl Default for Cheatcodes {
680    fn default() -> Self {
681        Self::new(Arc::default())
682    }
683}
684
685impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
686    /// Creates a new `Cheatcodes` with the given settings.
687    pub fn new(config: Arc<CheatsConfig>) -> Self {
688        Self {
689            analysis: None,
690            fs_commit: true,
691            labels: config.labels.clone(),
692            config,
693            block: Default::default(),
694            active_delegations: Default::default(),
695            active_blob_sidecar: Default::default(),
696            gas_price: Default::default(),
697            pranks: Default::default(),
698            expected_revert: Default::default(),
699            assume_no_revert: Default::default(),
700            fork_revert_diagnostic: Default::default(),
701            accesses: Default::default(),
702            recording_accesses: Default::default(),
703            recorded_account_diffs_stack: Default::default(),
704            recorded_logs: Default::default(),
705            record_debug_steps_info: Default::default(),
706            mocked_calls: Default::default(),
707            mocked_functions: Default::default(),
708            expected_calls: Default::default(),
709            expected_emits: Default::default(),
710            expected_creates: Default::default(),
711            allowed_mem_writes: Default::default(),
712            broadcast: Default::default(),
713            broadcastable_transactions: Default::default(),
714            access_list: Default::default(),
715            test_context: Default::default(),
716            serialized_jsons: Default::default(),
717            eth_deals: Default::default(),
718            gas_metering: Default::default(),
719            gas_snapshots: Default::default(),
720            mapping_slots: Default::default(),
721            pc: Default::default(),
722            breakpoints: Default::default(),
723            intercept_next_create_call: Default::default(),
724            test_runner: Default::default(),
725            ignored_traces: Default::default(),
726            arbitrary_storage: Default::default(),
727            deprecated: Default::default(),
728            wallets: Default::default(),
729            signatures_identifier: Default::default(),
730            dynamic_gas_limit: Default::default(),
731            execution_evm_version: None,
732            env_overrides: Default::default(),
733            env_overrides_snapshots: Default::default(),
734            in_isolation_context: false,
735        }
736    }
737
738    /// Enables cheatcode analysis capabilities by providing a solar compiler instance.
739    pub fn set_analysis(&mut self, analysis: CheatcodeAnalysis) {
740        self.analysis = Some(analysis);
741    }
742
743    /// Returns the env overrides for the given fork (`None` = no-fork / local).
744    pub fn env_overrides_for(&self, fork_id: Option<U256>) -> Option<&EnvOverrides> {
745        self.env_overrides.get(&fork_id).filter(|o| o.is_any_set())
746    }
747
748    /// Returns a mutable reference to the env overrides for the given fork, inserting a
749    /// default entry if absent.
750    pub fn env_overrides_for_mut(&mut self, fork_id: Option<U256>) -> &mut EnvOverrides {
751        self.env_overrides.entry(fork_id).or_default()
752    }
753
754    /// Returns the configured prank at given depth or the first prank configured at a lower depth.
755    /// For example, if pranks configured for depth 1, 3 and 5, the prank for depth 4 is the one
756    /// configured at depth 3.
757    pub fn get_prank(&self, depth: usize) -> Option<&Prank> {
758        self.pranks.range(..=depth).last().map(|(_, prank)| prank)
759    }
760
761    /// Returns the configured wallets if available, else creates a new instance.
762    pub fn wallets(&mut self) -> &Wallets {
763        self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None))
764    }
765
766    /// Sets the unlocked wallets.
767    pub fn set_wallets(&mut self, wallets: Wallets) {
768        self.wallets = Some(wallets);
769    }
770
771    /// Adds a delegation to the active delegations list.
772    pub fn add_delegation(&mut self, authorization: SignedAuthorization) {
773        self.active_delegations.push(authorization);
774    }
775
776    /// Returns the signatures identifier.
777    pub fn signatures_identifier(&self) -> Option<&SignaturesIdentifier> {
778        self.signatures_identifier.get_or_init(|| SignaturesIdentifier::new(true).ok()).as_ref()
779    }
780
781    /// Decodes the input data and applies the cheatcode.
782    fn apply_cheatcode(
783        &mut self,
784        ecx: &mut FoundryContextFor<'_, FEN>,
785        call: &CallInputs,
786        executor: &mut dyn CheatcodesExecutor<FEN>,
787    ) -> Result {
788        // decode the cheatcode call
789        let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| {
790            if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e {
791                let msg = format!(
792                    "unknown cheatcode with selector {selector}; \
793                     you may have a mismatch between the `Vm` interface (likely in `forge-std`) \
794                     and the `forge` version"
795                );
796                return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg));
797            }
798            e
799        })?;
800
801        let caller = call.caller;
802
803        // ensure the caller is allowed to execute cheatcodes,
804        // but only if the backend is in forking mode
805        ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
806
807        apply_dispatch(
808            &decoded,
809            &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller },
810            executor,
811        )
812    }
813
814    /// Grants cheat code access for new contracts if the caller also has
815    /// cheatcode access or the new contract is created in top most call.
816    ///
817    /// There may be cheatcodes in the constructor of the new contract, in order to allow them
818    /// automatically we need to determine the new address.
819    fn allow_cheatcodes_on_create(
820        &self,
821        ecx: &mut FoundryContextFor<FEN>,
822        caller: Address,
823        created_address: Address,
824    ) {
825        if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) {
826            ecx.db_mut().allow_cheatcode_access(created_address);
827        }
828    }
829
830    /// Apply EIP-2930 access list.
831    ///
832    /// If the transaction type is [TransactionType::Legacy] we need to upgrade it to
833    /// [TransactionType::Eip2930] in order to use access lists. Other transaction types support
834    /// access lists themselves.
835    fn apply_accesslist(&mut self, ecx: &mut FoundryContextFor<FEN>) {
836        if let Some(access_list) = &self.access_list {
837            ecx.tx_mut().set_access_list(access_list.clone());
838
839            if ecx.tx().tx_type() == TransactionType::Legacy as u8 {
840                ecx.tx_mut().set_tx_type(TransactionType::Eip2930 as u8);
841            }
842        }
843    }
844
845    /// Called when there was a revert.
846    ///
847    /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's
848    /// revert would run into issues.
849    pub fn on_revert(&mut self, ecx: &mut FoundryContextFor<FEN>) {
850        trace!(deals=?self.eth_deals.len(), "rolling back deals");
851
852        // Delay revert clean up until expected revert is handled, if set.
853        if self.expected_revert.is_some() {
854            return;
855        }
856
857        // we only want to apply cleanup top level
858        if ecx.journal().depth() > 0 {
859            return;
860        }
861
862        // Roll back all previously applied deals
863        // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine
864        // which rolls back any transfers.
865        while let Some(record) = self.eth_deals.pop() {
866            if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) {
867                acc.info.balance = record.old_balance;
868            }
869        }
870    }
871
872    pub fn call_with_executor(
873        &mut self,
874        ecx: &mut FoundryContextFor<'_, FEN>,
875        call: &mut CallInputs,
876        executor: &mut dyn CheatcodesExecutor<FEN>,
877    ) -> Option<CallOutcome> {
878        // Apply custom execution evm version.
879        if let Some(spec_id) = self.execution_evm_version {
880            ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id);
881        }
882
883        let gas = Gas::new(call.gas_limit);
884        let curr_depth = ecx.journal().depth();
885
886        // At the root call to test function or script `run()`/`setUp()` functions, we are
887        // decreasing sender nonce to ensure that it matches on-chain nonce once we start
888        // broadcasting.
889        if curr_depth == 0 {
890            let sender = ecx.tx().caller();
891            let account = match super::evm::journaled_account(ecx, sender) {
892                Ok(account) => account,
893                Err(err) => {
894                    return Some(CallOutcome {
895                        result: InterpreterResult {
896                            result: InstructionResult::Revert,
897                            output: err.abi_encode().into(),
898                            gas,
899                        },
900                        memory_offset: call.return_memory_offset.clone(),
901                        was_precompile_called: false,
902                        precompile_call_logs: vec![],
903                    });
904                }
905            };
906            let prev = account.info.nonce;
907            account.info.nonce = prev.saturating_sub(1);
908
909            trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
910        }
911
912        if call.target_address == CHEATCODE_ADDRESS {
913            return match self.apply_cheatcode(ecx, call, executor) {
914                Ok(retdata) => Some(CallOutcome {
915                    result: InterpreterResult {
916                        result: InstructionResult::Return,
917                        output: retdata.into(),
918                        gas,
919                    },
920                    memory_offset: call.return_memory_offset.clone(),
921                    was_precompile_called: true,
922                    precompile_call_logs: vec![],
923                }),
924                Err(err) => Some(CallOutcome {
925                    result: InterpreterResult {
926                        result: InstructionResult::Revert,
927                        output: err.abi_encode().into(),
928                        gas,
929                    },
930                    memory_offset: call.return_memory_offset.clone(),
931                    was_precompile_called: false,
932                    precompile_call_logs: vec![],
933                }),
934            };
935        }
936
937        if call.target_address == HARDHAT_CONSOLE_ADDRESS {
938            return None;
939        }
940
941        // `expectRevert`: track max call depth. This is also done in `initialize_interp`, but
942        // precompile calls don't create an interpreter frame so we must also track it here.
943        // The callee executes at `curr_depth + 1`.
944        if let Some(expected) = &mut self.expected_revert {
945            expected.max_depth = max(curr_depth + 1, expected.max_depth);
946        }
947
948        // Handle expected calls
949
950        // Grab the different calldatas expected.
951        if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&call.bytecode_address)
952        {
953            // Match every partial/full calldata
954            for (calldata, (expected, actual_count)) in expected_calls_for_target {
955                // Increment actual times seen if...
956                // The calldata is at most, as big as this call's input, and
957                if calldata.len() <= call.input.len() &&
958                    // Both calldata match, taking the length of the assumed smaller one (which will have at least the selector), and
959                    *calldata == call.input.bytes(ecx)[..calldata.len()] &&
960                    // The value matches, if provided
961                    expected
962                        .value.is_none_or(|value| Some(value) == call.transfer_value()) &&
963                    // The gas matches, if provided
964                    expected.gas.is_none_or(|gas| gas == call.gas_limit) &&
965                    // The minimum gas matches, if provided
966                    expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit)
967                {
968                    *actual_count += 1;
969                }
970            }
971        }
972
973        // Apply our prank
974        if let Some(prank) = &self.get_prank(curr_depth) {
975            // Apply delegate call, `call.caller`` will not equal `prank.prank_caller`
976            if prank.delegate_call
977                && curr_depth == prank.depth
978                && call.scheme == CallScheme::DelegateCall
979            {
980                call.target_address = prank.new_caller;
981                call.caller = prank.new_caller;
982                if let Some(new_origin) = prank.new_origin {
983                    ecx.tx_mut().set_caller(new_origin);
984                }
985            }
986
987            if curr_depth >= prank.depth && call.caller == prank.prank_caller {
988                // At the target depth we set `msg.sender`
989                let prank_applied = if curr_depth == prank.depth {
990                    // Ensure new caller is loaded and touched
991                    let _ = journaled_account(ecx, prank.new_caller);
992                    call.caller = prank.new_caller;
993                    true
994                } else {
995                    false
996                };
997
998                // At the target depth, or deeper, we set `tx.origin`
999                let prank_applied = if let Some(new_origin) = prank.new_origin {
1000                    ecx.tx_mut().set_caller(new_origin);
1001                    true
1002                } else {
1003                    prank_applied
1004                };
1005
1006                // If prank applied for first time, then update
1007                if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
1008                    self.pranks.insert(curr_depth, applied_prank);
1009                }
1010            }
1011        }
1012
1013        // Handle mocked calls
1014        if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
1015            let ctx = MockCallDataContext {
1016                calldata: call.input.bytes(ecx),
1017                value: call.transfer_value(),
1018            };
1019
1020            if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
1021                Some(queue) => Some(queue),
1022                None => mocks
1023                    .iter_mut()
1024                    .find(|(mock, _)| {
1025                        call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..])
1026                            && mock.value.is_none_or(|value| Some(value) == call.transfer_value())
1027                    })
1028                    .map(|(_, v)| v),
1029            } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned())
1030            {
1031                if let Some(value) = call.transfer_value() {
1032                    let checkpoint = ecx.journal_mut().checkpoint();
1033                    match ecx.journal_mut().transfer_loaded(
1034                        call.transfer_from(),
1035                        call.transfer_to(),
1036                        value,
1037                    ) {
1038                        None => {
1039                            if return_data.ret_type.is_ok() {
1040                                ecx.journal_mut().checkpoint_commit();
1041                            } else {
1042                                ecx.journal_mut().checkpoint_revert(checkpoint);
1043                            }
1044                        }
1045                        Some(err) => {
1046                            ecx.journal_mut().checkpoint_revert(checkpoint);
1047                            return Some(CallOutcome {
1048                                result: InterpreterResult {
1049                                    result: err.into(),
1050                                    output: Bytes::new(),
1051                                    gas,
1052                                },
1053                                memory_offset: call.return_memory_offset.clone(),
1054                                was_precompile_called: false,
1055                                precompile_call_logs: vec![],
1056                            });
1057                        }
1058                    }
1059                }
1060
1061                // If the mocked calls stack has a single element in it, don't empty it
1062                if return_data_queue.len() > 1 {
1063                    return_data_queue.pop_front();
1064                }
1065
1066                return Some(CallOutcome {
1067                    result: InterpreterResult {
1068                        result: return_data.ret_type,
1069                        output: return_data.data,
1070                        gas,
1071                    },
1072                    memory_offset: call.return_memory_offset.clone(),
1073                    was_precompile_called: true,
1074                    precompile_call_logs: vec![],
1075                });
1076            }
1077        }
1078
1079        // Apply EIP-2930 access list
1080        self.apply_accesslist(ecx);
1081
1082        // Apply our broadcast
1083        if let Some(broadcast) = &self.broadcast {
1084            // Additional check as transfers in forge scripts seem to be estimated at 2300
1085            // by revm leading to "Intrinsic gas too low" failure when simulated on chain.
1086            let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
1087            self.dynamic_gas_limit = false;
1088
1089            // We only apply a broadcast *to a specific depth*.
1090            //
1091            // We do this because any subsequent contract calls *must* exist on chain and
1092            // we only want to grab *this* call, not internal ones
1093            if curr_depth == broadcast.depth && call.caller == broadcast.original_caller {
1094                // At the target depth we set `msg.sender` & tx.origin.
1095                // We are simulating the caller as being an EOA, so *both* must be set to the
1096                // broadcast.origin.
1097                ecx.tx_mut().set_caller(broadcast.new_origin);
1098
1099                call.caller = broadcast.new_origin;
1100                // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here
1101                // because we only need the from, to, value, and data. We can later change this
1102                // into 1559, in the cli package, relatively easily once we
1103                // know the target chain supports EIP-1559.
1104                if !call.is_static {
1105                    if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
1106                        return Some(CallOutcome {
1107                            result: InterpreterResult {
1108                                result: InstructionResult::Revert,
1109                                output: Error::encode(err),
1110                                gas,
1111                            },
1112                            memory_offset: call.return_memory_offset.clone(),
1113                            was_precompile_called: false,
1114                            precompile_call_logs: vec![],
1115                        });
1116                    }
1117
1118                    let input = call.input.bytes(ecx);
1119                    let chain_id = ecx.cfg().chain_id();
1120                    let rpc = ecx.db().active_fork_url();
1121                    let account =
1122                        ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap();
1123
1124                    let mut tx_req = TransactionRequestFor::<FEN>::default()
1125                        .with_from(broadcast.new_origin)
1126                        .with_to(call.target_address)
1127                        .with_value(call.transfer_value().unwrap_or_default())
1128                        .with_input(input)
1129                        .with_nonce(account.info.nonce)
1130                        .with_chain_id(chain_id);
1131                    if is_fixed_gas_limit {
1132                        tx_req.set_gas_limit(call.gas_limit)
1133                    }
1134
1135                    let active_delegations = std::mem::take(&mut self.active_delegations);
1136                    // Set active blob sidecar, if any.
1137                    if let Some(blob_sidecar) = self.active_blob_sidecar.take() {
1138                        // Ensure blob and delegation are not set for the same tx.
1139                        if !active_delegations.is_empty() {
1140                            let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible";
1141                            return Some(CallOutcome {
1142                                result: InterpreterResult {
1143                                    result: InstructionResult::Revert,
1144                                    output: Error::encode(msg),
1145                                    gas,
1146                                },
1147                                memory_offset: call.return_memory_offset.clone(),
1148                                was_precompile_called: false,
1149                                precompile_call_logs: vec![],
1150                            });
1151                        }
1152                        tx_req.set_blob_sidecar(blob_sidecar);
1153                    }
1154
1155                    // Apply active EIP-7702 delegations, if any.
1156                    if !active_delegations.is_empty() {
1157                        for auth in &active_delegations {
1158                            let Ok(authority) = auth.recover_authority() else {
1159                                continue;
1160                            };
1161                            if authority == broadcast.new_origin {
1162                                // Increment nonce of broadcasting account to reflect signed
1163                                // authorization.
1164                                account.info.nonce += 1;
1165                            }
1166                        }
1167                        tx_req.set_authorization_list(active_delegations);
1168                    }
1169                    if let Some(fee_token) = self.config.fee_token {
1170                        tx_req.set_fee_token(fee_token);
1171                    }
1172                    self.broadcastable_transactions.push_back(BroadcastableTransaction {
1173                        rpc,
1174                        transaction: TransactionMaybeSigned::new(tx_req),
1175                    });
1176                    debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call");
1177
1178                    // Explicitly increment nonce if calls are not isolated.
1179                    if !self.config.evm_opts.isolate {
1180                        let prev = account.info.nonce;
1181                        account.info.nonce += 1;
1182                        debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce");
1183                    }
1184                } else if broadcast.single_call {
1185                    let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead";
1186                    return Some(CallOutcome {
1187                        result: InterpreterResult {
1188                            result: InstructionResult::Revert,
1189                            output: Error::encode(msg),
1190                            gas,
1191                        },
1192                        memory_offset: call.return_memory_offset.clone(),
1193                        was_precompile_called: false,
1194                        precompile_call_logs: vec![],
1195                    });
1196                }
1197            }
1198        }
1199
1200        // Record called accounts if `startStateDiffRecording` has been called
1201        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1202            // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero
1203            // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code
1204            let (initialized, old_balance, old_nonce) =
1205                if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) {
1206                    (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce)
1207                } else {
1208                    (false, U256::ZERO, 0)
1209                };
1210
1211            let kind = match call.scheme {
1212                CallScheme::Call => crate::Vm::AccountAccessKind::Call,
1213                CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode,
1214                CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall,
1215                CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall,
1216            };
1217
1218            // Record this call by pushing it to a new pending vector; all subsequent calls at
1219            // that depth will be pushed to the same vector. When the call ends, the
1220            // RecordedAccountAccess (and all subsequent RecordedAccountAccesses) will be
1221            // updated with the revert status of this call, since the EVM does not mark accounts
1222            // as "warm" if the call from which they were accessed is reverted
1223            recorded_account_diffs_stack.push(vec![AccountAccess {
1224                chainInfo: crate::Vm::ChainInfo {
1225                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
1226                    chainId: U256::from(ecx.cfg().chain_id()),
1227                },
1228                accessor: call.caller,
1229                account: call.bytecode_address,
1230                kind,
1231                initialized,
1232                oldBalance: old_balance,
1233                newBalance: U256::ZERO, // updated on call_end
1234                oldNonce: old_nonce,
1235                newNonce: 0, // updated on call_end
1236                value: call.call_value(),
1237                data: call.input.bytes(ecx),
1238                reverted: false,
1239                deployedCode: Bytes::new(),
1240                storageAccesses: vec![], // updated on step
1241                depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1242            }]);
1243        }
1244
1245        None
1246    }
1247
1248    pub fn rng(&mut self) -> &mut impl Rng {
1249        self.test_runner().rng()
1250    }
1251
1252    pub fn test_runner(&mut self) -> &mut TestRunner {
1253        self.test_runner.get_or_insert_with(|| match self.config.seed {
1254            Some(seed) => TestRunner::new_with_rng(
1255                proptest::test_runner::Config::default(),
1256                TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1257            ),
1258            None => TestRunner::new(proptest::test_runner::Config::default()),
1259        })
1260    }
1261
1262    pub fn set_seed(&mut self, seed: U256) {
1263        self.test_runner = Some(TestRunner::new_with_rng(
1264            proptest::test_runner::Config::default(),
1265            TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1266        ));
1267    }
1268
1269    /// Returns existing or set a default `ArbitraryStorage` option.
1270    /// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage.
1271    pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage {
1272        self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default)
1273    }
1274
1275    /// Whether the given address has arbitrary storage.
1276    pub fn has_arbitrary_storage(&self, address: &Address) -> bool {
1277        match &self.arbitrary_storage {
1278            Some(storage) => storage.values.contains_key(address),
1279            None => false,
1280        }
1281    }
1282
1283    /// Whether the given slot of address with arbitrary storage should be overwritten.
1284    /// True if address is marked as and overwrite and if no value was previously generated for
1285    /// given slot.
1286    pub fn should_overwrite_arbitrary_storage(
1287        &self,
1288        address: &Address,
1289        storage_slot: U256,
1290    ) -> bool {
1291        match &self.arbitrary_storage {
1292            Some(storage) => {
1293                storage.overwrites.contains(address)
1294                    && storage
1295                        .values
1296                        .get(address)
1297                        .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot))
1298                        .is_none()
1299            }
1300            None => false,
1301        }
1302    }
1303
1304    /// Whether the given address is a copy of an address with arbitrary storage.
1305    pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool {
1306        match &self.arbitrary_storage {
1307            Some(storage) => storage.copies.contains_key(address),
1308            None => false,
1309        }
1310    }
1311
1312    /// Returns struct definitions from the analysis, if available.
1313    pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> {
1314        self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok())
1315    }
1316}
1317
1318impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for Cheatcodes<FEN> {
1319    fn initialize_interp(
1320        &mut self,
1321        interpreter: &mut Interpreter,
1322        ecx: &mut FoundryContextFor<'_, FEN>,
1323    ) {
1324        // When the first interpreter is initialized we've circumvented the balance and gas checks,
1325        // so we apply our actual block data with the correct fees and all.
1326        if let Some(block) = self.block.take() {
1327            ecx.set_block(block);
1328        }
1329        if let Some(gas_price) = self.gas_price.take() {
1330            ecx.tx_mut().set_gas_price(gas_price);
1331        }
1332
1333        // Record gas for current frame.
1334        if self.gas_metering.paused {
1335            self.gas_metering.paused_frames.push(interpreter.gas);
1336        }
1337
1338        // `expectRevert`: track the max call depth during `expectRevert`
1339        if let Some(expected) = &mut self.expected_revert {
1340            expected.max_depth = max(ecx.journal().depth(), expected.max_depth);
1341        }
1342    }
1343
1344    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1345        self.pc = interpreter.bytecode.pc();
1346
1347        if self.broadcast.is_some() {
1348            self.set_gas_limit_type(interpreter);
1349        }
1350
1351        // `pauseGasMetering`: pause / resume interpreter gas.
1352        if self.gas_metering.paused {
1353            self.meter_gas(interpreter);
1354        }
1355
1356        // `resetGasMetering`: reset interpreter gas.
1357        if self.gas_metering.reset {
1358            self.meter_gas_reset(interpreter);
1359        }
1360
1361        // `record`: record storage reads and writes.
1362        if self.recording_accesses {
1363            self.record_accesses(interpreter);
1364        }
1365
1366        // `startStateDiffRecording`: record granular ordered storage accesses.
1367        if self.recorded_account_diffs_stack.is_some() {
1368            self.record_state_diffs(interpreter, ecx);
1369        }
1370
1371        // `expectSafeMemory`: check if the current opcode is allowed to interact with memory.
1372        if !self.allowed_mem_writes.is_empty() {
1373            self.check_mem_opcodes(
1374                interpreter,
1375                ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1376            );
1377        }
1378
1379        // `startMappingRecording`: record SSTORE and KECCAK256.
1380        if let Some(mapping_slots) = &mut self.mapping_slots {
1381            mapping_step(mapping_slots, interpreter);
1382        }
1383
1384        // `snapshotGas*`: take a snapshot of the current gas.
1385        if self.gas_metering.recording {
1386            self.meter_gas_record(interpreter, ecx);
1387        }
1388
1389        // Capture the opcode for `step_end` to use, since by the time
1390        // `step_end` runs the PC has already advanced past it. Also peek the
1391        // BLOBHASH index now (still on top of stack before execution) so we
1392        // can look up the override later.
1393        if !self.env_overrides.is_empty() {
1394            let fork_id = ecx.db().active_fork_id();
1395            if let Some(env_overrides) =
1396                self.env_overrides.get_mut(&fork_id).filter(|o| o.is_any_set())
1397            {
1398                // Always clear stale pending state first so a leftover value from
1399                // a prior step (e.g. when `peek` failed, or when an override
1400                // wasn't actually used) cannot leak into the next opcode.
1401                env_overrides.pending_opcode = None;
1402                env_overrides.pending_blobhash_index = None;
1403
1404                let opcode = interpreter.bytecode.opcode();
1405                match opcode {
1406                    op::BASEFEE | op::GASPRICE => {
1407                        env_overrides.pending_opcode = Some(opcode);
1408                    }
1409                    op::BLOBHASH => {
1410                        env_overrides.pending_opcode = Some(opcode);
1411                        env_overrides.pending_blobhash_index =
1412                            interpreter.stack.peek(0).ok().and_then(|index| index.try_into().ok());
1413                    }
1414                    _ => {}
1415                }
1416            }
1417        }
1418    }
1419
1420    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1421        if self.gas_metering.paused {
1422            self.meter_gas_end(interpreter);
1423        }
1424
1425        if self.gas_metering.touched {
1426            self.meter_gas_check(interpreter);
1427        }
1428
1429        // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage.
1430        if self.arbitrary_storage.is_some() {
1431            self.arbitrary_storage_end(interpreter, ecx);
1432        }
1433
1434        // Apply opcode-level env overrides (basefee/gasprice/blobhash). Needed
1435        // in isolation mode where the actual tx/block env is zeroed for
1436        // fee-accounting; in non-isolation mode the override and the real env
1437        // are kept in sync by the cheatcode handlers, so this is a no-op fixup.
1438        //
1439        // We must only rewrite the stack if the opcode actually completed
1440        // successfully and pushed its result; otherwise (stack underflow on
1441        // BLOBHASH, OOG before push, etc.) the stack is in an error state and
1442        // a blind `pop()+push()` would corrupt the failing frame.
1443        if !self.env_overrides.is_empty() {
1444            let fork_id = ecx.db().active_fork_id();
1445            if self.env_overrides.get(&fork_id).is_some_and(|o| o.is_any_set()) {
1446                // Mirrors the pattern used by `meter_gas_record`: when `action` is
1447                // `Some` with an `instruction_result`, the opcode has set a
1448                // non-continue result (halt/revert/error) — i.e. it didn't push
1449                // its normal result. `None` means "still running", which is the
1450                // success path for a stack-only opcode in `step_end`.
1451                let opcode_failed = interpreter
1452                    .bytecode
1453                    .action
1454                    .as_ref()
1455                    .and_then(|a| a.instruction_result())
1456                    .is_some();
1457                if opcode_failed {
1458                    if let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) {
1459                        env_overrides.pending_opcode = None;
1460                        env_overrides.pending_blobhash_index = None;
1461                    }
1462                } else {
1463                    self.apply_env_overrides(interpreter, fork_id);
1464                }
1465            }
1466        }
1467    }
1468
1469    fn log(&mut self, _ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1470        if !self.expected_emits.is_empty()
1471            && let Some(err) = expect::handle_expect_emit(self, &log, None)
1472        {
1473            // Because we do not have access to the interpreter here, we cannot fail the test
1474            // immediately. In most cases the failure will still be caught on `call_end`.
1475            // In the rare case it is not, we log the error here.
1476            let _ = sh_err!("{err:?}");
1477        }
1478
1479        // `recordLogs`
1480        record_logs(&mut self.recorded_logs, &log);
1481    }
1482
1483    fn log_full(
1484        &mut self,
1485        interpreter: &mut Interpreter,
1486        _ecx: &mut FoundryContextFor<'_, FEN>,
1487        log: Log,
1488    ) {
1489        if !self.expected_emits.is_empty() {
1490            expect::handle_expect_emit(self, &log, Some(interpreter));
1491        }
1492
1493        // `recordLogs`
1494        record_logs(&mut self.recorded_logs, &log);
1495    }
1496
1497    fn call(
1498        &mut self,
1499        ecx: &mut FoundryContextFor<'_, FEN>,
1500        inputs: &mut CallInputs,
1501    ) -> Option<CallOutcome> {
1502        Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor)
1503    }
1504
1505    fn call_end(
1506        &mut self,
1507        ecx: &mut FoundryContextFor<'_, FEN>,
1508        call: &CallInputs,
1509        outcome: &mut CallOutcome,
1510    ) {
1511        let cheatcode_call = call.target_address == CHEATCODE_ADDRESS
1512            || call.target_address == HARDHAT_CONSOLE_ADDRESS;
1513
1514        // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do
1515        // it for cheatcode calls because they are not applied for cheatcodes in the `call` hook.
1516        // This should be placed before the revert handling, because we might exit early there
1517        if !cheatcode_call {
1518            // Clean up pranks
1519            let curr_depth = ecx.journal().depth();
1520            if let Some(prank) = &self.get_prank(curr_depth)
1521                && curr_depth == prank.depth
1522            {
1523                ecx.tx_mut().set_caller(prank.prank_origin);
1524
1525                // Clean single-call prank once we have returned to the original depth
1526                if prank.single_call {
1527                    self.pranks.remove(&curr_depth);
1528                }
1529            }
1530
1531            // Clean up broadcast
1532            if let Some(broadcast) = &self.broadcast
1533                && curr_depth == broadcast.depth
1534            {
1535                ecx.tx_mut().set_caller(broadcast.original_origin);
1536
1537                // Clean single-call broadcast once we have returned to the original depth
1538                if broadcast.single_call {
1539                    let _ = self.broadcast.take();
1540                }
1541            }
1542        }
1543
1544        // Handle assume no revert cheatcode.
1545        if let Some(assume_no_revert) = &mut self.assume_no_revert {
1546            // Record current reverter address before processing the expect revert if call reverted,
1547            // expect revert is set with expected reverter address and no actual reverter set yet.
1548            if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() {
1549                assume_no_revert.reverted_by = Some(call.target_address);
1550            }
1551
1552            // allow multiple cheatcode calls at the same depth
1553            let curr_depth = ecx.journal().depth();
1554            if curr_depth <= assume_no_revert.depth && !cheatcode_call {
1555                // Discard run if we're at the same depth as cheatcode, call reverted, and no
1556                // specific reason was supplied
1557                if outcome.result.is_revert() {
1558                    let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap();
1559                    return match revert_handlers::handle_assume_no_revert(
1560                        &assume_no_revert,
1561                        outcome.result.result,
1562                        &outcome.result.output,
1563                        &self.config.available_artifacts,
1564                    ) {
1565                        // if result is Ok, it was an anticipated revert; return an "assume" error
1566                        // to reject this run
1567                        Ok(_) => {
1568                            outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into();
1569                        }
1570                        // if result is Error, it was an unanticipated revert; should revert
1571                        // normally
1572                        Err(error) => {
1573                            trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
1574                            outcome.result.result = InstructionResult::Revert;
1575                            outcome.result.output = error.abi_encode().into();
1576                        }
1577                    };
1578                }
1579                // Call didn't revert, reset `assume_no_revert` state.
1580                self.assume_no_revert = None;
1581            }
1582        }
1583
1584        // Handle expected reverts.
1585        if let Some(expected_revert) = &mut self.expected_revert {
1586            // Record current reverter address and call scheme before processing the expect revert
1587            // if call reverted.
1588            let call_failed = !matches!(outcome.result.result, return_ok!());
1589            if call_failed {
1590                // Record current reverter address if expect revert is set with expected reverter
1591                // address and no actual reverter was set yet or if we're expecting more than one
1592                // revert.
1593                if expected_revert.reverter.is_some()
1594                    && (expected_revert.reverted_by.is_none() || expected_revert.count > 1)
1595                {
1596                    expected_revert.reverted_by = Some(call.target_address);
1597                }
1598            }
1599
1600            let curr_depth = ecx.journal().depth();
1601            if curr_depth <= expected_revert.depth {
1602                // Decide whether this `call_end` should consume the pending `expectRevert`.
1603                // With `internal_expect_revert` enabled, a same-depth revert can satisfy it, but
1604                // we must not consume it for external calls that succeed (e.g. calls to
1605                // non-contract addresses that return `Stop` before Solidity's own revert).
1606                let internal = self.config.internal_expect_revert;
1607                let went_deeper = expected_revert.max_depth > expected_revert.depth;
1608                let needs_processing = match expected_revert.kind {
1609                    ExpectedRevertKind::Default => (|| {
1610                        // Cheatcode reverts propagate up; let the outer frame catch them.
1611                        if cheatcode_call {
1612                            return false;
1613                        }
1614                        // Any failure satisfies the expectation.
1615                        if call_failed {
1616                            return true;
1617                        }
1618                        // Traditional expectRevert: succeeded external call went deeper.
1619                        if !internal && went_deeper {
1620                            return true;
1621                        }
1622                        // Test function returned: catch dangling expectations.
1623                        if curr_depth == 0 {
1624                            return true;
1625                        }
1626                        // Same-depth success with internal mode off is an error; with it on,
1627                        // keep waiting for the actual revert.
1628                        !internal
1629                    })(),
1630                    // `pending_processing == true` means we're in the `call_end` hook for
1631                    // `vm.expectCheatcodeRevert` and shouldn't expect a revert here.
1632                    ExpectedRevertKind::Cheatcode { pending_processing } => {
1633                        cheatcode_call && !pending_processing
1634                    }
1635                };
1636
1637                if needs_processing {
1638                    let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
1639                    return match revert_handlers::handle_expect_revert(
1640                        cheatcode_call,
1641                        false,
1642                        self.config.internal_expect_revert,
1643                        &expected_revert,
1644                        outcome.result.result,
1645                        outcome.result.output.clone(),
1646                        &self.config.available_artifacts,
1647                    ) {
1648                        Err(error) => {
1649                            trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
1650                            outcome.result.result = InstructionResult::Revert;
1651                            outcome.result.output = error.abi_encode().into();
1652                        }
1653                        Ok((_, retdata)) => {
1654                            expected_revert.actual_count += 1;
1655                            if expected_revert.actual_count < expected_revert.count {
1656                                self.expected_revert = Some(expected_revert);
1657                            }
1658                            outcome.result.result = InstructionResult::Return;
1659                            outcome.result.output = retdata;
1660                        }
1661                    };
1662                }
1663
1664                // Flip `pending_processing` flag for cheatcode revert expectations, marking that
1665                // we've exited the `expectCheatcodeRevert` call scope
1666                if let ExpectedRevertKind::Cheatcode { pending_processing } =
1667                    &mut self.expected_revert.as_mut().unwrap().kind
1668                {
1669                    *pending_processing = false;
1670                }
1671            }
1672        }
1673
1674        // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode
1675        // invocations
1676        if cheatcode_call {
1677            return;
1678        }
1679
1680        // Record the gas usage of the call, this allows the `lastCallGas` cheatcode to
1681        // retrieve the gas usage of the last call.
1682        let gas = outcome.result.gas;
1683        self.gas_metering.last_call_gas = Some(crate::Vm::Gas {
1684            gasLimit: gas.limit(),
1685            gasTotalUsed: gas.total_gas_spent(),
1686            gasMemoryUsed: 0,
1687            gasRefunded: gas.refunded(),
1688            gasRemaining: gas.remaining(),
1689        });
1690
1691        // If `startStateDiffRecording` has been called, update the `reverted` status of the
1692        // previous call depth's recorded accesses, if any
1693        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1694            // The root call cannot be recorded.
1695            if ecx.journal().depth() > 0
1696                && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop()
1697            {
1698                // Update the reverted status of all deeper calls if this call reverted, in
1699                // accordance with EVM behavior
1700                if outcome.result.is_revert() {
1701                    for element in &mut *last_recorded_depth {
1702                        element.reverted = true;
1703                        for storage_access in &mut element.storageAccesses {
1704                            storage_access.reverted = true;
1705                        }
1706                    }
1707                }
1708
1709                if let Some(call_access) = last_recorded_depth.first_mut() {
1710                    // Assert that we're at the correct depth before recording post-call state
1711                    // changes. Depending on the depth the cheat was
1712                    // called at, there may not be any pending
1713                    // calls to update if execution has percolated up to a higher depth.
1714                    let curr_depth = ecx.journal().depth();
1715                    if call_access.depth == curr_depth as u64
1716                        && let Ok(acc) = ecx.journal_mut().load_account(call.target_address)
1717                    {
1718                        debug_assert!(access_is_call(call_access.kind));
1719                        call_access.newBalance = acc.data.info.balance;
1720                        call_access.newNonce = acc.data.info.nonce;
1721                    }
1722                    // Merge the last depth's AccountAccesses into the AccountAccesses at the
1723                    // current depth, or push them back onto the pending
1724                    // vector if higher depths were not recorded. This
1725                    // preserves ordering of accesses.
1726                    if let Some(last) = recorded_account_diffs_stack.last_mut() {
1727                        last.extend(last_recorded_depth);
1728                    } else {
1729                        recorded_account_diffs_stack.push(last_recorded_depth);
1730                    }
1731                }
1732            }
1733        }
1734
1735        // this will ensure we don't have false positives when trying to diagnose reverts in fork
1736        // mode
1737        let diag = self.fork_revert_diagnostic.take();
1738
1739        // If the call already reverted, preserve that primary failure and skip post-call
1740        // expect* validation so it cannot overwrite the original revert.
1741        if outcome.result.is_revert() {
1742            // if there's a revert and a previous call was diagnosed as fork related revert then we
1743            // can return a better error here
1744            if let Some(err) = diag {
1745                outcome.result.output = Error::encode(err.to_error_msg(&self.labels));
1746            }
1747            return;
1748        }
1749
1750        // At the end of the call,
1751        // we need to check if we've found all the emits.
1752        // We know we've found all the expected emits in the right order
1753        // if the queue is fully matched.
1754        // If it's not fully matched, then either:
1755        // 1. Not enough events were emitted (we'll know this because the amount of times we
1756        // inspected events will be less than the size of the queue) 2. The wrong events
1757        // were emitted (The inspected events should match the size of the queue, but still some
1758        // events will not be matched)
1759
1760        // First, check that we're at the call depth where the emits were declared from.
1761        let should_check_emits = self
1762            .expected_emits
1763            .iter()
1764            .any(|(expected, _)| {
1765                let curr_depth = ecx.journal().depth();
1766                expected.depth == curr_depth
1767            }) &&
1768            // Ignore staticcalls
1769            !call.is_static;
1770        if should_check_emits {
1771            let expected_counts = self
1772                .expected_emits
1773                .iter()
1774                .filter_map(|(expected, count_map)| {
1775                    let count = match expected.address {
1776                        Some(emitter) => match count_map.get(&emitter) {
1777                            Some(log_count) => expected
1778                                .log
1779                                .as_ref()
1780                                .map(|l| log_count.count(l))
1781                                .unwrap_or_else(|| log_count.count_unchecked()),
1782                            None => 0,
1783                        },
1784                        None => match &expected.log {
1785                            Some(log) => count_map.values().map(|logs| logs.count(log)).sum(),
1786                            None => count_map.values().map(|logs| logs.count_unchecked()).sum(),
1787                        },
1788                    };
1789
1790                    (count != expected.count).then_some((expected, count))
1791                })
1792                .collect::<Vec<_>>();
1793
1794            // Revert if not all emits expected were matched.
1795            if let Some((expected, _)) = self
1796                .expected_emits
1797                .iter()
1798                .find(|(expected, _)| !expected.found && expected.count > 0)
1799            {
1800                outcome.result.result = InstructionResult::Revert;
1801                let error_msg = expected.mismatch_error.as_deref().unwrap_or("log != expected log");
1802                outcome.result.output = error_msg.abi_encode().into();
1803                return;
1804            }
1805
1806            if !expected_counts.is_empty() {
1807                let msg = if outcome.result.is_ok() {
1808                    let (expected, count) = expected_counts.first().unwrap();
1809                    format!("log emitted {count} times, expected {}", expected.count)
1810                } else {
1811                    "expected an emit, but the call reverted instead. \
1812                     ensure you're testing the happy path when using `expectEmit`"
1813                        .to_string()
1814                };
1815
1816                outcome.result.result = InstructionResult::Revert;
1817                outcome.result.output = Error::encode(msg);
1818                return;
1819            }
1820
1821            // All emits were found, we're good.
1822            // Clear the queue, as we expect the user to declare more events for the next call
1823            // if they wanna match further events.
1824            self.expected_emits.clear()
1825        }
1826
1827        // try to diagnose reverts in multi-fork mode where a call is made to an address that does
1828        // not exist
1829        if let TxKind::Call(test_contract) = ecx.tx().kind() {
1830            // if a call to a different contract than the original test contract returned with
1831            // `Stop` we check if the contract actually exists on the active fork
1832            if ecx.db().is_forked_mode()
1833                && outcome.result.result == InstructionResult::Stop
1834                && call.target_address != test_contract
1835            {
1836                self.fork_revert_diagnostic =
1837                    ecx.db().diagnose_revert(call.target_address, ecx.journal().evm_state());
1838            }
1839        }
1840
1841        // If the depth is 0, then this is the root call terminating
1842        if ecx.journal().depth() == 0 {
1843            // If we already have a revert, we shouldn't run the below logic as it can obfuscate an
1844            // earlier error that happened first with unrelated information about
1845            // another error when using cheatcodes.
1846            if outcome.result.is_revert() {
1847                return;
1848            }
1849
1850            // If there's not a revert, we can continue on to run the last logic for expect*
1851            // cheatcodes.
1852
1853            // Match expected calls
1854            for (address, calldatas) in &self.expected_calls {
1855                // Loop over each address, and for each address, loop over each calldata it expects.
1856                for (calldata, (expected, actual_count)) in calldatas {
1857                    // Grab the values we expect to see
1858                    let ExpectedCallData { gas, min_gas, value, count, call_type } = expected;
1859
1860                    let failed = match call_type {
1861                        // If the cheatcode was called with a `count` argument,
1862                        // we must check that the EVM performed a CALL with this calldata exactly
1863                        // `count` times.
1864                        ExpectedCallType::Count => *count != *actual_count,
1865                        // If the cheatcode was called without a `count` argument,
1866                        // we must check that the EVM performed a CALL with this calldata at least
1867                        // `count` times. The amount of times to check was
1868                        // the amount of time the cheatcode was called.
1869                        ExpectedCallType::NonCount => *count > *actual_count,
1870                    };
1871                    if failed {
1872                        let expected_values = [
1873                            Some(format!("data {}", hex::encode_prefixed(calldata))),
1874                            value.as_ref().map(|v| format!("value {v}")),
1875                            gas.map(|g| format!("gas {g}")),
1876                            min_gas.map(|g| format!("minimum gas {g}")),
1877                        ]
1878                        .into_iter()
1879                        .flatten()
1880                        .join(", ");
1881                        let but = if outcome.result.is_ok() {
1882                            let s = if *actual_count == 1 { "" } else { "s" };
1883                            format!("was called {actual_count} time{s}")
1884                        } else {
1885                            "the call reverted instead; \
1886                             ensure you're testing the happy path when using `expectCall`"
1887                                .to_string()
1888                        };
1889                        let s = if *count == 1 { "" } else { "s" };
1890                        let msg = format!(
1891                            "expected call to {address} with {expected_values} \
1892                             to be called {count} time{s}, but {but}"
1893                        );
1894                        outcome.result.result = InstructionResult::Revert;
1895                        outcome.result.output = Error::encode(msg);
1896
1897                        return;
1898                    }
1899                }
1900            }
1901
1902            // Check if we have any leftover expected emits
1903            // First, if any emits were found at the root call, then we its ok and we remove them.
1904            // For count=0 expectations, NOT being found is success, so mark them as found
1905            for (expected, _) in &mut self.expected_emits {
1906                if expected.count == 0 && !expected.found {
1907                    expected.found = true;
1908                }
1909            }
1910            self.expected_emits.retain(|(expected, _)| !expected.found);
1911            // If not empty, we got mismatched emits
1912            if !self.expected_emits.is_empty() {
1913                let msg = if outcome.result.is_ok() {
1914                    "expected an emit, but no logs were emitted afterwards. \
1915                     you might have mismatched events or not enough events were emitted"
1916                } else {
1917                    "expected an emit, but the call reverted instead. \
1918                     ensure you're testing the happy path when using `expectEmit`"
1919                };
1920                outcome.result.result = InstructionResult::Revert;
1921                outcome.result.output = Error::encode(msg);
1922                return;
1923            }
1924
1925            // Check for leftover expected creates
1926            if let Some(expected_create) = self.expected_creates.first() {
1927                let msg = format!(
1928                    "expected {} call by address {} for bytecode {} but not found",
1929                    expected_create.create_scheme,
1930                    hex::encode_prefixed(expected_create.deployer),
1931                    hex::encode_prefixed(&expected_create.bytecode),
1932                );
1933                outcome.result.result = InstructionResult::Revert;
1934                outcome.result.output = Error::encode(msg);
1935            }
1936        }
1937    }
1938
1939    fn create(
1940        &mut self,
1941        ecx: &mut FoundryContextFor<'_, FEN>,
1942        mut input: &mut CreateInputs,
1943    ) -> Option<CreateOutcome> {
1944        // Apply custom execution evm version.
1945        if let Some(spec_id) = self.execution_evm_version {
1946            ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id);
1947        }
1948
1949        let gas = Gas::new(input.gas_limit());
1950        // Check if we should intercept this create
1951        if self.intercept_next_create_call {
1952            // Reset the flag
1953            self.intercept_next_create_call = false;
1954
1955            // Get initcode from the input
1956            let output = input.init_code();
1957
1958            // Return a revert with the initcode as error data
1959            return Some(CreateOutcome {
1960                result: InterpreterResult { result: InstructionResult::Revert, output, gas },
1961                address: None,
1962            });
1963        }
1964
1965        let curr_depth = ecx.journal().depth();
1966
1967        // Apply our prank
1968        if let Some(prank) = &self.get_prank(curr_depth)
1969            && curr_depth >= prank.depth
1970            && input.caller() == prank.prank_caller
1971        {
1972            // At the target depth we set `msg.sender`
1973            let prank_applied = if curr_depth == prank.depth {
1974                // Ensure new caller is loaded and touched
1975                let _ = journaled_account(ecx, prank.new_caller);
1976                input.set_caller(prank.new_caller);
1977                true
1978            } else {
1979                false
1980            };
1981
1982            // At the target depth, or deeper, we set `tx.origin`
1983            let prank_applied = if let Some(new_origin) = prank.new_origin {
1984                ecx.tx_mut().set_caller(new_origin);
1985                true
1986            } else {
1987                prank_applied
1988            };
1989
1990            // If prank applied for first time, then update
1991            if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
1992                self.pranks.insert(curr_depth, applied_prank);
1993            }
1994        }
1995
1996        // Apply EIP-2930 access list
1997        self.apply_accesslist(ecx);
1998
1999        // Apply our broadcast
2000        if let Some(broadcast) = &mut self.broadcast
2001            && curr_depth >= broadcast.depth
2002            && input.caller() == broadcast.original_caller
2003        {
2004            if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
2005                return Some(CreateOutcome {
2006                    result: InterpreterResult {
2007                        result: InstructionResult::Revert,
2008                        output: Error::encode(err),
2009                        gas,
2010                    },
2011                    address: None,
2012                });
2013            }
2014
2015            ecx.tx_mut().set_caller(broadcast.new_origin);
2016
2017            if curr_depth == broadcast.depth || broadcast.deploy_from_code {
2018                // Reset deploy from code flag for upcoming calls;
2019                broadcast.deploy_from_code = false;
2020
2021                input.set_caller(broadcast.new_origin);
2022
2023                let rpc = ecx.db().active_fork_url();
2024                let account = &ecx.journal().evm_state()[&broadcast.new_origin];
2025                let mut tx_req = TransactionRequestFor::<FEN>::default()
2026                    .with_from(broadcast.new_origin)
2027                    .with_kind(TxKind::Create)
2028                    .with_value(input.value())
2029                    .with_input(input.init_code())
2030                    .with_nonce(account.info.nonce);
2031                if let Some(fee_token) = self.config.fee_token {
2032                    tx_req.set_fee_token(fee_token);
2033                }
2034                self.broadcastable_transactions.push_back(BroadcastableTransaction {
2035                    rpc,
2036                    transaction: TransactionMaybeSigned::new(tx_req),
2037                });
2038
2039                input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create));
2040            }
2041        }
2042
2043        // Allow cheatcodes from the address of the new contract
2044        let address = input.allow_cheatcodes(self, ecx);
2045
2046        // If `recordAccountAccesses` has been called, record the create
2047        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2048            recorded_account_diffs_stack.push(vec![AccountAccess {
2049                chainInfo: crate::Vm::ChainInfo {
2050                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
2051                    chainId: U256::from(ecx.cfg().chain_id()),
2052                },
2053                accessor: input.caller(),
2054                account: address,
2055                kind: crate::Vm::AccountAccessKind::Create,
2056                initialized: true,
2057                oldBalance: U256::ZERO, // updated on create_end
2058                newBalance: U256::ZERO, // updated on create_end
2059                oldNonce: 0,            // new contract starts with nonce 0
2060                newNonce: 1,            // updated on create_end (contracts start with nonce 1)
2061                value: input.value(),
2062                data: input.init_code(),
2063                reverted: false,
2064                deployedCode: Bytes::new(), // updated on create_end
2065                storageAccesses: vec![],    // updated on create_end
2066                depth: curr_depth as u64,
2067            }]);
2068        }
2069
2070        None
2071    }
2072
2073    fn create_end(
2074        &mut self,
2075        ecx: &mut FoundryContextFor<'_, FEN>,
2076        call: &CreateInputs,
2077        outcome: &mut CreateOutcome,
2078    ) {
2079        let call = Some(call);
2080        let curr_depth = ecx.journal().depth();
2081
2082        // Clean up pranks
2083        if let Some(prank) = &self.get_prank(curr_depth)
2084            && curr_depth == prank.depth
2085        {
2086            ecx.tx_mut().set_caller(prank.prank_origin);
2087
2088            // Clean single-call prank once we have returned to the original depth
2089            if prank.single_call {
2090                std::mem::take(&mut self.pranks);
2091            }
2092        }
2093
2094        // Clean up broadcasts
2095        if let Some(broadcast) = &self.broadcast
2096            && curr_depth == broadcast.depth
2097        {
2098            ecx.tx_mut().set_caller(broadcast.original_origin);
2099
2100            // Clean single-call broadcast once we have returned to the original depth
2101            if broadcast.single_call {
2102                std::mem::take(&mut self.broadcast);
2103            }
2104        }
2105
2106        // Handle expected reverts.
2107        if let Some(expected_revert) = &mut self.expected_revert {
2108            // Record the would-be deployed address as the reverter, picking the innermost
2109            // reverting CREATE: this hook runs at every depth, the deepest frame fires
2110            // first, and the `is_none()` lock pins it. For `count > 1` the lock is
2111            // released after each successful iteration (see below) so each iteration
2112            // independently records its own innermost CREATE.
2113            //
2114            // This intentionally differs from `call_end` for `count > 1`, where
2115            // legacy nested CALL handling reports the outermost call per iteration.
2116            //
2117            // `outcome.address` is `None` for pre-frame rejection (depth/balance/nonce);
2118            // in that case the surrounding `call_end` records the caller as the reverter.
2119            if outcome.result.is_revert()
2120                && expected_revert.reverter.is_some()
2121                && expected_revert.reverted_by.is_none()
2122                && let Some(addr) = outcome.address
2123            {
2124                expected_revert.reverted_by = Some(addr);
2125            }
2126
2127            if curr_depth <= expected_revert.depth
2128                && matches!(expected_revert.kind, ExpectedRevertKind::Default)
2129            {
2130                let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
2131                return match revert_handlers::handle_expect_revert(
2132                    false,
2133                    true,
2134                    self.config.internal_expect_revert,
2135                    &expected_revert,
2136                    outcome.result.result,
2137                    outcome.result.output.clone(),
2138                    &self.config.available_artifacts,
2139                ) {
2140                    Ok((address, retdata)) => {
2141                        expected_revert.actual_count += 1;
2142                        if expected_revert.actual_count < expected_revert.count {
2143                            // Reset so the next iteration's innermost CREATE wins again.
2144                            expected_revert.reverted_by = None;
2145                            self.expected_revert = Some(expected_revert.clone());
2146                        }
2147
2148                        outcome.result.result = InstructionResult::Return;
2149                        outcome.result.output = retdata;
2150                        outcome.address = address;
2151                    }
2152                    Err(err) => {
2153                        outcome.result.result = InstructionResult::Revert;
2154                        outcome.result.output = err.abi_encode().into();
2155                    }
2156                };
2157            }
2158        }
2159
2160        // If `startStateDiffRecording` has been called, update the `reverted` status of the
2161        // previous call depth's recorded accesses, if any
2162        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2163            // The root call cannot be recorded.
2164            if curr_depth > 0
2165                && let Some(last_depth) = &mut recorded_account_diffs_stack.pop()
2166            {
2167                // Update the reverted status of all deeper calls if this call reverted, in
2168                // accordance with EVM behavior
2169                if outcome.result.is_revert() {
2170                    for element in &mut *last_depth {
2171                        element.reverted = true;
2172                        for storage_access in &mut element.storageAccesses {
2173                            storage_access.reverted = true;
2174                        }
2175                    }
2176                }
2177
2178                if let Some(create_access) = last_depth.first_mut() {
2179                    // Assert that we're at the correct depth before recording post-create state
2180                    // changes. Depending on what depth the cheat was called at, there
2181                    // may not be any pending calls to update if execution has
2182                    // percolated up to a higher depth.
2183                    let depth = ecx.journal().depth();
2184                    if create_access.depth == depth as u64 {
2185                        debug_assert_eq!(
2186                            create_access.kind as u8,
2187                            crate::Vm::AccountAccessKind::Create as u8
2188                        );
2189                        if let Some(address) = outcome.address
2190                            && let Ok(created_acc) = ecx.journal_mut().load_account(address)
2191                        {
2192                            create_access.newBalance = created_acc.data.info.balance;
2193                            create_access.newNonce = created_acc.data.info.nonce;
2194                            create_access.deployedCode = created_acc
2195                                .data
2196                                .info
2197                                .code
2198                                .clone()
2199                                .unwrap_or_default()
2200                                .original_bytes();
2201                        }
2202                    }
2203                    // Merge the last depth's AccountAccesses into the AccountAccesses at the
2204                    // current depth, or push them back onto the pending
2205                    // vector if higher depths were not recorded. This
2206                    // preserves ordering of accesses.
2207                    if let Some(last) = recorded_account_diffs_stack.last_mut() {
2208                        last.append(last_depth);
2209                    } else {
2210                        recorded_account_diffs_stack.push(last_depth.clone());
2211                    }
2212                }
2213            }
2214        }
2215
2216        // Match the create against expected_creates
2217        if !self.expected_creates.is_empty()
2218            && let (Some(address), Some(call)) = (outcome.address, call)
2219            && let Ok(created_acc) = ecx.journal_mut().load_account(address)
2220        {
2221            let bytecode = created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
2222            if let Some((index, _)) =
2223                self.expected_creates.iter().find_position(|expected_create| {
2224                    expected_create.deployer == call.caller()
2225                        && expected_create.create_scheme.eq(call.scheme().into())
2226                        && expected_create.bytecode == bytecode
2227                })
2228            {
2229                self.expected_creates.swap_remove(index);
2230            }
2231        }
2232    }
2233}
2234
2235impl<FEN: FoundryEvmNetwork> InspectorExt for Cheatcodes<FEN> {
2236    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2237        if let CreateScheme::Create2 { .. } = inputs.scheme() {
2238            let target_depth = if let Some(prank) = &self.get_prank(depth) {
2239                prank.depth
2240            } else if let Some(broadcast) = &self.broadcast {
2241                broadcast.depth
2242            } else {
2243                1
2244            };
2245
2246            depth == target_depth
2247                && (self.broadcast.is_some() || self.config.always_use_create_2_factory)
2248        } else {
2249            false
2250        }
2251    }
2252
2253    fn create2_deployer(&self) -> Address {
2254        self.config.evm_opts.create2_deployer
2255    }
2256}
2257
2258impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
2259    #[cold]
2260    fn meter_gas(&mut self, interpreter: &mut Interpreter) {
2261        if let Some(paused_gas) = self.gas_metering.paused_frames.last() {
2262            // Keep gas constant if paused.
2263            // Make sure we record the memory changes so that memory expansion is not paused.
2264            let memory = *interpreter.gas.memory();
2265            interpreter.gas = *paused_gas;
2266            interpreter.gas.memory_mut().words_num = memory.words_num;
2267            interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost;
2268        } else {
2269            // Record frame paused gas.
2270            self.gas_metering.paused_frames.push(interpreter.gas);
2271        }
2272    }
2273
2274    #[cold]
2275    fn meter_gas_record(
2276        &mut self,
2277        interpreter: &mut Interpreter,
2278        ecx: &mut FoundryContextFor<'_, FEN>,
2279    ) {
2280        if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() {
2281            self.gas_metering.gas_records.iter_mut().for_each(|record| {
2282                let curr_depth = ecx.journal().depth();
2283                if curr_depth == record.depth {
2284                    // Skip the first opcode of the first call frame as it includes the gas cost of
2285                    // creating the snapshot.
2286                    if self.gas_metering.last_gas_used != 0 {
2287                        let gas_diff = interpreter
2288                            .gas
2289                            .total_gas_spent()
2290                            .saturating_sub(self.gas_metering.last_gas_used);
2291                        record.gas_used = record.gas_used.saturating_add(gas_diff);
2292                    }
2293
2294                    // Update `last_gas_used` to the current spent gas for the next iteration to
2295                    // compare against.
2296                    self.gas_metering.last_gas_used = interpreter.gas.total_gas_spent();
2297                }
2298            });
2299        }
2300    }
2301
2302    #[cold]
2303    fn meter_gas_end(&mut self, interpreter: &mut Interpreter) {
2304        // Remove recorded gas if we exit frame.
2305        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
2306            && will_exit(interpreter_action)
2307        {
2308            self.gas_metering.paused_frames.pop();
2309        }
2310    }
2311
2312    #[cold]
2313    fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) {
2314        let mut gas = Gas::new(interpreter.gas.limit());
2315        gas.memory_mut().words_num = interpreter.gas.memory().words_num;
2316        gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost;
2317        interpreter.gas = gas;
2318        self.gas_metering.reset = false;
2319    }
2320
2321    #[cold]
2322    fn meter_gas_check(&mut self, interpreter: &mut Interpreter) {
2323        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
2324            && will_exit(interpreter_action)
2325        {
2326            // Reset gas if spent is less than refunded.
2327            // This can happen if gas was paused / resumed or reset.
2328            // https://github.com/foundry-rs/foundry/issues/4370
2329            if interpreter.gas.total_gas_spent()
2330                < u64::try_from(interpreter.gas.refunded()).unwrap_or_default()
2331            {
2332                interpreter.gas = Gas::new(interpreter.gas.limit());
2333            }
2334        }
2335    }
2336
2337    /// Applies opcode-level overrides for `BASEFEE`, `GASPRICE` and `BLOBHASH`.
2338    ///
2339    /// Called from `step_end` *after* the opcode has executed and only when the
2340    /// opcode succeeded (the caller checks `instruction_result`). The opcode
2341    /// pushed its (possibly zeroed) result onto the stack; we replace the top
2342    /// of stack with the cheatcode-set override. This is what makes `vm.fee`,
2343    /// `vm.txGasPrice` and `vm.blobhashes` visible to called contracts under
2344    /// `--isolate` / `--gas-report`, where the inner transaction zeroes the
2345    /// real fee fields for fee-accounting purposes.
2346    ///
2347    /// We can't read the just-executed opcode from `interpreter.bytecode.opcode()`
2348    /// here because the PC has already advanced; instead `step` stashes it in
2349    /// `env_overrides.pending_opcode` for us.
2350    #[cold]
2351    fn apply_env_overrides(&mut self, interpreter: &mut Interpreter, fork_id: Option<U256>) {
2352        let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) else { return };
2353        let Some(opcode) = env_overrides.pending_opcode.take() else { return };
2354        match opcode {
2355            op::BASEFEE => {
2356                if let Some(basefee) = env_overrides.basefee {
2357                    // BASEFEE pushed one value; replace it.
2358                    Self::replace_top_of_stack(interpreter, U256::from(basefee));
2359                }
2360            }
2361            op::GASPRICE => {
2362                if let Some(gas_price) = env_overrides.gas_price {
2363                    // GASPRICE pushed one value; replace it.
2364                    Self::replace_top_of_stack(interpreter, U256::from(gas_price));
2365                }
2366            }
2367            op::BLOBHASH => {
2368                let blob_hashes = env_overrides.blob_hashes.clone();
2369                let blobhash_index = env_overrides.pending_blobhash_index.take();
2370                if let Some(ref blob_hashes) = blob_hashes
2371                    && let Some(index) = blobhash_index
2372                {
2373                    // BLOBHASH popped the index and pushed the hash; replace
2374                    // the hash with our override (zero for out-of-range, per EIP-4844).
2375                    let hash = blob_hashes.get(index as usize).copied().unwrap_or_default();
2376                    Self::replace_top_of_stack(interpreter, hash.into());
2377                }
2378            }
2379            _ => {}
2380        }
2381    }
2382
2383    /// Replaces the top of the interpreter stack with `value`.
2384    ///
2385    /// The caller must only invoke this after a successful opcode that pushed
2386    /// a value onto the stack; the `pop()` is therefore expected to succeed.
2387    /// If it does not (e.g. because of a bug in the caller's success gating)
2388    /// we bail out instead of pushing on top of an unexpected stack, which
2389    /// would silently grow the stack and corrupt the frame.
2390    fn replace_top_of_stack(interpreter: &mut Interpreter, value: U256) {
2391        if interpreter.stack.pop().is_err() {
2392            debug_assert!(false, "env override expected opcode result on stack");
2393            return;
2394        }
2395        let _ = interpreter.stack.push(value);
2396    }
2397
2398    /// Generates or copies arbitrary values for storage slots.
2399    /// Invoked in inspector `step_end` (when the current opcode is not executed), if current opcode
2400    /// to execute is `SLOAD` and storage slot is cold.
2401    /// Ensures that in next step (when `SLOAD` opcode is executed) an arbitrary value is returned:
2402    /// - copies the existing arbitrary storage value (or the new generated one if no value in
2403    ///   cache) from mapped source address to the target address.
2404    /// - generates arbitrary value and saves it in target address storage.
2405    #[cold]
2406    fn arbitrary_storage_end(
2407        &mut self,
2408        interpreter: &mut Interpreter,
2409        ecx: &mut FoundryContextFor<'_, FEN>,
2410    ) {
2411        let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD {
2412            (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address)
2413        } else {
2414            return;
2415        };
2416
2417        let Some(value) = ecx.sload(target_address, key) else {
2418            return;
2419        };
2420
2421        if (value.is_cold && value.data.is_zero())
2422            || self.should_overwrite_arbitrary_storage(&target_address, key)
2423        {
2424            if self.has_arbitrary_storage(&target_address) {
2425                let arbitrary_value = self.rng().random();
2426                self.arbitrary_storage.as_mut().unwrap().save(
2427                    ecx,
2428                    target_address,
2429                    key,
2430                    arbitrary_value,
2431                );
2432            } else if self.is_arbitrary_storage_copy(&target_address) {
2433                let arbitrary_value = self.rng().random();
2434                self.arbitrary_storage.as_mut().unwrap().copy(
2435                    ecx,
2436                    target_address,
2437                    key,
2438                    arbitrary_value,
2439                );
2440            }
2441        }
2442    }
2443
2444    /// Records storage slots reads and writes.
2445    #[cold]
2446    fn record_accesses(&mut self, interpreter: &mut Interpreter) {
2447        let access = &mut self.accesses;
2448        match interpreter.bytecode.opcode() {
2449            op::SLOAD => {
2450                let key = try_or_return!(interpreter.stack.peek(0));
2451                access.record_read(interpreter.input.target_address, key);
2452            }
2453            op::SSTORE => {
2454                let key = try_or_return!(interpreter.stack.peek(0));
2455                access.record_write(interpreter.input.target_address, key);
2456            }
2457            _ => {}
2458        }
2459    }
2460
2461    #[cold]
2462    fn record_state_diffs(
2463        &mut self,
2464        interpreter: &mut Interpreter,
2465        ecx: &mut FoundryContextFor<'_, FEN>,
2466    ) {
2467        let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return };
2468        match interpreter.bytecode.opcode() {
2469            op::SELFDESTRUCT => {
2470                // Ensure that we're not selfdestructing a context recording was initiated on
2471                let Some(last) = account_accesses.last_mut() else { return };
2472
2473                // get previous balance, nonce and initialized status of the target account
2474                let target = try_or_return!(interpreter.stack.peek(0));
2475                let target = Address::from_word(B256::from(target));
2476                let (initialized, old_balance, old_nonce) = ecx
2477                    .journal_mut()
2478                    .load_account(target)
2479                    .map(|account| {
2480                        (
2481                            account.data.info.exists(),
2482                            account.data.info.balance,
2483                            account.data.info.nonce,
2484                        )
2485                    })
2486                    .unwrap_or_default();
2487
2488                // load balance of this account
2489                let value = ecx
2490                    .balance(interpreter.input.target_address)
2491                    .map(|b| b.data)
2492                    .unwrap_or(U256::ZERO);
2493
2494                // register access for the target account
2495                last.push(crate::Vm::AccountAccess {
2496                    chainInfo: crate::Vm::ChainInfo {
2497                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
2498                        chainId: U256::from(ecx.cfg().chain_id()),
2499                    },
2500                    accessor: interpreter.input.target_address,
2501                    account: target,
2502                    kind: crate::Vm::AccountAccessKind::SelfDestruct,
2503                    initialized,
2504                    oldBalance: old_balance,
2505                    newBalance: old_balance + value,
2506                    oldNonce: old_nonce,
2507                    newNonce: old_nonce, // nonce doesn't change on selfdestruct
2508                    value,
2509                    data: Bytes::new(),
2510                    reverted: false,
2511                    deployedCode: Bytes::new(),
2512                    storageAccesses: vec![],
2513                    depth: ecx
2514                        .journal()
2515                        .depth()
2516                        .try_into()
2517                        .expect("journaled state depth exceeds u64"),
2518                });
2519            }
2520
2521            op::SLOAD => {
2522                let Some(last) = account_accesses.last_mut() else { return };
2523
2524                let key = try_or_return!(interpreter.stack.peek(0));
2525                let address = interpreter.input.target_address;
2526
2527                // Try to include present value for informational purposes, otherwise assume
2528                // it's not set (zero value)
2529                // Try to load the account and the slot's present value
2530                let present_value = if ecx.journal_mut().load_account(address).is_ok()
2531                    && let Some(previous) = ecx.sload(address, key)
2532                {
2533                    previous.data
2534                } else {
2535                    U256::ZERO
2536                };
2537                let access = crate::Vm::StorageAccess {
2538                    account: interpreter.input.target_address,
2539                    slot: key.into(),
2540                    isWrite: false,
2541                    previousValue: present_value.into(),
2542                    newValue: present_value.into(),
2543                    reverted: false,
2544                };
2545                let curr_depth =
2546                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2547                append_storage_access(last, access, curr_depth);
2548            }
2549            op::SSTORE => {
2550                let Some(last) = account_accesses.last_mut() else { return };
2551
2552                let key = try_or_return!(interpreter.stack.peek(0));
2553                let value = try_or_return!(interpreter.stack.peek(1));
2554                let address = interpreter.input.target_address;
2555                // Try to load the account and the slot's previous value, otherwise, assume it's
2556                // not set (zero value)
2557                let previous_value = if ecx.journal_mut().load_account(address).is_ok()
2558                    && let Some(previous) = ecx.sload(address, key)
2559                {
2560                    previous.data
2561                } else {
2562                    U256::ZERO
2563                };
2564
2565                let access = crate::Vm::StorageAccess {
2566                    account: address,
2567                    slot: key.into(),
2568                    isWrite: true,
2569                    previousValue: previous_value.into(),
2570                    newValue: value.into(),
2571                    reverted: false,
2572                };
2573                let curr_depth =
2574                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2575                append_storage_access(last, access, curr_depth);
2576            }
2577
2578            // Record account accesses via the EXT family of opcodes
2579            op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => {
2580                let kind = match interpreter.bytecode.opcode() {
2581                    op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy,
2582                    op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize,
2583                    op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash,
2584                    op::BALANCE => crate::Vm::AccountAccessKind::Balance,
2585                    _ => unreachable!(),
2586                };
2587                let address =
2588                    Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0))));
2589                let (initialized, balance, nonce) =
2590                    if let Ok(acc) = ecx.journal_mut().load_account(address) {
2591                        (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce)
2592                    } else {
2593                        (false, U256::ZERO, 0)
2594                    };
2595                let curr_depth =
2596                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2597                let account_access = crate::Vm::AccountAccess {
2598                    chainInfo: crate::Vm::ChainInfo {
2599                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
2600                        chainId: U256::from(ecx.cfg().chain_id()),
2601                    },
2602                    accessor: interpreter.input.target_address,
2603                    account: address,
2604                    kind,
2605                    initialized,
2606                    oldBalance: balance,
2607                    newBalance: balance,
2608                    oldNonce: nonce,
2609                    newNonce: nonce, // EXT* operations don't change nonce
2610                    value: U256::ZERO,
2611                    data: Bytes::new(),
2612                    reverted: false,
2613                    deployedCode: Bytes::new(),
2614                    storageAccesses: vec![],
2615                    depth: curr_depth,
2616                };
2617                // Record the EXT* call as an account access at the current depth
2618                // (future storage accesses will be recorded in a new "Resume" context)
2619                if let Some(last) = account_accesses.last_mut() {
2620                    last.push(account_access);
2621                } else {
2622                    account_accesses.push(vec![account_access]);
2623                }
2624            }
2625            _ => {}
2626        }
2627    }
2628
2629    /// Checks to see if the current opcode can either mutate directly or expand memory.
2630    ///
2631    /// If the opcode at the current program counter is a match, check if the modified memory lies
2632    /// within the allowed ranges. If not, revert and fail the test.
2633    #[cold]
2634    fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) {
2635        let Some(ranges) = self.allowed_mem_writes.get(&depth) else {
2636            return;
2637        };
2638
2639        // The `mem_opcode_match` macro is used to match the current opcode against a list of
2640        // opcodes that can mutate memory (either directly or expansion via reading). If the
2641        // opcode is a match, the memory offsets that are being written to are checked to be
2642        // within the allowed ranges. If not, the test is failed and the transaction is
2643        // reverted. For all opcodes that can mutate memory aside from MSTORE,
2644        // MSTORE8, and MLOAD, the size and destination offset are on the stack, and
2645        // the macro expands all of these cases. For MSTORE, MSTORE8, and MLOAD, the
2646        // size of the memory write is implicit, so these cases are hard-coded.
2647        macro_rules! mem_opcode_match {
2648            ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => {
2649                match interpreter.bytecode.opcode() {
2650                    ////////////////////////////////////////////////////////////////
2651                    //    OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING     //
2652                    ////////////////////////////////////////////////////////////////
2653
2654                    op::MSTORE => {
2655                        // The offset of the mstore operation is at the top of the stack.
2656                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2657
2658                        // If none of the allowed ranges contain [offset, offset + 32), memory has been
2659                        // unexpectedly mutated.
2660                        if !ranges.iter().any(|range| {
2661                            range.contains(&offset) && range.contains(&(offset + 31))
2662                        }) {
2663                            // SPECIAL CASE: When the compiler attempts to store the selector for
2664                            // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory
2665                            // pointer, which could have been updated to the exclusive upper bound during
2666                            // execution.
2667                            let value = try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>();
2668                            if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
2669                                return
2670                            }
2671
2672                            disallowed_mem_write(offset, 32, interpreter, ranges);
2673                            return
2674                        }
2675                    }
2676                    op::MSTORE8 => {
2677                        // The offset of the mstore8 operation is at the top of the stack.
2678                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2679
2680                        // If none of the allowed ranges contain the offset, memory has been
2681                        // unexpectedly mutated.
2682                        if !ranges.iter().any(|range| range.contains(&offset)) {
2683                            disallowed_mem_write(offset, 1, interpreter, ranges);
2684                            return
2685                        }
2686                    }
2687
2688                    ////////////////////////////////////////////////////////////////
2689                    //        OPERATIONS THAT CAN EXPAND MEMORY BY READING        //
2690                    ////////////////////////////////////////////////////////////////
2691
2692                    op::MLOAD => {
2693                        // The offset of the mload operation is at the top of the stack
2694                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2695
2696                        // If the offset being loaded is >= than the memory size, the
2697                        // memory is being expanded. If none of the allowed ranges contain
2698                        // [offset, offset + 32), memory has been unexpectedly mutated.
2699                        if offset >= interpreter.memory.size() as u64 && !ranges.iter().any(|range| {
2700                            range.contains(&offset) && range.contains(&(offset + 31))
2701                        }) {
2702                            disallowed_mem_write(offset, 32, interpreter, ranges);
2703                            return
2704                        }
2705                    }
2706
2707                    ////////////////////////////////////////////////////////////////
2708                    //          OPERATIONS WITH OFFSET AND SIZE ON STACK          //
2709                    ////////////////////////////////////////////////////////////////
2710
2711                    op::CALL => {
2712                        // The destination offset of the operation is the fifth element on the stack.
2713                        let dest_offset = try_or_return!(interpreter.stack.peek(5)).saturating_to::<u64>();
2714
2715                        // The size of the data that will be copied is the sixth element on the stack.
2716                        let size = try_or_return!(interpreter.stack.peek(6)).saturating_to::<u64>();
2717
2718                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
2719                        // memory outside of the expected ranges has been touched. If the opcode
2720                        // only reads from memory, this is okay as long as the memory is not expanded.
2721                        let fail_cond = !ranges.iter().any(|range| {
2722                            range.contains(&dest_offset) &&
2723                                range.contains(&(dest_offset + size.saturating_sub(1)))
2724                        });
2725
2726                        // If the failure condition is met, set the output buffer to a revert string
2727                        // that gives information about the allowed ranges and revert.
2728                        if fail_cond {
2729                            // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed.
2730                            // It allocated calldata at the current free memory pointer, and will attempt to read
2731                            // from this memory region to perform the call.
2732                            let to = Address::from_word(try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>().into());
2733                            if to == CHEATCODE_ADDRESS {
2734                                let args_offset = try_or_return!(interpreter.stack.peek(3)).saturating_to::<usize>();
2735                                let args_size = try_or_return!(interpreter.stack.peek(4)).saturating_to::<usize>();
2736                                let memory_word = interpreter.memory.slice_len(args_offset, args_size);
2737                                if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
2738                                    return
2739                                }
2740                            }
2741
2742                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
2743                            return
2744                        }
2745                    }
2746
2747                    $(op::$opcode => {
2748                        // The destination offset of the operation.
2749                        let dest_offset = try_or_return!(interpreter.stack.peek($offset_depth)).saturating_to::<u64>();
2750
2751                        // The size of the data that will be copied.
2752                        let size = try_or_return!(interpreter.stack.peek($size_depth)).saturating_to::<u64>();
2753
2754                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
2755                        // memory outside of the expected ranges has been touched. If the opcode
2756                        // only reads from memory, this is okay as long as the memory is not expanded.
2757                        let fail_cond = !ranges.iter().any(|range| {
2758                                range.contains(&dest_offset) &&
2759                                    range.contains(&(dest_offset + size.saturating_sub(1)))
2760                            }) && ($writes ||
2761                                [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| {
2762                                    offset >= interpreter.memory.size() as u64
2763                                })
2764                            );
2765
2766                        // If the failure condition is met, set the output buffer to a revert string
2767                        // that gives information about the allowed ranges and revert.
2768                        if fail_cond {
2769                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
2770                            return
2771                        }
2772                    })*
2773
2774                    _ => {}
2775                }
2776            }
2777        }
2778
2779        // Check if the current opcode can write to memory, and if so, check if the memory
2780        // being written to is registered as safe to modify.
2781        mem_opcode_match!(
2782            (CALLDATACOPY, 0, 2, true),
2783            (CODECOPY, 0, 2, true),
2784            (RETURNDATACOPY, 0, 2, true),
2785            (EXTCODECOPY, 1, 3, true),
2786            (CALLCODE, 5, 6, true),
2787            (STATICCALL, 4, 5, true),
2788            (DELEGATECALL, 4, 5, true),
2789            (KECCAK256, 0, 1, false),
2790            (LOG0, 0, 1, false),
2791            (LOG1, 0, 1, false),
2792            (LOG2, 0, 1, false),
2793            (LOG3, 0, 1, false),
2794            (LOG4, 0, 1, false),
2795            (CREATE, 1, 2, false),
2796            (CREATE2, 1, 2, false),
2797            (RETURN, 0, 1, false),
2798            (REVERT, 0, 1, false),
2799        );
2800    }
2801
2802    #[cold]
2803    fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
2804        match interpreter.bytecode.opcode() {
2805            op::CREATE2 => self.dynamic_gas_limit = true,
2806            op::CALL => {
2807                // If first element of the stack is close to current remaining gas then assume
2808                // dynamic gas limit.
2809                self.dynamic_gas_limit =
2810                    try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100
2811            }
2812            _ => self.dynamic_gas_limit = false,
2813        }
2814    }
2815}
2816
2817/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write,
2818/// and sets the return range to the revert string's location in memory.
2819///
2820/// This will set the interpreter's next action to a return with the revert string as the output.
2821/// And trigger a revert.
2822fn disallowed_mem_write(
2823    dest_offset: u64,
2824    size: u64,
2825    interpreter: &mut Interpreter,
2826    ranges: &[Range<u64>],
2827) {
2828    let revert_string = format!(
2829        "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}",
2830        dest_offset,
2831        size,
2832        ranges.iter().map(|r| format!("[0x{:02X}, 0x{:02X})", r.start, r.end)).join(" U ")
2833    );
2834
2835    interpreter.bytecode.set_action(InterpreterAction::new_return(
2836        InstructionResult::Revert,
2837        Bytes::from(revert_string.into_bytes()),
2838        interpreter.gas,
2839    ));
2840}
2841
2842/// Returns true if the kind of account access is a call.
2843const fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool {
2844    matches!(
2845        kind,
2846        crate::Vm::AccountAccessKind::Call
2847            | crate::Vm::AccountAccessKind::StaticCall
2848            | crate::Vm::AccountAccessKind::CallCode
2849            | crate::Vm::AccountAccessKind::DelegateCall
2850    )
2851}
2852
2853/// Records a log into the recorded logs vector, if it exists.
2854fn record_logs(recorded_logs: &mut Option<Vec<Vm::Log>>, log: &Log) {
2855    if let Some(storage_recorded_logs) = recorded_logs {
2856        storage_recorded_logs.push(Vm::Log {
2857            topics: log.data.topics().to_vec(),
2858            data: log.data.data.clone(),
2859            emitter: log.address,
2860        });
2861    }
2862}
2863
2864/// Appends an AccountAccess that resumes the recording of the current context.
2865fn append_storage_access(
2866    last: &mut Vec<AccountAccess>,
2867    storage_access: crate::Vm::StorageAccess,
2868    storage_depth: u64,
2869) {
2870    // Assert that there's an existing record for the current context.
2871    if !last.is_empty() && last.first().unwrap().depth < storage_depth {
2872        // Three cases to consider:
2873        // 1. If there hasn't been a context switch since the start of this context, then add the
2874        //    storage access to the current context record.
2875        // 2. If there's an existing Resume record, then add the storage access to it.
2876        // 3. Otherwise, create a new Resume record based on the current context.
2877        if last.len() == 1 {
2878            last.first_mut().unwrap().storageAccesses.push(storage_access);
2879        } else {
2880            let last_record = last.last_mut().unwrap();
2881            if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 {
2882                last_record.storageAccesses.push(storage_access);
2883            } else {
2884                let entry = last.first().unwrap();
2885                let resume_record = crate::Vm::AccountAccess {
2886                    chainInfo: crate::Vm::ChainInfo {
2887                        forkId: entry.chainInfo.forkId,
2888                        chainId: entry.chainInfo.chainId,
2889                    },
2890                    accessor: entry.accessor,
2891                    account: entry.account,
2892                    kind: crate::Vm::AccountAccessKind::Resume,
2893                    initialized: entry.initialized,
2894                    storageAccesses: vec![storage_access],
2895                    reverted: entry.reverted,
2896                    // The remaining fields are defaults
2897                    oldBalance: U256::ZERO,
2898                    newBalance: U256::ZERO,
2899                    oldNonce: 0,
2900                    newNonce: 0,
2901                    value: U256::ZERO,
2902                    data: Bytes::new(),
2903                    deployedCode: Bytes::new(),
2904                    depth: entry.depth,
2905                };
2906                last.push(resume_record);
2907            }
2908        }
2909    }
2910}
2911
2912/// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor.
2913const fn cheatcode_of<T: spec::CheatcodeDef>(_: &T) -> &'static spec::Cheatcode<'static> {
2914    T::CHEATCODE
2915}
2916
2917fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str {
2918    cheat.func.signature.split('(').next().unwrap()
2919}
2920
2921const fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str {
2922    cheat.func.id
2923}
2924
2925const fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str {
2926    cheat.func.signature
2927}
2928
2929/// Dispatches the cheatcode call to the appropriate function.
2930fn apply_dispatch<FEN: FoundryEvmNetwork>(
2931    calls: &Vm::VmCalls,
2932    ccx: &mut CheatsCtxt<'_, '_, FEN>,
2933    executor: &mut dyn CheatcodesExecutor<FEN>,
2934) -> Result {
2935    // Extract metadata for logging/deprecation via CheatcodeDef.
2936    macro_rules! get_cheatcode {
2937        ($($variant:ident),*) => {
2938            match calls {
2939                $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)*
2940            }
2941        };
2942    }
2943    let cheat = vm_calls!(get_cheatcode);
2944
2945    let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered();
2946    trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying");
2947
2948    if let spec::Status::Deprecated(replacement) = cheat.status {
2949        ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement);
2950    }
2951
2952    // Monomorphized dispatch: calls apply_full directly, no trait objects.
2953    macro_rules! dispatch {
2954        ($($variant:ident),*) => {
2955            match calls {
2956                $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)*
2957            }
2958        };
2959    }
2960    let mut result = vm_calls!(dispatch);
2961
2962    // Format the error message to include the cheatcode name.
2963    if let Err(e) = &mut result
2964        && e.is_str()
2965    {
2966        let name = cheatcode_name(cheat);
2967        // Skip showing the cheatcode name for:
2968        // - assertions: too verbose, and can already be inferred from the error message
2969        // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl`
2970        if !name.contains("assert") && name != "rpcUrl" {
2971            *e = fmt_err!("vm.{name}: {e}");
2972        }
2973    }
2974
2975    trace!(
2976        target: "cheatcodes",
2977        return = %match &result {
2978            Ok(b) => hex::encode(b),
2979            Err(e) => e.to_string(),
2980        }
2981    );
2982
2983    result
2984}
2985
2986/// Helper function to check if frame execution will exit.
2987const fn will_exit(action: &InterpreterAction) -> bool {
2988    match action {
2989        InterpreterAction::Return(result) => {
2990            result.result.is_ok_or_revert() || result.result.is_error()
2991        }
2992        _ => false,
2993    }
2994}