foundry_cheatcodes/
inspector.rs

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