Skip to main content

foundry_evm/inspectors/
stack.rs

1use super::{
2    Cheatcodes, CheatsConfig, ChiselState, CmpOperands, CustomPrintTracer, EdgeCovConfig,
3    EdgeCovInspector, EdgeCoverage, Fuzzer, LineCoverageCollector, LogCollector, RevertDiagnostic,
4    ScriptExecutionInspector, TempoLabels, TracingInspector,
5};
6use alloy_primitives::{
7    Address, B256, Bytes, Log, TxKind, U256, keccak256,
8    map::{AddressHashMap, AddressMap},
9};
10
11use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, NestedEvmClosure, Wallets};
12use foundry_common::{compile::Analysis, sh_warn};
13use foundry_config::FuzzCorpusConfig;
14use foundry_evm_core::{
15    FoundryBlock, FoundryTransaction, InspectorExt,
16    backend::{DatabaseError, DatabaseExt, JournaledState},
17    constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH,
18    env::FoundryContextExt,
19    evm::{
20        BlockEnvFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, FoundryEvmFactory,
21        FoundryEvmNetwork, SpecFor, TxEnvFor, get_create2_factory_call_inputs, with_cloned_context,
22    },
23};
24use foundry_evm_coverage::HitMaps;
25use foundry_evm_networks::NetworkConfigs;
26use foundry_evm_traces::{SparsedTraceArena, TraceMode};
27use revm::{
28    Inspector,
29    context::{
30        Block, Cfg, ContextTr, JournalTr, Transaction, TransactionType,
31        result::{EVMError, ExecutionResult, Output},
32    },
33    context_interface::CreateScheme,
34    handler::FrameResult,
35    interpreter::{
36        CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas,
37        InstructionResult, Interpreter, InterpreterResult, return_ok,
38    },
39    primitives::KECCAK_EMPTY,
40    state::{Account, AccountStatus},
41};
42use std::{
43    ops::{Deref, DerefMut},
44    sync::Arc,
45};
46
47#[derive(Clone, Debug)]
48#[must_use = "builders do nothing unless you call `build` on them"]
49pub struct InspectorStackBuilder<BLOCK: Clone> {
50    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities.
51    pub analysis: Option<Analysis>,
52    /// The block environment.
53    ///
54    /// Used in the cheatcode handler to overwrite the block environment separately from the
55    /// execution block environment.
56    pub block: Option<BLOCK>,
57    /// The gas price.
58    ///
59    /// Used in the cheatcode handler to overwrite the gas price separately from the gas price
60    /// in the execution environment.
61    pub gas_price: Option<u128>,
62    /// The cheatcodes config.
63    pub cheatcodes: Option<Arc<CheatsConfig>>,
64    /// The fuzzer inspector and its state, if it exists.
65    pub fuzzer: Option<Fuzzer>,
66    /// Whether to enable tracing and revert diagnostics.
67    pub trace_mode: TraceMode,
68    /// Whether logs should be collected.
69    /// - None for no log collection.
70    /// - Some(true) for realtime console.log-ing.
71    /// - Some(false) for log collection.
72    pub logs: Option<bool>,
73    /// Whether line coverage info should be collected.
74    pub line_coverage: Option<bool>,
75    /// Whether to print all opcode traces into the console. Useful for debugging the EVM.
76    pub print: Option<bool>,
77    /// The chisel state inspector.
78    pub chisel_state: Option<usize>,
79    /// Whether to enable call isolation.
80    /// In isolation mode all top-level calls are executed as a separate transaction in a separate
81    /// EVM context, enabling more precise gas accounting and transaction state changes.
82    pub enable_isolation: bool,
83    /// Networks with enabled features.
84    pub networks: NetworkConfigs,
85    /// The wallets to set in the cheatcodes context.
86    pub wallets: Option<Wallets>,
87    /// The CREATE2 deployer address.
88    pub create2_deployer: Address,
89}
90
91impl<BLOCK: Clone> Default for InspectorStackBuilder<BLOCK> {
92    fn default() -> Self {
93        Self {
94            analysis: None,
95            block: None,
96            gas_price: None,
97            cheatcodes: None,
98            fuzzer: None,
99            trace_mode: TraceMode::None,
100            logs: None,
101            line_coverage: None,
102            print: None,
103            chisel_state: None,
104            enable_isolation: false,
105            networks: NetworkConfigs::default(),
106            wallets: None,
107            create2_deployer: Default::default(),
108        }
109    }
110}
111
112impl<BLOCK: Clone> InspectorStackBuilder<BLOCK> {
113    /// Create a new inspector stack builder.
114    #[inline]
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Set the solar compiler instance that grants syntactic and semantic analysis capabilities
120    #[inline]
121    pub fn set_analysis(mut self, analysis: Analysis) -> Self {
122        self.analysis = Some(analysis);
123        self
124    }
125
126    /// Set the block environment.
127    #[inline]
128    pub fn block(mut self, block: BLOCK) -> Self {
129        self.block = Some(block);
130        self
131    }
132
133    /// Set the gas price.
134    #[inline]
135    pub const fn gas_price(mut self, gas_price: u128) -> Self {
136        self.gas_price = Some(gas_price);
137        self
138    }
139
140    /// Enable cheatcodes with the given config.
141    #[inline]
142    pub fn cheatcodes(mut self, config: Arc<CheatsConfig>) -> Self {
143        self.cheatcodes = Some(config);
144        self
145    }
146
147    /// Set the wallets.
148    #[inline]
149    pub fn wallets(mut self, wallets: Wallets) -> Self {
150        self.wallets = Some(wallets);
151        self
152    }
153
154    /// Set the fuzzer inspector.
155    #[inline]
156    pub fn fuzzer(mut self, fuzzer: Fuzzer) -> Self {
157        self.fuzzer = Some(fuzzer);
158        self
159    }
160
161    /// Set the Chisel inspector.
162    #[inline]
163    pub const fn chisel_state(mut self, final_pc: usize) -> Self {
164        self.chisel_state = Some(final_pc);
165        self
166    }
167
168    /// Set the log collector, and whether to print the logs directly to stdout.
169    #[inline]
170    pub const fn logs(mut self, live_logs: bool) -> Self {
171        self.logs = Some(live_logs);
172        self
173    }
174
175    /// Set whether to collect line coverage information.
176    #[inline]
177    pub const fn line_coverage(mut self, yes: bool) -> Self {
178        self.line_coverage = Some(yes);
179        self
180    }
181
182    /// Set whether to enable the trace printer.
183    #[inline]
184    pub const fn print(mut self, yes: bool) -> Self {
185        self.print = Some(yes);
186        self
187    }
188
189    /// Set whether to enable the tracer.
190    /// Revert diagnostic inspector is activated when `mode != TraceMode::None`
191    #[inline]
192    pub fn trace_mode(mut self, mode: TraceMode) -> Self {
193        if self.trace_mode < mode {
194            self.trace_mode = mode
195        }
196        self
197    }
198
199    /// Set whether to enable the call isolation.
200    /// For description of call isolation, see [`InspectorStack::enable_isolation`].
201    #[inline]
202    pub const fn enable_isolation(mut self, yes: bool) -> Self {
203        self.enable_isolation = yes;
204        self
205    }
206
207    /// Set networks with enabled features.
208    #[inline]
209    pub const fn networks(mut self, networks: NetworkConfigs) -> Self {
210        self.networks = networks;
211        self
212    }
213
214    #[inline]
215    pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self {
216        self.create2_deployer = create2_deployer;
217        self
218    }
219
220    /// Builds the stack of inspectors to use when transacting/committing on the EVM.
221    pub fn build<FEN: FoundryEvmNetwork<EvmFactory: FoundryEvmFactory<BlockEnv = BLOCK>>>(
222        self,
223    ) -> InspectorStack<FEN> {
224        let Self {
225            analysis,
226            block,
227            gas_price,
228            cheatcodes,
229            fuzzer,
230            trace_mode,
231            logs,
232            line_coverage,
233            print,
234            chisel_state,
235            enable_isolation,
236            networks,
237            wallets,
238            create2_deployer,
239        } = self;
240        let mut stack = InspectorStack::new();
241
242        // inspectors
243        if let Some(config) = cheatcodes {
244            let mut cheatcodes = Cheatcodes::new(config);
245            // Set analysis capabilities if they are provided
246            if let Some(analysis) = analysis {
247                stack.set_analysis(analysis.clone());
248                cheatcodes.set_analysis(CheatcodeAnalysis::new(analysis));
249            }
250            // Set wallets if they are provided
251            if let Some(wallets) = wallets {
252                cheatcodes.set_wallets(wallets);
253            }
254            stack.set_cheatcodes(cheatcodes);
255        }
256
257        if let Some(fuzzer) = fuzzer {
258            stack.set_fuzzer(fuzzer);
259        }
260        if let Some(chisel_state) = chisel_state {
261            stack.set_chisel(chisel_state);
262        }
263        stack.collect_line_coverage(line_coverage.unwrap_or(false));
264        stack.collect_logs(logs);
265        stack.print(print.unwrap_or(false));
266        stack.tracing(trace_mode);
267
268        stack.enable_isolation(enable_isolation);
269        stack.networks(networks);
270        stack.set_create2_deployer(create2_deployer);
271
272        if networks.is_tempo() {
273            stack.inner.tempo_labels = Some(Box::default());
274        }
275
276        // environment, must come after all of the inspectors
277        if let Some(block) = block {
278            stack.set_block(block);
279        }
280        if let Some(gas_price) = gas_price {
281            stack.set_gas_price(gas_price);
282        }
283
284        stack
285    }
286}
287
288/// Helper macro to call the same method on multiple inspectors without resorting to dynamic
289/// dispatch.
290#[macro_export]
291macro_rules! call_inspectors {
292    ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {
293        $(
294            if let Some($id) = $inspector {
295                $crate::utils::cold_path();
296                $body;
297            }
298        )+
299    };
300    (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {{
301        $(
302            if let Some($id) = $inspector {
303                $crate::utils::cold_path();
304                if let Some(result) = $body {
305                    return result;
306                }
307            }
308        )+
309    }};
310}
311
312/// The collected results of [`InspectorStack`].
313pub struct InspectorData<FEN: FoundryEvmNetwork> {
314    pub logs: Vec<Log>,
315    pub labels: AddressHashMap<String>,
316    pub traces: Option<SparsedTraceArena>,
317    pub line_coverage: Option<HitMaps>,
318    pub edge_coverage: Option<EdgeCoverage>,
319    pub evm_cmp_values: Option<Vec<CmpOperands>>,
320    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
321    pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
322    pub reverter: Option<Address>,
323}
324
325/// Contains data about the state of outer/main EVM which created and invoked the inner EVM context.
326/// Used to adjust EVM state while in inner context.
327///
328/// We need this to avoid breaking changes due to EVM behavior differences in isolated vs
329/// non-isolated mode. For descriptions and workarounds for those changes see: <https://github.com/foundry-rs/foundry/pull/7186#issuecomment-1959102195>
330#[derive(Debug, Clone)]
331pub struct InnerContextData {
332    /// Origin of the transaction in the outer EVM context.
333    original_origin: Address,
334}
335
336/// An inspector that calls multiple inspectors in sequence.
337///
338/// If a call to an inspector returns a value (indicating a stop or revert) the remaining inspectors
339/// are not called.
340///
341/// Stack is divided into [Cheatcodes] and `InspectorStackInner`. This is done to allow assembling
342/// `InspectorStackRefMut` inside [Cheatcodes] to allow usage of it as [revm::Inspector]. This gives
343/// us ability to create and execute separate EVM frames from inside cheatcodes while still having
344/// access to entire stack of inspectors and correctly handling traces, logs, debugging info
345/// collection, etc.
346#[derive(Clone, Debug)]
347pub struct InspectorStack<FEN: FoundryEvmNetwork = EthEvmNetwork> {
348    #[allow(clippy::type_complexity)]
349    pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
350    pub inner: InspectorStackInner,
351}
352
353/// All used inpectors besides [Cheatcodes].
354///
355/// See [`InspectorStack`].
356#[derive(Default, Clone, Debug)]
357pub struct InspectorStackInner {
358    /// Solar compiler instance, to grant syntactic and semantic analysis capabilities.
359    pub analysis: Option<Analysis>,
360
361    // Inspectors.
362    // These are boxed to reduce the size of the struct and slightly improve performance of the
363    // `if let Some` checks.
364    pub chisel_state: Option<Box<ChiselState>>,
365    pub edge_coverage: Option<Box<EdgeCovInspector>>,
366    pub fuzzer: Option<Box<Fuzzer>>,
367    pub line_coverage: Option<Box<LineCoverageCollector>>,
368    pub log_collector: Option<Box<LogCollector>>,
369    pub printer: Option<Box<CustomPrintTracer>>,
370    pub revert_diag: Option<Box<RevertDiagnostic>>,
371    pub script_execution_inspector: Option<Box<ScriptExecutionInspector>>,
372    pub tempo_labels: Option<Box<TempoLabels>>,
373    pub tracer: Option<Box<TracingInspector>>,
374
375    // FoundryInspectorExt and other internal data.
376    /// Whether to collect sancov edge coverage from instrumented native crates.
377    pub sancov_edges: bool,
378    /// Whether to capture sancov trace-cmp operands for dictionary injection.
379    pub sancov_trace_cmp: bool,
380    pub enable_isolation: bool,
381    pub networks: NetworkConfigs,
382    pub create2_deployer: Address,
383    /// Flag marking if we are in the inner EVM context.
384    pub in_inner_context: bool,
385    pub inner_context_data: Option<InnerContextData>,
386    pub top_frame_journal: AddressMap<Account>,
387    /// Address that reverted the call, if any.
388    pub reverter: Option<Address>,
389    /// LIFO stack tracking CREATE2 frames that were redirected to the CREATE2 factory.
390    /// Each entry records the journal depth at which the redirect occurred.
391    pub pending_create2_redirects: Vec<usize>,
392    /// Pending CREATE2 deployer validation error, deferred from `frame_start` to `create` so
393    /// it goes through the normal inspector lifecycle (tracing, etc.).
394    pub pending_create2_error: Option<CreateOutcome>,
395    /// Counter for CREATE2 salt in `--batch` CREATE rewrites.
396    pub batch_create_counter: u64,
397    /// Whether the one-shot `--batch` rewrite warning has already been emitted.
398    pub batch_rewrite_warned: bool,
399    /// Per-inspector random seed mixed into `--batch` CREATE2 salts, ensuring re-runs
400    /// at identical on-chain state still produce distinct salts. Lazily initialized.
401    pub batch_rewrite_process_salt: Option<u64>,
402}
403
404/// Struct keeping mutable references to both parts of [InspectorStack] and implementing
405/// [revm::Inspector]. This struct can be obtained via [InspectorStack::as_mut].
406pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> {
407    pub cheatcodes: Option<&'a mut Cheatcodes<FEN>>,
408    pub inner: &'a mut InspectorStackInner,
409}
410
411impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for InspectorStackInner {
412    fn with_nested_evm(
413        &mut self,
414        cheats: &mut Cheatcodes<FEN>,
415        ecx: &mut FoundryContextFor<'_, FEN>,
416        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
417    ) -> Result<(), EVMError<DatabaseError>> {
418        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
419        with_cloned_context(ecx, |db, evm_env, journal_inner| {
420            let mut evm =
421                FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector);
422            *evm.journal_inner_mut() = journal_inner;
423            f(&mut *evm)?;
424            let sub_inner = evm.journal_inner_mut().clone();
425            let sub_evm_env = evm.to_evm_env();
426            Ok((sub_evm_env, sub_inner))
427        })
428    }
429
430    fn with_fresh_nested_evm(
431        &mut self,
432        cheats: &mut Cheatcodes<FEN>,
433        db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
434        evm_env: EvmEnvFor<FEN>,
435        f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
436    ) -> Result<EvmEnvFor<FEN>, EVMError<DatabaseError>> {
437        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
438        let mut evm =
439            FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector);
440        f(&mut *evm)?;
441        Ok(evm.to_evm_env())
442    }
443
444    fn transact_on_db(
445        &mut self,
446        cheats: &mut Cheatcodes<FEN>,
447        ecx: &mut FoundryContextFor<'_, FEN>,
448        fork_id: Option<U256>,
449        transaction: B256,
450    ) -> eyre::Result<()> {
451        let evm_env = ecx.evm_clone();
452        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
453        let (db, inner) = ecx.db_journal_inner_mut();
454        db.transact(fork_id, transaction, evm_env, inner, &mut inspector)
455    }
456
457    fn transact_from_tx_on_db(
458        &mut self,
459        cheats: &mut Cheatcodes<FEN>,
460        ecx: &mut FoundryContextFor<'_, FEN>,
461        tx_env: TxEnvFor<FEN>,
462    ) -> eyre::Result<()> {
463        let evm_env = ecx.evm_clone();
464        let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
465        let (db, inner) = ecx.db_journal_inner_mut();
466        db.transact_from_tx(tx_env, evm_env, inner, &mut inspector)
467    }
468
469    fn console_log(&mut self, msg: &str) {
470        if let Some(ref mut collector) = self.log_collector {
471            InspectorExt::console_log(&mut **collector, msg);
472        }
473    }
474
475    fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
476        self.tracer.as_deref_mut()
477    }
478
479    fn set_in_inner_context(&mut self, enabled: bool, original_origin: Option<Address>) {
480        self.in_inner_context = enabled;
481        self.inner_context_data = enabled.then(|| InnerContextData {
482            original_origin: original_origin.expect("origin required when enabling inner ctx"),
483        });
484    }
485}
486
487impl<FEN: FoundryEvmNetwork> Default for InspectorStack<FEN> {
488    fn default() -> Self {
489        Self::new()
490    }
491}
492
493impl<FEN: FoundryEvmNetwork> InspectorStack<FEN> {
494    /// Creates a new inspector stack.
495    ///
496    /// Note that the stack is empty by default, and you must add inspectors to it.
497    /// This is done by calling the `set_*` methods on the stack directly, or by building the stack
498    /// with [`InspectorStack`].
499    #[inline]
500    pub fn new() -> Self {
501        Self { cheatcodes: None, inner: InspectorStackInner::default() }
502    }
503
504    /// Set the solar compiler instance.
505    #[inline]
506    pub fn set_analysis(&mut self, analysis: Analysis) {
507        self.analysis = Some(analysis);
508    }
509
510    /// Sets the block for the relevant inspectors.
511    #[inline]
512    pub fn set_block(&mut self, block: BlockEnvFor<FEN>) {
513        if let Some(cheatcodes) = &mut self.cheatcodes {
514            cheatcodes.block = Some(block);
515        }
516    }
517
518    /// Sets the gas price for the relevant inspectors.
519    #[inline]
520    pub fn set_gas_price(&mut self, gas_price: u128) {
521        if let Some(cheatcodes) = &mut self.cheatcodes {
522            cheatcodes.gas_price = Some(gas_price);
523        }
524    }
525
526    /// Set the cheatcodes inspector.
527    #[inline]
528    pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes<FEN>) {
529        self.cheatcodes = Some(cheatcodes.into());
530    }
531
532    /// Set the fuzzer inspector.
533    #[inline]
534    pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) {
535        self.fuzzer = Some(fuzzer.into());
536    }
537
538    /// Set the Chisel inspector.
539    #[inline]
540    pub fn set_chisel(&mut self, final_pc: usize) {
541        self.chisel_state = Some(ChiselState::new(final_pc).into());
542    }
543
544    /// Set whether to enable the line coverage collector.
545    #[inline]
546    pub fn collect_line_coverage(&mut self, yes: bool) {
547        self.line_coverage = yes.then(Default::default);
548    }
549
550    /// Set whether to enable the edge coverage collector with default config.
551    #[inline]
552    pub fn collect_edge_coverage(&mut self, yes: bool) {
553        self.edge_coverage =
554            yes.then(|| EdgeCovInspector::with_config(EdgeCovConfig::default()).into());
555    }
556
557    /// Configure the edge coverage collector from a [`FuzzCorpusConfig`].
558    ///
559    /// Derives both the on/off gate and [`EdgeCovConfig`] from `corpus`.
560    #[inline]
561    pub fn collect_edge_coverage_with_config(&mut self, corpus: &FuzzCorpusConfig) {
562        self.edge_coverage = corpus
563            .collect_evm_edge_coverage()
564            .then(|| EdgeCovInspector::with_config(corpus.into()).into());
565    }
566
567    /// Set whether to collect EVM comparison operands.
568    #[inline]
569    pub fn collect_evm_cmp_log(&mut self, yes: bool) {
570        if yes {
571            self.edge_coverage
572                .get_or_insert_with(|| EdgeCovInspector::new().into())
573                .enable_cmp_log(true);
574        } else if let Some(edge_coverage) = &mut self.edge_coverage {
575            edge_coverage.enable_cmp_log(false);
576        }
577    }
578
579    /// Set whether to collect sancov edge coverage from instrumented native crates.
580    #[inline]
581    pub const fn collect_sancov_edges(&mut self, yes: bool) {
582        self.inner.sancov_edges = yes;
583    }
584
585    /// Set whether to capture sancov trace-cmp operands for dictionary injection.
586    #[inline]
587    pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) {
588        self.inner.sancov_trace_cmp = yes;
589    }
590
591    /// Set whether to enable call isolation.
592    #[inline]
593    pub const fn enable_isolation(&mut self, yes: bool) {
594        self.inner.enable_isolation = yes;
595    }
596
597    /// Set networks with enabled features.
598    #[inline]
599    pub const fn networks(&mut self, networks: NetworkConfigs) {
600        self.inner.networks = networks;
601    }
602
603    /// Set the CREATE2 deployer address.
604    #[inline]
605    pub fn set_create2_deployer(&mut self, deployer: Address) {
606        self.create2_deployer = deployer;
607    }
608
609    /// Set whether to enable the log collector.
610    /// - None for no log collection.
611    /// - Some(true) for realtime console.log-ing.
612    /// - Some(false) for log collection.
613    #[inline]
614    pub fn collect_logs(&mut self, live_logs: Option<bool>) {
615        self.log_collector = live_logs.map(|live_logs| {
616            Box::new(if live_logs {
617                LogCollector::LiveLogs
618            } else {
619                LogCollector::Capture { logs: Vec::new() }
620            })
621        });
622    }
623
624    /// Set whether to enable the trace printer.
625    #[inline]
626    pub fn print(&mut self, yes: bool) {
627        self.printer = yes.then(Default::default);
628    }
629
630    /// Set whether to enable the tracer.
631    /// Revert diagnostic inspector is activated when `mode != TraceMode::None`
632    #[inline]
633    pub fn tracing(&mut self, mode: TraceMode) {
634        self.revert_diag = (!mode.is_none()).then(RevertDiagnostic::default).map(Into::into);
635
636        if let Some(config) = mode.into_config() {
637            *self.tracer.get_or_insert_with(Default::default).config_mut() = config;
638        } else {
639            self.tracer = None;
640        }
641    }
642
643    /// Set whether to enable script execution inspector.
644    #[inline]
645    pub fn script(&mut self, script_address: Address) {
646        self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
647            script_address;
648    }
649
650    #[inline(always)]
651    fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> {
652        InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner }
653    }
654
655    /// Collects all the data gathered during inspection into a single struct.
656    pub fn collect(self) -> InspectorData<FEN> {
657        let Self {
658            mut cheatcodes,
659            inner:
660                InspectorStackInner {
661                    chisel_state,
662                    line_coverage,
663                    edge_coverage,
664                    log_collector,
665                    tempo_labels,
666                    tracer,
667                    reverter,
668                    ..
669                },
670        } = self;
671
672        let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| {
673            let ignored = cheatcodes
674                .as_mut()
675                .map(|cheatcodes| {
676                    let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored);
677
678                    // If the last pause call was not resumed, ignore the rest of the trace
679                    if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call {
680                        ignored.insert(last_pause_call, (arena.nodes().len(), 0));
681                    }
682
683                    ignored
684                })
685                .unwrap_or_default();
686
687            SparsedTraceArena { arena, ignored }
688        });
689
690        let (edge_coverage, evm_cmp_values) = edge_coverage
691            .map(|edge_coverage| {
692                let (hitcount, cmp_values) = edge_coverage.into_parts();
693                (Some(hitcount), (!cmp_values.is_empty()).then_some(cmp_values))
694            })
695            .unwrap_or_default();
696
697        InspectorData {
698            logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(),
699            labels: {
700                let mut labels = cheatcodes.as_ref().map(|c| c.labels.clone()).unwrap_or_default();
701                if let Some(tempo_labels) = tempo_labels {
702                    labels.extend(tempo_labels.labels);
703                }
704                labels
705            },
706            traces,
707            line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()),
708            edge_coverage,
709            evm_cmp_values,
710            cheatcodes,
711            chisel_state: chisel_state.and_then(|state| state.state),
712            reverter,
713        }
714    }
715}
716
717impl<FEN: FoundryEvmNetwork> InspectorStackRefMut<'_, FEN> {
718    /// Adjusts the EVM data for the inner EVM context.
719    /// Should be called on the top-level call of inner context (depth == 0 &&
720    /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility
721    /// Updates tx.origin to the value before entering inner context
722    fn adjust_evm_data_for_inner_context<CTX: FoundryContextExt>(&mut self, ecx: &mut CTX) {
723        let inner_context_data =
724            self.inner_context_data.as_ref().expect("should be called in inner context");
725        ecx.tx_mut().set_caller(inner_context_data.original_origin);
726    }
727
728    fn do_call_end(
729        &mut self,
730        ecx: &mut FoundryContextFor<'_, FEN>,
731        inputs: &CallInputs,
732        outcome: &mut CallOutcome,
733    ) {
734        let result = outcome.result.result;
735        call_inspectors!(
736            #[ret]
737            [
738                &mut self.fuzzer,
739                &mut self.tracer,
740                &mut self.cheatcodes,
741                &mut self.printer,
742                &mut self.revert_diag
743            ],
744            |inspector| {
745                let previous_outcome = outcome.clone();
746                inspector.call_end(ecx, inputs, outcome);
747
748                // If the inspector returns a different status or a revert with a non-empty message,
749                // we assume it wants to tell us something
750                let different = outcome.result.result != result
751                    || (outcome.result.result == InstructionResult::Revert
752                        && outcome.output() != previous_outcome.output());
753                different.then_some(())
754            },
755        );
756
757        // Record first address that reverted the call.
758        if result.is_revert() && self.reverter.is_none() {
759            self.reverter = Some(inputs.target_address);
760        }
761    }
762
763    fn do_create_end(
764        &mut self,
765        ecx: &mut FoundryContextFor<'_, FEN>,
766        call: &CreateInputs,
767        outcome: &mut CreateOutcome,
768    ) {
769        let result = outcome.result.result;
770        call_inspectors!(
771            #[ret]
772            [&mut self.tracer, &mut self.cheatcodes, &mut self.printer],
773            |inspector| {
774                let previous_outcome = outcome.clone();
775                inspector.create_end(ecx, call, outcome);
776
777                // If the inspector returns a different status or a revert with a non-empty message,
778                // we assume it wants to tell us something
779                let different = outcome.result.result != result
780                    || (outcome.result.result == InstructionResult::Revert
781                        && outcome.output() != previous_outcome.output());
782                different.then_some(())
783            },
784        );
785    }
786
787    fn transact_inner(
788        &mut self,
789        ecx: &mut FoundryContextFor<'_, FEN>,
790        kind: TxKind,
791        caller: Address,
792        input: Bytes,
793        gas_limit: u64,
794        value: U256,
795    ) -> (InterpreterResult, Option<Address>) {
796        let cached_evm_env = ecx.evm_clone();
797        let cached_tx_env = ecx.tx_clone();
798
799        ecx.block_mut().set_basefee(0);
800
801        let chain_id = ecx.cfg().chain_id();
802        ecx.tx_mut().set_chain_id(Some(chain_id));
803        ecx.tx_mut().set_caller(caller);
804        ecx.tx_mut().set_kind(kind);
805        ecx.tx_mut().set_data(input);
806        ecx.tx_mut().set_value(value);
807        // Add 21000 to the gas limit to account for the base cost of transaction.
808        ecx.tx_mut().set_gas_limit(gas_limit + 21000);
809
810        // If we haven't disabled gas limit checks, ensure that transaction gas limit will not
811        // exceed block gas limit.
812        if !ecx.cfg().is_block_gas_limit_disabled() {
813            let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit());
814            ecx.tx_mut().set_gas_limit(gas_limit);
815        }
816        ecx.tx_mut().set_gas_price(0);
817        // If the cached tx is EIP-4844 (e.g. set by `vm.blobhashes`), downgrade
818        // it to EIP-1559 and drop the blob hashes so revm doesn't reject the
819        // synthetic inner tx because `gas_price = 0` plus blob fields fail
820        // 4844 validation. The contract-visible `BLOBHASH` opcode is restored
821        // via `EnvOverrides`. Other types (incl. EIP-2930 access lists) are
822        // intentionally left intact, and the full original tx is restored
823        // from `cached_tx_env` after the inner call.
824        if ecx.tx().tx_type() == TransactionType::Eip4844 as u8 {
825            ecx.tx_mut().set_tx_type(TransactionType::Eip1559 as u8);
826            ecx.tx_mut().set_blob_hashes(Vec::new());
827        }
828
829        self.inner_context_data =
830            Some(InnerContextData { original_origin: cached_tx_env.caller() });
831        self.in_inner_context = true;
832
833        // Tell cheatcodes we're entering the synthetic inner transaction so
834        // env-mutating cheatcodes route through `env_overrides` instead of
835        // fighting with the fee-accounting zeroing above. See `EnvOverrides`.
836        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
837            cheats.in_isolation_context = true;
838        }
839
840        let evm_env = ecx.evm_clone();
841        let tx_env = ecx.tx_clone();
842
843        let res = self.with_inspector(|mut inspector| {
844            let (res, nested_env) = {
845                let (db, journal) = ecx.db_journal_inner_mut();
846                let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(
847                    db,
848                    evm_env,
849                    &mut inspector,
850                );
851
852                evm.journal_inner_mut().state = {
853                    let mut state = journal.state.clone();
854
855                    for (addr, acc_mut) in &mut state {
856                        // mark all accounts cold, besides preloaded addresses
857                        if journal.warm_addresses.is_cold(addr) {
858                            acc_mut.mark_cold();
859                        }
860
861                        // mark all slots cold
862                        for slot_mut in acc_mut.storage.values_mut() {
863                            slot_mut.is_cold = true;
864                            slot_mut.original_value = slot_mut.present_value;
865                        }
866                    }
867
868                    state
869                };
870
871                // set depth to 1 to make sure traces are collected correctly
872                evm.journal_inner_mut().depth = 1;
873
874                let res = evm.transact_raw(tx_env);
875                let nested_evm_env = evm.to_evm_env();
876                (res, nested_evm_env)
877            };
878
879            // Restore env, preserving cheatcode cfg/block changes from the nested EVM
880            // but restoring the original tx and basefee (which we zeroed for the nested call).
881            let mut restored_evm_env = nested_env;
882            restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
883            ecx.set_evm(restored_evm_env);
884            ecx.set_tx(cached_tx_env);
885
886            res
887        });
888
889        self.in_inner_context = false;
890        self.inner_context_data = None;
891
892        // Reset the cheatcodes isolation flag now that the synthetic inner
893        // transaction has finished.
894        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
895            cheats.in_isolation_context = false;
896        }
897
898        let mut gas = Gas::new(gas_limit);
899
900        let Ok(res) = res else {
901            // Should we match, encode and propagate error as a revert reason?
902            let result =
903                InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas };
904            return (result, None);
905        };
906
907        for (addr, mut acc) in res.state {
908            let Some(acc_mut) = ecx.journal_mut().evm_state_mut().get_mut(&addr) else {
909                ecx.journal_mut().evm_state_mut().insert(addr, acc);
910                continue;
911            };
912
913            // make sure accounts that were warmed earlier do not become cold
914            if acc.status.contains(AccountStatus::Cold)
915                && !acc_mut.status.contains(AccountStatus::Cold)
916            {
917                acc.status -= AccountStatus::Cold;
918            }
919            acc_mut.info = acc.info;
920            acc_mut.status |= acc.status;
921
922            for (key, val) in acc.storage {
923                let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
924                    acc_mut.storage.insert(key, val);
925                    continue;
926                };
927                slot_mut.present_value = val.present_value;
928                slot_mut.is_cold &= val.is_cold;
929            }
930        }
931
932        let (result, address, output) = match res.result {
933            ExecutionResult::Success { reason, gas: result_gas, logs: _, output } => {
934                gas.set_refund(result_gas.final_refunded() as i64);
935                let _ = gas.record_regular_cost(result_gas.tx_gas_used());
936                let address = match output {
937                    Output::Create(_, address) => address,
938                    Output::Call(_) => None,
939                };
940                (reason.into(), address, output.into_data())
941            }
942            ExecutionResult::Halt { reason, gas: result_gas, .. } => {
943                let _ = gas.record_regular_cost(result_gas.tx_gas_used());
944                (InstructionResult::from(reason), None, Bytes::new())
945            }
946            ExecutionResult::Revert { gas: result_gas, output, .. } => {
947                let _ = gas.record_regular_cost(result_gas.tx_gas_used());
948                (InstructionResult::Revert, None, output)
949            }
950        };
951        (InterpreterResult { result, output, gas }, address)
952    }
953
954    /// Moves out of references, constructs a new [`InspectorStackRefMut`] and runs the given
955    /// closure with it.
956    fn with_inspector<O>(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O {
957        let mut cheatcodes = self
958            .cheatcodes
959            .as_deref_mut()
960            .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone())));
961        let mut inner = std::mem::take(self.inner);
962
963        // Save pending CREATE2 redirects so frame_end in the nested EVM doesn't consume them.
964        // These belong to the outer EVM's frame lifecycle and must be restored after.
965        let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects);
966
967        let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner });
968
969        if let Some(cheats) = self.cheatcodes.as_deref_mut() {
970            *cheats = cheatcodes.unwrap();
971        }
972
973        inner.pending_create2_redirects = saved_create2_redirects;
974        *self.inner = inner;
975
976        out
977    }
978
979    /// Invoked at the beginning of a new top-level (0 depth) frame.
980    fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
981        if self.enable_isolation {
982            // If we're in isolation mode, we need to keep track of the state at the beginning of
983            // the frame to be able to roll back on revert
984            self.top_frame_journal.clone_from(ecx.journal().evm_state());
985        }
986    }
987
988    /// Invoked at the end of root frame.
989    fn top_level_frame_end(
990        &mut self,
991        ecx: &mut FoundryContextFor<'_, FEN>,
992        result: InstructionResult,
993    ) {
994        if !result.is_revert() {
995            return;
996        }
997        // Encountered a revert, since cheatcodes may have altered the evm state in such a way
998        // that violates some constraints, e.g. `deal`, we need to manually roll back on revert
999        // before revm reverts the state itself
1000        if let Some(cheats) = self.cheatcodes.as_mut() {
1001            cheats.on_revert(ecx);
1002        }
1003
1004        // If we're in isolation mode, we need to rollback to state before the root frame was
1005        // created We can't rely on revm's journal because it doesn't account for changes
1006        // made by isolated calls
1007        if self.enable_isolation {
1008            *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal);
1009        }
1010    }
1011
1012    // We take extra care in optimizing `step` and `step_end`, as they're are likely the most
1013    // hot functions in all of Foundry.
1014    // We want to `#[inline(always)]` these functions so that `InspectorStack` does not
1015    // delegate to `InspectorStackRefMut` in this case.
1016
1017    #[inline(always)]
1018    fn step_inlined(
1019        &mut self,
1020        interpreter: &mut Interpreter,
1021        ecx: &mut FoundryContextFor<'_, FEN>,
1022    ) {
1023        call_inspectors!(
1024            [
1025                // These are sorted in definition order.
1026                &mut self.edge_coverage,
1027                &mut self.fuzzer,
1028                &mut self.line_coverage,
1029                &mut self.printer,
1030                &mut self.revert_diag,
1031                &mut self.script_execution_inspector,
1032                &mut self.tracer,
1033                // Keep `cheatcodes` last to make use of the tail call.
1034                &mut self.cheatcodes,
1035            ],
1036            |inspector| (**inspector).step(interpreter, ecx),
1037        );
1038    }
1039
1040    #[inline(always)]
1041    fn step_end_inlined(
1042        &mut self,
1043        interpreter: &mut Interpreter,
1044        ecx: &mut FoundryContextFor<'_, FEN>,
1045    ) {
1046        call_inspectors!(
1047            [
1048                // These are sorted in definition order.
1049                &mut self.chisel_state,
1050                &mut self.printer,
1051                &mut self.revert_diag,
1052                &mut self.tracer,
1053                // Keep `cheatcodes` last to make use of the tail call.
1054                &mut self.cheatcodes,
1055            ],
1056            |inspector| (**inspector).step_end(interpreter, ecx),
1057        );
1058    }
1059}
1060
1061impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>>
1062    for InspectorStackRefMut<'_, FEN>
1063{
1064    fn initialize_interp(
1065        &mut self,
1066        interpreter: &mut Interpreter,
1067        ecx: &mut FoundryContextFor<'_, FEN>,
1068    ) {
1069        call_inspectors!(
1070            [
1071                &mut self.line_coverage,
1072                &mut self.tracer,
1073                &mut self.cheatcodes,
1074                &mut self.script_execution_inspector,
1075                &mut self.printer
1076            ],
1077            |inspector| inspector.initialize_interp(interpreter, ecx),
1078        );
1079    }
1080
1081    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1082        self.step_inlined(interpreter, ecx);
1083    }
1084
1085    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1086        self.step_end_inlined(interpreter, ecx);
1087    }
1088
1089    #[allow(clippy::redundant_clone)]
1090    fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1091        call_inspectors!(
1092            [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer],
1093            |inspector| inspector.log(ecx, log.clone()),
1094        );
1095    }
1096
1097    #[allow(clippy::redundant_clone)]
1098    fn log_full(
1099        &mut self,
1100        interpreter: &mut Interpreter,
1101        ecx: &mut FoundryContextFor<'_, FEN>,
1102        log: Log,
1103    ) {
1104        call_inspectors!(
1105            [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer],
1106            |inspector| inspector.log_full(interpreter, ecx, log.clone()),
1107        );
1108    }
1109
1110    fn frame_start(
1111        &mut self,
1112        ecx: &mut FoundryContextFor<'_, FEN>,
1113        frame_input: &mut FrameInput,
1114    ) -> Option<FrameResult> {
1115        if let FrameInput::Create(inputs) = frame_input
1116            && self.should_use_create2_factory(ecx.journal().depth(), inputs)
1117        {
1118            // `get_create2_factory_call_inputs` forwards `inputs.value()` to the factory
1119            // as `CallValue::Transfer`, and the default factory runtime forwards CALLVALUE
1120            // into CREATE2, so payable deploys are supported on both the explicit
1121            // `new C{salt, value}` path and the `--batch` rewrite path.
1122            let salt = match inputs.scheme() {
1123                CreateScheme::Create2 { salt } => salt,
1124                // --batch: process_salt (random per run) + counter make each deploy unique.
1125                // The nonce below is from the EVM frame caller (script contract), not the EOA.
1126                CreateScheme::Create => {
1127                    if !self.inner.batch_rewrite_warned {
1128                        let _ = sh_warn!(
1129                            "--batch rewrites CREATE → CREATE2 via the Arachnid factory; \
1130                             deployed addresses follow the CREATE2 formula and constructor \
1131                             msg.sender is the factory, not the EOA."
1132                        );
1133                        self.inner.batch_rewrite_warned = true;
1134                    }
1135                    let chain_id = ecx.cfg().chain_id();
1136                    let nonce = ecx.journal_mut().load_account(inputs.caller()).ok()?.info.nonce;
1137                    self.inner.next_batch_create_salt(chain_id, nonce)
1138                }
1139                _ => return None,
1140            };
1141
1142            let gas_limit = inputs.gas_limit();
1143            let create2_deployer = self.create2_deployer();
1144
1145            // Validate deployer before rewriting.
1146            let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash;
1147            if code_hash == KECCAK_EMPTY {
1148                // Store the revert so `create` can return it inside the normal inspector
1149                // lifecycle (avoids tracing mismatch from short-circuiting in frame_start).
1150                self.inner.pending_create2_error = Some(CreateOutcome {
1151                    result: InterpreterResult {
1152                        result: InstructionResult::Revert,
1153                        output: Bytes::from(
1154                            format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(),
1155                        ),
1156                        gas: Gas::new(gas_limit),
1157                    },
1158                    address: None,
1159                });
1160                return None;
1161            } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH {
1162                self.inner.pending_create2_error = Some(CreateOutcome {
1163                    result: InterpreterResult {
1164                        result: InstructionResult::Revert,
1165                        output: "invalid CREATE2 deployer bytecode".into(),
1166                        gas: Gas::new(gas_limit),
1167                    },
1168                    address: None,
1169                });
1170                return None;
1171            }
1172
1173            let call_inputs =
1174                get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut())
1175                    .ok()?;
1176
1177            // Record the redirect depth *after* validation succeeds.
1178            self.inner.pending_create2_redirects.push(ecx.journal().depth());
1179
1180            // Rewrite the frame input from Create to Call.
1181            *frame_input = FrameInput::Call(Box::new(call_inputs));
1182        }
1183
1184        None
1185    }
1186
1187    fn frame_end(
1188        &mut self,
1189        ecx: &mut FoundryContextFor<'_, FEN>,
1190        _frame_input: &FrameInput,
1191        frame_result: &mut FrameResult,
1192    ) {
1193        let depth = ecx.journal().depth();
1194        if self.inner.pending_create2_redirects.last().copied() != Some(depth) {
1195            return;
1196        }
1197
1198        self.inner.pending_create2_redirects.pop();
1199
1200        let FrameResult::Call(call) = frame_result else {
1201            debug_assert!(false, "pending CREATE2 redirect ended with non-call result");
1202            return;
1203        };
1204
1205        let address = match call.instruction_result() {
1206            return_ok!() => Address::try_from(call.output().as_ref())
1207                .map_err(|_| {
1208                    call.result = InterpreterResult {
1209                        result: InstructionResult::Revert,
1210                        output: "invalid CREATE2 factory output".into(),
1211                        gas: Gas::new(call.result.gas.limit()),
1212                    };
1213                })
1214                .ok(),
1215            _ => None,
1216        };
1217
1218        *frame_result = FrameResult::Create(CreateOutcome { result: call.result.clone(), address });
1219    }
1220
1221    fn call(
1222        &mut self,
1223        ecx: &mut FoundryContextFor<'_, FEN>,
1224        call: &mut CallInputs,
1225    ) -> Option<CallOutcome> {
1226        if self.in_inner_context && ecx.journal().depth() == 1 {
1227            self.adjust_evm_data_for_inner_context(ecx);
1228            return None;
1229        }
1230
1231        if ecx.journal().depth() == 0 {
1232            self.top_level_frame_start(ecx);
1233        }
1234
1235        call_inspectors!(
1236            #[ret]
1237            [
1238                &mut self.fuzzer,
1239                &mut self.tracer,
1240                &mut self.log_collector,
1241                &mut self.printer,
1242                &mut self.revert_diag,
1243                &mut self.tempo_labels
1244            ],
1245            |inspector| {
1246                let mut out = None;
1247                if let Some(output) = inspector.call(ecx, call) {
1248                    out = Some(Some(output));
1249                }
1250                out
1251            },
1252        );
1253
1254        if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
1255            // Handle mocked functions, replace bytecode address with mock if matched.
1256            if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) {
1257                let input_bytes = call.input.bytes(ecx);
1258                // Check if any mock function set for call data or if catch-all mock function set
1259                // for selector.
1260                if let Some(target) = mocks
1261                    .get(&input_bytes)
1262                    .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector)))
1263                {
1264                    call.bytecode_address = *target;
1265
1266                    let target = ecx
1267                        .journal_mut()
1268                        .load_account_with_code(*target)
1269                        .expect("failed to load account");
1270                    call.known_bytecode =
1271                        (target.info.code_hash, target.info.code.clone().unwrap_or_default());
1272                }
1273            }
1274
1275            if let Some(output) = cheatcodes.call_with_executor(ecx, call, self.inner) {
1276                return Some(output);
1277            }
1278        }
1279
1280        if self.enable_isolation && !self.in_inner_context && ecx.journal().depth() == 1 {
1281            match call.scheme {
1282                // Isolate CALLs
1283                CallScheme::Call => {
1284                    let input = call.input.bytes(ecx);
1285                    let (result, _) = self.transact_inner(
1286                        ecx,
1287                        TxKind::Call(call.target_address),
1288                        call.caller,
1289                        input,
1290                        call.gas_limit,
1291                        call.value.get(),
1292                    );
1293                    return Some(CallOutcome {
1294                        result,
1295                        memory_offset: call.return_memory_offset.clone(),
1296                        was_precompile_called: true,
1297                        precompile_call_logs: vec![],
1298                        charged_new_account_state_gas: false,
1299                    });
1300                }
1301                // Mark accounts and storage cold before STATICCALLs
1302                CallScheme::StaticCall => {
1303                    let (_, journal_inner) = ecx.db_journal_inner_mut();
1304                    let JournaledState { state, warm_addresses, .. } = journal_inner;
1305                    for (addr, acc_mut) in state {
1306                        // Do not mark accounts and storage cold accounts with arbitrary storage.
1307                        if let Some(cheatcodes) = &self.cheatcodes
1308                            && cheatcodes.has_arbitrary_storage(addr)
1309                        {
1310                            continue;
1311                        }
1312
1313                        if warm_addresses.is_cold(addr) {
1314                            acc_mut.mark_cold();
1315                        }
1316
1317                        for slot_mut in acc_mut.storage.values_mut() {
1318                            slot_mut.is_cold = true;
1319                        }
1320                    }
1321                }
1322                // Process other variants as usual
1323                CallScheme::CallCode | CallScheme::DelegateCall => {}
1324            }
1325        }
1326
1327        None
1328    }
1329
1330    fn call_end(
1331        &mut self,
1332        ecx: &mut FoundryContextFor<'_, FEN>,
1333        inputs: &CallInputs,
1334        outcome: &mut CallOutcome,
1335    ) {
1336        // We are processing inner context outputs in the outer context, so need to avoid processing
1337        // twice.
1338        if self.in_inner_context && ecx.journal().depth() == 1 {
1339            return;
1340        }
1341
1342        self.do_call_end(ecx, inputs, outcome);
1343
1344        if ecx.journal().depth() == 0 {
1345            self.top_level_frame_end(ecx, outcome.result.result);
1346        }
1347    }
1348
1349    fn create(
1350        &mut self,
1351        ecx: &mut FoundryContextFor<'_, FEN>,
1352        create: &mut CreateInputs,
1353    ) -> Option<CreateOutcome> {
1354        if self.in_inner_context && ecx.journal().depth() == 1 {
1355            self.adjust_evm_data_for_inner_context(ecx);
1356            return None;
1357        }
1358
1359        if ecx.journal().depth() == 0 {
1360            self.top_level_frame_start(ecx);
1361        }
1362
1363        call_inspectors!(
1364            #[ret]
1365            [&mut self.tracer, &mut self.line_coverage, &mut self.cheatcodes],
1366            |inspector| inspector.create(ecx, create).map(Some),
1367        );
1368
1369        // If frame_start detected an invalid CREATE2 deployer, return the error here
1370        // (after sub-inspectors have been notified) so tracing stays balanced.
1371        if let Some(error) = self.inner.pending_create2_error.take() {
1372            return Some(error);
1373        }
1374
1375        if !matches!(create.scheme(), CreateScheme::Create2 { .. })
1376            && self.enable_isolation
1377            && !self.in_inner_context
1378            && ecx.journal().depth() == 1
1379        {
1380            // In isolation mode, transact_inner returns None for the address on revert; pre-compute
1381            // the would-be deployed address so create_end can enforce expected_revert reverter
1382            // checks.
1383            let precomputed_address = ecx
1384                .journal()
1385                .evm_state()
1386                .get(&create.caller())
1387                .map(|acc| create.caller().create(acc.info.nonce));
1388
1389            let (result, address) = self.transact_inner(
1390                ecx,
1391                TxKind::Create,
1392                create.caller(),
1393                create.init_code().clone(),
1394                create.gas_limit(),
1395                create.value(),
1396            );
1397            let address =
1398                address.or_else(|| if result.is_revert() { precomputed_address } else { None });
1399            return Some(CreateOutcome { result, address });
1400        }
1401
1402        None
1403    }
1404
1405    fn create_end(
1406        &mut self,
1407        ecx: &mut FoundryContextFor<'_, FEN>,
1408        call: &CreateInputs,
1409        outcome: &mut CreateOutcome,
1410    ) {
1411        // We are processing inner context outputs in the outer context, so need to avoid processing
1412        // twice.
1413        if self.in_inner_context && ecx.journal().depth() == 1 {
1414            return;
1415        }
1416
1417        self.do_create_end(ecx, call, outcome);
1418
1419        if ecx.journal().depth() == 0 {
1420            self.top_level_frame_end(ecx, outcome.result.result);
1421        }
1422    }
1423
1424    fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
1425        call_inspectors!([&mut self.printer], |inspector| {
1426            Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
1427                inspector, contract, target, value,
1428            )
1429        });
1430    }
1431}
1432
1433impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStackRefMut<'_, FEN> {
1434    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
1435        call_inspectors!(
1436            #[ret]
1437            [&mut self.cheatcodes],
1438            |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) },
1439        );
1440
1441        false
1442    }
1443
1444    fn console_log(&mut self, msg: &str) {
1445        call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log(
1446            inspector, msg
1447        ));
1448    }
1449
1450    fn get_networks(&self) -> NetworkConfigs {
1451        self.inner.networks
1452    }
1453
1454    fn create2_deployer(&self) -> Address {
1455        self.inner.create2_deployer
1456    }
1457}
1458
1459impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for InspectorStack<FEN> {
1460    fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1461        self.as_mut().step_inlined(interpreter, ecx)
1462    }
1463
1464    fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1465        self.as_mut().step_end_inlined(interpreter, ecx)
1466    }
1467
1468    fn call(
1469        &mut self,
1470        context: &mut FoundryContextFor<'_, FEN>,
1471        inputs: &mut CallInputs,
1472    ) -> Option<CallOutcome> {
1473        self.as_mut().call(context, inputs)
1474    }
1475
1476    fn call_end(
1477        &mut self,
1478        context: &mut FoundryContextFor<'_, FEN>,
1479        inputs: &CallInputs,
1480        outcome: &mut CallOutcome,
1481    ) {
1482        self.as_mut().call_end(context, inputs, outcome)
1483    }
1484
1485    fn create(
1486        &mut self,
1487        context: &mut FoundryContextFor<'_, FEN>,
1488        create: &mut CreateInputs,
1489    ) -> Option<CreateOutcome> {
1490        self.as_mut().create(context, create)
1491    }
1492
1493    fn create_end(
1494        &mut self,
1495        context: &mut FoundryContextFor<'_, FEN>,
1496        call: &CreateInputs,
1497        outcome: &mut CreateOutcome,
1498    ) {
1499        self.as_mut().create_end(context, call, outcome)
1500    }
1501
1502    fn initialize_interp(
1503        &mut self,
1504        interpreter: &mut Interpreter,
1505        ecx: &mut FoundryContextFor<'_, FEN>,
1506    ) {
1507        self.as_mut().initialize_interp(interpreter, ecx)
1508    }
1509
1510    fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1511        self.as_mut().log(ecx, log)
1512    }
1513
1514    fn log_full(
1515        &mut self,
1516        interpreter: &mut Interpreter,
1517        ecx: &mut FoundryContextFor<'_, FEN>,
1518        log: Log,
1519    ) {
1520        self.as_mut().log_full(interpreter, ecx, log)
1521    }
1522
1523    fn frame_start(
1524        &mut self,
1525        context: &mut FoundryContextFor<'_, FEN>,
1526        frame_input: &mut FrameInput,
1527    ) -> Option<FrameResult> {
1528        self.as_mut().frame_start(context, frame_input)
1529    }
1530
1531    fn frame_end(
1532        &mut self,
1533        context: &mut FoundryContextFor<'_, FEN>,
1534        frame_input: &FrameInput,
1535        frame_result: &mut FrameResult,
1536    ) {
1537        self.as_mut().frame_end(context, frame_input, frame_result)
1538    }
1539
1540    fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
1541        call_inspectors!([&mut self.inner.printer], |inspector| {
1542            Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
1543                inspector, contract, target, value,
1544            )
1545        });
1546    }
1547}
1548
1549impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStack<FEN> {
1550    fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
1551        self.as_mut().should_use_create2_factory(depth, inputs)
1552    }
1553
1554    fn get_networks(&self) -> NetworkConfigs {
1555        self.networks
1556    }
1557
1558    fn create2_deployer(&self) -> Address {
1559        self.create2_deployer
1560    }
1561}
1562
1563impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> {
1564    type Target = &'a mut InspectorStackInner;
1565
1566    fn deref(&self) -> &Self::Target {
1567        &self.inner
1568    }
1569}
1570
1571impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStackRefMut<'_, FEN> {
1572    fn deref_mut(&mut self) -> &mut Self::Target {
1573        &mut self.inner
1574    }
1575}
1576
1577impl<FEN: FoundryEvmNetwork> Deref for InspectorStack<FEN> {
1578    type Target = InspectorStackInner;
1579
1580    fn deref(&self) -> &Self::Target {
1581        &self.inner
1582    }
1583}
1584
1585impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStack<FEN> {
1586    fn deref_mut(&mut self) -> &mut Self::Target {
1587        &mut self.inner
1588    }
1589}
1590
1591impl InspectorStackInner {
1592    /// Derive the next `--batch` CREATE2 salt and advance the per-batch counter.
1593    /// The per-inspector random seed is lazily initialized on first use.
1594    fn next_batch_create_salt(&mut self, chain_id: u64, nonce: u64) -> U256 {
1595        let process_salt = *self.batch_rewrite_process_salt.get_or_insert_with(rand::random);
1596        let counter = self.batch_create_counter;
1597        self.batch_create_counter = counter.wrapping_add(1);
1598        compute_batch_create_salt(process_salt, chain_id, nonce, counter)
1599    }
1600}
1601
1602/// Derive the CREATE2 salt used by `--batch` CREATE rewrites.
1603///
1604/// Mixes a per-inspector random seed, the chain id, the caller nonce, and a per-batch counter
1605/// so that two simulations launched at the same on-chain state produce distinct salts and
1606/// do not collide at the Arachnid factory.
1607fn compute_batch_create_salt(process_salt: u64, chain_id: u64, nonce: u64, counter: u64) -> U256 {
1608    let mut buf = [0u8; 32];
1609    buf[0..8].copy_from_slice(&process_salt.to_be_bytes());
1610    buf[8..16].copy_from_slice(&chain_id.to_be_bytes());
1611    buf[16..24].copy_from_slice(&nonce.to_be_bytes());
1612    buf[24..32].copy_from_slice(&counter.to_be_bytes());
1613    U256::from_be_bytes(keccak256(buf).0)
1614}
1615
1616#[cfg(test)]
1617mod tests {
1618    use super::{Address, InspectorStackInner, compute_batch_create_salt};
1619
1620    #[test]
1621    fn distinct_salts_across_simulations_at_same_nonce() {
1622        // Two "simulations" with different per-inspector seeds but identical on-chain state.
1623        let a = compute_batch_create_salt(0xabcd_ef01_2345_6789, 1, 5, 0);
1624        let b = compute_batch_create_salt(0x1122_3344_5566_7788, 1, 5, 0);
1625        assert_ne!(a, b);
1626    }
1627
1628    #[test]
1629    fn counter_changes_salt() {
1630        let a = compute_batch_create_salt(1, 1, 5, 0);
1631        let b = compute_batch_create_salt(1, 1, 5, 1);
1632        assert_ne!(a, b);
1633    }
1634
1635    #[test]
1636    fn chain_id_changes_salt() {
1637        let a = compute_batch_create_salt(1, 1, 5, 0);
1638        let b = compute_batch_create_salt(1, 2, 5, 0);
1639        assert_ne!(a, b);
1640    }
1641
1642    #[test]
1643    fn deterministic_for_same_inputs() {
1644        let a = compute_batch_create_salt(42, 1, 5, 7);
1645        let b = compute_batch_create_salt(42, 1, 5, 7);
1646        assert_eq!(a, b);
1647    }
1648
1649    #[test]
1650    fn distinct_create2_addresses_across_inspector_instances_at_same_onchain_state() {
1651        // Two fresh inspectors fed identical (chain_id, nonce) must produce CREATE2 addresses
1652        // that differ when routed through the same factory with the same init code.
1653        let factory = Address::with_last_byte(0x42);
1654        let init_code = b"\x60\x80\x60\x40".as_slice();
1655
1656        let mut a = InspectorStackInner::default();
1657        let mut b = InspectorStackInner::default();
1658        let salt_a = a.next_batch_create_salt(1, 5).to_be_bytes::<32>();
1659        let salt_b = b.next_batch_create_salt(1, 5).to_be_bytes::<32>();
1660
1661        let addr_a = factory.create2_from_code(salt_a, init_code);
1662        let addr_b = factory.create2_from_code(salt_b, init_code);
1663        assert_ne!(addr_a, addr_b);
1664    }
1665}