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