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