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, AddressHashSet, AddressMap},
9};
10
11use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, NestedEvmClosureFor, Wallets};
12use foundry_common::{compile::Analysis, sh_warn};
13use foundry_config::FuzzCorpusConfig;
14use foundry_evm_core::{
15 FoundryBlock, FoundryTransaction, InspectorExt,
16 backend::{ContextUpdateFor, DatabaseError, DatabaseExt, JournaledState},
17 constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH,
18 env::FoundryContextExt,
19 evm::{
20 BlockEnvFor, ChainFor, EthEvmNetwork, EvmEnvFor, EvmFactoryFor, FoundryContextFor,
21 FoundryEvmFactory, FoundryEvmNetwork, TxEnvFor, get_create2_factory_call_inputs,
22 merge_child_state, prepare_child_state, with_inherited_evm,
23 },
24 precompiles::P256_VERIFY,
25 refresh_chain_journal,
26};
27use foundry_evm_coverage::HitMaps;
28use foundry_evm_networks::{NetworkConfigs, arbitrum};
29use foundry_evm_traces::{SparsedTraceArena, TraceRequirements};
30use revm::{
31 Inspector,
32 context::{
33 Block, Cfg, ContextTr, JournalTr, Transaction, TransactionType,
34 result::{EVMError, ExecutionResult, Output},
35 },
36 context_interface::CreateScheme,
37 handler::FrameResult,
38 interpreter::{
39 CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas,
40 InstructionResult, Interpreter, InterpreterResult,
41 bytecode::opcode as op,
42 interpreter_types::{InputsTr, Jumps},
43 return_ok,
44 },
45 primitives::KECCAK_EMPTY,
46 state::Account,
47};
48use std::{
49 ops::{Deref, DerefMut},
50 sync::Arc,
51};
52
53use crate::executors::{EarlyExit, EvmExecutionCancellation, calculate_stipend};
54
55#[derive(Clone, Debug)]
56#[must_use = "builders do nothing unless you call `build` on them"]
57pub struct InspectorStackBuilder<BLOCK: Clone> {
58 pub analysis: Option<Analysis>,
60 pub block: Option<BLOCK>,
65 pub gas_price: Option<u128>,
70 pub cheatcodes: Option<Arc<CheatsConfig>>,
72 pub fuzzer: Option<Fuzzer>,
74 pub trace_requirements: TraceRequirements,
76 pub logs: Option<bool>,
81 pub line_coverage: Option<bool>,
83 pub print: Option<bool>,
85 pub chisel_state: Option<usize>,
87 pub enable_isolation: bool,
91 pub networks: NetworkConfigs,
95 tempo_labels: Option<Box<TempoLabels>>,
97 pub extra_cheatcode_addresses: &'static [Address],
99 pub wallets: Option<Wallets>,
101 pub create2_deployer: Address,
103}
104
105impl<BLOCK: Clone> Default for InspectorStackBuilder<BLOCK> {
106 fn default() -> Self {
107 Self {
108 analysis: None,
109 block: None,
110 gas_price: None,
111 cheatcodes: None,
112 fuzzer: None,
113 trace_requirements: TraceRequirements::none(),
114 logs: None,
115 line_coverage: None,
116 print: None,
117 chisel_state: None,
118 enable_isolation: false,
119 networks: NetworkConfigs::default(),
120 tempo_labels: None,
121 extra_cheatcode_addresses: &[],
122 wallets: None,
123 create2_deployer: Default::default(),
124 }
125 }
126}
127
128impl<BLOCK: Clone> InspectorStackBuilder<BLOCK> {
129 #[inline]
131 pub fn new() -> Self {
132 Self::default()
133 }
134
135 #[inline]
137 pub fn set_analysis(mut self, analysis: Analysis) -> Self {
138 self.analysis = Some(analysis);
139 self
140 }
141
142 #[inline]
144 pub fn block(mut self, block: BLOCK) -> Self {
145 self.block = Some(block);
146 self
147 }
148
149 #[inline]
151 pub const fn gas_price(mut self, gas_price: u128) -> Self {
152 self.gas_price = Some(gas_price);
153 self
154 }
155
156 #[inline]
158 pub fn cheatcodes(mut self, config: Arc<CheatsConfig>) -> Self {
159 self.cheatcodes = Some(config);
160 self
161 }
162
163 #[inline]
165 pub fn wallets(mut self, wallets: Wallets) -> Self {
166 self.wallets = Some(wallets);
167 self
168 }
169
170 #[inline]
172 pub fn fuzzer(mut self, fuzzer: Fuzzer) -> Self {
173 self.fuzzer = Some(fuzzer);
174 self
175 }
176
177 #[inline]
179 pub const fn chisel_state(mut self, final_pc: usize) -> Self {
180 self.chisel_state = Some(final_pc);
181 self
182 }
183
184 #[inline]
186 pub const fn logs(mut self, live_logs: bool) -> Self {
187 self.logs = Some(live_logs);
188 self
189 }
190
191 #[inline]
193 pub const fn line_coverage(mut self, yes: bool) -> Self {
194 self.line_coverage = Some(yes);
195 self
196 }
197
198 #[inline]
200 pub const fn print(mut self, yes: bool) -> Self {
201 self.print = Some(yes);
202 self
203 }
204
205 #[inline]
207 pub const fn trace_requirements(mut self, requirements: TraceRequirements) -> Self {
208 self.trace_requirements = self.trace_requirements.merge(requirements);
209 self
210 }
211
212 #[inline]
215 pub const fn enable_isolation(mut self, yes: bool) -> Self {
216 self.enable_isolation = yes;
217 self
218 }
219
220 #[inline]
225 pub const fn networks(mut self, networks: NetworkConfigs) -> Self {
226 self.networks = networks;
227 self
228 }
229
230 #[inline]
232 pub(crate) fn tempo_labels(mut self, inspector: TempoLabels) -> Self {
233 self.tempo_labels = Some(Box::new(inspector));
234 self
235 }
236
237 #[inline]
239 pub const fn extra_cheatcode_addresses(mut self, addresses: &'static [Address]) -> Self {
240 self.extra_cheatcode_addresses = addresses;
241 self
242 }
243
244 #[inline]
245 pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self {
246 self.create2_deployer = create2_deployer;
247 self
248 }
249
250 pub fn build<FEN: FoundryEvmNetwork<EvmFactory: FoundryEvmFactory<BlockEnv = BLOCK>>>(
252 self,
253 ) -> InspectorStack<FEN> {
254 let Self {
255 analysis,
256 block,
257 gas_price,
258 cheatcodes,
259 fuzzer,
260 trace_requirements,
261 logs,
262 line_coverage,
263 print,
264 chisel_state,
265 enable_isolation,
266 networks,
267 tempo_labels,
268 extra_cheatcode_addresses,
269 wallets,
270 create2_deployer,
271 } = self;
272 let mut stack = InspectorStack::new();
273 if let Some(config) = cheatcodes {
275 let mut cheatcodes = Cheatcodes::new(config);
276 cheatcodes.set_extra_cheatcode_addresses(extra_cheatcode_addresses);
277 if let Some(analysis) = analysis {
279 stack.set_analysis(analysis.clone());
280 cheatcodes.set_analysis(CheatcodeAnalysis::new(analysis));
281 }
282 if let Some(wallets) = wallets {
284 cheatcodes.set_wallets(wallets);
285 }
286 stack.set_cheatcodes(cheatcodes);
287 }
288
289 if let Some(fuzzer) = fuzzer {
290 stack.set_fuzzer(fuzzer);
291 }
292 if let Some(chisel_state) = chisel_state {
293 stack.set_chisel(chisel_state);
294 }
295 stack.collect_line_coverage(line_coverage.unwrap_or(false));
296 stack.collect_logs(logs);
297 stack.print(print.unwrap_or(false));
298 stack.tracing_requirements(trace_requirements);
299
300 stack.enable_isolation(enable_isolation);
301 stack.networks(networks);
302 stack.inner.tempo_labels = tempo_labels;
303 stack.set_extra_cheatcode_addresses(extra_cheatcode_addresses);
304 stack.set_create2_deployer(create2_deployer);
305
306 if let Some(block) = block {
308 stack.set_block(block);
309 }
310 if let Some(gas_price) = gas_price {
311 stack.set_gas_price(gas_price);
312 }
313
314 stack
315 }
316}
317
318#[macro_export]
321macro_rules! call_inspectors {
322 ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {
323 $(
324 if let Some($id) = $inspector {
325 $crate::utils::cold_path();
326 $body;
327 }
328 )+
329 };
330 (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {{
331 $(
332 if let Some($id) = $inspector {
333 $crate::utils::cold_path();
334 if let Some(result) = $body {
335 return result;
336 }
337 }
338 )+
339 }};
340}
341
342pub struct InspectorData<FEN: FoundryEvmNetwork> {
344 pub logs: Vec<Log>,
345 pub labels: AddressHashMap<String>,
346 pub traces: Option<SparsedTraceArena>,
347 pub line_coverage: Option<HitMaps>,
348 pub edge_coverage: Option<EdgeCoverage>,
349 pub evm_cmp_values: Option<Vec<CmpOperands>>,
350 pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
351 pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
352 pub reverter: Option<Address>,
353}
354
355#[derive(Debug, Clone)]
361pub struct InnerContextData {
362 original_origin: Address,
364 locally_created_accounts: AddressHashSet,
366}
367
368struct IsolatedGas {
370 regular_limit: u64,
372 reservoir: u64,
374 precharged_state: Option<u64>,
376}
377
378#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
379enum OpcodeStepDispatch {
380 #[default]
381 None,
382 FuzzerOnly,
383 General,
384}
385
386#[derive(Clone, Debug)]
397pub struct InspectorStack<FEN: FoundryEvmNetwork = EthEvmNetwork> {
398 #[allow(clippy::type_complexity)]
399 pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
400 pub inner: InspectorStackInner,
401}
402
403#[cfg(test)]
404#[derive(Clone, Debug)]
405struct EarlyExitTestGate {
406 entered: std::sync::mpsc::Sender<()>,
407 release: Arc<std::sync::Mutex<std::sync::mpsc::Receiver<()>>>,
408 target_pc: usize,
409 notified: Arc<std::sync::atomic::AtomicBool>,
410}
411
412#[derive(Clone, Copy, Debug)]
413struct PendingCreate2Redirect {
414 depth: usize,
415 charged_create_state_gas: bool,
416}
417
418#[derive(Clone, Copy, Debug)]
419struct PendingCallTrace {
420 trace_idx: usize,
421 executed_address: Option<Address>,
422}
423
424#[derive(Default, Clone, Debug)]
428pub struct InspectorStackInner {
429 pub analysis: Option<Analysis>,
431
432 pub chisel_state: Option<Box<ChiselState>>,
436 pub edge_coverage: Option<Box<EdgeCovInspector>>,
437 pub fuzzer: Option<Box<Fuzzer>>,
438 pub line_coverage: Option<Box<LineCoverageCollector>>,
439 pub log_collector: Option<Box<LogCollector>>,
440 pub printer: Option<Box<CustomPrintTracer>>,
441 pub revert_diag: Option<Box<RevertDiagnostic>>,
442 pub script_execution_inspector: Option<Box<ScriptExecutionInspector>>,
443 pub tempo_labels: Option<Box<TempoLabels>>,
444 pub tracer: Option<Box<TracingInspector>>,
445
446 pub sancov_edges: bool,
449 pub sancov_trace_cmp: bool,
451 pub enable_isolation: bool,
452 pub networks: NetworkConfigs,
453 pub extra_cheatcode_addresses: &'static [Address],
455 pub create2_deployer: Address,
456 pub in_inner_context: bool,
458 pub inner_context_data: Option<InnerContextData>,
459 pub locally_created_accounts: AddressHashSet,
461 pub top_frame_journal: AddressMap<Account>,
462 top_level_frame_failed_before_rewrite: bool,
464 isolated_call_was_precompile: Option<bool>,
466 pub reverter: Option<Address>,
468 pending_create2_redirects: Vec<PendingCreate2Redirect>,
470 pending_call_traces: Vec<PendingCallTrace>,
472 pub pending_create2_error: Option<CreateOutcome>,
475 pub batch_create_counter: u64,
477 pub batch_rewrite_warned: bool,
479 pub batch_rewrite_process_salt: Option<u64>,
482 execution_cancellation: Option<EvmExecutionCancellation>,
484 cancellation_poll_counter: u8,
486 execution_cancelled: bool,
488 #[cfg(test)]
489 early_exit_test_gate: Option<EarlyExitTestGate>,
490 static_step_dispatch: OpcodeStepDispatch,
491 has_static_step_end_inspectors: bool,
492}
493
494pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> {
497 pub cheatcodes: Option<&'a mut Cheatcodes<FEN>>,
498 pub inner: &'a mut InspectorStackInner,
499}
500
501impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for InspectorStackInner {
502 fn with_nested_evm(
503 &mut self,
504 cheats: &mut Cheatcodes<FEN>,
505 ecx: &mut FoundryContextFor<'_, FEN>,
506 f: NestedEvmClosureFor<'_, FEN>,
507 ) -> Result<(), EVMError<DatabaseError>> {
508 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
509 with_inherited_evm::<FEN::EvmFactory, _>(ecx, &mut inspector, f)
510 }
511
512 fn with_fresh_nested_evm(
513 &mut self,
514 cheats: &mut Cheatcodes<FEN>,
515 db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
516 evm_env: EvmEnvFor<FEN>,
517 chain_context: ChainFor<FEN>,
518 f: NestedEvmClosureFor<'_, FEN>,
519 ) -> Result<EvmEnvFor<FEN>, EVMError<DatabaseError>> {
520 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
521 let mut evm = FEN::EvmFactory::default().create_nested_evm_with_inspector(
522 db,
523 evm_env,
524 &mut inspector,
525 );
526 *evm.chain_mut() = chain_context;
527 f(&mut *evm)?;
528 Ok(evm.to_evm_env())
529 }
530
531 fn transact_on_db(
532 &mut self,
533 cheats: &mut Cheatcodes<FEN>,
534 ecx: &mut FoundryContextFor<'_, FEN>,
535 fork_id: Option<U256>,
536 transaction: B256,
537 ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>> {
538 let evm_env = ecx.evm_clone();
539 let outer_tx_env = ecx.tx_clone();
540 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
541 let (db, inner) = ecx.db_journal_inner_mut();
542 db.transact(fork_id, transaction, evm_env, &outer_tx_env, inner, &mut inspector)
543 }
544
545 fn transact_from_tx_on_db(
546 &mut self,
547 cheats: &mut Cheatcodes<FEN>,
548 ecx: &mut FoundryContextFor<'_, FEN>,
549 tx_env: TxEnvFor<FEN>,
550 ) -> eyre::Result<()> {
551 let evm_env = ecx.evm_clone();
552 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
553 let (db, inner) = ecx.db_journal_inner_mut();
554 db.transact_from_tx(tx_env, evm_env, inner, &mut inspector)
555 }
556
557 fn console_log(&mut self, msg: &str) {
558 if let Some(ref mut collector) = self.log_collector {
559 InspectorExt::console_log(&mut **collector, msg);
560 }
561 }
562
563 fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
564 self.tracer.as_deref_mut()
565 }
566
567 fn set_in_inner_context(&mut self, enabled: bool, original_origin: Option<Address>) {
568 self.in_inner_context = enabled;
569 self.inner_context_data = enabled.then(|| InnerContextData {
570 original_origin: original_origin.expect("origin required when enabling inner ctx"),
571 locally_created_accounts: AddressHashSet::default(),
572 });
573 }
574}
575
576impl<FEN: FoundryEvmNetwork> Default for InspectorStack<FEN> {
577 fn default() -> Self {
578 Self::new()
579 }
580}
581
582impl<FEN: FoundryEvmNetwork> InspectorStack<FEN> {
583 #[inline]
589 pub fn new() -> Self {
590 Self { cheatcodes: None, inner: InspectorStackInner::default() }
591 }
592
593 #[inline]
595 pub fn set_analysis(&mut self, analysis: Analysis) {
596 self.analysis = Some(analysis);
597 }
598
599 #[inline]
601 pub(crate) fn set_early_exit(&mut self, early_exit: EarlyExit) {
602 self.execution_cancellation = Some(EvmExecutionCancellation::early_exit(early_exit));
603 }
604
605 #[inline]
607 pub(crate) fn set_execution_cancellation(&mut self, cancellation: EvmExecutionCancellation) {
608 self.execution_cancellation = Some(cancellation);
609 }
610
611 #[inline]
613 pub(crate) const fn execution_cancelled(&self) -> bool {
614 self.inner.execution_cancelled
615 }
616
617 #[cfg(test)]
618 pub(crate) fn set_early_exit_test_gate(
619 &mut self,
620 entered: std::sync::mpsc::Sender<()>,
621 release: std::sync::mpsc::Receiver<()>,
622 target_pc: usize,
623 ) {
624 self.early_exit_test_gate = Some(EarlyExitTestGate {
625 entered,
626 release: Arc::new(std::sync::Mutex::new(release)),
627 target_pc,
628 notified: Arc::new(std::sync::atomic::AtomicBool::new(false)),
629 });
630 }
631
632 #[inline]
634 pub fn set_block(&mut self, block: BlockEnvFor<FEN>) {
635 if let Some(cheatcodes) = &mut self.cheatcodes {
636 cheatcodes.block = Some(block);
637 }
638 }
639
640 #[inline]
642 pub fn set_gas_price(&mut self, gas_price: u128) {
643 if let Some(cheatcodes) = &mut self.cheatcodes {
644 cheatcodes.gas_price = Some(gas_price);
645 }
646 }
647
648 #[inline]
650 pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes<FEN>) {
651 self.cheatcodes = Some(cheatcodes.into());
652 }
653
654 #[inline]
656 pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) {
657 self.fuzzer = Some(fuzzer.into());
658 self.refresh_static_step_dispatch();
659 }
660
661 #[inline]
663 pub fn set_chisel(&mut self, final_pc: usize) {
664 self.chisel_state = Some(ChiselState::new(final_pc).into());
665 self.refresh_static_step_end_dispatch();
666 }
667
668 #[inline]
670 pub fn collect_line_coverage(&mut self, yes: bool) {
671 self.line_coverage = yes.then(Default::default);
672 self.refresh_static_step_dispatch();
673 }
674
675 #[inline]
677 pub fn collect_edge_coverage(&mut self, yes: bool) {
678 self.edge_coverage =
679 yes.then(|| EdgeCovInspector::with_config(EdgeCovConfig::default()).into());
680 self.refresh_static_step_dispatch();
681 }
682
683 #[inline]
687 pub fn collect_edge_coverage_with_config(&mut self, corpus: &FuzzCorpusConfig) {
688 self.edge_coverage = corpus
689 .collect_evm_edge_coverage()
690 .then(|| EdgeCovInspector::with_config(corpus.into()).into());
691 self.refresh_static_step_dispatch();
692 }
693
694 #[inline]
696 pub fn collect_evm_cmp_log(&mut self, yes: bool) {
697 if yes {
698 self.edge_coverage
699 .get_or_insert_with(|| EdgeCovInspector::with_cmp_log_only().into())
700 .enable_cmp_log(true);
701 } else if let Some(edge_coverage) = &mut self.edge_coverage {
702 edge_coverage.enable_cmp_log(false);
703 }
704 self.refresh_static_step_dispatch();
705 }
706
707 #[inline]
709 pub const fn collect_sancov_edges(&mut self, yes: bool) {
710 self.inner.sancov_edges = yes;
711 }
712
713 #[inline]
715 pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) {
716 self.inner.sancov_trace_cmp = yes;
717 }
718
719 #[inline]
721 pub const fn enable_isolation(&mut self, yes: bool) {
722 self.inner.enable_isolation = yes;
723 }
724
725 #[inline]
727 pub const fn networks(&mut self, networks: NetworkConfigs) {
728 self.inner.networks = networks;
729 }
730
731 #[inline]
733 pub const fn extra_cheatcode_addresses(&self) -> &'static [Address] {
734 self.inner.extra_cheatcode_addresses
735 }
736
737 #[inline]
739 pub const fn set_extra_cheatcode_addresses(&mut self, addresses: &'static [Address]) {
740 self.inner.extra_cheatcode_addresses = addresses;
741 }
742
743 #[inline]
745 pub fn set_create2_deployer(&mut self, deployer: Address) {
746 self.create2_deployer = deployer;
747 }
748
749 #[inline]
754 pub fn collect_logs(&mut self, live_logs: Option<bool>) {
755 self.log_collector = live_logs.map(|live_logs| {
756 Box::new(if live_logs {
757 LogCollector::LiveLogs
758 } else {
759 LogCollector::Capture { logs: Vec::new() }
760 })
761 });
762 }
763
764 #[inline]
766 pub fn print(&mut self, yes: bool) {
767 self.printer = yes.then(Default::default);
768 self.refresh_static_opcode_dispatch();
769 }
770
771 #[inline]
773 pub fn tracing_requirements(&mut self, requirements: TraceRequirements) {
774 let config = requirements.into_config();
775 self.revert_diag = config.is_some().then(RevertDiagnostic::default).map(Into::into);
776
777 if let Some(config) = config {
778 *self.tracer.get_or_insert_with(Default::default).config_mut() = config;
779 } else {
780 self.tracer = None;
781 }
782 self.refresh_static_opcode_dispatch();
783 }
784
785 #[inline]
787 pub fn script(&mut self, script_address: Address) {
788 self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
789 script_address;
790 if let Some(cheatcodes) = &mut self.cheatcodes {
791 cheatcodes.script_address = Some(script_address);
792 }
793 self.refresh_static_step_dispatch();
794 }
795
796 #[inline(always)]
797 fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> {
798 InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner }
799 }
800
801 pub fn collect(self) -> InspectorData<FEN> {
803 let Self {
804 mut cheatcodes,
805 inner:
806 InspectorStackInner {
807 chisel_state,
808 line_coverage,
809 edge_coverage,
810 log_collector,
811 tempo_labels,
812 tracer,
813 revert_diag,
814 reverter,
815 ..
816 },
817 } = self;
818
819 let trace_diagnostics =
820 revert_diag.map(|revert_diag| revert_diag.into_diagnostics()).unwrap_or_default();
821
822 let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| {
823 let ignored = cheatcodes
824 .as_mut()
825 .map(|cheatcodes| {
826 let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored);
827
828 if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call {
830 ignored.insert(last_pause_call, (arena.nodes().len(), 0));
831 }
832
833 ignored
834 })
835 .unwrap_or_default();
836
837 SparsedTraceArena { arena, ignored, diagnostics: trace_diagnostics }
838 });
839
840 let (edge_coverage, evm_cmp_values) = edge_coverage
841 .map(|edge_coverage| {
842 let (hitcount, cmp_values) = edge_coverage.into_parts();
843 (Some(hitcount), (!cmp_values.is_empty()).then_some(cmp_values))
844 })
845 .unwrap_or_default();
846
847 InspectorData {
848 logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(),
849 labels: {
850 let mut labels = cheatcodes.as_ref().map(|c| c.labels.clone()).unwrap_or_default();
851 if let Some(tempo_labels) = tempo_labels {
852 labels.extend(tempo_labels.labels);
853 }
854 labels
855 },
856 traces,
857 line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()),
858 edge_coverage,
859 evm_cmp_values,
860 cheatcodes,
861 chisel_state: chisel_state.and_then(|state| state.state),
862 reverter,
863 }
864 }
865}
866
867impl<FEN: FoundryEvmNetwork> InspectorStackRefMut<'_, FEN> {
868 fn finish_create2_redirect(&mut self, depth: usize, frame_result: &mut FrameResult) {
869 let Some(redirect) = self
870 .inner
871 .pending_create2_redirects
872 .last()
873 .copied()
874 .filter(|redirect| redirect.depth == depth)
875 else {
876 return;
877 };
878 self.inner.pending_create2_redirects.pop();
879
880 let FrameResult::Call(call) = frame_result else {
881 debug_assert!(false, "pending CREATE2 redirect ended with non-call result");
882 return;
883 };
884
885 let address = match call.instruction_result() {
886 return_ok!() => Address::try_from(call.output().as_ref())
887 .map_err(|_| {
888 call.result = InterpreterResult {
889 result: InstructionResult::Revert,
890 output: "invalid CREATE2 factory output".into(),
891 gas: Gas::new(call.result.gas.limit()),
892 };
893 })
894 .ok(),
895 _ => None,
896 };
897
898 *frame_result = FrameResult::Create(CreateOutcome {
899 result: call.result.clone(),
900 address,
901 charged_create_state_gas: redirect.charged_create_state_gas,
902 });
903 }
904
905 fn adjust_evm_data_for_inner_context<CTX: FoundryContextExt>(&mut self, ecx: &mut CTX) {
910 let inner_context_data =
911 self.inner_context_data.as_ref().expect("should be called in inner context");
912 ecx.tx_mut().set_caller(inner_context_data.original_origin);
913 }
914
915 fn do_call_end(
916 &mut self,
917 ecx: &mut FoundryContextFor<'_, FEN>,
918 inputs: &CallInputs,
919 outcome: &mut CallOutcome,
920 ) {
921 let storage_hook_active =
922 self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
923 if !storage_hook_active && let Some(fuzzer) = &mut self.fuzzer {
924 fuzzer.call_end(ecx, inputs, outcome);
925 }
926
927 let result = outcome.result.result;
928 call_inspectors!(
929 #[ret]
930 [&mut self.tracer, &mut self.cheatcodes, &mut self.printer, &mut self.revert_diag],
931 |inspector| {
932 let previous_output = outcome.output().clone();
933 inspector.call_end(ecx, inputs, outcome);
934
935 let different = outcome.result.result != result
938 || (outcome.result.result == InstructionResult::Revert
939 && outcome.output() != &previous_output);
940 different.then_some(())
941 },
942 );
943
944 if result.is_revert() && self.reverter.is_none() {
946 self.reverter = Some(inputs.target_address);
947 }
948 }
949
950 fn do_create_end(
951 &mut self,
952 ecx: &mut FoundryContextFor<'_, FEN>,
953 call: &CreateInputs,
954 outcome: &mut CreateOutcome,
955 ) {
956 let result = outcome.result.result;
957 call_inspectors!(
958 #[ret]
959 [&mut self.line_coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer],
960 |inspector| {
961 let previous_output = outcome.output().clone();
962 inspector.create_end(ecx, call, outcome);
963
964 let different = outcome.result.result != result
967 || (outcome.result.result == InstructionResult::Revert
968 && outcome.output() != &previous_output);
969 different.then_some(())
970 },
971 );
972 }
973
974 fn transact_inner(
975 &mut self,
976 ecx: &mut FoundryContextFor<'_, FEN>,
977 kind: TxKind,
978 caller: Address,
979 input: Bytes,
980 gas: IsolatedGas,
981 value: U256,
982 ) -> (InterpreterResult, Option<Address>, bool) {
983 let IsolatedGas { regular_limit, reservoir, precharged_state } = gas;
984 let cached_evm_env = ecx.evm_clone();
985 let cached_tx_env = ecx.tx_clone();
986 self.isolated_call_was_precompile = None;
987
988 ecx.block_mut().set_basefee(0);
989
990 let chain_id = ecx.cfg().chain_id();
991 ecx.tx_mut().set_chain_id(Some(chain_id));
992 ecx.tx_mut().set_caller(caller);
993 ecx.tx_mut().set_kind(kind);
994 ecx.tx_mut().set_data(input);
995 ecx.tx_mut().set_value(value);
996 let initial_gas = calculate_stipend(ecx.tx(), ecx.cfg());
997 let regular_gas_limit = regular_limit.saturating_add(initial_gas);
1001 ecx.cfg_env_mut().tx_gas_limit_cap = Some(regular_gas_limit);
1002 let mut tx_gas_limit = regular_gas_limit.saturating_add(reservoir);
1003 if let Some(precharged_state) = precharged_state {
1004 tx_gas_limit = tx_gas_limit.saturating_add(precharged_state);
1005 }
1006 ecx.tx_mut().set_gas_limit(tx_gas_limit);
1007
1008 if !ecx.cfg().is_block_gas_limit_disabled() {
1011 let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit());
1012 ecx.tx_mut().set_gas_limit(gas_limit);
1013 }
1014 ecx.tx_mut().set_gas_price(0);
1015 if ecx.tx().tx_type() == TransactionType::Eip4844 as u8 {
1023 ecx.tx_mut().set_tx_type(TransactionType::Eip1559 as u8);
1024 ecx.tx_mut().set_blob_hashes(Vec::new());
1025 }
1026
1027 let locally_created_accounts = ecx
1028 .journal()
1029 .evm_state()
1030 .iter()
1031 .filter_map(|(addr, acc)| acc.is_created_locally().then_some(*addr))
1032 .collect();
1033 self.inner_context_data = Some(InnerContextData {
1034 original_origin: cached_tx_env.caller(),
1035 locally_created_accounts,
1036 });
1037 self.in_inner_context = true;
1038
1039 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1043 cheats.in_isolation_context = true;
1044 }
1045
1046 let evm_env = ecx.evm_clone();
1047 let tx_env = ecx.tx_clone();
1048 let factory = FEN::EvmFactory::default();
1049 let chain_context = ecx.chain().clone();
1050
1051 let isolated_state = prepare_child_state(ecx.journal_inner());
1052
1053 #[cfg(feature = "monad")]
1054 let state = foundry_evm_core::FoundryJournal::capture_reserve_balance(ecx.journal());
1055 #[cfg(feature = "monad")]
1056 let mut reserve_balance = None;
1057 let mut nested_chain_context = None;
1058 let res = self.with_inspector(|mut inspector| {
1059 let (res, nested_env) = {
1060 let (db, _) = ecx.db_journal_inner_mut();
1061 let mut evm = factory.create_nested_evm_with_inspector(db, evm_env, &mut inspector);
1062 *evm.chain_mut() = chain_context;
1063 evm.journal_inner_mut().state = isolated_state;
1064 #[cfg(feature = "monad")]
1065 {
1066 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1067 evm.journal_mut(),
1068 state,
1069 );
1070 foundry_evm_core::evm::refresh_nested_chain_journal(&mut *evm);
1071 foundry_evm_core::FoundryJournal::set_preserve_reserve_balance(
1072 evm.journal_mut(),
1073 true,
1074 );
1075 }
1076 evm.journal_inner_mut().depth = 1;
1078 let res = evm.transact_raw(tx_env);
1079 nested_chain_context = Some(evm.chain_mut().clone());
1080 #[cfg(feature = "monad")]
1081 {
1082 reserve_balance =
1083 Some(foundry_evm_core::FoundryJournal::capture_reserve_balance(
1084 evm.journal_mut(),
1085 ));
1086 }
1087 (res, evm.to_evm_env())
1088 };
1089
1090 let mut restored_evm_env = nested_env;
1093 restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1094 restored_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1095 ecx.set_evm(restored_evm_env);
1096 ecx.set_tx(cached_tx_env);
1097
1098 res
1099 });
1100 *ecx.chain_mut() = nested_chain_context.expect("nested EVM chain context was captured");
1101
1102 self.in_inner_context = false;
1103 self.inner_context_data = None;
1104
1105 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1108 cheats.in_isolation_context = false;
1109 }
1110
1111 let mut gas = Gas::new_with_regular_gas_and_reservoir(regular_limit, reservoir);
1112 let was_precompile_called = self.isolated_call_was_precompile.take().unwrap_or(false);
1113
1114 let Ok(res) = res else {
1115 #[cfg(feature = "monad")]
1116 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1117 ecx.journal_mut(),
1118 reserve_balance.expect("isolated transaction state was captured"),
1119 );
1120 refresh_chain_journal(ecx);
1121 let result =
1123 InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas };
1124 return (result, None, was_precompile_called);
1125 };
1126
1127 let transaction_gas = res.result.gas();
1128 let mut state_gas_used = transaction_gas.block_state_gas_used();
1129 if let Some(precharged_state) = precharged_state {
1130 state_gas_used = state_gas_used.saturating_sub(precharged_state);
1131 }
1132 if state_gas_used == 0 {
1133 let mut snapshot_gas = Gas::new(regular_limit);
1134 let _ = snapshot_gas.record_regular_cost(transaction_gas.tx_gas_used());
1135 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1136 cheats.gas_metering.set_isolated_snapshot_gas_used(snapshot_gas.total_gas_spent());
1137 }
1138 }
1139 let _ = gas.record_state_cost(state_gas_used);
1140 let _ = gas.record_regular_cost(transaction_gas.block_regular_gas_used());
1141
1142 let rolled_back = !res.result.is_success();
1143
1144 merge_child_state(ecx.journal_mut().evm_state_mut(), res.state);
1145 #[cfg(feature = "monad")]
1146 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1147 ecx.journal_mut(),
1148 reserve_balance.expect("isolated transaction state was captured"),
1149 );
1150 refresh_chain_journal(ecx);
1151
1152 let (result, address, output) = match res.result {
1153 ExecutionResult::Success { reason, gas: result_gas, logs: _, output } => {
1154 gas.set_refund(result_gas.final_refunded() as i64);
1155 let address = match output {
1156 Output::Create(_, address) => address,
1157 Output::Call(_) => None,
1158 };
1159 (reason.into(), address, output.into_data())
1160 }
1161 ExecutionResult::Halt { reason, .. } => {
1162 (InstructionResult::from(reason), None, Bytes::new())
1163 }
1164 ExecutionResult::Revert { output, .. } => (InstructionResult::Revert, None, output),
1165 };
1166 if rolled_back {
1167 refresh_chain_journal(ecx);
1168 }
1169 (InterpreterResult { result, output, gas }, address, was_precompile_called)
1170 }
1171
1172 fn with_inspector<O>(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O {
1175 let mut cheatcodes = self
1176 .cheatcodes
1177 .as_deref_mut()
1178 .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone())));
1179 let mut inner = std::mem::take(self.inner);
1180
1181 let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects);
1184
1185 let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner });
1186
1187 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1188 *cheats = cheatcodes.unwrap();
1189 }
1190
1191 inner.pending_create2_redirects = saved_create2_redirects;
1192 *self.inner = inner;
1193
1194 out
1195 }
1196
1197 fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
1199 self.locally_created_accounts.clear();
1200 self.top_level_frame_failed_before_rewrite = false;
1201 if let Some(cheatcodes) = &mut self.cheatcodes {
1202 cheatcodes.clear_storage_hook_mapping_slots();
1203 }
1204
1205 if self.enable_isolation {
1206 self.top_frame_journal.clone_from(ecx.journal().evm_state());
1209 }
1210 }
1211
1212 fn top_level_frame_end(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, failed: bool) {
1214 if let Some(cheatcodes) = &mut self.cheatcodes {
1215 cheatcodes.clear_storage_hook_mapping_slots();
1216 }
1217 if !failed {
1218 return;
1219 }
1220 if let Some(cheats) = self.cheatcodes.as_mut() {
1223 cheats.on_revert(ecx);
1224 }
1225
1226 if self.enable_isolation {
1229 *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal);
1230 }
1231
1232 refresh_chain_journal(ecx);
1233 }
1234
1235 #[inline(always)]
1241 fn step_inlined(
1242 &mut self,
1243 interpreter: &mut Interpreter,
1244 ecx: &mut FoundryContextFor<'_, FEN>,
1245 ) {
1246 let storage_hook_active = if let Some(cheats) = self.cheatcodes.as_mut() {
1247 if cheats.has_storage_hooks() && cheats.finish_storage_hook_callback(interpreter, ecx) {
1248 return;
1249 }
1250 cheats.pc = interpreter.bytecode.pc();
1251 cheats.is_storage_hook_active()
1252 } else {
1253 false
1254 };
1255
1256 #[cfg(test)]
1257 if interpreter.bytecode.opcode() == op::JUMPDEST
1258 && let Some(gate) = &self.inner.early_exit_test_gate
1259 && interpreter.bytecode.pc() == gate.target_pc
1260 && !gate.notified.swap(true, std::sync::atomic::Ordering::Relaxed)
1261 {
1262 let _ = gate.entered.send(());
1263 let _ = gate
1264 .release
1265 .lock()
1266 .expect("early-exit test gate lock poisoned")
1267 .recv_timeout(std::time::Duration::from_secs(1));
1268 }
1269
1270 if let Some(cancellation) = &self.inner.execution_cancellation {
1271 let poll_deadline = self.inner.cancellation_poll_counter == 0;
1272 self.inner.cancellation_poll_counter =
1273 self.inner.cancellation_poll_counter.wrapping_add(1);
1274 if cancellation.should_stop(poll_deadline) {
1275 self.inner.execution_cancelled = true;
1276 interpreter.halt(InstructionResult::Stop);
1277 return;
1278 }
1279 }
1280
1281 match self.static_step_dispatch {
1282 OpcodeStepDispatch::None => {}
1283 OpcodeStepDispatch::FuzzerOnly => {
1284 if !storage_hook_active && let Some(inspector) = &mut self.fuzzer {
1285 inspector.step(interpreter, ecx);
1286 }
1287 }
1288 OpcodeStepDispatch::General => {
1289 if !storage_hook_active {
1290 call_inspectors!(
1291 [
1292 &mut self.edge_coverage,
1294 &mut self.fuzzer,
1295 &mut self.line_coverage,
1296 ],
1297 |inspector| (**inspector).step(interpreter, ecx),
1298 );
1299 }
1300 call_inspectors!(
1301 [
1302 &mut self.printer,
1304 &mut self.revert_diag,
1305 &mut self.script_execution_inspector,
1306 &mut self.tracer,
1307 ],
1308 |inspector| (**inspector).step(interpreter, ecx),
1309 );
1310 }
1311 }
1312
1313 if let Some(cheats) = self.cheatcodes.as_mut()
1314 && cheats.has_step_hooks()
1315 {
1316 let opcode = interpreter.bytecode.opcode();
1317 if !cheats.has_recording_accesses_only_step_hook()
1318 || matches!(opcode, op::SLOAD | op::SSTORE)
1319 {
1320 crate::utils::cold_path();
1321 cheats.step(interpreter, ecx);
1322 }
1323 }
1324 }
1325
1326 #[inline(always)]
1327 fn step_end_inlined(
1328 &mut self,
1329 interpreter: &mut Interpreter,
1330 ecx: &mut FoundryContextFor<'_, FEN>,
1331 ) {
1332 if self.has_static_step_end_inspectors {
1333 call_inspectors!(
1334 [
1335 &mut self.chisel_state,
1337 &mut self.printer,
1338 &mut self.revert_diag,
1339 &mut self.tracer,
1340 ],
1341 |inspector| (**inspector).step_end(interpreter, ecx),
1342 );
1343 }
1344
1345 if let Some(fuzzer) = &mut self.fuzzer
1346 && fuzzer.mapping_slots.is_some()
1347 {
1348 fuzzer.step_end(interpreter, ecx);
1349 }
1350
1351 if let Some(cheats) = self.cheatcodes.as_mut()
1352 && cheats.has_step_end_hooks()
1353 {
1354 crate::utils::cold_path();
1355 cheats.step_end(interpreter, ecx);
1356 }
1357 }
1358}
1359
1360impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>>
1361 for InspectorStackRefMut<'_, FEN>
1362{
1363 fn initialize_interp(
1364 &mut self,
1365 interpreter: &mut Interpreter,
1366 ecx: &mut FoundryContextFor<'_, FEN>,
1367 ) {
1368 let address = interpreter.input.target_address();
1369 let should_mark_created_locally = self.locally_created_accounts.contains(&address)
1370 || self
1371 .inner_context_data
1372 .as_ref()
1373 .is_some_and(|ctx| ctx.locally_created_accounts.contains(&address));
1374 if should_mark_created_locally
1375 && let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&address)
1376 {
1377 account.mark_created_locally();
1378 }
1379
1380 call_inspectors!(
1381 [
1382 &mut self.line_coverage,
1383 &mut self.tracer,
1384 &mut self.cheatcodes,
1385 &mut self.script_execution_inspector,
1386 &mut self.printer
1387 ],
1388 |inspector| inspector.initialize_interp(interpreter, ecx),
1389 );
1390 }
1391
1392 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1393 self.step_inlined(interpreter, ecx);
1394 }
1395
1396 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1397 self.step_end_inlined(interpreter, ecx);
1398 }
1399
1400 #[allow(clippy::redundant_clone)]
1401 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1402 call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1403 inspector.log(ecx, log.clone())
1404 });
1405 if let Some(inspector) = &mut self.cheatcodes
1406 && inspector.has_log_hooks()
1407 {
1408 crate::utils::cold_path();
1409 inspector.log(ecx, log.clone());
1410 }
1411 call_inspectors!([&mut self.printer], |inspector| { inspector.log(ecx, log.clone()) });
1412 }
1413
1414 #[allow(clippy::redundant_clone)]
1415 fn log_full(
1416 &mut self,
1417 interpreter: &mut Interpreter,
1418 ecx: &mut FoundryContextFor<'_, FEN>,
1419 log: Log,
1420 ) {
1421 call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1422 inspector.log_full(interpreter, ecx, log.clone())
1423 });
1424 if let Some(inspector) = &mut self.cheatcodes
1425 && inspector.has_log_hooks()
1426 {
1427 crate::utils::cold_path();
1428 inspector.log_full(interpreter, ecx, log.clone());
1429 }
1430 call_inspectors!([&mut self.printer], |inspector| {
1431 inspector.log_full(interpreter, ecx, log.clone())
1432 });
1433 }
1434
1435 fn frame_start(
1436 &mut self,
1437 ecx: &mut FoundryContextFor<'_, FEN>,
1438 frame_input: &mut FrameInput,
1439 ) -> Option<FrameResult> {
1440 if let FrameInput::Create(inputs) = frame_input
1441 && self.should_use_create2_factory(ecx.journal().depth(), inputs)
1442 {
1443 let salt = match inputs.scheme() {
1448 CreateScheme::Create2 { salt } => salt,
1449 CreateScheme::Create => {
1452 if !self.inner.batch_rewrite_warned {
1453 let _ = sh_warn!(
1454 "--batch rewrites CREATE → CREATE2 via the Arachnid factory; \
1455 deployed addresses follow the CREATE2 formula and constructor \
1456 msg.sender is the factory, not the EOA."
1457 );
1458 self.inner.batch_rewrite_warned = true;
1459 }
1460 let chain_id = ecx.cfg().chain_id();
1461 let nonce = ecx.journal_mut().load_account(inputs.caller()).ok()?.info.nonce;
1462 self.inner.next_batch_create_salt(chain_id, nonce)
1463 }
1464 _ => return None,
1465 };
1466
1467 let gas_limit = inputs.gas_limit();
1468 let create2_deployer = self.create2_deployer();
1469
1470 let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash;
1472 if code_hash == KECCAK_EMPTY {
1473 self.inner.pending_create2_error = Some(CreateOutcome {
1476 result: InterpreterResult {
1477 result: InstructionResult::Revert,
1478 output: Bytes::from(
1479 format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(),
1480 ),
1481 gas: Gas::new(gas_limit),
1482 },
1483 address: None,
1484 charged_create_state_gas: inputs.charged_create_state_gas(),
1485 });
1486 return None;
1487 } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH {
1488 self.inner.pending_create2_error = Some(CreateOutcome {
1489 result: InterpreterResult {
1490 result: InstructionResult::Revert,
1491 output: "invalid CREATE2 deployer bytecode".into(),
1492 gas: Gas::new(gas_limit),
1493 },
1494 address: None,
1495 charged_create_state_gas: inputs.charged_create_state_gas(),
1496 });
1497 return None;
1498 }
1499
1500 let call_inputs =
1501 get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut())
1502 .ok()?;
1503
1504 self.inner.pending_create2_redirects.push(PendingCreate2Redirect {
1506 depth: ecx.journal().depth(),
1507 charged_create_state_gas: inputs.charged_create_state_gas(),
1508 });
1509
1510 *frame_input = FrameInput::Call(Box::new(call_inputs));
1512 }
1513
1514 None
1515 }
1516
1517 fn frame_end(
1518 &mut self,
1519 ecx: &mut FoundryContextFor<'_, FEN>,
1520 _frame_input: &FrameInput,
1521 frame_result: &mut FrameResult,
1522 ) {
1523 let depth = ecx.journal().depth();
1524 self.finish_create2_redirect(depth, frame_result);
1525
1526 let result = frame_result.instruction_result();
1527 if !self.in_inner_context && depth == 0 {
1528 let failed = std::mem::take(&mut self.inner.top_level_frame_failed_before_rewrite)
1529 || !result.is_ok();
1530 self.top_level_frame_end(ecx, failed);
1531 }
1532 }
1533
1534 fn call(
1535 &mut self,
1536 ecx: &mut FoundryContextFor<'_, FEN>,
1537 call: &mut CallInputs,
1538 ) -> Option<CallOutcome> {
1539 if self.in_inner_context && ecx.journal().depth() == 1 {
1540 self.adjust_evm_data_for_inner_context(ecx);
1541 return None;
1542 }
1543
1544 if ecx.journal().depth() == 0 {
1545 self.top_level_frame_start(ecx);
1546 }
1547
1548 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1549 revert_diag.frame_start();
1550 }
1551
1552 let storage_hook_callback = self
1553 .cheatcodes
1554 .as_deref()
1555 .is_some_and(|cheatcodes| cheatcodes.is_storage_hook_callback(ecx, call));
1556 let storage_hook_active =
1557 self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
1558
1559 if !storage_hook_active {
1560 call_inspectors!(
1561 #[ret]
1562 [&mut self.fuzzer],
1563 |inspector| {
1564 let mut out = None;
1565 if let Some(output) = inspector.call(ecx, call) {
1566 out = Some(Some(output));
1567 }
1568 out
1569 }
1570 );
1571 }
1572
1573 if self.tracer.is_some() {
1574 crate::utils::cold_path();
1575 let (output, trace_idx) = {
1576 let tracer = self.tracer.as_deref_mut().unwrap();
1577 let output = tracer.call(ecx, call);
1578 (output, tracer.traces().nodes().len() - 1)
1579 };
1580 self.pending_call_traces.push(PendingCallTrace { trace_idx, executed_address: None });
1581 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1582 revert_diag.set_trace_node(trace_idx);
1583 }
1584 if output.is_some() {
1585 return output;
1586 }
1587 }
1588
1589 call_inspectors!(
1590 #[ret]
1591 [
1592 &mut self.log_collector,
1593 &mut self.printer,
1594 &mut self.revert_diag,
1595 &mut self.tempo_labels
1596 ],
1597 |inspector| inspector.call(ecx, call).map(Some),
1598 );
1599
1600 if storage_hook_callback {
1603 if let Some(pending) = self.pending_call_traces.last_mut() {
1604 pending.executed_address = Some(call.bytecode_address);
1605 }
1606 return None;
1607 }
1608
1609 let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
1613 let isolate = self.enable_isolation && !self.in_inner_context && ecx.journal().depth() == 1;
1614 let mut cheatcode_outcome = None;
1615 if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
1616 if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) {
1618 let input_bytes = call.input.bytes(ecx);
1619 if let Some(target) = mocks
1622 .get(&input_bytes)
1623 .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector)))
1624 {
1625 call.bytecode_address = *target;
1626
1627 let target = ecx
1628 .journal_mut()
1629 .load_account_with_code(*target)
1630 .expect("failed to load account");
1631 call.known_bytecode =
1632 (target.info.code_hash, target.info.code.clone().unwrap_or_default());
1633 }
1634 }
1635
1636 cheatcode_outcome = cheatcodes.call_with_executor(
1637 ecx,
1638 call,
1639 self.inner,
1640 isolate && call.scheme == CallScheme::Call,
1641 );
1642 }
1643
1644 if let Some(trace_idx) = trace_idx
1645 && let Some(tracer) = self.tracer.as_deref_mut()
1646 {
1647 let caller = match call.scheme {
1648 CallScheme::DelegateCall | CallScheme::CallCode => call.target_address,
1649 CallScheme::Call | CallScheme::StaticCall => call.caller,
1650 };
1651 let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
1652 debug_assert_eq!(node.trace.depth, ecx.journal().depth());
1653 node.trace.caller = caller;
1654 }
1655
1656 if let Some(output) = cheatcode_outcome {
1657 return Some(output);
1658 }
1659
1660 if let Some(outcome) = handle_arbitrum_system_call::<FEN>(ecx, call) {
1661 return Some(outcome);
1662 }
1663
1664 if let Some(pending) = self.pending_call_traces.last_mut() {
1665 pending.executed_address = Some(call.bytecode_address);
1666 }
1667
1668 if isolate {
1669 match call.scheme {
1670 CallScheme::Call => {
1672 let input = call.input.bytes(ecx);
1673 let precharged_state = call
1674 .charged_new_account_state_gas
1675 .then_some(ecx.cfg().gas_params().new_account_state_gas());
1676 let (result, _, was_precompile_called) = self.transact_inner(
1677 ecx,
1678 TxKind::Call(call.target_address),
1679 call.caller,
1680 input,
1681 IsolatedGas {
1682 regular_limit: call.gas_limit,
1683 reservoir: call.reservoir,
1684 precharged_state,
1685 },
1686 call.value.get(),
1687 );
1688 return Some(CallOutcome {
1689 result,
1690 memory_offset: call.return_memory_offset.clone(),
1691 was_precompile_called,
1692 precompile_call_logs: vec![],
1693 charged_new_account_state_gas: call.charged_new_account_state_gas,
1694 });
1695 }
1696 CallScheme::StaticCall => {
1698 let (_, journal_inner) = ecx.db_journal_inner_mut();
1699 let JournaledState { state, warm_addresses, .. } = journal_inner;
1700 for (addr, acc_mut) in state {
1701 if let Some(cheatcodes) = &self.cheatcodes
1703 && cheatcodes.has_arbitrary_storage(addr)
1704 {
1705 continue;
1706 }
1707
1708 if warm_addresses.is_cold(addr) {
1709 acc_mut.mark_cold();
1710 }
1711
1712 for slot_mut in acc_mut.storage.values_mut() {
1713 slot_mut.is_cold = true;
1714 }
1715 }
1716 }
1717 CallScheme::CallCode | CallScheme::DelegateCall => {}
1719 }
1720 }
1721
1722 None
1723 }
1724
1725 fn call_end(
1726 &mut self,
1727 ecx: &mut FoundryContextFor<'_, FEN>,
1728 inputs: &CallInputs,
1729 outcome: &mut CallOutcome,
1730 ) {
1731 if self.in_inner_context && ecx.journal().depth() == 1 {
1734 self.isolated_call_was_precompile = Some(outcome.was_precompile_called);
1735 return;
1736 }
1737
1738 if ecx.journal().depth() == 0 {
1739 self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
1740 }
1741
1742 if let Some(pending) = self.pending_call_traces.pop()
1743 && let Some(tracer) = self.tracer.as_deref_mut()
1744 {
1745 let trace = &mut tracer.traces_mut().nodes_mut()[pending.trace_idx].trace;
1746 if trace.address == P256_VERIFY {
1747 trace.maybe_precompile = Some(
1748 pending.executed_address == Some(P256_VERIFY) && outcome.was_precompile_called,
1749 );
1750 }
1751 }
1752
1753 self.do_call_end(ecx, inputs, outcome);
1754
1755 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1756 revert_diag.frame_end();
1757 }
1758 }
1759
1760 fn create(
1761 &mut self,
1762 ecx: &mut FoundryContextFor<'_, FEN>,
1763 create: &mut CreateInputs,
1764 ) -> Option<CreateOutcome> {
1765 if self.in_inner_context && ecx.journal().depth() == 1 {
1766 self.adjust_evm_data_for_inner_context(ecx);
1767 return None;
1768 }
1769
1770 if ecx.journal().depth() == 0 {
1771 self.top_level_frame_start(ecx);
1772 }
1773
1774 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1775 revert_diag.frame_start();
1776 }
1777
1778 if self.tracer.is_some() {
1779 crate::utils::cold_path();
1780 let (output, trace_idx) = {
1781 let tracer = self.tracer.as_deref_mut().unwrap();
1782 let output = tracer.create(ecx, create);
1783 (output, tracer.traces().nodes().len() - 1)
1784 };
1785 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1786 revert_diag.set_trace_node(trace_idx);
1787 }
1788 if output.is_some() {
1789 return output;
1790 }
1791 }
1792
1793 call_inspectors!(
1794 #[ret]
1795 [&mut self.line_coverage],
1796 |inspector| inspector.create(ecx, create).map(Some),
1797 );
1798
1799 let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
1800 let mut cheatcode_outcome = None;
1801 if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
1802 cheatcode_outcome = cheatcodes.create(ecx, create);
1803 }
1804
1805 if let Some(trace_idx) = trace_idx
1806 && let Some(tracer) = self.tracer.as_deref_mut()
1807 {
1808 let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
1809 debug_assert_eq!(node.trace.depth, ecx.journal().depth());
1810 node.trace.caller = create.caller();
1811 }
1812
1813 if let Some(output) = cheatcode_outcome {
1814 return Some(output);
1815 }
1816
1817 if let Some(error) = self.inner.pending_create2_error.take() {
1820 return Some(error);
1821 }
1822
1823 if !matches!(create.scheme(), CreateScheme::Create2 { .. })
1824 && self.enable_isolation
1825 && !self.in_inner_context
1826 && ecx.journal().depth() == 1
1827 {
1828 let precomputed_address = ecx
1832 .journal()
1833 .evm_state()
1834 .get(&create.caller())
1835 .map(|acc| create.caller().create(acc.info.nonce));
1836 let precharged_state = create
1837 .charged_create_state_gas()
1838 .then_some(ecx.cfg().gas_params().create_state_gas());
1839
1840 let (result, address, _) = self.transact_inner(
1841 ecx,
1842 TxKind::Create,
1843 create.caller(),
1844 create.init_code().clone(),
1845 IsolatedGas {
1846 regular_limit: create.gas_limit(),
1847 reservoir: create.reservoir(),
1848 precharged_state,
1849 },
1850 create.value(),
1851 );
1852 let address =
1853 address.or_else(|| if result.is_revert() { precomputed_address } else { None });
1854 return Some(CreateOutcome {
1855 result,
1856 address,
1857 charged_create_state_gas: create.charged_create_state_gas(),
1858 });
1859 }
1860
1861 None
1862 }
1863
1864 fn create_end(
1865 &mut self,
1866 ecx: &mut FoundryContextFor<'_, FEN>,
1867 call: &CreateInputs,
1868 outcome: &mut CreateOutcome,
1869 ) {
1870 if outcome.result.result.is_ok()
1871 && let Some(address) = outcome.address
1872 {
1873 self.locally_created_accounts.insert(address);
1874
1875 if self.in_inner_context
1876 && let Some(inner_context) = &mut self.inner_context_data
1877 {
1878 inner_context.locally_created_accounts.insert(address);
1879 }
1880 }
1881
1882 if self.in_inner_context && ecx.journal().depth() == 1 {
1885 return;
1886 }
1887
1888 if ecx.journal().depth() == 0 {
1889 self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
1890 }
1891
1892 self.do_create_end(ecx, call, outcome);
1893
1894 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1895 revert_diag.frame_end();
1896 }
1897 }
1898
1899 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
1900 call_inspectors!([&mut self.printer], |inspector| {
1901 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
1902 inspector, contract, target, value,
1903 )
1904 });
1905 }
1906}
1907
1908fn handle_arbitrum_system_call<FEN: FoundryEvmNetwork>(
1909 ecx: &mut FoundryContextFor<'_, FEN>,
1910 call: &CallInputs,
1911) -> Option<CallOutcome> {
1912 if call.target_address != arbitrum::ARB_SYS_ADDRESS
1913 || call.bytecode_address != arbitrum::ARB_SYS_ADDRESS
1914 || !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id())
1915 {
1916 return None;
1917 }
1918
1919 let input = call.input.bytes(ecx);
1920 if input.get(..4) != Some(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR) {
1921 return None;
1922 }
1923
1924 let block_number = ecx.db().active_fork_block_number()?;
1925 let Some((gas_cost, output)) = arbitrum::arb_block_number_call(call.gas_limit, block_number)
1926 else {
1927 return Some(arbitrum_call_outcome(
1928 call,
1929 InstructionResult::PrecompileOOG,
1930 0,
1931 Bytes::new(),
1932 ));
1933 };
1934
1935 Some(arbitrum_call_outcome(call, InstructionResult::Return, gas_cost, output))
1936}
1937
1938fn arbitrum_call_outcome(
1939 call: &CallInputs,
1940 result: InstructionResult,
1941 gas_used: u64,
1942 output: Bytes,
1943) -> CallOutcome {
1944 let mut gas = Gas::new(call.gas_limit);
1945 if result.is_ok() {
1946 let _ = gas.record_regular_cost(gas_used);
1947 } else {
1948 gas.spend_all();
1949 }
1950
1951 CallOutcome {
1952 result: InterpreterResult { result, output, gas },
1953 memory_offset: call.return_memory_offset.clone(),
1954 was_precompile_called: true,
1955 precompile_call_logs: vec![],
1956 charged_new_account_state_gas: call.charged_new_account_state_gas,
1957 }
1958}
1959
1960impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStackRefMut<'_, FEN> {
1961 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
1962 call_inspectors!(
1963 #[ret]
1964 [&mut self.cheatcodes],
1965 |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) },
1966 );
1967
1968 false
1969 }
1970
1971 fn console_log(&mut self, msg: &str) {
1972 call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log(
1973 inspector, msg
1974 ));
1975 }
1976
1977 fn get_networks(&self) -> NetworkConfigs {
1978 self.inner.networks
1979 }
1980
1981 fn create2_deployer(&self) -> Address {
1982 self.inner.create2_deployer
1983 }
1984}
1985
1986impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for InspectorStack<FEN> {
1987 #[inline(always)]
1988 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1989 self.as_mut().step_inlined(interpreter, ecx)
1990 }
1991
1992 #[inline(always)]
1993 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1994 self.as_mut().step_end_inlined(interpreter, ecx)
1995 }
1996
1997 fn call(
1998 &mut self,
1999 context: &mut FoundryContextFor<'_, FEN>,
2000 inputs: &mut CallInputs,
2001 ) -> Option<CallOutcome> {
2002 self.as_mut().call(context, inputs)
2003 }
2004
2005 fn call_end(
2006 &mut self,
2007 context: &mut FoundryContextFor<'_, FEN>,
2008 inputs: &CallInputs,
2009 outcome: &mut CallOutcome,
2010 ) {
2011 self.as_mut().call_end(context, inputs, outcome)
2012 }
2013
2014 fn create(
2015 &mut self,
2016 context: &mut FoundryContextFor<'_, FEN>,
2017 create: &mut CreateInputs,
2018 ) -> Option<CreateOutcome> {
2019 self.as_mut().create(context, create)
2020 }
2021
2022 fn create_end(
2023 &mut self,
2024 context: &mut FoundryContextFor<'_, FEN>,
2025 call: &CreateInputs,
2026 outcome: &mut CreateOutcome,
2027 ) {
2028 self.as_mut().create_end(context, call, outcome)
2029 }
2030
2031 fn initialize_interp(
2032 &mut self,
2033 interpreter: &mut Interpreter,
2034 ecx: &mut FoundryContextFor<'_, FEN>,
2035 ) {
2036 self.as_mut().initialize_interp(interpreter, ecx)
2037 }
2038
2039 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
2040 self.as_mut().log(ecx, log)
2041 }
2042
2043 fn log_full(
2044 &mut self,
2045 interpreter: &mut Interpreter,
2046 ecx: &mut FoundryContextFor<'_, FEN>,
2047 log: Log,
2048 ) {
2049 self.as_mut().log_full(interpreter, ecx, log)
2050 }
2051
2052 fn frame_start(
2053 &mut self,
2054 context: &mut FoundryContextFor<'_, FEN>,
2055 frame_input: &mut FrameInput,
2056 ) -> Option<FrameResult> {
2057 self.as_mut().frame_start(context, frame_input)
2058 }
2059
2060 fn frame_end(
2061 &mut self,
2062 context: &mut FoundryContextFor<'_, FEN>,
2063 frame_input: &FrameInput,
2064 frame_result: &mut FrameResult,
2065 ) {
2066 self.as_mut().frame_end(context, frame_input, frame_result)
2067 }
2068
2069 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
2070 call_inspectors!([&mut self.inner.printer], |inspector| {
2071 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
2072 inspector, contract, target, value,
2073 )
2074 });
2075 }
2076}
2077
2078impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStack<FEN> {
2079 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2080 self.as_mut().should_use_create2_factory(depth, inputs)
2081 }
2082
2083 fn get_networks(&self) -> NetworkConfigs {
2084 self.networks
2085 }
2086
2087 fn create2_deployer(&self) -> Address {
2088 self.create2_deployer
2089 }
2090}
2091
2092impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> {
2093 type Target = &'a mut InspectorStackInner;
2094
2095 fn deref(&self) -> &Self::Target {
2096 &self.inner
2097 }
2098}
2099
2100impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStackRefMut<'_, FEN> {
2101 fn deref_mut(&mut self) -> &mut Self::Target {
2102 &mut self.inner
2103 }
2104}
2105
2106impl<FEN: FoundryEvmNetwork> Deref for InspectorStack<FEN> {
2107 type Target = InspectorStackInner;
2108
2109 fn deref(&self) -> &Self::Target {
2110 &self.inner
2111 }
2112}
2113
2114impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStack<FEN> {
2115 fn deref_mut(&mut self) -> &mut Self::Target {
2116 &mut self.inner
2117 }
2118}
2119
2120impl InspectorStackInner {
2121 #[inline]
2122 const fn refresh_static_opcode_dispatch(&mut self) {
2123 self.refresh_static_step_dispatch();
2124 self.refresh_static_step_end_dispatch();
2125 }
2126
2127 #[inline]
2128 const fn refresh_static_step_dispatch(&mut self) {
2129 self.static_step_dispatch = if self.edge_coverage.is_none()
2130 && self.line_coverage.is_none()
2131 && self.printer.is_none()
2132 && self.revert_diag.is_none()
2133 && self.script_execution_inspector.is_none()
2134 && self.tracer.is_none()
2135 {
2136 if self.fuzzer.is_some() {
2137 OpcodeStepDispatch::FuzzerOnly
2138 } else {
2139 OpcodeStepDispatch::None
2140 }
2141 } else {
2142 OpcodeStepDispatch::General
2143 };
2144 }
2145
2146 #[inline]
2147 const fn refresh_static_step_end_dispatch(&mut self) {
2148 self.has_static_step_end_inspectors = self.chisel_state.is_some()
2149 || self.printer.is_some()
2150 || self.revert_diag.is_some()
2151 || self.tracer.is_some();
2152 }
2153
2154 fn next_batch_create_salt(&mut self, chain_id: u64, nonce: u64) -> U256 {
2157 let process_salt = *self.batch_rewrite_process_salt.get_or_insert_with(rand::random);
2158 let counter = self.batch_create_counter;
2159 self.batch_create_counter = counter.wrapping_add(1);
2160 compute_batch_create_salt(process_salt, chain_id, nonce, counter)
2161 }
2162}
2163
2164fn compute_batch_create_salt(process_salt: u64, chain_id: u64, nonce: u64, counter: u64) -> U256 {
2170 let mut buf = [0u8; 32];
2171 buf[0..8].copy_from_slice(&process_salt.to_be_bytes());
2172 buf[8..16].copy_from_slice(&chain_id.to_be_bytes());
2173 buf[16..24].copy_from_slice(&nonce.to_be_bytes());
2174 buf[24..32].copy_from_slice(&counter.to_be_bytes());
2175 U256::from_be_bytes(keccak256(buf).0)
2176}
2177
2178#[cfg(test)]
2179mod tests {
2180 use super::{
2181 Address, Fuzzer, InspectorStack, InspectorStackInner, OpcodeStepDispatch, RevertDiagnostic,
2182 TraceRequirements, compute_batch_create_salt,
2183 };
2184 use foundry_evm_core::evm::EthEvmNetwork;
2185
2186 #[test]
2187 fn opcode_dispatch_defaults_to_no_static_inspectors() {
2188 let stack = InspectorStackInner::default();
2189
2190 assert_eq!(stack.static_step_dispatch, OpcodeStepDispatch::None);
2191 assert!(!stack.has_static_step_end_inspectors);
2192 }
2193
2194 #[test]
2195 fn opcode_dispatch_uses_fuzzer_fast_path_when_fuzzer_is_only_static_step_inspector() {
2196 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2197 stack.set_fuzzer(Fuzzer::new(16, None));
2198
2199 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2200 assert!(!stack.inner.has_static_step_end_inspectors);
2201
2202 stack.collect_line_coverage(true);
2203 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2204
2205 stack.collect_line_coverage(false);
2206 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2207 }
2208
2209 #[test]
2210 fn opcode_dispatch_tracks_general_step_and_step_end_inspectors() {
2211 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2212
2213 stack.print(true);
2214 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2215 assert!(stack.inner.has_static_step_end_inspectors);
2216
2217 stack.print(false);
2218 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2219 assert!(!stack.inner.has_static_step_end_inspectors);
2220
2221 stack.tracing_requirements(TraceRequirements::none().with_calls(true));
2222 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2223 assert!(stack.inner.has_static_step_end_inspectors);
2224
2225 stack.tracing_requirements(TraceRequirements::none());
2226 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2227 assert!(!stack.inner.has_static_step_end_inspectors);
2228
2229 stack.set_chisel(0);
2230 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2231 assert!(stack.inner.has_static_step_end_inspectors);
2232 }
2233
2234 #[test]
2235 fn opcode_dispatch_tracks_script_and_edge_coverage_inspectors() {
2236 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2237
2238 stack.script(Address::with_last_byte(1));
2239 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2240 assert!(!stack.inner.has_static_step_end_inspectors);
2241
2242 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2243 stack.collect_edge_coverage(true);
2244 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2245 stack.collect_edge_coverage(false);
2246 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2247 }
2248
2249 #[test]
2250 fn revert_diagnostic_frames_remain_balanced() {
2251 let mut inspector = RevertDiagnostic::default();
2252 inspector.frame_start();
2253 inspector.set_trace_node(0);
2254 inspector.frame_start();
2255 inspector.set_trace_node(1);
2256 inspector.frame_end();
2257 inspector.frame_end();
2258
2259 assert!(inspector.into_diagnostics().is_empty());
2260 }
2261
2262 #[test]
2263 fn distinct_salts_across_simulations_at_same_nonce() {
2264 let a = compute_batch_create_salt(0xabcd_ef01_2345_6789, 1, 5, 0);
2266 let b = compute_batch_create_salt(0x1122_3344_5566_7788, 1, 5, 0);
2267 assert_ne!(a, b);
2268 }
2269
2270 #[test]
2271 fn counter_changes_salt() {
2272 let a = compute_batch_create_salt(1, 1, 5, 0);
2273 let b = compute_batch_create_salt(1, 1, 5, 1);
2274 assert_ne!(a, b);
2275 }
2276
2277 #[test]
2278 fn chain_id_changes_salt() {
2279 let a = compute_batch_create_salt(1, 1, 5, 0);
2280 let b = compute_batch_create_salt(1, 2, 5, 0);
2281 assert_ne!(a, b);
2282 }
2283
2284 #[test]
2285 fn deterministic_for_same_inputs() {
2286 let a = compute_batch_create_salt(42, 1, 5, 7);
2287 let b = compute_batch_create_salt(42, 1, 5, 7);
2288 assert_eq!(a, b);
2289 }
2290
2291 #[test]
2292 fn distinct_create2_addresses_across_inspector_instances_at_same_onchain_state() {
2293 let factory = Address::with_last_byte(0x42);
2296 let init_code = b"\x60\x80\x60\x40".as_slice();
2297
2298 let mut a = InspectorStackInner::default();
2299 let mut b = InspectorStackInner::default();
2300 let salt_a = a.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2301 let salt_b = b.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2302
2303 let addr_a = factory.create2_from_code(salt_a, init_code);
2304 let addr_b = factory.create2_from_code(salt_b, init_code);
2305 assert_ne!(addr_a, addr_b);
2306 }
2307}