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.get_or_init(|| SignaturesIdentifier::new(true).ok()).as_ref()
778    }
779
780    /// Decodes the input data and applies the cheatcode.
781    fn apply_cheatcode(
782        &mut self,
783        ecx: &mut FoundryContextFor<'_, FEN>,
784        call: &CallInputs,
785        executor: &mut dyn CheatcodesExecutor<FEN>,
786    ) -> Result {
787        // decode the cheatcode call
788        let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| {
789            if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e {
790                let msg = format!(
791                    "unknown cheatcode with selector {selector}; \
792                     you may have a mismatch between the `Vm` interface (likely in `forge-std`) \
793                     and the `forge` version"
794                );
795                return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg));
796            }
797            e
798        })?;
799
800        let caller = call.caller;
801
802        // ensure the caller is allowed to execute cheatcodes,
803        // but only if the backend is in forking mode
804        ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
805
806        apply_dispatch(
807            &decoded,
808            &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller },
809            executor,
810        )
811    }
812
813    /// Grants cheat code access for new contracts if the caller also has
814    /// cheatcode access or the new contract is created in top most call.
815    ///
816    /// There may be cheatcodes in the constructor of the new contract, in order to allow them
817    /// automatically we need to determine the new address.
818    fn allow_cheatcodes_on_create(
819        &self,
820        ecx: &mut FoundryContextFor<FEN>,
821        caller: Address,
822        created_address: Address,
823    ) {
824        if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) {
825            ecx.db_mut().allow_cheatcode_access(created_address);
826        }
827    }
828
829    /// Apply EIP-2930 access list.
830    ///
831    /// If the transaction type is [TransactionType::Legacy] we need to upgrade it to
832    /// [TransactionType::Eip2930] in order to use access lists. Other transaction types support
833    /// access lists themselves.
834    fn apply_accesslist(&mut self, ecx: &mut FoundryContextFor<FEN>) {
835        if let Some(access_list) = &self.access_list {
836            ecx.tx_mut().set_access_list(access_list.clone());
837
838            if ecx.tx().tx_type() == TransactionType::Legacy as u8 {
839                ecx.tx_mut().set_tx_type(TransactionType::Eip2930 as u8);
840            }
841        }
842    }
843
844    /// Called when there was a revert.
845    ///
846    /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's
847    /// revert would run into issues.
848    pub fn on_revert(&mut self, ecx: &mut FoundryContextFor<FEN>) {
849        trace!(deals=?self.eth_deals.len(), "rolling back deals");
850
851        // Delay revert clean up until expected revert is handled, if set.
852        if self.expected_revert.is_some() {
853            return;
854        }
855
856        // we only want to apply cleanup top level
857        if ecx.journal().depth() > 0 {
858            return;
859        }
860
861        // Roll back all previously applied deals
862        // This will prevent overflow issues in revm's [`JournaledState::journal_revert`] routine
863        // which rolls back any transfers.
864        while let Some(record) = self.eth_deals.pop() {
865            if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) {
866                acc.info.balance = record.old_balance;
867            }
868        }
869    }
870
871    pub fn call_with_executor(
872        &mut self,
873        ecx: &mut FoundryContextFor<'_, FEN>,
874        call: &mut CallInputs,
875        executor: &mut dyn CheatcodesExecutor<FEN>,
876    ) -> Option<CallOutcome> {
877        // Apply custom execution evm version.
878        if let Some(spec_id) = self.execution_evm_version {
879            ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id);
880        }
881
882        let gas = Gas::new(call.gas_limit);
883        let curr_depth = ecx.journal().depth();
884
885        // At the root call to test function or script `run()`/`setUp()` functions, we are
886        // decreasing sender nonce to ensure that it matches on-chain nonce once we start
887        // broadcasting.
888        if curr_depth == 0 {
889            let sender = ecx.tx().caller();
890            let account = match super::evm::journaled_account(ecx, sender) {
891                Ok(account) => account,
892                Err(err) => {
893                    return Some(CallOutcome {
894                        result: InterpreterResult {
895                            result: InstructionResult::Revert,
896                            output: err.abi_encode().into(),
897                            gas,
898                        },
899                        memory_offset: call.return_memory_offset.clone(),
900                        was_precompile_called: false,
901                        precompile_call_logs: vec![],
902                        charged_new_account_state_gas: false,
903                    });
904                }
905            };
906            let prev = account.info.nonce;
907            account.info.nonce = prev.saturating_sub(1);
908
909            trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
910        }
911
912        if call.target_address == CHEATCODE_ADDRESS {
913            return match self.apply_cheatcode(ecx, call, executor) {
914                Ok(retdata) => Some(CallOutcome {
915                    result: InterpreterResult {
916                        result: InstructionResult::Return,
917                        output: retdata.into(),
918                        gas,
919                    },
920                    memory_offset: call.return_memory_offset.clone(),
921                    was_precompile_called: true,
922                    precompile_call_logs: vec![],
923                    charged_new_account_state_gas: false,
924                }),
925                Err(err) => Some(CallOutcome {
926                    result: InterpreterResult {
927                        result: InstructionResult::Revert,
928                        output: err.abi_encode().into(),
929                        gas,
930                    },
931                    memory_offset: call.return_memory_offset.clone(),
932                    was_precompile_called: false,
933                    precompile_call_logs: vec![],
934                    charged_new_account_state_gas: false,
935                }),
936            };
937        }
938
939        if call.target_address == HARDHAT_CONSOLE_ADDRESS {
940            return None;
941        }
942
943        // `expectRevert`: track max call depth. This is also done in `initialize_interp`, but
944        // precompile calls don't create an interpreter frame so we must also track it here.
945        // The callee executes at `curr_depth + 1`.
946        if let Some(expected) = &mut self.expected_revert {
947            expected.max_depth = max(curr_depth + 1, expected.max_depth);
948        }
949
950        // Handle expected calls
951
952        // Grab the different calldatas expected.
953        if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&call.bytecode_address)
954        {
955            // Match every partial/full calldata
956            for (calldata, (expected, actual_count)) in expected_calls_for_target {
957                // Increment actual times seen if...
958                // The calldata is at most, as big as this call's input, and
959                if calldata.len() <= call.input.len() &&
960                    // Both calldata match, taking the length of the assumed smaller one (which will have at least the selector), and
961                    *calldata == call.input.bytes(ecx)[..calldata.len()] &&
962                    // The value matches, if provided
963                    expected
964                        .value.is_none_or(|value| Some(value) == call.transfer_value()) &&
965                    // The gas matches, if provided
966                    expected.gas.is_none_or(|gas| gas == call.gas_limit) &&
967                    // The minimum gas matches, if provided
968                    expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit)
969                {
970                    *actual_count += 1;
971                }
972            }
973        }
974
975        // Apply our prank
976        if let Some(prank) = &self.get_prank(curr_depth) {
977            // Apply delegate call, `call.caller`` will not equal `prank.prank_caller`
978            if prank.delegate_call
979                && curr_depth == prank.depth
980                && call.scheme == CallScheme::DelegateCall
981            {
982                call.target_address = prank.new_caller;
983                call.caller = prank.new_caller;
984                if let Some(new_origin) = prank.new_origin {
985                    ecx.tx_mut().set_caller(new_origin);
986                }
987            }
988
989            if curr_depth >= prank.depth && call.caller == prank.prank_caller {
990                // At the target depth we set `msg.sender`
991                let prank_applied = if curr_depth == prank.depth {
992                    // Ensure new caller is loaded and touched
993                    let _ = journaled_account(ecx, prank.new_caller);
994                    call.caller = prank.new_caller;
995                    true
996                } else {
997                    false
998                };
999
1000                // At the target depth, or deeper, we set `tx.origin`
1001                let prank_applied = if let Some(new_origin) = prank.new_origin {
1002                    ecx.tx_mut().set_caller(new_origin);
1003                    true
1004                } else {
1005                    prank_applied
1006                };
1007
1008                // If prank applied for first time, then update
1009                if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
1010                    self.pranks.insert(curr_depth, applied_prank);
1011                }
1012            }
1013        }
1014
1015        // Handle mocked calls
1016        if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
1017            let ctx = MockCallDataContext {
1018                calldata: call.input.bytes(ecx),
1019                value: call.transfer_value(),
1020            };
1021
1022            if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
1023                Some(queue) => Some(queue),
1024                None => mocks
1025                    .iter_mut()
1026                    .find(|(mock, _)| {
1027                        call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..])
1028                            && mock.value.is_none_or(|value| Some(value) == call.transfer_value())
1029                    })
1030                    .map(|(_, v)| v),
1031            } && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned())
1032            {
1033                if let Some(value) = call.transfer_value() {
1034                    let checkpoint = ecx.journal_mut().checkpoint();
1035                    match ecx.journal_mut().transfer_loaded(
1036                        call.transfer_from(),
1037                        call.transfer_to(),
1038                        value,
1039                    ) {
1040                        None => {
1041                            if return_data.ret_type.is_ok() {
1042                                ecx.journal_mut().checkpoint_commit();
1043                            } else {
1044                                ecx.journal_mut().checkpoint_revert(checkpoint);
1045                            }
1046                        }
1047                        Some(err) => {
1048                            ecx.journal_mut().checkpoint_revert(checkpoint);
1049                            return Some(CallOutcome {
1050                                result: InterpreterResult {
1051                                    result: err.into(),
1052                                    output: Bytes::new(),
1053                                    gas,
1054                                },
1055                                memory_offset: call.return_memory_offset.clone(),
1056                                was_precompile_called: false,
1057                                precompile_call_logs: vec![],
1058                                charged_new_account_state_gas: false,
1059                            });
1060                        }
1061                    }
1062                }
1063
1064                // If the mocked calls stack has a single element in it, don't empty it
1065                if return_data_queue.len() > 1 {
1066                    return_data_queue.pop_front();
1067                }
1068
1069                return Some(CallOutcome {
1070                    result: InterpreterResult {
1071                        result: return_data.ret_type,
1072                        output: return_data.data,
1073                        gas,
1074                    },
1075                    memory_offset: call.return_memory_offset.clone(),
1076                    was_precompile_called: true,
1077                    precompile_call_logs: vec![],
1078                    charged_new_account_state_gas: false,
1079                });
1080            }
1081        }
1082
1083        // Apply EIP-2930 access list
1084        self.apply_accesslist(ecx);
1085
1086        // Apply our broadcast
1087        if let Some(broadcast) = &self.broadcast {
1088            // Additional check as transfers in forge scripts seem to be estimated at 2300
1089            // by revm leading to "Intrinsic gas too low" failure when simulated on chain.
1090            let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
1091            self.dynamic_gas_limit = false;
1092
1093            // We only apply a broadcast *to a specific depth*.
1094            //
1095            // We do this because any subsequent contract calls *must* exist on chain and
1096            // we only want to grab *this* call, not internal ones
1097            if curr_depth == broadcast.depth && call.caller == broadcast.original_caller {
1098                // At the target depth we set `msg.sender` & tx.origin.
1099                // We are simulating the caller as being an EOA, so *both* must be set to the
1100                // broadcast.origin.
1101                ecx.tx_mut().set_caller(broadcast.new_origin);
1102
1103                call.caller = broadcast.new_origin;
1104                // Add a `legacy` transaction to the VecDeque. We use a legacy transaction here
1105                // because we only need the from, to, value, and data. We can later change this
1106                // into 1559, in the cli package, relatively easily once we
1107                // know the target chain supports EIP-1559.
1108                if !call.is_static {
1109                    if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
1110                        return Some(CallOutcome {
1111                            result: InterpreterResult {
1112                                result: InstructionResult::Revert,
1113                                output: Error::encode(err),
1114                                gas,
1115                            },
1116                            memory_offset: call.return_memory_offset.clone(),
1117                            was_precompile_called: false,
1118                            precompile_call_logs: vec![],
1119                            charged_new_account_state_gas: false,
1120                        });
1121                    }
1122
1123                    let input = call.input.bytes(ecx);
1124                    let chain_id = ecx.cfg().chain_id();
1125                    let rpc = ecx.db().active_fork_url();
1126                    let account =
1127                        ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap();
1128
1129                    let mut tx_req = TransactionRequestFor::<FEN>::default()
1130                        .with_from(broadcast.new_origin)
1131                        .with_to(call.target_address)
1132                        .with_value(call.transfer_value().unwrap_or_default())
1133                        .with_input(input)
1134                        .with_nonce(account.info.nonce)
1135                        .with_chain_id(chain_id);
1136                    if is_fixed_gas_limit {
1137                        tx_req.set_gas_limit(call.gas_limit)
1138                    }
1139
1140                    let active_delegations = std::mem::take(&mut self.active_delegations);
1141                    // Set active blob sidecar, if any.
1142                    if let Some(blob_sidecar) = self.active_blob_sidecar.take() {
1143                        // Ensure blob and delegation are not set for the same tx.
1144                        if !active_delegations.is_empty() {
1145                            let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible";
1146                            return Some(CallOutcome {
1147                                result: InterpreterResult {
1148                                    result: InstructionResult::Revert,
1149                                    output: Error::encode(msg),
1150                                    gas,
1151                                },
1152                                memory_offset: call.return_memory_offset.clone(),
1153                                was_precompile_called: false,
1154                                precompile_call_logs: vec![],
1155                                charged_new_account_state_gas: false,
1156                            });
1157                        }
1158                        tx_req.set_blob_sidecar(blob_sidecar);
1159                    }
1160
1161                    // Apply active EIP-7702 delegations, if any.
1162                    if !active_delegations.is_empty() {
1163                        for auth in &active_delegations {
1164                            let Ok(authority) = auth.recover_authority() else {
1165                                continue;
1166                            };
1167                            if authority == broadcast.new_origin {
1168                                // Increment nonce of broadcasting account to reflect signed
1169                                // authorization.
1170                                account.info.nonce += 1;
1171                            }
1172                        }
1173                        tx_req.set_authorization_list(active_delegations);
1174                    }
1175                    if let Some(fee_token) = self.config.fee_token {
1176                        tx_req.set_fee_token(fee_token);
1177                    }
1178                    self.broadcastable_transactions.push_back(BroadcastableTransaction {
1179                        rpc,
1180                        transaction: TransactionMaybeSigned::new(tx_req),
1181                    });
1182                    debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call");
1183
1184                    // Explicitly increment nonce if calls are not isolated.
1185                    if !self.config.evm_opts.isolate {
1186                        let prev = account.info.nonce;
1187                        account.info.nonce += 1;
1188                        debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce");
1189                    }
1190                } else if broadcast.single_call {
1191                    let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead";
1192                    return Some(CallOutcome {
1193                        result: InterpreterResult {
1194                            result: InstructionResult::Revert,
1195                            output: Error::encode(msg),
1196                            gas,
1197                        },
1198                        memory_offset: call.return_memory_offset.clone(),
1199                        was_precompile_called: false,
1200                        precompile_call_logs: vec![],
1201                        charged_new_account_state_gas: false,
1202                    });
1203                }
1204            }
1205        }
1206
1207        // Record called accounts if `startStateDiffRecording` has been called
1208        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1209            // Determine if account is "initialized," ie, it has a non-zero balance, a non-zero
1210            // nonce, a non-zero KECCAK_EMPTY codehash, or non-empty code
1211            let (initialized, old_balance, old_nonce) =
1212                if let Ok(acc) = ecx.journal_mut().load_account(call.target_address) {
1213                    (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce)
1214                } else {
1215                    (false, U256::ZERO, 0)
1216                };
1217
1218            let kind = match call.scheme {
1219                CallScheme::Call => crate::Vm::AccountAccessKind::Call,
1220                CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode,
1221                CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall,
1222                CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall,
1223            };
1224
1225            // Record this call by pushing it to a new pending vector; all subsequent calls at
1226            // that depth will be pushed to the same vector. When the call ends, the
1227            // RecordedAccountAccess (and all subsequent RecordedAccountAccesses) will be
1228            // updated with the revert status of this call, since the EVM does not mark accounts
1229            // as "warm" if the call from which they were accessed is reverted
1230            recorded_account_diffs_stack.push(vec![AccountAccess {
1231                chainInfo: crate::Vm::ChainInfo {
1232                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
1233                    chainId: U256::from(ecx.cfg().chain_id()),
1234                },
1235                accessor: call.caller,
1236                account: call.bytecode_address,
1237                kind,
1238                initialized,
1239                oldBalance: old_balance,
1240                newBalance: U256::ZERO, // updated on call_end
1241                oldNonce: old_nonce,
1242                newNonce: 0, // updated on call_end
1243                value: call.call_value(),
1244                data: call.input.bytes(ecx),
1245                reverted: false,
1246                deployedCode: Bytes::new(),
1247                storageAccesses: vec![], // updated on step
1248                depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1249            }]);
1250        }
1251
1252        None
1253    }
1254
1255    pub fn rng(&mut self) -> &mut impl Rng {
1256        self.test_runner().rng()
1257    }
1258
1259    pub fn test_runner(&mut self) -> &mut TestRunner {
1260        self.test_runner.get_or_insert_with(|| match self.config.seed {
1261            Some(seed) => TestRunner::new_with_rng(
1262                proptest::test_runner::Config::default(),
1263                TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1264            ),
1265            None => TestRunner::new(proptest::test_runner::Config::default()),
1266        })
1267    }
1268
1269    pub fn set_seed(&mut self, seed: U256) {
1270        self.test_runner = Some(TestRunner::new_with_rng(
1271            proptest::test_runner::Config::default(),
1272            TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1273        ));
1274    }
1275
1276    /// Returns existing or set a default `ArbitraryStorage` option.
1277    /// Used by `setArbitraryStorage` cheatcode to track addresses with arbitrary storage.
1278    pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage {
1279        self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default)
1280    }
1281
1282    /// Whether the given address has arbitrary storage.
1283    pub fn has_arbitrary_storage(&self, address: &Address) -> bool {
1284        match &self.arbitrary_storage {
1285            Some(storage) => storage.values.contains_key(address),
1286            None => false,
1287        }
1288    }
1289
1290    /// Whether the given slot of address with arbitrary storage should be overwritten.
1291    /// True if address is marked as and overwrite and if no value was previously generated for
1292    /// given slot.
1293    pub fn should_overwrite_arbitrary_storage(
1294        &self,
1295        address: &Address,
1296        storage_slot: U256,
1297    ) -> bool {
1298        match &self.arbitrary_storage {
1299            Some(storage) => {
1300                storage.overwrites.contains(address)
1301                    && storage
1302                        .values
1303                        .get(address)
1304                        .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot))
1305                        .is_none()
1306            }
1307            None => false,
1308        }
1309    }
1310
1311    /// Whether the given address is a copy of an address with arbitrary storage.
1312    pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool {
1313        match &self.arbitrary_storage {
1314            Some(storage) => storage.copies.contains_key(address),
1315            None => false,
1316        }
1317    }
1318
1319    /// Returns struct definitions from the analysis, if available.
1320    pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> {
1321        self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok())
1322    }
1323}
1324
1325impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for Cheatcodes<FEN> {
1326    fn initialize_interp(
1327        &mut self,
1328        interpreter: &mut Interpreter,
1329        ecx: &mut FoundryContextFor<'_, FEN>,
1330    ) {
1331        // When the first interpreter is initialized we've circumvented the balance and gas checks,
1332        // so we apply our actual block data with the correct fees and all.
1333        if let Some(block) = self.block.take() {
1334            ecx.set_block(block);
1335        }
1336        if let Some(gas_price) = self.gas_price.take() {
1337            ecx.tx_mut().set_gas_price(gas_price);
1338        }
1339
1340        // Record gas for current frame.
1341        if self.gas_metering.paused {
1342            self.gas_metering.paused_frames.push(interpreter.gas);
1343        }
1344
1345        // `expectRevert`: track the max call depth during `expectRevert`
1346        if let Some(expected) = &mut self.expected_revert {
1347            expected.max_depth = max(ecx.journal().depth(), expected.max_depth);
1348        }
1349    }
1350
1351    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1352        self.pc = interpreter.bytecode.pc();
1353
1354        if self.broadcast.is_some() {
1355            self.set_gas_limit_type(interpreter);
1356        }
1357
1358        // `pauseGasMetering`: pause / resume interpreter gas.
1359        if self.gas_metering.paused {
1360            self.meter_gas(interpreter);
1361        }
1362
1363        // `resetGasMetering`: reset interpreter gas.
1364        if self.gas_metering.reset {
1365            self.meter_gas_reset(interpreter);
1366        }
1367
1368        // `record`: record storage reads and writes.
1369        if self.recording_accesses {
1370            self.record_accesses(interpreter);
1371        }
1372
1373        // `startStateDiffRecording`: record granular ordered storage accesses.
1374        if self.recorded_account_diffs_stack.is_some() {
1375            self.record_state_diffs(interpreter, ecx);
1376        }
1377
1378        // `expectSafeMemory`: check if the current opcode is allowed to interact with memory.
1379        if !self.allowed_mem_writes.is_empty() {
1380            self.check_mem_opcodes(
1381                interpreter,
1382                ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1383            );
1384        }
1385
1386        // `startMappingRecording`: record SSTORE and KECCAK256.
1387        if let Some(mapping_slots) = &mut self.mapping_slots {
1388            mapping_step(mapping_slots, interpreter);
1389        }
1390
1391        // `snapshotGas*`: take a snapshot of the current gas.
1392        if self.gas_metering.recording {
1393            self.meter_gas_record(interpreter, ecx);
1394        }
1395
1396        // Capture the opcode for `step_end` to use, since by the time
1397        // `step_end` runs the PC has already advanced past it. Also peek the
1398        // BLOBHASH index now (still on top of stack before execution) so we
1399        // can look up the override later.
1400        if !self.env_overrides.is_empty() {
1401            let fork_id = ecx.db().active_fork_id();
1402            if let Some(env_overrides) =
1403                self.env_overrides.get_mut(&fork_id).filter(|o| o.is_any_set())
1404            {
1405                // Always clear stale pending state first so a leftover value from
1406                // a prior step (e.g. when `peek` failed, or when an override
1407                // wasn't actually used) cannot leak into the next opcode.
1408                env_overrides.pending_opcode = None;
1409                env_overrides.pending_blobhash_index = None;
1410
1411                let opcode = interpreter.bytecode.opcode();
1412                match opcode {
1413                    op::BASEFEE | op::GASPRICE => {
1414                        env_overrides.pending_opcode = Some(opcode);
1415                    }
1416                    op::BLOBHASH => {
1417                        env_overrides.pending_opcode = Some(opcode);
1418                        env_overrides.pending_blobhash_index =
1419                            interpreter.stack.peek(0).ok().and_then(|index| index.try_into().ok());
1420                    }
1421                    _ => {}
1422                }
1423            }
1424        }
1425    }
1426
1427    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1428        if self.gas_metering.paused {
1429            self.meter_gas_end(interpreter);
1430        }
1431
1432        if self.gas_metering.touched {
1433            self.meter_gas_check(interpreter);
1434        }
1435
1436        // `setArbitraryStorage` and `copyStorage`: add arbitrary values to storage.
1437        if self.arbitrary_storage.is_some() {
1438            self.arbitrary_storage_end(interpreter, ecx);
1439        }
1440
1441        // Apply opcode-level env overrides (basefee/gasprice/blobhash). Needed
1442        // in isolation mode where the actual tx/block env is zeroed for
1443        // fee-accounting; in non-isolation mode the override and the real env
1444        // are kept in sync by the cheatcode handlers, so this is a no-op fixup.
1445        //
1446        // We must only rewrite the stack if the opcode actually completed
1447        // successfully and pushed its result; otherwise (stack underflow on
1448        // BLOBHASH, OOG before push, etc.) the stack is in an error state and
1449        // a blind `pop()+push()` would corrupt the failing frame.
1450        if !self.env_overrides.is_empty() {
1451            let fork_id = ecx.db().active_fork_id();
1452            if self.env_overrides.get(&fork_id).is_some_and(|o| o.is_any_set()) {
1453                // Mirrors the pattern used by `meter_gas_record`: when `action` is
1454                // `Some` with an `instruction_result`, the opcode has set a
1455                // non-continue result (halt/revert/error) — i.e. it didn't push
1456                // its normal result. `None` means "still running", which is the
1457                // success path for a stack-only opcode in `step_end`.
1458                let opcode_failed = interpreter
1459                    .bytecode
1460                    .action
1461                    .as_ref()
1462                    .and_then(|a| a.instruction_result())
1463                    .is_some();
1464                if opcode_failed {
1465                    if let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) {
1466                        env_overrides.pending_opcode = None;
1467                        env_overrides.pending_blobhash_index = None;
1468                    }
1469                } else {
1470                    self.apply_env_overrides(interpreter, fork_id);
1471                }
1472            }
1473        }
1474    }
1475
1476    fn log(&mut self, _ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1477        if !self.expected_emits.is_empty()
1478            && let Some(err) = expect::handle_expect_emit(self, &log, None)
1479        {
1480            // Because we do not have access to the interpreter here, we cannot fail the test
1481            // immediately. In most cases the failure will still be caught on `call_end`.
1482            // In the rare case it is not, we log the error here.
1483            let _ = sh_err!("{err:?}");
1484        }
1485
1486        // `recordLogs`
1487        record_logs(&mut self.recorded_logs, &log);
1488    }
1489
1490    fn log_full(
1491        &mut self,
1492        interpreter: &mut Interpreter,
1493        _ecx: &mut FoundryContextFor<'_, FEN>,
1494        log: Log,
1495    ) {
1496        if !self.expected_emits.is_empty() {
1497            expect::handle_expect_emit(self, &log, Some(interpreter));
1498        }
1499
1500        // `recordLogs`
1501        record_logs(&mut self.recorded_logs, &log);
1502    }
1503
1504    fn call(
1505        &mut self,
1506        ecx: &mut FoundryContextFor<'_, FEN>,
1507        inputs: &mut CallInputs,
1508    ) -> Option<CallOutcome> {
1509        Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor)
1510    }
1511
1512    fn call_end(
1513        &mut self,
1514        ecx: &mut FoundryContextFor<'_, FEN>,
1515        call: &CallInputs,
1516        outcome: &mut CallOutcome,
1517    ) {
1518        let cheatcode_call = call.target_address == CHEATCODE_ADDRESS
1519            || call.target_address == HARDHAT_CONSOLE_ADDRESS;
1520
1521        // Clean up pranks/broadcasts if it's not a cheatcode call end. We shouldn't do
1522        // it for cheatcode calls because they are not applied for cheatcodes in the `call` hook.
1523        // This should be placed before the revert handling, because we might exit early there
1524        if !cheatcode_call {
1525            // Clean up pranks
1526            let curr_depth = ecx.journal().depth();
1527            if let Some(prank) = &self.get_prank(curr_depth)
1528                && curr_depth == prank.depth
1529            {
1530                ecx.tx_mut().set_caller(prank.prank_origin);
1531
1532                // Clean single-call prank once we have returned to the original depth
1533                if prank.single_call {
1534                    self.pranks.remove(&curr_depth);
1535                }
1536            }
1537
1538            // Clean up broadcast
1539            if let Some(broadcast) = &self.broadcast
1540                && curr_depth == broadcast.depth
1541            {
1542                ecx.tx_mut().set_caller(broadcast.original_origin);
1543
1544                // Clean single-call broadcast once we have returned to the original depth
1545                if broadcast.single_call {
1546                    let _ = self.broadcast.take();
1547                }
1548            }
1549        }
1550
1551        // Handle assume no revert cheatcode.
1552        if let Some(assume_no_revert) = &mut self.assume_no_revert {
1553            // Record current reverter address before processing the expect revert if call reverted,
1554            // expect revert is set with expected reverter address and no actual reverter set yet.
1555            if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() {
1556                assume_no_revert.reverted_by = Some(call.target_address);
1557            }
1558
1559            // allow multiple cheatcode calls at the same depth
1560            let curr_depth = ecx.journal().depth();
1561            if curr_depth <= assume_no_revert.depth && !cheatcode_call {
1562                // Discard run if we're at the same depth as cheatcode, call reverted, and no
1563                // specific reason was supplied
1564                if outcome.result.is_revert() {
1565                    let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap();
1566                    return match revert_handlers::handle_assume_no_revert(
1567                        &assume_no_revert,
1568                        outcome.result.result,
1569                        &outcome.result.output,
1570                        &self.config.available_artifacts,
1571                    ) {
1572                        // if result is Ok, it was an anticipated revert; return an "assume" error
1573                        // to reject this run
1574                        Ok(_) => {
1575                            outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into();
1576                        }
1577                        // if result is Error, it was an unanticipated revert; should revert
1578                        // normally
1579                        Err(error) => {
1580                            trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
1581                            outcome.result.result = InstructionResult::Revert;
1582                            outcome.result.output = error.abi_encode().into();
1583                        }
1584                    };
1585                }
1586                // Call didn't revert, reset `assume_no_revert` state.
1587                self.assume_no_revert = None;
1588            }
1589        }
1590
1591        // Handle expected reverts.
1592        if let Some(expected_revert) = &mut self.expected_revert {
1593            // Record current reverter address and call scheme before processing the expect revert
1594            // if call reverted.
1595            let call_failed = !matches!(outcome.result.result, return_ok!());
1596            if call_failed {
1597                // Record current reverter address if expect revert is set with expected reverter
1598                // address and no actual reverter was set yet or if we're expecting more than one
1599                // revert.
1600                if expected_revert.reverter.is_some()
1601                    && (expected_revert.reverted_by.is_none() || expected_revert.count > 1)
1602                {
1603                    expected_revert.reverted_by = Some(call.target_address);
1604                }
1605            }
1606
1607            let curr_depth = ecx.journal().depth();
1608            if curr_depth <= expected_revert.depth {
1609                // Decide whether this `call_end` should consume the pending `expectRevert`.
1610                // With `internal_expect_revert` enabled, a same-depth revert can satisfy it, but
1611                // we must not consume it for external calls that succeed (e.g. calls to
1612                // non-contract addresses that return `Stop` before Solidity's own revert).
1613                let internal = self.config.internal_expect_revert;
1614                let went_deeper = expected_revert.max_depth > expected_revert.depth;
1615                let needs_processing = match expected_revert.kind {
1616                    ExpectedRevertKind::Default => (|| {
1617                        // Cheatcode reverts propagate up; let the outer frame catch them.
1618                        if cheatcode_call {
1619                            return false;
1620                        }
1621                        // Any failure satisfies the expectation.
1622                        if call_failed {
1623                            return true;
1624                        }
1625                        // Traditional expectRevert: succeeded external call went deeper.
1626                        if !internal && went_deeper {
1627                            return true;
1628                        }
1629                        // Test function returned: catch dangling expectations.
1630                        if curr_depth == 0 {
1631                            return true;
1632                        }
1633                        // Same-depth success with internal mode off is an error; with it on,
1634                        // keep waiting for the actual revert.
1635                        !internal
1636                    })(),
1637                    // `pending_processing == true` means we're in the `call_end` hook for
1638                    // `vm.expectCheatcodeRevert` and shouldn't expect a revert here.
1639                    ExpectedRevertKind::Cheatcode { pending_processing } => {
1640                        cheatcode_call && !pending_processing
1641                    }
1642                };
1643
1644                if needs_processing {
1645                    let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
1646                    return match revert_handlers::handle_expect_revert(
1647                        cheatcode_call,
1648                        false,
1649                        self.config.internal_expect_revert,
1650                        &expected_revert,
1651                        outcome.result.result,
1652                        outcome.result.output.clone(),
1653                        &self.config.available_artifacts,
1654                    ) {
1655                        Err(error) => {
1656                            trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
1657                            outcome.result.result = InstructionResult::Revert;
1658                            outcome.result.output = error.abi_encode().into();
1659                        }
1660                        Ok((_, retdata)) => {
1661                            expected_revert.actual_count += 1;
1662                            if expected_revert.actual_count < expected_revert.count {
1663                                self.expected_revert = Some(expected_revert);
1664                            }
1665                            outcome.result.result = InstructionResult::Return;
1666                            outcome.result.output = retdata;
1667                        }
1668                    };
1669                }
1670
1671                // Flip `pending_processing` flag for cheatcode revert expectations, marking that
1672                // we've exited the `expectCheatcodeRevert` call scope
1673                if let ExpectedRevertKind::Cheatcode { pending_processing } =
1674                    &mut self.expected_revert.as_mut().unwrap().kind
1675                {
1676                    *pending_processing = false;
1677                }
1678            }
1679        }
1680
1681        // Exit early for calls to cheatcodes as other logic is not relevant for cheatcode
1682        // invocations
1683        if cheatcode_call {
1684            return;
1685        }
1686
1687        // Record the gas usage of the call, this allows the `lastCallGas` cheatcode to
1688        // retrieve the gas usage of the last call.
1689        let gas = outcome.result.gas;
1690        self.gas_metering.last_call_gas = Some(crate::Vm::Gas {
1691            gasLimit: gas.limit(),
1692            gasTotalUsed: gas.total_gas_spent(),
1693            gasMemoryUsed: 0,
1694            gasRefunded: gas.refunded(),
1695            gasRemaining: gas.remaining(),
1696        });
1697
1698        // If `startStateDiffRecording` has been called, update the `reverted` status of the
1699        // previous call depth's recorded accesses, if any
1700        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1701            // The root call cannot be recorded.
1702            if ecx.journal().depth() > 0
1703                && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop()
1704            {
1705                // Update the reverted status of all deeper calls if this call reverted, in
1706                // accordance with EVM behavior
1707                if outcome.result.is_revert() {
1708                    for element in &mut *last_recorded_depth {
1709                        element.reverted = true;
1710                        for storage_access in &mut element.storageAccesses {
1711                            storage_access.reverted = true;
1712                        }
1713                    }
1714                }
1715
1716                if let Some(call_access) = last_recorded_depth.first_mut() {
1717                    // Assert that we're at the correct depth before recording post-call state
1718                    // changes. Depending on the depth the cheat was
1719                    // called at, there may not be any pending
1720                    // calls to update if execution has percolated up to a higher depth.
1721                    let curr_depth = ecx.journal().depth();
1722                    if call_access.depth == curr_depth as u64
1723                        && let Ok(acc) = ecx.journal_mut().load_account(call.target_address)
1724                    {
1725                        debug_assert!(access_is_call(call_access.kind));
1726                        call_access.newBalance = acc.data.info.balance;
1727                        call_access.newNonce = acc.data.info.nonce;
1728                    }
1729                    // Merge the last depth's AccountAccesses into the AccountAccesses at the
1730                    // current depth, or push them back onto the pending
1731                    // vector if higher depths were not recorded. This
1732                    // preserves ordering of accesses.
1733                    if let Some(last) = recorded_account_diffs_stack.last_mut() {
1734                        last.extend(last_recorded_depth);
1735                    } else {
1736                        recorded_account_diffs_stack.push(last_recorded_depth);
1737                    }
1738                }
1739            }
1740        }
1741
1742        // this will ensure we don't have false positives when trying to diagnose reverts in fork
1743        // mode
1744        let diag = self.fork_revert_diagnostic.take();
1745
1746        // If the call already reverted, preserve that primary failure and skip post-call
1747        // expect* validation so it cannot overwrite the original revert.
1748        if outcome.result.is_revert() {
1749            // if there's a revert and a previous call was diagnosed as fork related revert then we
1750            // can return a better error here
1751            if let Some(err) = diag {
1752                outcome.result.output = Error::encode(err.to_error_msg(&self.labels));
1753            }
1754            return;
1755        }
1756
1757        // At the end of the call,
1758        // we need to check if we've found all the emits.
1759        // We know we've found all the expected emits in the right order
1760        // if the queue is fully matched.
1761        // If it's not fully matched, then either:
1762        // 1. Not enough events were emitted (we'll know this because the amount of times we
1763        // inspected events will be less than the size of the queue) 2. The wrong events
1764        // were emitted (The inspected events should match the size of the queue, but still some
1765        // events will not be matched)
1766
1767        // First, check that we're at the call depth where the emits were declared from.
1768        let should_check_emits = self
1769            .expected_emits
1770            .iter()
1771            .any(|(expected, _)| {
1772                let curr_depth = ecx.journal().depth();
1773                expected.depth == curr_depth
1774            }) &&
1775            // Ignore staticcalls
1776            !call.is_static;
1777        if should_check_emits {
1778            let expected_counts = self
1779                .expected_emits
1780                .iter()
1781                .filter_map(|(expected, count_map)| {
1782                    let count = match expected.address {
1783                        Some(emitter) => match count_map.get(&emitter) {
1784                            Some(log_count) => expected
1785                                .log
1786                                .as_ref()
1787                                .map(|l| log_count.count(l))
1788                                .unwrap_or_else(|| log_count.count_unchecked()),
1789                            None => 0,
1790                        },
1791                        None => match &expected.log {
1792                            Some(log) => count_map.values().map(|logs| logs.count(log)).sum(),
1793                            None => count_map.values().map(|logs| logs.count_unchecked()).sum(),
1794                        },
1795                    };
1796
1797                    (count != expected.count).then_some((expected, count))
1798                })
1799                .collect::<Vec<_>>();
1800
1801            // Revert if not all emits expected were matched.
1802            if let Some((expected, _)) = self
1803                .expected_emits
1804                .iter()
1805                .find(|(expected, _)| !expected.found && expected.count > 0)
1806            {
1807                outcome.result.result = InstructionResult::Revert;
1808                let error_msg = expected.mismatch_error.as_deref().unwrap_or("log != expected log");
1809                outcome.result.output = error_msg.abi_encode().into();
1810                return;
1811            }
1812
1813            if !expected_counts.is_empty() {
1814                let msg = if outcome.result.is_ok() {
1815                    let (expected, count) = expected_counts.first().unwrap();
1816                    format!("log emitted {count} times, expected {}", expected.count)
1817                } else {
1818                    "expected an emit, but the call reverted instead. \
1819                     ensure you're testing the happy path when using `expectEmit`"
1820                        .to_string()
1821                };
1822
1823                outcome.result.result = InstructionResult::Revert;
1824                outcome.result.output = Error::encode(msg);
1825                return;
1826            }
1827
1828            // All emits were found, we're good.
1829            // Clear the queue, as we expect the user to declare more events for the next call
1830            // if they wanna match further events.
1831            self.expected_emits.clear()
1832        }
1833
1834        // try to diagnose reverts in multi-fork mode where a call is made to an address that does
1835        // not exist
1836        if let TxKind::Call(test_contract) = ecx.tx().kind() {
1837            // if a call to a different contract than the original test contract returned with
1838            // `Stop` we check if the contract actually exists on the active fork
1839            if ecx.db().is_forked_mode()
1840                && outcome.result.result == InstructionResult::Stop
1841                && call.target_address != test_contract
1842            {
1843                self.fork_revert_diagnostic =
1844                    ecx.db().diagnose_revert(call.target_address, ecx.journal().evm_state());
1845            }
1846        }
1847
1848        // If the depth is 0, then this is the root call terminating
1849        if ecx.journal().depth() == 0 {
1850            // If we already have a revert, we shouldn't run the below logic as it can obfuscate an
1851            // earlier error that happened first with unrelated information about
1852            // another error when using cheatcodes.
1853            if outcome.result.is_revert() {
1854                return;
1855            }
1856
1857            // If there's not a revert, we can continue on to run the last logic for expect*
1858            // cheatcodes.
1859
1860            // Match expected calls
1861            for (address, calldatas) in &self.expected_calls {
1862                // Loop over each address, and for each address, loop over each calldata it expects.
1863                for (calldata, (expected, actual_count)) in calldatas {
1864                    // Grab the values we expect to see
1865                    let ExpectedCallData { gas, min_gas, value, count, call_type } = expected;
1866
1867                    let failed = match call_type {
1868                        // If the cheatcode was called with a `count` argument,
1869                        // we must check that the EVM performed a CALL with this calldata exactly
1870                        // `count` times.
1871                        ExpectedCallType::Count => *count != *actual_count,
1872                        // If the cheatcode was called without a `count` argument,
1873                        // we must check that the EVM performed a CALL with this calldata at least
1874                        // `count` times. The amount of times to check was
1875                        // the amount of time the cheatcode was called.
1876                        ExpectedCallType::NonCount => *count > *actual_count,
1877                    };
1878                    if failed {
1879                        let expected_values = [
1880                            Some(format!("data {}", hex::encode_prefixed(calldata))),
1881                            value.as_ref().map(|v| format!("value {v}")),
1882                            gas.map(|g| format!("gas {g}")),
1883                            min_gas.map(|g| format!("minimum gas {g}")),
1884                        ]
1885                        .into_iter()
1886                        .flatten()
1887                        .join(", ");
1888                        let but = if outcome.result.is_ok() {
1889                            let s = if *actual_count == 1 { "" } else { "s" };
1890                            format!("was called {actual_count} time{s}")
1891                        } else {
1892                            "the call reverted instead; \
1893                             ensure you're testing the happy path when using `expectCall`"
1894                                .to_string()
1895                        };
1896                        let s = if *count == 1 { "" } else { "s" };
1897                        let msg = format!(
1898                            "expected call to {address} with {expected_values} \
1899                             to be called {count} time{s}, but {but}"
1900                        );
1901                        outcome.result.result = InstructionResult::Revert;
1902                        outcome.result.output = Error::encode(msg);
1903
1904                        return;
1905                    }
1906                }
1907            }
1908
1909            // Check if we have any leftover expected emits
1910            // First, if any emits were found at the root call, then we its ok and we remove them.
1911            // For count=0 expectations, NOT being found is success, so mark them as found
1912            for (expected, _) in &mut self.expected_emits {
1913                if expected.count == 0 && !expected.found {
1914                    expected.found = true;
1915                }
1916            }
1917            self.expected_emits.retain(|(expected, _)| !expected.found);
1918            // If not empty, we got mismatched emits
1919            if !self.expected_emits.is_empty() {
1920                let msg = if outcome.result.is_ok() {
1921                    "expected an emit, but no logs were emitted afterwards. \
1922                     you might have mismatched events or not enough events were emitted"
1923                } else {
1924                    "expected an emit, but the call reverted instead. \
1925                     ensure you're testing the happy path when using `expectEmit`"
1926                };
1927                outcome.result.result = InstructionResult::Revert;
1928                outcome.result.output = Error::encode(msg);
1929                return;
1930            }
1931
1932            // Check for leftover expected creates
1933            if let Some(expected_create) = self.expected_creates.first() {
1934                let msg = format!(
1935                    "expected {} call by address {} for bytecode {} but not found",
1936                    expected_create.create_scheme,
1937                    hex::encode_prefixed(expected_create.deployer),
1938                    hex::encode_prefixed(&expected_create.bytecode),
1939                );
1940                outcome.result.result = InstructionResult::Revert;
1941                outcome.result.output = Error::encode(msg);
1942            }
1943        }
1944    }
1945
1946    fn create(
1947        &mut self,
1948        ecx: &mut FoundryContextFor<'_, FEN>,
1949        mut input: &mut CreateInputs,
1950    ) -> Option<CreateOutcome> {
1951        // Apply custom execution evm version.
1952        if let Some(spec_id) = self.execution_evm_version {
1953            ecx.cfg_mut().set_spec_and_mainnet_gas_params(spec_id);
1954        }
1955
1956        let gas = Gas::new(input.gas_limit());
1957        // Check if we should intercept this create
1958        if self.intercept_next_create_call {
1959            // Reset the flag
1960            self.intercept_next_create_call = false;
1961
1962            // Get initcode from the input
1963            let output = input.init_code();
1964
1965            // Return a revert with the initcode as error data
1966            return Some(CreateOutcome {
1967                result: InterpreterResult { result: InstructionResult::Revert, output, gas },
1968                address: None,
1969            });
1970        }
1971
1972        let curr_depth = ecx.journal().depth();
1973
1974        // Apply our prank
1975        if let Some(prank) = &self.get_prank(curr_depth)
1976            && curr_depth >= prank.depth
1977            && input.caller() == prank.prank_caller
1978        {
1979            // At the target depth we set `msg.sender`
1980            let prank_applied = if curr_depth == prank.depth {
1981                // Ensure new caller is loaded and touched
1982                let _ = journaled_account(ecx, prank.new_caller);
1983                input.set_caller(prank.new_caller);
1984                true
1985            } else {
1986                false
1987            };
1988
1989            // At the target depth, or deeper, we set `tx.origin`
1990            let prank_applied = if let Some(new_origin) = prank.new_origin {
1991                ecx.tx_mut().set_caller(new_origin);
1992                true
1993            } else {
1994                prank_applied
1995            };
1996
1997            // If prank applied for first time, then update
1998            if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
1999                self.pranks.insert(curr_depth, applied_prank);
2000            }
2001        }
2002
2003        // Apply EIP-2930 access list
2004        self.apply_accesslist(ecx);
2005
2006        // Apply our broadcast
2007        if let Some(broadcast) = &mut self.broadcast
2008            && curr_depth >= broadcast.depth
2009            && input.caller() == broadcast.original_caller
2010        {
2011            if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
2012                return Some(CreateOutcome {
2013                    result: InterpreterResult {
2014                        result: InstructionResult::Revert,
2015                        output: Error::encode(err),
2016                        gas,
2017                    },
2018                    address: None,
2019                });
2020            }
2021
2022            ecx.tx_mut().set_caller(broadcast.new_origin);
2023
2024            if curr_depth == broadcast.depth || broadcast.deploy_from_code {
2025                // Reset deploy from code flag for upcoming calls;
2026                broadcast.deploy_from_code = false;
2027
2028                input.set_caller(broadcast.new_origin);
2029
2030                let rpc = ecx.db().active_fork_url();
2031                let account = &ecx.journal().evm_state()[&broadcast.new_origin];
2032                let mut tx_req = TransactionRequestFor::<FEN>::default()
2033                    .with_from(broadcast.new_origin)
2034                    .with_kind(TxKind::Create)
2035                    .with_value(input.value())
2036                    .with_input(input.init_code())
2037                    .with_nonce(account.info.nonce);
2038                if let Some(fee_token) = self.config.fee_token {
2039                    tx_req.set_fee_token(fee_token);
2040                }
2041                self.broadcastable_transactions.push_back(BroadcastableTransaction {
2042                    rpc,
2043                    transaction: TransactionMaybeSigned::new(tx_req),
2044                });
2045
2046                input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create));
2047            }
2048        }
2049
2050        // Allow cheatcodes from the address of the new contract
2051        let address = input.allow_cheatcodes(self, ecx);
2052
2053        // If `recordAccountAccesses` has been called, record the create
2054        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2055            recorded_account_diffs_stack.push(vec![AccountAccess {
2056                chainInfo: crate::Vm::ChainInfo {
2057                    forkId: ecx.db().active_fork_id().unwrap_or_default(),
2058                    chainId: U256::from(ecx.cfg().chain_id()),
2059                },
2060                accessor: input.caller(),
2061                account: address,
2062                kind: crate::Vm::AccountAccessKind::Create,
2063                initialized: true,
2064                oldBalance: U256::ZERO, // updated on create_end
2065                newBalance: U256::ZERO, // updated on create_end
2066                oldNonce: 0,            // new contract starts with nonce 0
2067                newNonce: 1,            // updated on create_end (contracts start with nonce 1)
2068                value: input.value(),
2069                data: input.init_code(),
2070                reverted: false,
2071                deployedCode: Bytes::new(), // updated on create_end
2072                storageAccesses: vec![],    // updated on create_end
2073                depth: curr_depth as u64,
2074            }]);
2075        }
2076
2077        None
2078    }
2079
2080    fn create_end(
2081        &mut self,
2082        ecx: &mut FoundryContextFor<'_, FEN>,
2083        call: &CreateInputs,
2084        outcome: &mut CreateOutcome,
2085    ) {
2086        let call = Some(call);
2087        let curr_depth = ecx.journal().depth();
2088
2089        // Clean up pranks
2090        if let Some(prank) = &self.get_prank(curr_depth)
2091            && curr_depth == prank.depth
2092        {
2093            ecx.tx_mut().set_caller(prank.prank_origin);
2094
2095            // Clean single-call prank once we have returned to the original depth
2096            if prank.single_call {
2097                std::mem::take(&mut self.pranks);
2098            }
2099        }
2100
2101        // Clean up broadcasts
2102        if let Some(broadcast) = &self.broadcast
2103            && curr_depth == broadcast.depth
2104        {
2105            ecx.tx_mut().set_caller(broadcast.original_origin);
2106
2107            // Clean single-call broadcast once we have returned to the original depth
2108            if broadcast.single_call {
2109                std::mem::take(&mut self.broadcast);
2110            }
2111        }
2112
2113        // Handle expected reverts.
2114        if let Some(expected_revert) = &mut self.expected_revert {
2115            // Record the would-be deployed address as the reverter, picking the innermost
2116            // reverting CREATE: this hook runs at every depth, the deepest frame fires
2117            // first, and the `is_none()` lock pins it. For `count > 1` the lock is
2118            // released after each successful iteration (see below) so each iteration
2119            // independently records its own innermost CREATE.
2120            //
2121            // This intentionally differs from `call_end` for `count > 1`, where
2122            // legacy nested CALL handling reports the outermost call per iteration.
2123            //
2124            // `outcome.address` is `None` for pre-frame rejection (depth/balance/nonce);
2125            // in that case the surrounding `call_end` records the caller as the reverter.
2126            if outcome.result.is_revert()
2127                && expected_revert.reverter.is_some()
2128                && expected_revert.reverted_by.is_none()
2129                && let Some(addr) = outcome.address
2130            {
2131                expected_revert.reverted_by = Some(addr);
2132            }
2133
2134            if curr_depth <= expected_revert.depth
2135                && matches!(expected_revert.kind, ExpectedRevertKind::Default)
2136            {
2137                let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
2138                return match revert_handlers::handle_expect_revert(
2139                    false,
2140                    true,
2141                    self.config.internal_expect_revert,
2142                    &expected_revert,
2143                    outcome.result.result,
2144                    outcome.result.output.clone(),
2145                    &self.config.available_artifacts,
2146                ) {
2147                    Ok((address, retdata)) => {
2148                        expected_revert.actual_count += 1;
2149                        if expected_revert.actual_count < expected_revert.count {
2150                            // Reset so the next iteration's innermost CREATE wins again.
2151                            expected_revert.reverted_by = None;
2152                            self.expected_revert = Some(expected_revert.clone());
2153                        }
2154
2155                        outcome.result.result = InstructionResult::Return;
2156                        outcome.result.output = retdata;
2157                        outcome.address = address;
2158                    }
2159                    Err(err) => {
2160                        outcome.result.result = InstructionResult::Revert;
2161                        outcome.result.output = err.abi_encode().into();
2162                    }
2163                };
2164            }
2165        }
2166
2167        // If `startStateDiffRecording` has been called, update the `reverted` status of the
2168        // previous call depth's recorded accesses, if any
2169        if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2170            // The root call cannot be recorded.
2171            if curr_depth > 0
2172                && let Some(last_depth) = &mut recorded_account_diffs_stack.pop()
2173            {
2174                // Update the reverted status of all deeper calls if this call reverted, in
2175                // accordance with EVM behavior
2176                if outcome.result.is_revert() {
2177                    for element in &mut *last_depth {
2178                        element.reverted = true;
2179                        for storage_access in &mut element.storageAccesses {
2180                            storage_access.reverted = true;
2181                        }
2182                    }
2183                }
2184
2185                if let Some(create_access) = last_depth.first_mut() {
2186                    // Assert that we're at the correct depth before recording post-create state
2187                    // changes. Depending on what depth the cheat was called at, there
2188                    // may not be any pending calls to update if execution has
2189                    // percolated up to a higher depth.
2190                    let depth = ecx.journal().depth();
2191                    if create_access.depth == depth as u64 {
2192                        debug_assert_eq!(
2193                            create_access.kind as u8,
2194                            crate::Vm::AccountAccessKind::Create as u8
2195                        );
2196                        if let Some(address) = outcome.address
2197                            && let Ok(created_acc) = ecx.journal_mut().load_account(address)
2198                        {
2199                            create_access.newBalance = created_acc.data.info.balance;
2200                            create_access.newNonce = created_acc.data.info.nonce;
2201                            create_access.deployedCode = created_acc
2202                                .data
2203                                .info
2204                                .code
2205                                .clone()
2206                                .unwrap_or_default()
2207                                .original_bytes();
2208                        }
2209                    }
2210                    // Merge the last depth's AccountAccesses into the AccountAccesses at the
2211                    // current depth, or push them back onto the pending
2212                    // vector if higher depths were not recorded. This
2213                    // preserves ordering of accesses.
2214                    if let Some(last) = recorded_account_diffs_stack.last_mut() {
2215                        last.append(last_depth);
2216                    } else {
2217                        recorded_account_diffs_stack.push(last_depth.clone());
2218                    }
2219                }
2220            }
2221        }
2222
2223        // Match the create against expected_creates
2224        if !self.expected_creates.is_empty()
2225            && let (Some(address), Some(call)) = (outcome.address, call)
2226            && let Ok(created_acc) = ecx.journal_mut().load_account(address)
2227        {
2228            let bytecode = created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
2229            if let Some((index, _)) =
2230                self.expected_creates.iter().find_position(|expected_create| {
2231                    expected_create.deployer == call.caller()
2232                        && expected_create.create_scheme.eq(call.scheme().into())
2233                        && expected_create.bytecode == bytecode
2234                })
2235            {
2236                self.expected_creates.swap_remove(index);
2237            }
2238        }
2239    }
2240}
2241
2242impl<FEN: FoundryEvmNetwork> InspectorExt for Cheatcodes<FEN> {
2243    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2244        let target_depth = if let Some(prank) = &self.get_prank(depth) {
2245            prank.depth
2246        } else if let Some(broadcast) = &self.broadcast {
2247            broadcast.depth
2248        } else {
2249            1
2250        };
2251
2252        if depth != target_depth {
2253            return false;
2254        }
2255
2256        match inputs.scheme() {
2257            CreateScheme::Create2 { .. } => {
2258                self.broadcast.is_some() || self.config.always_use_create_2_factory
2259            }
2260            CreateScheme::Create => self.config.batch_rewrite_creates && self.broadcast.is_some(),
2261            _ => false,
2262        }
2263    }
2264
2265    fn create2_deployer(&self) -> Address {
2266        self.config.evm_opts.create2_deployer
2267    }
2268}
2269
2270impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
2271    #[cold]
2272    fn meter_gas(&mut self, interpreter: &mut Interpreter) {
2273        if let Some(paused_gas) = self.gas_metering.paused_frames.last() {
2274            // Keep gas constant if paused.
2275            // Make sure we record the memory changes so that memory expansion is not paused.
2276            let memory = *interpreter.gas.memory();
2277            interpreter.gas = *paused_gas;
2278            interpreter.gas.memory_mut().words_num = memory.words_num;
2279            interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost;
2280        } else {
2281            // Record frame paused gas.
2282            self.gas_metering.paused_frames.push(interpreter.gas);
2283        }
2284    }
2285
2286    #[cold]
2287    fn meter_gas_record(
2288        &mut self,
2289        interpreter: &mut Interpreter,
2290        ecx: &mut FoundryContextFor<'_, FEN>,
2291    ) {
2292        if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() {
2293            self.gas_metering.gas_records.iter_mut().for_each(|record| {
2294                let curr_depth = ecx.journal().depth();
2295                if curr_depth == record.depth {
2296                    // Skip the first opcode of the first call frame as it includes the gas cost of
2297                    // creating the snapshot.
2298                    if self.gas_metering.last_gas_used != 0 {
2299                        let gas_diff = interpreter
2300                            .gas
2301                            .total_gas_spent()
2302                            .saturating_sub(self.gas_metering.last_gas_used);
2303                        record.gas_used = record.gas_used.saturating_add(gas_diff);
2304                    }
2305
2306                    // Update `last_gas_used` to the current spent gas for the next iteration to
2307                    // compare against.
2308                    self.gas_metering.last_gas_used = interpreter.gas.total_gas_spent();
2309                }
2310            });
2311        }
2312    }
2313
2314    #[cold]
2315    fn meter_gas_end(&mut self, interpreter: &mut Interpreter) {
2316        // Remove recorded gas if we exit frame.
2317        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
2318            && will_exit(interpreter_action)
2319        {
2320            self.gas_metering.paused_frames.pop();
2321        }
2322    }
2323
2324    #[cold]
2325    const fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) {
2326        let mut gas = Gas::new(interpreter.gas.limit());
2327        gas.memory_mut().words_num = interpreter.gas.memory().words_num;
2328        gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost;
2329        interpreter.gas = gas;
2330        self.gas_metering.reset = false;
2331    }
2332
2333    #[cold]
2334    fn meter_gas_check(&mut self, interpreter: &mut Interpreter) {
2335        if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
2336            && will_exit(interpreter_action)
2337        {
2338            // Reset gas if spent is less than refunded.
2339            // This can happen if gas was paused / resumed or reset.
2340            // https://github.com/foundry-rs/foundry/issues/4370
2341            if interpreter.gas.total_gas_spent()
2342                < u64::try_from(interpreter.gas.refunded()).unwrap_or_default()
2343            {
2344                interpreter.gas = Gas::new(interpreter.gas.limit());
2345            }
2346        }
2347    }
2348
2349    /// Applies opcode-level overrides for `BASEFEE`, `GASPRICE` and `BLOBHASH`.
2350    ///
2351    /// Called from `step_end` *after* the opcode has executed and only when the
2352    /// opcode succeeded (the caller checks `instruction_result`). The opcode
2353    /// pushed its (possibly zeroed) result onto the stack; we replace the top
2354    /// of stack with the cheatcode-set override. This is what makes `vm.fee`,
2355    /// `vm.txGasPrice` and `vm.blobhashes` visible to called contracts under
2356    /// `--isolate` / `--gas-report`, where the inner transaction zeroes the
2357    /// real fee fields for fee-accounting purposes.
2358    ///
2359    /// We can't read the just-executed opcode from `interpreter.bytecode.opcode()`
2360    /// here because the PC has already advanced; instead `step` stashes it in
2361    /// `env_overrides.pending_opcode` for us.
2362    #[cold]
2363    fn apply_env_overrides(&mut self, interpreter: &mut Interpreter, fork_id: Option<U256>) {
2364        let Some(env_overrides) = self.env_overrides.get_mut(&fork_id) else { return };
2365        let Some(opcode) = env_overrides.pending_opcode.take() else { return };
2366        match opcode {
2367            op::BASEFEE => {
2368                if let Some(basefee) = env_overrides.basefee {
2369                    // BASEFEE pushed one value; replace it.
2370                    Self::replace_top_of_stack(interpreter, U256::from(basefee));
2371                }
2372            }
2373            op::GASPRICE => {
2374                if let Some(gas_price) = env_overrides.gas_price {
2375                    // GASPRICE pushed one value; replace it.
2376                    Self::replace_top_of_stack(interpreter, U256::from(gas_price));
2377                }
2378            }
2379            op::BLOBHASH => {
2380                let blob_hashes = env_overrides.blob_hashes.clone();
2381                let blobhash_index = env_overrides.pending_blobhash_index.take();
2382                if let Some(ref blob_hashes) = blob_hashes
2383                    && let Some(index) = blobhash_index
2384                {
2385                    // BLOBHASH popped the index and pushed the hash; replace
2386                    // the hash with our override (zero for out-of-range, per EIP-4844).
2387                    let hash = blob_hashes.get(index as usize).copied().unwrap_or_default();
2388                    Self::replace_top_of_stack(interpreter, hash.into());
2389                }
2390            }
2391            _ => {}
2392        }
2393    }
2394
2395    /// Replaces the top of the interpreter stack with `value`.
2396    ///
2397    /// The caller must only invoke this after a successful opcode that pushed
2398    /// a value onto the stack; the `pop()` is therefore expected to succeed.
2399    /// If it does not (e.g. because of a bug in the caller's success gating)
2400    /// we bail out instead of pushing on top of an unexpected stack, which
2401    /// would silently grow the stack and corrupt the frame.
2402    fn replace_top_of_stack(interpreter: &mut Interpreter, value: U256) {
2403        if interpreter.stack.pop().is_err() {
2404            debug_assert!(false, "env override expected opcode result on stack");
2405            return;
2406        }
2407        let _ = interpreter.stack.push(value);
2408    }
2409
2410    /// Generates or copies arbitrary values for storage slots.
2411    /// Invoked in inspector `step_end` (when the current opcode is not executed), if current opcode
2412    /// to execute is `SLOAD` and storage slot is cold.
2413    /// Ensures that in next step (when `SLOAD` opcode is executed) an arbitrary value is returned:
2414    /// - copies the existing arbitrary storage value (or the new generated one if no value in
2415    ///   cache) from mapped source address to the target address.
2416    /// - generates arbitrary value and saves it in target address storage.
2417    #[cold]
2418    fn arbitrary_storage_end(
2419        &mut self,
2420        interpreter: &mut Interpreter,
2421        ecx: &mut FoundryContextFor<'_, FEN>,
2422    ) {
2423        let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD {
2424            (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address)
2425        } else {
2426            return;
2427        };
2428
2429        let Some(value) = ecx.sload(target_address, key) else {
2430            return;
2431        };
2432
2433        if (value.is_cold && value.data.is_zero())
2434            || self.should_overwrite_arbitrary_storage(&target_address, key)
2435        {
2436            if self.has_arbitrary_storage(&target_address) {
2437                let arbitrary_value = self.rng().random();
2438                self.arbitrary_storage.as_mut().unwrap().save(
2439                    ecx,
2440                    target_address,
2441                    key,
2442                    arbitrary_value,
2443                );
2444            } else if self.is_arbitrary_storage_copy(&target_address) {
2445                let arbitrary_value = self.rng().random();
2446                self.arbitrary_storage.as_mut().unwrap().copy(
2447                    ecx,
2448                    target_address,
2449                    key,
2450                    arbitrary_value,
2451                );
2452            }
2453        }
2454    }
2455
2456    /// Records storage slots reads and writes.
2457    #[cold]
2458    fn record_accesses(&mut self, interpreter: &mut Interpreter) {
2459        let access = &mut self.accesses;
2460        match interpreter.bytecode.opcode() {
2461            op::SLOAD => {
2462                let key = try_or_return!(interpreter.stack.peek(0));
2463                access.record_read(interpreter.input.target_address, key);
2464            }
2465            op::SSTORE => {
2466                let key = try_or_return!(interpreter.stack.peek(0));
2467                access.record_write(interpreter.input.target_address, key);
2468            }
2469            _ => {}
2470        }
2471    }
2472
2473    #[cold]
2474    fn record_state_diffs(
2475        &mut self,
2476        interpreter: &mut Interpreter,
2477        ecx: &mut FoundryContextFor<'_, FEN>,
2478    ) {
2479        let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return };
2480        match interpreter.bytecode.opcode() {
2481            op::SELFDESTRUCT => {
2482                // Ensure that we're not selfdestructing a context recording was initiated on
2483                let Some(last) = account_accesses.last_mut() else { return };
2484
2485                // get previous balance, nonce and initialized status of the target account
2486                let target = try_or_return!(interpreter.stack.peek(0));
2487                let target = Address::from_word(B256::from(target));
2488                let (initialized, old_balance, old_nonce) = ecx
2489                    .journal_mut()
2490                    .load_account(target)
2491                    .map(|account| {
2492                        (
2493                            account.data.info.exists(),
2494                            account.data.info.balance,
2495                            account.data.info.nonce,
2496                        )
2497                    })
2498                    .unwrap_or_default();
2499
2500                // load balance of this account
2501                let value = ecx
2502                    .balance(interpreter.input.target_address)
2503                    .map(|b| b.data)
2504                    .unwrap_or(U256::ZERO);
2505
2506                // register access for the target account
2507                last.push(crate::Vm::AccountAccess {
2508                    chainInfo: crate::Vm::ChainInfo {
2509                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
2510                        chainId: U256::from(ecx.cfg().chain_id()),
2511                    },
2512                    accessor: interpreter.input.target_address,
2513                    account: target,
2514                    kind: crate::Vm::AccountAccessKind::SelfDestruct,
2515                    initialized,
2516                    oldBalance: old_balance,
2517                    newBalance: old_balance + value,
2518                    oldNonce: old_nonce,
2519                    newNonce: old_nonce, // nonce doesn't change on selfdestruct
2520                    value,
2521                    data: Bytes::new(),
2522                    reverted: false,
2523                    deployedCode: Bytes::new(),
2524                    storageAccesses: vec![],
2525                    depth: ecx
2526                        .journal()
2527                        .depth()
2528                        .try_into()
2529                        .expect("journaled state depth exceeds u64"),
2530                });
2531            }
2532
2533            op::SLOAD => {
2534                let Some(last) = account_accesses.last_mut() else { return };
2535
2536                let key = try_or_return!(interpreter.stack.peek(0));
2537                let address = interpreter.input.target_address;
2538
2539                // Try to include present value for informational purposes, otherwise assume
2540                // it's not set (zero value)
2541                // Try to load the account and the slot's present value
2542                let present_value = if ecx.journal_mut().load_account(address).is_ok()
2543                    && let Some(previous) = ecx.sload(address, key)
2544                {
2545                    previous.data
2546                } else {
2547                    U256::ZERO
2548                };
2549                let access = crate::Vm::StorageAccess {
2550                    account: interpreter.input.target_address,
2551                    slot: key.into(),
2552                    isWrite: false,
2553                    previousValue: present_value.into(),
2554                    newValue: present_value.into(),
2555                    reverted: false,
2556                };
2557                let curr_depth =
2558                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2559                append_storage_access(last, access, curr_depth);
2560            }
2561            op::SSTORE => {
2562                let Some(last) = account_accesses.last_mut() else { return };
2563
2564                let key = try_or_return!(interpreter.stack.peek(0));
2565                let value = try_or_return!(interpreter.stack.peek(1));
2566                let address = interpreter.input.target_address;
2567                // Try to load the account and the slot's previous value, otherwise, assume it's
2568                // not set (zero value)
2569                let previous_value = if ecx.journal_mut().load_account(address).is_ok()
2570                    && let Some(previous) = ecx.sload(address, key)
2571                {
2572                    previous.data
2573                } else {
2574                    U256::ZERO
2575                };
2576
2577                let access = crate::Vm::StorageAccess {
2578                    account: address,
2579                    slot: key.into(),
2580                    isWrite: true,
2581                    previousValue: previous_value.into(),
2582                    newValue: value.into(),
2583                    reverted: false,
2584                };
2585                let curr_depth =
2586                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2587                append_storage_access(last, access, curr_depth);
2588            }
2589
2590            // Record account accesses via the EXT family of opcodes
2591            op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => {
2592                let kind = match interpreter.bytecode.opcode() {
2593                    op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy,
2594                    op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize,
2595                    op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash,
2596                    op::BALANCE => crate::Vm::AccountAccessKind::Balance,
2597                    _ => unreachable!(),
2598                };
2599                let address =
2600                    Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0))));
2601                let (initialized, balance, nonce) =
2602                    if let Ok(acc) = ecx.journal_mut().load_account(address) {
2603                        (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce)
2604                    } else {
2605                        (false, U256::ZERO, 0)
2606                    };
2607                let curr_depth =
2608                    ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2609                let account_access = crate::Vm::AccountAccess {
2610                    chainInfo: crate::Vm::ChainInfo {
2611                        forkId: ecx.db().active_fork_id().unwrap_or_default(),
2612                        chainId: U256::from(ecx.cfg().chain_id()),
2613                    },
2614                    accessor: interpreter.input.target_address,
2615                    account: address,
2616                    kind,
2617                    initialized,
2618                    oldBalance: balance,
2619                    newBalance: balance,
2620                    oldNonce: nonce,
2621                    newNonce: nonce, // EXT* operations don't change nonce
2622                    value: U256::ZERO,
2623                    data: Bytes::new(),
2624                    reverted: false,
2625                    deployedCode: Bytes::new(),
2626                    storageAccesses: vec![],
2627                    depth: curr_depth,
2628                };
2629                // Record the EXT* call as an account access at the current depth
2630                // (future storage accesses will be recorded in a new "Resume" context)
2631                if let Some(last) = account_accesses.last_mut() {
2632                    last.push(account_access);
2633                } else {
2634                    account_accesses.push(vec![account_access]);
2635                }
2636            }
2637            _ => {}
2638        }
2639    }
2640
2641    /// Checks to see if the current opcode can either mutate directly or expand memory.
2642    ///
2643    /// If the opcode at the current program counter is a match, check if the modified memory lies
2644    /// within the allowed ranges. If not, revert and fail the test.
2645    #[cold]
2646    fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) {
2647        let Some(ranges) = self.allowed_mem_writes.get(&depth) else {
2648            return;
2649        };
2650
2651        // The `mem_opcode_match` macro is used to match the current opcode against a list of
2652        // opcodes that can mutate memory (either directly or expansion via reading). If the
2653        // opcode is a match, the memory offsets that are being written to are checked to be
2654        // within the allowed ranges. If not, the test is failed and the transaction is
2655        // reverted. For all opcodes that can mutate memory aside from MSTORE,
2656        // MSTORE8, and MLOAD, the size and destination offset are on the stack, and
2657        // the macro expands all of these cases. For MSTORE, MSTORE8, and MLOAD, the
2658        // size of the memory write is implicit, so these cases are hard-coded.
2659        macro_rules! mem_opcode_match {
2660            ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => {
2661                match interpreter.bytecode.opcode() {
2662                    ////////////////////////////////////////////////////////////////
2663                    //    OPERATIONS THAT CAN EXPAND/MUTATE MEMORY BY WRITING     //
2664                    ////////////////////////////////////////////////////////////////
2665
2666                    op::MSTORE => {
2667                        // The offset of the mstore operation is at the top of the stack.
2668                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2669
2670                        // If none of the allowed ranges contain [offset, offset + 32), memory has been
2671                        // unexpectedly mutated.
2672                        if !ranges.iter().any(|range| {
2673                            range.contains(&offset) && range.contains(&(offset + 31))
2674                        }) {
2675                            // SPECIAL CASE: When the compiler attempts to store the selector for
2676                            // `stopExpectSafeMemory`, this is allowed. It will do so at the current free memory
2677                            // pointer, which could have been updated to the exclusive upper bound during
2678                            // execution.
2679                            let value = try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>();
2680                            if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
2681                                return
2682                            }
2683
2684                            disallowed_mem_write(offset, 32, interpreter, ranges);
2685                            return
2686                        }
2687                    }
2688                    op::MSTORE8 => {
2689                        // The offset of the mstore8 operation is at the top of the stack.
2690                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2691
2692                        // If none of the allowed ranges contain the offset, memory has been
2693                        // unexpectedly mutated.
2694                        if !ranges.iter().any(|range| range.contains(&offset)) {
2695                            disallowed_mem_write(offset, 1, interpreter, ranges);
2696                            return
2697                        }
2698                    }
2699
2700                    ////////////////////////////////////////////////////////////////
2701                    //        OPERATIONS THAT CAN EXPAND MEMORY BY READING        //
2702                    ////////////////////////////////////////////////////////////////
2703
2704                    op::MLOAD => {
2705                        // The offset of the mload operation is at the top of the stack
2706                        let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2707
2708                        // If the offset being loaded is >= than the memory size, the
2709                        // memory is being expanded. If none of the allowed ranges contain
2710                        // [offset, offset + 32), memory has been unexpectedly mutated.
2711                        if offset >= interpreter.memory.size() as u64 && !ranges.iter().any(|range| {
2712                            range.contains(&offset) && range.contains(&(offset + 31))
2713                        }) {
2714                            disallowed_mem_write(offset, 32, interpreter, ranges);
2715                            return
2716                        }
2717                    }
2718
2719                    ////////////////////////////////////////////////////////////////
2720                    //          OPERATIONS WITH OFFSET AND SIZE ON STACK          //
2721                    ////////////////////////////////////////////////////////////////
2722
2723                    op::CALL => {
2724                        // The destination offset of the operation is the fifth element on the stack.
2725                        let dest_offset = try_or_return!(interpreter.stack.peek(5)).saturating_to::<u64>();
2726
2727                        // The size of the data that will be copied is the sixth element on the stack.
2728                        let size = try_or_return!(interpreter.stack.peek(6)).saturating_to::<u64>();
2729
2730                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
2731                        // memory outside of the expected ranges has been touched. If the opcode
2732                        // only reads from memory, this is okay as long as the memory is not expanded.
2733                        let fail_cond = !ranges.iter().any(|range| {
2734                            range.contains(&dest_offset) &&
2735                                range.contains(&(dest_offset + size.saturating_sub(1)))
2736                        });
2737
2738                        // If the failure condition is met, set the output buffer to a revert string
2739                        // that gives information about the allowed ranges and revert.
2740                        if fail_cond {
2741                            // SPECIAL CASE: When a call to `stopExpectSafeMemory` is performed, this is allowed.
2742                            // It allocated calldata at the current free memory pointer, and will attempt to read
2743                            // from this memory region to perform the call.
2744                            let to = Address::from_word(try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>().into());
2745                            if to == CHEATCODE_ADDRESS {
2746                                let args_offset = try_or_return!(interpreter.stack.peek(3)).saturating_to::<usize>();
2747                                let args_size = try_or_return!(interpreter.stack.peek(4)).saturating_to::<usize>();
2748                                let memory_word = interpreter.memory.slice_len(args_offset, args_size);
2749                                if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
2750                                    return
2751                                }
2752                            }
2753
2754                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
2755                            return
2756                        }
2757                    }
2758
2759                    $(op::$opcode => {
2760                        // The destination offset of the operation.
2761                        let dest_offset = try_or_return!(interpreter.stack.peek($offset_depth)).saturating_to::<u64>();
2762
2763                        // The size of the data that will be copied.
2764                        let size = try_or_return!(interpreter.stack.peek($size_depth)).saturating_to::<u64>();
2765
2766                        // If none of the allowed ranges contain [dest_offset, dest_offset + size),
2767                        // memory outside of the expected ranges has been touched. If the opcode
2768                        // only reads from memory, this is okay as long as the memory is not expanded.
2769                        let fail_cond = !ranges.iter().any(|range| {
2770                                range.contains(&dest_offset) &&
2771                                    range.contains(&(dest_offset + size.saturating_sub(1)))
2772                            }) && ($writes ||
2773                                [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| {
2774                                    offset >= interpreter.memory.size() as u64
2775                                })
2776                            );
2777
2778                        // If the failure condition is met, set the output buffer to a revert string
2779                        // that gives information about the allowed ranges and revert.
2780                        if fail_cond {
2781                            disallowed_mem_write(dest_offset, size, interpreter, ranges);
2782                            return
2783                        }
2784                    })*
2785
2786                    _ => {}
2787                }
2788            }
2789        }
2790
2791        // Check if the current opcode can write to memory, and if so, check if the memory
2792        // being written to is registered as safe to modify.
2793        mem_opcode_match!(
2794            (CALLDATACOPY, 0, 2, true),
2795            (CODECOPY, 0, 2, true),
2796            (RETURNDATACOPY, 0, 2, true),
2797            (EXTCODECOPY, 1, 3, true),
2798            (CALLCODE, 5, 6, true),
2799            (STATICCALL, 4, 5, true),
2800            (DELEGATECALL, 4, 5, true),
2801            (KECCAK256, 0, 1, false),
2802            (LOG0, 0, 1, false),
2803            (LOG1, 0, 1, false),
2804            (LOG2, 0, 1, false),
2805            (LOG3, 0, 1, false),
2806            (LOG4, 0, 1, false),
2807            (CREATE, 1, 2, false),
2808            (CREATE2, 1, 2, false),
2809            (RETURN, 0, 1, false),
2810            (REVERT, 0, 1, false),
2811        );
2812    }
2813
2814    #[cold]
2815    fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
2816        match interpreter.bytecode.opcode() {
2817            op::CREATE2 => self.dynamic_gas_limit = true,
2818            op::CALL => {
2819                // If first element of the stack is close to current remaining gas then assume
2820                // dynamic gas limit.
2821                self.dynamic_gas_limit =
2822                    try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100
2823            }
2824            _ => self.dynamic_gas_limit = false,
2825        }
2826    }
2827}
2828
2829/// Helper that expands memory, stores a revert string pertaining to a disallowed memory write,
2830/// and sets the return range to the revert string's location in memory.
2831///
2832/// This will set the interpreter's next action to a return with the revert string as the output.
2833/// And trigger a revert.
2834fn disallowed_mem_write(
2835    dest_offset: u64,
2836    size: u64,
2837    interpreter: &mut Interpreter,
2838    ranges: &[Range<u64>],
2839) {
2840    let revert_string = format!(
2841        "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}",
2842        dest_offset,
2843        size,
2844        ranges.iter().map(|r| format!("[0x{:02X}, 0x{:02X})", r.start, r.end)).join(" U ")
2845    );
2846
2847    interpreter.bytecode.set_action(InterpreterAction::new_return(
2848        InstructionResult::Revert,
2849        Bytes::from(revert_string.into_bytes()),
2850        interpreter.gas,
2851    ));
2852}
2853
2854/// Returns true if the kind of account access is a call.
2855const fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool {
2856    matches!(
2857        kind,
2858        crate::Vm::AccountAccessKind::Call
2859            | crate::Vm::AccountAccessKind::StaticCall
2860            | crate::Vm::AccountAccessKind::CallCode
2861            | crate::Vm::AccountAccessKind::DelegateCall
2862    )
2863}
2864
2865/// Records a log into the recorded logs vector, if it exists.
2866fn record_logs(recorded_logs: &mut Option<Vec<Vm::Log>>, log: &Log) {
2867    if let Some(storage_recorded_logs) = recorded_logs {
2868        storage_recorded_logs.push(Vm::Log {
2869            topics: log.data.topics().to_vec(),
2870            data: log.data.data.clone(),
2871            emitter: log.address,
2872        });
2873    }
2874}
2875
2876/// Appends an AccountAccess that resumes the recording of the current context.
2877fn append_storage_access(
2878    last: &mut Vec<AccountAccess>,
2879    storage_access: crate::Vm::StorageAccess,
2880    storage_depth: u64,
2881) {
2882    // Assert that there's an existing record for the current context.
2883    if !last.is_empty() && last.first().unwrap().depth < storage_depth {
2884        // Three cases to consider:
2885        // 1. If there hasn't been a context switch since the start of this context, then add the
2886        //    storage access to the current context record.
2887        // 2. If there's an existing Resume record, then add the storage access to it.
2888        // 3. Otherwise, create a new Resume record based on the current context.
2889        if last.len() == 1 {
2890            last.first_mut().unwrap().storageAccesses.push(storage_access);
2891        } else {
2892            let last_record = last.last_mut().unwrap();
2893            if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 {
2894                last_record.storageAccesses.push(storage_access);
2895            } else {
2896                let entry = last.first().unwrap();
2897                let resume_record = crate::Vm::AccountAccess {
2898                    chainInfo: crate::Vm::ChainInfo {
2899                        forkId: entry.chainInfo.forkId,
2900                        chainId: entry.chainInfo.chainId,
2901                    },
2902                    accessor: entry.accessor,
2903                    account: entry.account,
2904                    kind: crate::Vm::AccountAccessKind::Resume,
2905                    initialized: entry.initialized,
2906                    storageAccesses: vec![storage_access],
2907                    reverted: entry.reverted,
2908                    // The remaining fields are defaults
2909                    oldBalance: U256::ZERO,
2910                    newBalance: U256::ZERO,
2911                    oldNonce: 0,
2912                    newNonce: 0,
2913                    value: U256::ZERO,
2914                    data: Bytes::new(),
2915                    deployedCode: Bytes::new(),
2916                    depth: entry.depth,
2917                };
2918                last.push(resume_record);
2919            }
2920        }
2921    }
2922}
2923
2924/// Returns the [`spec::Cheatcode`] definition for a given [`spec::CheatcodeDef`] implementor.
2925const fn cheatcode_of<T: spec::CheatcodeDef>(_: &T) -> &'static spec::Cheatcode<'static> {
2926    T::CHEATCODE
2927}
2928
2929fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str {
2930    cheat.func.signature.split('(').next().unwrap()
2931}
2932
2933const fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str {
2934    cheat.func.id
2935}
2936
2937const fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str {
2938    cheat.func.signature
2939}
2940
2941/// Dispatches the cheatcode call to the appropriate function.
2942fn apply_dispatch<FEN: FoundryEvmNetwork>(
2943    calls: &Vm::VmCalls,
2944    ccx: &mut CheatsCtxt<'_, '_, FEN>,
2945    executor: &mut dyn CheatcodesExecutor<FEN>,
2946) -> Result {
2947    // Extract metadata for logging/deprecation via CheatcodeDef.
2948    macro_rules! get_cheatcode {
2949        ($($variant:ident),*) => {
2950            match calls {
2951                $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)*
2952            }
2953        };
2954    }
2955    let cheat = vm_calls!(get_cheatcode);
2956
2957    let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered();
2958    trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying");
2959
2960    if let spec::Status::Deprecated(replacement) = cheat.status {
2961        ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement);
2962    }
2963
2964    // Monomorphized dispatch: calls apply_full directly, no trait objects.
2965    macro_rules! dispatch {
2966        ($($variant:ident),*) => {
2967            match calls {
2968                $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)*
2969            }
2970        };
2971    }
2972    let mut result = vm_calls!(dispatch);
2973
2974    // Format the error message to include the cheatcode name.
2975    if let Err(e) = &mut result
2976        && e.is_str()
2977    {
2978        let name = cheatcode_name(cheat);
2979        // Skip showing the cheatcode name for:
2980        // - assertions: too verbose, and can already be inferred from the error message
2981        // - `rpcUrl`: forge-std relies on it in `getChainWithUpdatedRpcUrl`
2982        if !name.contains("assert") && name != "rpcUrl" {
2983            *e = fmt_err!("vm.{name}: {e}");
2984        }
2985    }
2986
2987    trace!(
2988        target: "cheatcodes",
2989        return = %match &result {
2990            Ok(b) => hex::encode(b),
2991            Err(e) => e.to_string(),
2992        }
2993    );
2994
2995    result
2996}
2997
2998/// Helper function to check if frame execution will exit.
2999const fn will_exit(action: &InterpreterAction) -> bool {
3000    match action {
3001        InterpreterAction::Return(result) => {
3002            result.result.is_ok_or_revert() || result.result.is_halt()
3003        }
3004        _ => false,
3005    }
3006}
3007
3008#[cfg(test)]
3009mod tests {
3010    use super::*;
3011
3012    fn cheats(flag: bool, broadcast: Option<Broadcast>) -> Cheatcodes {
3013        let config = CheatsConfig { batch_rewrite_creates: flag, ..Default::default() };
3014        let mut cheats = Cheatcodes::new(Arc::new(config));
3015        cheats.broadcast = broadcast;
3016        cheats
3017    }
3018
3019    fn create_inputs() -> CreateInputs {
3020        CreateInputs::new(Address::ZERO, CreateScheme::Create, U256::ZERO, Bytes::new(), 100_000, 0)
3021    }
3022
3023    fn broadcast_at(depth: usize) -> Broadcast {
3024        Broadcast { depth, ..Default::default() }
3025    }
3026
3027    #[test]
3028    fn flag_off_with_broadcast_returns_false() {
3029        let mut cheats = cheats(false, Some(broadcast_at(1)));
3030        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
3031    }
3032
3033    #[test]
3034    fn flag_on_without_broadcast_returns_false() {
3035        let mut cheats = cheats(true, None);
3036        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
3037    }
3038
3039    #[test]
3040    fn flag_on_with_broadcast_depth_mismatch_returns_false() {
3041        let mut cheats = cheats(true, Some(broadcast_at(2)));
3042        assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
3043    }
3044
3045    #[test]
3046    fn flag_on_with_broadcast_depth_match_returns_true() {
3047        let mut cheats = cheats(true, Some(broadcast_at(1)));
3048        assert!(cheats.should_use_create2_factory(1, &create_inputs()));
3049    }
3050}