1use super::{
2 Cheatcodes, CheatsConfig, ChiselState, CmpOperands, CustomPrintTracer, DebugRecorder,
3 EdgeCovConfig, EdgeCovInspector, EdgeCoverage, Fuzzer, LineCoverageCollector, LogCollector,
4 RevertDiagnostic, 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};
26use foundry_evm_coverage::HitMaps;
27use foundry_evm_networks::{NetworkConfigs, arbitrum};
28use foundry_evm_traces::{SparsedTraceArena, TraceRequirements};
29use revm::{
30 Inspector, JournalEntry,
31 context::{
32 Block, Cfg, ContextTr, JournalTr, Transaction, TransactionType,
33 result::{EVMError, ExecutionResult, Output},
34 },
35 context_interface::{CreateScheme, journaled_state::JournalCheckpoint},
36 handler::FrameResult,
37 interpreter::{
38 CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas,
39 InstructionResult, Interpreter, InterpreterResult,
40 bytecode::opcode as op,
41 interpreter_types::{InputsTr, Jumps, LoopControl},
42 return_ok,
43 },
44 primitives::KECCAK_EMPTY,
45 state::Account,
46};
47use std::{
48 ops::{Deref, DerefMut},
49 sync::Arc,
50};
51
52use crate::executors::{EarlyExit, EvmExecutionCancellation, calculate_initial_gas};
53
54#[cfg(feature = "monad")]
55use foundry_evm_core::evm::refresh_chain_journal;
56
57#[derive(Clone, Debug)]
58#[must_use = "builders do nothing unless you call `build` on them"]
59pub struct InspectorStackBuilder<BLOCK: Clone> {
60 pub analysis: Option<Analysis>,
62 pub block: Option<BLOCK>,
67 pub gas_price: Option<u128>,
72 pub cheatcodes: Option<Arc<CheatsConfig>>,
74 pub fuzzer: Option<Fuzzer>,
76 pub trace_requirements: TraceRequirements,
78 pub logs: Option<bool>,
83 pub line_coverage: Option<bool>,
85 pub print: Option<bool>,
87 pub chisel_state: Option<usize>,
89 pub enable_isolation: bool,
93 pub networks: NetworkConfigs,
97 tempo_labels: Option<Box<TempoLabels>>,
99 pub extra_cheatcode_addresses: &'static [Address],
101 pub wallets: Option<Wallets>,
103 pub create2_deployer: Address,
105}
106
107impl<BLOCK: Clone> Default for InspectorStackBuilder<BLOCK> {
108 fn default() -> Self {
109 Self {
110 analysis: None,
111 block: None,
112 gas_price: None,
113 cheatcodes: None,
114 fuzzer: None,
115 trace_requirements: TraceRequirements::none(),
116 logs: None,
117 line_coverage: None,
118 print: None,
119 chisel_state: None,
120 enable_isolation: false,
121 networks: NetworkConfigs::default(),
122 tempo_labels: None,
123 extra_cheatcode_addresses: &[],
124 wallets: None,
125 create2_deployer: Default::default(),
126 }
127 }
128}
129
130impl<BLOCK: Clone> InspectorStackBuilder<BLOCK> {
131 #[inline]
133 pub fn new() -> Self {
134 Self::default()
135 }
136
137 #[inline]
139 pub fn set_analysis(mut self, analysis: Analysis) -> Self {
140 self.analysis = Some(analysis);
141 self
142 }
143
144 #[inline]
146 pub fn block(mut self, block: BLOCK) -> Self {
147 self.block = Some(block);
148 self
149 }
150
151 #[inline]
153 pub const fn gas_price(mut self, gas_price: u128) -> Self {
154 self.gas_price = Some(gas_price);
155 self
156 }
157
158 #[inline]
160 pub fn cheatcodes(mut self, config: Arc<CheatsConfig>) -> Self {
161 self.cheatcodes = Some(config);
162 self
163 }
164
165 #[inline]
167 pub fn wallets(mut self, wallets: Wallets) -> Self {
168 self.wallets = Some(wallets);
169 self
170 }
171
172 #[inline]
174 pub fn fuzzer(mut self, fuzzer: Fuzzer) -> Self {
175 self.fuzzer = Some(fuzzer);
176 self
177 }
178
179 #[inline]
181 pub const fn chisel_state(mut self, final_pc: usize) -> Self {
182 self.chisel_state = Some(final_pc);
183 self
184 }
185
186 #[inline]
188 pub const fn logs(mut self, live_logs: bool) -> Self {
189 self.logs = Some(live_logs);
190 self
191 }
192
193 #[inline]
195 pub const fn line_coverage(mut self, yes: bool) -> Self {
196 self.line_coverage = Some(yes);
197 self
198 }
199
200 #[inline]
202 pub const fn print(mut self, yes: bool) -> Self {
203 self.print = Some(yes);
204 self
205 }
206
207 #[inline]
209 pub const fn trace_requirements(mut self, requirements: TraceRequirements) -> Self {
210 self.trace_requirements = self.trace_requirements.merge(requirements);
211 self
212 }
213
214 #[inline]
217 pub const fn enable_isolation(mut self, yes: bool) -> Self {
218 self.enable_isolation = yes;
219 self
220 }
221
222 #[inline]
227 pub const fn networks(mut self, networks: NetworkConfigs) -> Self {
228 self.networks = networks;
229 self
230 }
231
232 #[inline]
234 pub(crate) fn tempo_labels(mut self, inspector: TempoLabels) -> Self {
235 self.tempo_labels = Some(Box::new(inspector));
236 self
237 }
238
239 #[inline]
241 pub const fn extra_cheatcode_addresses(mut self, addresses: &'static [Address]) -> Self {
242 self.extra_cheatcode_addresses = addresses;
243 self
244 }
245
246 #[inline]
247 pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self {
248 self.create2_deployer = create2_deployer;
249 self
250 }
251
252 pub fn build<FEN: FoundryEvmNetwork<EvmFactory: FoundryEvmFactory<BlockEnv = BLOCK>>>(
254 self,
255 ) -> InspectorStack<FEN> {
256 let Self {
257 analysis,
258 block,
259 gas_price,
260 cheatcodes,
261 fuzzer,
262 trace_requirements,
263 logs,
264 line_coverage,
265 print,
266 chisel_state,
267 enable_isolation,
268 networks,
269 tempo_labels,
270 extra_cheatcode_addresses,
271 wallets,
272 create2_deployer,
273 } = self;
274 let mut stack = InspectorStack::new();
275 if let Some(config) = cheatcodes {
277 let mut cheatcodes = Cheatcodes::new(config);
278 cheatcodes.set_extra_cheatcode_addresses(extra_cheatcode_addresses);
279 if let Some(analysis) = analysis {
281 stack.set_analysis(analysis.clone());
282 cheatcodes.core.set_analysis(CheatcodeAnalysis::new(analysis));
283 }
284 if let Some(wallets) = wallets {
286 cheatcodes.core.set_wallets(wallets);
287 }
288 stack.set_cheatcodes(cheatcodes);
289 }
290
291 if let Some(fuzzer) = fuzzer {
292 stack.set_fuzzer(fuzzer);
293 }
294 if let Some(chisel_state) = chisel_state {
295 stack.set_chisel(chisel_state);
296 }
297 stack.collect_line_coverage(line_coverage.unwrap_or(false));
298 stack.collect_logs(logs);
299 stack.print(print.unwrap_or(false));
300 stack.tracing_requirements(trace_requirements);
301
302 stack.enable_isolation(enable_isolation);
303 stack.networks(networks);
304 stack.inner.tempo_labels = tempo_labels;
305 stack.set_extra_cheatcode_addresses(extra_cheatcode_addresses);
306 stack.set_create2_deployer(create2_deployer);
307
308 if let Some(block) = block {
310 stack.set_block(block);
311 }
312 if let Some(gas_price) = gas_price {
313 stack.set_gas_price(gas_price);
314 }
315
316 stack
317 }
318}
319
320#[macro_export]
323macro_rules! call_inspectors {
324 ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {
325 $(
326 if let Some($id) = $inspector {
327 $crate::utils::cold_path();
328 $body;
329 }
330 )+
331 };
332 (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {{
333 $(
334 if let Some($id) = $inspector {
335 $crate::utils::cold_path();
336 if let Some(result) = $body {
337 return result;
338 }
339 }
340 )+
341 }};
342}
343
344pub struct InspectorData<FEN: FoundryEvmNetwork> {
346 pub logs: Vec<Log>,
347 pub labels: AddressHashMap<String>,
348 pub traces: Option<SparsedTraceArena>,
349 pub line_coverage: Option<HitMaps>,
350 pub edge_coverage: Option<EdgeCoverage>,
351 pub evm_cmp_values: Option<Vec<CmpOperands>>,
352 pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
353 pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
354 pub reverter: Option<Address>,
355}
356
357#[derive(Debug, Clone)]
363pub struct InnerContextData {
364 original_origin: Address,
366 locally_created_accounts: AddressHashSet,
368 root_depth: usize,
370}
371
372struct IsolatedGas {
374 regular_limit: u64,
376 reservoir: u64,
378 precharged_state: Option<u64>,
380}
381
382#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
383enum OpcodeStepDispatch {
384 #[default]
385 None,
386 FuzzerOnly,
387 General,
388}
389
390#[derive(Clone, Debug)]
401pub struct InspectorStack<FEN: FoundryEvmNetwork = EthEvmNetwork> {
402 #[allow(clippy::type_complexity)]
403 pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
404 pub inner: InspectorStackInner,
405}
406
407#[cfg(test)]
408#[derive(Clone, Debug)]
409struct EarlyExitTestGate {
410 entered: std::sync::mpsc::Sender<()>,
411 release: Arc<std::sync::Mutex<std::sync::mpsc::Receiver<()>>>,
412 target_pc: usize,
413 notified: Arc<std::sync::atomic::AtomicBool>,
414}
415
416#[derive(Clone, Copy, Debug)]
417struct PendingCreate2Redirect {
418 depth: usize,
419 charged_create_state_gas: bool,
420}
421
422#[derive(Clone, Copy, Debug)]
423struct PendingCallTrace {
424 trace_idx: usize,
425 executed_address: Option<Address>,
426}
427
428#[derive(Clone, Copy, Debug)]
429struct IsolatedFrameCheckpoint {
430 checkpoint: JournalCheckpoint,
431 restore_len: usize,
432 return_depth: usize,
433 is_create: bool,
434 failed: bool,
435}
436
437#[derive(Default, Clone, Debug)]
441pub struct InspectorStackInner {
442 pub analysis: Option<Analysis>,
444
445 pub chisel_state: Option<Box<ChiselState>>,
449 pub debug_recorder: Option<Box<DebugRecorder>>,
450 pub edge_coverage: Option<Box<EdgeCovInspector>>,
451 pub fuzzer: Option<Box<Fuzzer>>,
452 pub line_coverage: Option<Box<LineCoverageCollector>>,
453 pub log_collector: Option<Box<LogCollector>>,
454 pub printer: Option<Box<CustomPrintTracer>>,
455 pub revert_diag: Option<Box<RevertDiagnostic>>,
456 pub script_execution_inspector: Option<Box<ScriptExecutionInspector>>,
457 pub tempo_labels: Option<Box<TempoLabels>>,
458 pub tracer: Option<Box<TracingInspector>>,
459
460 pub sancov_edges: bool,
463 pub sancov_trace_cmp: bool,
465 pub enable_isolation: bool,
466 pub networks: NetworkConfigs,
467 pub extra_cheatcode_addresses: &'static [Address],
469 pub create2_deployer: Address,
470 pub in_inner_context: bool,
472 pub inner_context_data: Option<InnerContextData>,
473 pub locally_created_accounts: AddressHashSet,
475 pub top_frame_journal: AddressMap<Account>,
476 top_level_frame_failed_before_rewrite: bool,
478 outer_disable_fee_charge: Option<bool>,
480 isolated_call_was_precompile: Option<bool>,
482 synthetic_create_depth: Option<usize>,
484 pub reverter: Option<Address>,
486 pending_create2_redirects: Vec<PendingCreate2Redirect>,
488 pending_call_traces: Vec<PendingCallTrace>,
490 isolated_frame_checkpoints: Vec<IsolatedFrameCheckpoint>,
493 pub pending_create2_error: Option<CreateOutcome>,
496 pub batch_create_counter: u64,
498 pub batch_rewrite_warned: bool,
500 pub batch_rewrite_process_salt: Option<u64>,
503 execution_cancellation: Option<EvmExecutionCancellation>,
505 cancellation_poll_counter: u8,
507 execution_cancelled: bool,
509 #[cfg(test)]
510 early_exit_test_gate: Option<EarlyExitTestGate>,
511 static_step_dispatch: OpcodeStepDispatch,
512 has_static_step_end_inspectors: bool,
513}
514
515pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> {
518 pub cheatcodes: Option<&'a mut Cheatcodes<FEN>>,
519 pub inner: &'a mut InspectorStackInner,
520}
521
522impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for InspectorStackInner {
523 fn with_nested_evm(
524 &mut self,
525 cheats: &mut Cheatcodes<FEN>,
526 ecx: &mut FoundryContextFor<'_, FEN>,
527 f: NestedEvmClosureFor<'_, FEN>,
528 ) -> Result<(), EVMError<DatabaseError>> {
529 let previous = self.synthetic_create_depth;
530 let create2_redirects = self.pending_create2_redirects.len();
531 let snapshot_checkpoints = self.isolated_frame_checkpoints.len();
532 let snapshot_restores = cheats.isolated_snapshot_restores.len();
533 let pending_snapshot_journal = cheats
534 .track_isolated_snapshots
535 .then(|| cheats.pending_isolated_snapshot_journal.clone());
536 let capture_snapshot_restore = cheats.capture_isolated_snapshot_restore;
537 self.synthetic_create_depth = (ecx.journal().depth() == 1).then_some(2);
538 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
539 let result = with_inherited_evm::<FEN::EvmFactory, _>(ecx, &mut inspector, f);
540 self.synthetic_create_depth = previous;
541 self.pending_create2_redirects.truncate(create2_redirects);
543 if result.is_err()
544 && let Some(pending_snapshot_journal) = pending_snapshot_journal
545 {
546 self.isolated_frame_checkpoints.truncate(snapshot_checkpoints);
547 cheats.isolated_snapshot_restores.truncate(snapshot_restores);
548 cheats.pending_isolated_snapshot_journal = pending_snapshot_journal;
549 cheats.capture_isolated_snapshot_restore = capture_snapshot_restore;
550 }
551 result
552 }
553
554 fn with_fresh_nested_evm(
555 &mut self,
556 cheats: &mut Cheatcodes<FEN>,
557 ecx: &mut FoundryContextFor<'_, FEN>,
558 chain_context: ChainFor<FEN>,
559 f: NestedEvmClosureFor<'_, FEN>,
560 ) -> Result<EvmEnvFor<FEN>, EVMError<DatabaseError>> {
561 self.with_snapshot_tracking(cheats, |inner, cheats| {
562 let depth = ecx.journal().depth();
563 let previous_inner_context = inner.in_inner_context;
564 let previous_inner_context_data = inner.inner_context_data.replace(InnerContextData {
565 original_origin: ecx.tx().caller(),
566 locally_created_accounts: AddressHashSet::default(),
567 root_depth: depth,
568 });
569 let previous_precompile = inner.isolated_call_was_precompile.take();
570 inner.in_inner_context = true;
571 let mut evm_env = ecx.evm_clone();
572 let (db, _) = ecx.db_journal_inner_mut();
573 let inherited_disable_fee_charge = evm_env.cfg_env.disable_fee_charge;
574 if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
575 evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
576 }
577 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
578 let mut evm = FEN::EvmFactory::default().create_nested_evm_with_inspector(
579 db,
580 evm_env,
581 &mut inspector,
582 );
583 *evm.chain_mut() = chain_context;
584 evm.journal_inner_mut().depth = depth;
587 let result = f(&mut *evm).map(|()| {
588 let mut evm_env = evm.to_evm_env();
589 evm_env.cfg_env.disable_fee_charge = inherited_disable_fee_charge;
590 evm_env
591 });
592 drop(evm);
593 inner.in_inner_context = previous_inner_context;
594 inner.inner_context_data = previous_inner_context_data;
595 inner.isolated_call_was_precompile = previous_precompile;
596 result
597 })
598 }
599
600 fn transact_on_db(
601 &mut self,
602 cheats: &mut Cheatcodes<FEN>,
603 ecx: &mut FoundryContextFor<'_, FEN>,
604 fork_id: Option<U256>,
605 transaction: B256,
606 ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>> {
607 self.with_snapshot_tracking(cheats, |inner, cheats| {
608 let mut evm_env = ecx.evm_clone();
609 if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
610 evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
611 }
612 let outer_tx_env = ecx.tx_clone();
613 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
614 let (db, inner) = ecx.db_journal_inner_mut();
615 db.transact(fork_id, transaction, evm_env, &outer_tx_env, inner, &mut inspector)
616 })
617 }
618
619 fn transact_from_tx_on_db(
620 &mut self,
621 cheats: &mut Cheatcodes<FEN>,
622 ecx: &mut FoundryContextFor<'_, FEN>,
623 tx_env: TxEnvFor<FEN>,
624 ) -> eyre::Result<()> {
625 self.with_snapshot_tracking(cheats, |inner, cheats| {
626 let mut evm_env = ecx.evm_clone();
627 if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
628 evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
629 }
630 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
631 let (db, inner) = ecx.db_journal_inner_mut();
632 db.transact_from_tx(tx_env, evm_env, inner, &mut inspector)
633 })
634 }
635
636 fn console_log(&mut self, msg: &str) {
637 if let Some(ref mut collector) = self.log_collector {
638 InspectorExt::console_log(&mut **collector, msg);
639 }
640 }
641
642 fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
643 self.tracer.as_deref_mut()
644 }
645}
646
647impl<FEN: FoundryEvmNetwork> Default for InspectorStack<FEN> {
648 fn default() -> Self {
649 Self::new()
650 }
651}
652
653impl<FEN: FoundryEvmNetwork> InspectorStack<FEN> {
654 #[inline]
660 pub fn new() -> Self {
661 Self { cheatcodes: None, inner: InspectorStackInner::default() }
662 }
663
664 #[inline]
666 pub fn set_analysis(&mut self, analysis: Analysis) {
667 self.analysis = Some(analysis);
668 }
669
670 #[inline]
672 pub(crate) fn set_early_exit(&mut self, early_exit: EarlyExit) {
673 self.set_execution_cancellation(EvmExecutionCancellation::early_exit(early_exit));
674 }
675
676 #[inline]
678 pub(crate) fn set_execution_cancellation(&mut self, cancellation: EvmExecutionCancellation) {
679 self.execution_cancellation = Some(cancellation);
680 }
681
682 #[inline]
684 pub(crate) const fn execution_cancelled(&self) -> bool {
685 self.inner.execution_cancelled
686 }
687
688 #[cfg(test)]
689 pub(crate) fn set_early_exit_test_gate(
690 &mut self,
691 entered: std::sync::mpsc::Sender<()>,
692 release: std::sync::mpsc::Receiver<()>,
693 target_pc: usize,
694 ) {
695 self.early_exit_test_gate = Some(EarlyExitTestGate {
696 entered,
697 release: Arc::new(std::sync::Mutex::new(release)),
698 target_pc,
699 notified: Arc::new(std::sync::atomic::AtomicBool::new(false)),
700 });
701 }
702
703 #[inline]
705 pub fn set_block(&mut self, block: BlockEnvFor<FEN>) {
706 if let Some(cheatcodes) = &mut self.cheatcodes {
707 cheatcodes.block = Some(block);
708 }
709 }
710
711 #[inline]
713 pub fn set_gas_price(&mut self, gas_price: u128) {
714 if let Some(cheatcodes) = &mut self.cheatcodes {
715 cheatcodes.gas_price = Some(gas_price);
716 }
717 }
718
719 #[inline]
721 pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes<FEN>) {
722 self.cheatcodes = Some(cheatcodes.into());
723 }
724
725 #[inline]
727 pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) {
728 self.fuzzer = Some(fuzzer.into());
729 self.refresh_static_step_dispatch();
730 }
731
732 #[inline]
734 pub fn set_chisel(&mut self, final_pc: usize) {
735 self.chisel_state = Some(ChiselState::new(final_pc).into());
736 self.refresh_static_step_end_dispatch();
737 }
738
739 #[inline]
741 pub fn collect_line_coverage(&mut self, yes: bool) {
742 self.line_coverage = yes.then(Default::default);
743 self.refresh_static_step_dispatch();
744 }
745
746 #[inline]
748 pub fn collect_edge_coverage(&mut self, yes: bool) {
749 self.edge_coverage = yes.then(|| EdgeCovInspector::default().into());
750 self.refresh_static_step_dispatch();
751 }
752
753 #[inline]
755 pub fn collect_edge_coverage_with_edge_config(&mut self, config: EdgeCovConfig) {
756 self.edge_coverage = Some(EdgeCovInspector::with_config(config).into());
757 self.refresh_static_step_dispatch();
758 }
759
760 #[inline]
764 pub fn collect_edge_coverage_with_config(&mut self, corpus: &FuzzCorpusConfig) {
765 self.edge_coverage = corpus
766 .collect_evm_edge_coverage()
767 .then(|| EdgeCovInspector::with_config(corpus.into()).into());
768 self.refresh_static_step_dispatch();
769 }
770
771 #[inline]
773 pub fn collect_evm_cmp_log(&mut self, yes: bool) {
774 if yes {
775 self.edge_coverage
776 .get_or_insert_with(|| EdgeCovInspector::with_cmp_log_only().into())
777 .enable_cmp_log(true);
778 } else if let Some(edge_coverage) = &mut self.edge_coverage {
779 edge_coverage.enable_cmp_log(false);
780 }
781 self.refresh_static_step_dispatch();
782 }
783
784 #[inline]
786 pub const fn collect_sancov_edges(&mut self, yes: bool) {
787 self.inner.sancov_edges = yes;
788 }
789
790 #[inline]
792 pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) {
793 self.inner.sancov_trace_cmp = yes;
794 }
795
796 #[inline]
798 pub const fn enable_isolation(&mut self, yes: bool) {
799 self.inner.enable_isolation = yes;
800 }
801
802 #[inline]
804 pub const fn networks(&mut self, networks: NetworkConfigs) {
805 self.inner.networks = networks;
806 }
807
808 #[inline]
810 pub const fn extra_cheatcode_addresses(&self) -> &'static [Address] {
811 self.inner.extra_cheatcode_addresses
812 }
813
814 #[inline]
816 pub const fn set_extra_cheatcode_addresses(&mut self, addresses: &'static [Address]) {
817 self.inner.extra_cheatcode_addresses = addresses;
818 }
819
820 #[inline]
822 pub fn set_create2_deployer(&mut self, deployer: Address) {
823 self.create2_deployer = deployer;
824 }
825
826 #[inline]
831 pub fn collect_logs(&mut self, live_logs: Option<bool>) {
832 self.log_collector = live_logs.map(|live_logs| {
833 Box::new(if live_logs {
834 LogCollector::LiveLogs
835 } else {
836 LogCollector::Capture { logs: Vec::new() }
837 })
838 });
839 }
840
841 #[inline]
843 pub fn print(&mut self, yes: bool) {
844 self.printer = yes.then(Default::default);
845 self.refresh_static_opcode_dispatch();
846 }
847
848 #[inline]
850 pub fn tracing_requirements(&mut self, requirements: TraceRequirements) {
851 let config = requirements.into_config();
852 self.revert_diag = config.is_some().then(RevertDiagnostic::default).map(Into::into);
853
854 if let Some(config) = config {
855 *self.tracer.get_or_insert_with(Default::default).config_mut() = config;
856 } else {
857 self.tracer = None;
858 }
859 if requirements.debug_recording() {
860 self.debug_recorder.get_or_insert_with(Default::default);
861 } else {
862 self.debug_recorder = None;
863 }
864 self.refresh_static_opcode_dispatch();
865 }
866
867 #[inline]
869 pub fn script(&mut self, script_address: Address) {
870 self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
871 script_address;
872 if let Some(cheatcodes) = &mut self.cheatcodes {
873 cheatcodes.script_address = Some(script_address);
874 }
875 self.refresh_static_step_dispatch();
876 }
877
878 #[inline(always)]
879 fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> {
880 InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner }
881 }
882
883 pub fn collect(self) -> InspectorData<FEN> {
885 let Self {
886 mut cheatcodes,
887 inner:
888 InspectorStackInner {
889 chisel_state,
890 mut debug_recorder,
891 line_coverage,
892 edge_coverage,
893 log_collector,
894 tempo_labels,
895 tracer,
896 revert_diag,
897 reverter,
898 ..
899 },
900 } = self;
901
902 let trace_diagnostics =
903 revert_diag.map(|revert_diag| revert_diag.into_diagnostics()).unwrap_or_default();
904
905 let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| {
906 let ignored = cheatcodes
907 .as_mut()
908 .map(|cheatcodes| {
909 let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored);
910
911 if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call {
913 ignored.insert(last_pause_call, (arena.nodes().len(), 0));
914 }
915
916 ignored
917 })
918 .unwrap_or_default();
919
920 SparsedTraceArena {
921 arena,
922 ignored,
923 diagnostics: trace_diagnostics,
924 debug_steps: debug_recorder.as_deref_mut().map(DebugRecorder::finish),
925 }
926 });
927
928 let (edge_coverage, evm_cmp_values) = edge_coverage
929 .map(|edge_coverage| {
930 let (hitcount, cmp_values) = edge_coverage.into_parts();
931 (Some(hitcount), (!cmp_values.is_empty()).then_some(cmp_values))
932 })
933 .unwrap_or_default();
934
935 InspectorData {
936 logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(),
937 labels: {
938 let mut labels =
939 cheatcodes.as_ref().map(|c| c.core.labels.clone()).unwrap_or_default();
940 if let Some(tempo_labels) = tempo_labels {
941 labels.extend(tempo_labels.labels);
942 }
943 labels
944 },
945 traces,
946 line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()),
947 edge_coverage,
948 evm_cmp_values,
949 cheatcodes,
950 chisel_state: chisel_state.and_then(|state| state.state),
951 reverter,
952 }
953 }
954}
955
956impl<FEN: FoundryEvmNetwork> InspectorStackRefMut<'_, FEN> {
957 fn finish_create2_redirect(&mut self, depth: usize, frame_result: &mut FrameResult) {
958 let Some(redirect) = self
959 .inner
960 .pending_create2_redirects
961 .last()
962 .copied()
963 .filter(|redirect| redirect.depth == depth)
964 else {
965 return;
966 };
967 self.inner.pending_create2_redirects.pop();
968
969 let FrameResult::Call(call) = frame_result else {
970 debug_assert!(false, "pending CREATE2 redirect ended with non-call result");
971 return;
972 };
973
974 let address = match call.instruction_result() {
975 return_ok!() => Address::try_from(call.output().as_ref())
976 .map_err(|_| {
977 call.result = InterpreterResult {
978 result: InstructionResult::Revert,
979 output: "invalid CREATE2 factory output".into(),
980 gas: Gas::new(call.result.gas.limit()),
981 };
982 })
983 .ok(),
984 _ => None,
985 };
986
987 *frame_result = FrameResult::Create(CreateOutcome {
988 result: call.result.clone(),
989 address,
990 charged_create_state_gas: redirect.charged_create_state_gas,
991 });
992 }
993
994 fn adjust_evm_data_for_inner_context<CTX: FoundryContextExt>(&mut self, ecx: &mut CTX) {
999 let inner_context_data =
1000 self.inner_context_data.as_ref().expect("should be called in inner context");
1001 ecx.tx_mut().set_caller(inner_context_data.original_origin);
1002 }
1003
1004 fn do_call_end(
1005 &mut self,
1006 ecx: &mut FoundryContextFor<'_, FEN>,
1007 inputs: &CallInputs,
1008 outcome: &mut CallOutcome,
1009 ) {
1010 let storage_hook_active =
1011 self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
1012 if !storage_hook_active && let Some(fuzzer) = &mut self.fuzzer {
1013 fuzzer.call_end(ecx, inputs, outcome);
1014 }
1015
1016 let result = outcome.result.result;
1017 call_inspectors!(
1018 #[ret]
1019 [
1020 &mut self.tracer,
1021 &mut self.debug_recorder,
1022 &mut self.cheatcodes,
1023 &mut self.printer,
1024 &mut self.revert_diag
1025 ],
1026 |inspector| {
1027 let previous_output = outcome.output().clone();
1028 inspector.call_end(ecx, inputs, outcome);
1029
1030 let different = outcome.result.result != result
1033 || (outcome.result.result == InstructionResult::Revert
1034 && outcome.output() != &previous_output);
1035 different.then_some(())
1036 },
1037 );
1038
1039 if result.is_revert() && self.reverter.is_none() {
1041 self.reverter = Some(inputs.transfer_to());
1042 }
1043 }
1044
1045 fn do_create_end(
1046 &mut self,
1047 ecx: &mut FoundryContextFor<'_, FEN>,
1048 call: &CreateInputs,
1049 outcome: &mut CreateOutcome,
1050 ) {
1051 let result = outcome.result.result;
1052 call_inspectors!(
1053 #[ret]
1054 [
1055 &mut self.line_coverage,
1056 &mut self.tracer,
1057 &mut self.debug_recorder,
1058 &mut self.cheatcodes,
1059 &mut self.printer
1060 ],
1061 |inspector| {
1062 let previous_output = outcome.output().clone();
1063 inspector.create_end(ecx, call, outcome);
1064
1065 let different = outcome.result.result != result
1068 || (outcome.result.result == InstructionResult::Revert
1069 && outcome.output() != &previous_output);
1070 different.then_some(())
1071 },
1072 );
1073 }
1074
1075 fn transact_inner(
1076 &mut self,
1077 ecx: &mut FoundryContextFor<'_, FEN>,
1078 kind: TxKind,
1079 caller: Address,
1080 input: Bytes,
1081 gas: IsolatedGas,
1082 value: U256,
1083 ) -> (InterpreterResult, Option<Address>, bool) {
1084 let IsolatedGas { regular_limit, reservoir, precharged_state } = gas;
1085 let source_fork_id = ecx.db().active_fork_id();
1086 let cached_evm_env = ecx.evm_clone();
1087 let cached_tx_env = ecx.tx_clone();
1088 self.isolated_call_was_precompile = None;
1089
1090 ecx.block_mut().set_basefee(0);
1091 ecx.cfg_env_mut().disable_fee_charge = true;
1092
1093 let chain_id = ecx.cfg().chain_id();
1094 ecx.tx_mut().set_chain_id(Some(chain_id));
1095 ecx.tx_mut().set_caller(caller);
1096 ecx.tx_mut().set_kind(kind);
1097 ecx.tx_mut().set_data(input);
1098 ecx.tx_mut().set_enveloped_tx(Bytes::new());
1099 ecx.tx_mut().set_value(value);
1100 let initial_gas = calculate_initial_gas(ecx.tx(), ecx.cfg());
1101 let regular_gas_limit = regular_limit.saturating_add(initial_gas.initial_total_gas());
1105 ecx.cfg_env_mut().tx_gas_limit_cap = Some(regular_gas_limit);
1106 let floor_gas =
1110 (initial_gas.floor_gas() > regular_gas_limit).then_some(initial_gas.floor_gas());
1111 if floor_gas.is_some() {
1112 ecx.cfg_env_mut().disable_eip7623 = true;
1113 }
1114 let mut tx_gas_limit = regular_gas_limit.saturating_add(reservoir);
1115 if let Some(precharged_state) = precharged_state {
1116 tx_gas_limit = tx_gas_limit.saturating_add(precharged_state);
1117 }
1118 ecx.tx_mut().set_gas_limit(tx_gas_limit);
1119
1120 if !ecx.cfg().is_block_gas_limit_disabled() {
1123 let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit());
1124 ecx.tx_mut().set_gas_limit(gas_limit);
1125 }
1126 ecx.tx_mut().set_gas_price(0);
1127 if ecx.tx().tx_type() == TransactionType::Eip4844 as u8 {
1135 ecx.tx_mut().set_tx_type(TransactionType::Eip1559 as u8);
1136 ecx.tx_mut().set_blob_hashes(Vec::new());
1137 }
1138
1139 let locally_created_accounts = ecx
1140 .journal()
1141 .evm_state()
1142 .iter()
1143 .filter_map(|(addr, acc)| acc.is_created_locally().then_some(*addr))
1144 .collect();
1145 let root_depth = ecx.journal().depth();
1146 self.inner_context_data = Some(InnerContextData {
1147 original_origin: cached_tx_env.caller(),
1148 locally_created_accounts,
1149 root_depth,
1150 });
1151 self.outer_disable_fee_charge = Some(cached_evm_env.cfg_env.disable_fee_charge);
1152 self.in_inner_context = true;
1153
1154 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1158 cheats.pending_isolated_snapshot_journal = None;
1159 cheats.track_isolated_snapshots = true;
1160 cheats.isolated_snapshot_restores.clear();
1161 cheats.in_isolation_context = true;
1162 }
1163 self.inner.isolated_frame_checkpoints.clear();
1164
1165 let evm_env = ecx.evm_clone();
1166 let tx_env = ecx.tx_clone();
1167 let factory = FEN::EvmFactory::default();
1168 let chain_context = ecx.chain().clone();
1169
1170 let isolated_state = prepare_child_state(ecx.journal_inner());
1171
1172 #[cfg(feature = "monad")]
1173 let state = foundry_evm_core::FoundryJournal::capture_reserve_balance(ecx.journal());
1174 #[cfg(feature = "monad")]
1175 let mut reserve_balance = None;
1176 let mut nested_chain_context = None;
1177 let res = self.with_inspector(|mut inspector| {
1178 let (res, nested_env) = {
1179 let (db, _) = ecx.db_journal_inner_mut();
1180 let mut evm = factory.create_nested_evm_with_inspector(db, evm_env, &mut inspector);
1181 *evm.chain_mut() = chain_context;
1182 evm.journal_inner_mut().state = isolated_state;
1183 #[cfg(feature = "monad")]
1184 {
1185 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1186 evm.journal_mut(),
1187 state,
1188 );
1189 foundry_evm_core::evm::refresh_nested_chain_journal(&mut *evm);
1190 foundry_evm_core::FoundryJournal::set_preserve_reserve_balance(
1191 evm.journal_mut(),
1192 true,
1193 );
1194 }
1195 evm.journal_inner_mut().depth = root_depth;
1197 let res = evm.transact_raw(tx_env);
1198 nested_chain_context = Some(evm.chain_mut().clone());
1199 #[cfg(feature = "monad")]
1200 {
1201 reserve_balance =
1202 Some(foundry_evm_core::FoundryJournal::capture_reserve_balance(
1203 evm.journal_mut(),
1204 ));
1205 }
1206 (res, evm.to_evm_env())
1207 };
1208
1209 let mut restored_evm_env = nested_env;
1212 restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1213 restored_evm_env.cfg_env.disable_fee_charge = cached_evm_env.cfg_env.disable_fee_charge;
1214 restored_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1215 restored_evm_env.cfg_env.disable_eip7623 = cached_evm_env.cfg_env.disable_eip7623;
1216 ecx.set_evm(restored_evm_env);
1217 ecx.set_tx(cached_tx_env);
1218
1219 res
1220 });
1221 *ecx.chain_mut() = nested_chain_context.expect("nested EVM chain context was captured");
1222
1223 self.in_inner_context = false;
1224 self.inner_context_data = None;
1225 self.outer_disable_fee_charge = None;
1226
1227 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1230 cheats.in_isolation_context = false;
1231 cheats.track_isolated_snapshots = false;
1232 }
1233
1234 let mut gas = Gas::new_with_regular_gas_and_reservoir(regular_limit, reservoir);
1235 let was_precompile_called = self.isolated_call_was_precompile.take().unwrap_or(false);
1236
1237 let Ok(res) = res else {
1238 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1239 cheats.pending_isolated_snapshot_journal = None;
1240 cheats.isolated_snapshot_restores.clear();
1241 }
1242 self.inner.isolated_frame_checkpoints.clear();
1243 #[cfg(feature = "monad")]
1244 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1245 ecx.journal_mut(),
1246 reserve_balance.expect("isolated transaction state was captured"),
1247 );
1248 #[cfg(feature = "monad")]
1249 refresh_chain_journal(ecx);
1250 let result =
1252 InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas };
1253 return (result, None, was_precompile_called);
1254 };
1255
1256 let mut transaction_gas = *res.result.gas();
1257 if let Some(floor_gas) = floor_gas {
1258 transaction_gas = transaction_gas.with_floor_gas(floor_gas);
1259 }
1260 let tx_state_gas_used = transaction_gas.block_state_gas_used();
1261 let precharged_state = precharged_state.map_or(0, |gas| gas.min(tx_state_gas_used));
1264 let state_gas_used = tx_state_gas_used - precharged_state;
1265 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1266 cheats
1267 .gas_metering
1268 .set_isolated_snapshot_gas_used(transaction_gas.tx_gas_used(), precharged_state);
1269 }
1270 if !(gas.record_state_cost(state_gas_used)
1274 && gas.record_regular_cost(transaction_gas.block_regular_gas_used()))
1275 {
1276 gas.spend_all();
1277 }
1278
1279 let rolled_back = !res.result.is_success();
1280
1281 let restored_journal = self
1282 .cheatcodes
1283 .as_deref_mut()
1284 .and_then(|cheats| cheats.pending_isolated_snapshot_journal.take());
1285 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1286 cheats.isolated_snapshot_restores.clear();
1287 }
1288 self.inner.isolated_frame_checkpoints.clear();
1289 let restored_snapshot = if !rolled_back && let Some(restored_journal) = restored_journal {
1290 let (_, journaled_state) = ecx.db_journal_inner_mut();
1291 journaled_state.journal = restored_journal;
1292 true
1293 } else {
1294 false
1295 };
1296 if source_fork_id == ecx.db().active_fork_id() {
1297 merge_child_state(ecx.journal_mut().evm_state_mut(), res.state, restored_snapshot);
1298 } else {
1299 *ecx.journal_mut().evm_state_mut() = res.state;
1300 }
1301 #[cfg(feature = "monad")]
1302 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1303 ecx.journal_mut(),
1304 reserve_balance.expect("isolated transaction state was captured"),
1305 );
1306 #[cfg(feature = "monad")]
1307 refresh_chain_journal(ecx);
1308
1309 let (result, address, output) = match res.result {
1310 ExecutionResult::Success { reason, gas: _, logs: _, output } => {
1311 gas.set_refund(transaction_gas.final_refunded() as i64);
1312 let address = match output {
1313 Output::Create(_, address) => address,
1314 Output::Call(_) => None,
1315 };
1316 (reason.into(), address, output.into_data())
1317 }
1318 ExecutionResult::Halt { reason, .. } => {
1319 (InstructionResult::from(reason), None, Bytes::new())
1320 }
1321 ExecutionResult::Revert { output, .. } => (InstructionResult::Revert, None, output),
1322 };
1323 #[cfg(feature = "monad")]
1324 if rolled_back {
1325 refresh_chain_journal(ecx);
1326 }
1327 (InterpreterResult { result, output, gas }, address, was_precompile_called)
1328 }
1329
1330 fn with_inspector<O>(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O {
1333 let mut cheatcodes = self
1334 .cheatcodes
1335 .as_deref_mut()
1336 .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.core.config.clone())));
1337 let mut inner = std::mem::take(self.inner);
1338
1339 let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects);
1342
1343 let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner });
1344
1345 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1346 *cheats = cheatcodes.unwrap();
1347 }
1348
1349 inner.pending_create2_redirects = saved_create2_redirects;
1350 *self.inner = inner;
1351
1352 out
1353 }
1354
1355 fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
1357 self.locally_created_accounts.clear();
1358 self.top_level_frame_failed_before_rewrite = false;
1359 warm_arbitrum_system_contract::<FEN>(ecx);
1360 if let Some(cheatcodes) = &mut self.cheatcodes {
1361 cheatcodes.clear_storage_hook_mapping_slots();
1362 }
1363
1364 if self.enable_isolation {
1365 self.top_frame_journal.clone_from(ecx.journal().evm_state());
1368 }
1369 }
1370
1371 fn top_level_frame_end(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, failed: bool) {
1373 if let Some(cheatcodes) = &mut self.cheatcodes {
1374 cheatcodes.clear_storage_hook_mapping_slots();
1375 }
1376 if !failed {
1377 return;
1378 }
1379 if let Some(cheats) = self.cheatcodes.as_mut() {
1382 cheats.on_revert(ecx);
1383 }
1384
1385 if self.enable_isolation {
1388 *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal);
1389 }
1390
1391 #[cfg(feature = "monad")]
1392 refresh_chain_journal(ecx);
1393 }
1394
1395 #[inline(always)]
1401 fn step_inlined(
1402 &mut self,
1403 interpreter: &mut Interpreter,
1404 ecx: &mut FoundryContextFor<'_, FEN>,
1405 ) {
1406 let storage_hook_active = if let Some(cheats) = self.cheatcodes.as_mut() {
1407 if cheats.has_storage_hooks() && cheats.finish_storage_hook_callback(interpreter, ecx) {
1408 return;
1409 }
1410 cheats.pc = interpreter.bytecode.pc();
1411 cheats.is_storage_hook_active()
1412 } else {
1413 false
1414 };
1415
1416 #[cfg(test)]
1417 if interpreter.bytecode.opcode() == op::JUMPDEST
1418 && let Some(gate) = &self.inner.early_exit_test_gate
1419 && interpreter.bytecode.pc() == gate.target_pc
1420 && !gate.notified.swap(true, std::sync::atomic::Ordering::Relaxed)
1421 {
1422 let _ = gate.entered.send(());
1423 let _ = gate
1424 .release
1425 .lock()
1426 .expect("early-exit test gate lock poisoned")
1427 .recv_timeout(std::time::Duration::from_secs(1));
1428 }
1429
1430 if let Some(cancellation) = &self.inner.execution_cancellation {
1431 let poll_deadline = self.inner.cancellation_poll_counter == 0;
1432 self.inner.cancellation_poll_counter =
1433 self.inner.cancellation_poll_counter.wrapping_add(1);
1434 if cancellation.should_stop(poll_deadline) {
1435 self.inner.execution_cancelled = true;
1436 interpreter.halt(InstructionResult::Stop);
1437 return;
1438 }
1439 }
1440
1441 match self.static_step_dispatch {
1442 OpcodeStepDispatch::None => {}
1443 OpcodeStepDispatch::FuzzerOnly => {
1444 if !storage_hook_active && let Some(inspector) = &mut self.fuzzer {
1445 inspector.step(interpreter, ecx);
1446 }
1447 }
1448 OpcodeStepDispatch::General => {
1449 if !storage_hook_active {
1450 call_inspectors!(
1451 [
1452 &mut self.edge_coverage,
1454 &mut self.fuzzer,
1455 &mut self.line_coverage,
1456 ],
1457 |inspector| (**inspector).step(interpreter, ecx),
1458 );
1459 }
1460 call_inspectors!(
1461 [
1462 &mut self.printer,
1464 &mut self.revert_diag,
1465 &mut self.script_execution_inspector,
1466 &mut self.tracer,
1467 &mut self.debug_recorder,
1468 ],
1469 |inspector| (**inspector).step(interpreter, ecx),
1470 );
1471 }
1472 }
1473
1474 if let Some(cheats) = self.cheatcodes.as_mut()
1475 && cheats.has_step_hooks()
1476 {
1477 let opcode = interpreter.bytecode.opcode();
1478 if !cheats.has_recording_accesses_only_step_hook()
1479 || matches!(opcode, op::SLOAD | op::SSTORE)
1480 {
1481 crate::utils::cold_path();
1482 cheats.step(interpreter, ecx);
1483 }
1484 }
1485
1486 if matches!(self.static_step_dispatch, OpcodeStepDispatch::General)
1489 && let Some(recorder) = self.inner.debug_recorder.as_deref_mut()
1490 && interpreter.bytecode.is_end()
1491 {
1492 recorder.end_step_not_run(interpreter);
1493 }
1494 }
1495
1496 #[inline(always)]
1497 fn step_end_inlined(
1498 &mut self,
1499 interpreter: &mut Interpreter,
1500 ecx: &mut FoundryContextFor<'_, FEN>,
1501 ) {
1502 if self.has_static_step_end_inspectors {
1503 call_inspectors!(
1504 [
1505 &mut self.chisel_state,
1507 &mut self.printer,
1508 &mut self.revert_diag,
1509 &mut self.tracer,
1510 &mut self.debug_recorder,
1511 ],
1512 |inspector| (**inspector).step_end(interpreter, ecx),
1513 );
1514 }
1515
1516 if let Some(fuzzer) = &mut self.fuzzer
1517 && fuzzer.mapping_slots.is_some()
1518 {
1519 fuzzer.step_end(interpreter, ecx);
1520 }
1521
1522 if let Some(cheats) = self.cheatcodes.as_mut()
1523 && cheats.has_step_end_hooks()
1524 {
1525 crate::utils::cold_path();
1526 cheats.step_end(interpreter, ecx);
1527 }
1528 }
1529
1530 fn finish_isolated_snapshot_frame(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
1531 let Some(frame) = self.inner.isolated_frame_checkpoints.pop() else { return };
1532
1533 if frame.failed
1534 && let Some(cheats) = self.cheatcodes.as_deref_mut()
1535 && cheats.isolated_snapshot_restores.len() > frame.restore_len
1536 {
1537 cheats.isolated_snapshot_restores.truncate(frame.restore_len + 1);
1538 let mut journal = cheats.isolated_snapshot_restores.pop().unwrap();
1539 journal.checkpoint_revert(frame.checkpoint);
1540 journal.depth = frame.return_depth;
1541 if cheats.isolated_snapshot_restores.is_empty() {
1542 cheats.pending_isolated_snapshot_journal = None;
1543 }
1544 ecx.set_journal_inner(journal);
1545 }
1546
1547 if let Some(caller) = self.inner.isolated_frame_checkpoints.last()
1551 && let Some(cheats) = self.cheatcodes.as_deref_mut()
1552 {
1553 cheats.isolated_snapshot_restores.truncate(caller.restore_len + 1);
1554 }
1555
1556 if let Some(cheats) = self.cheatcodes.as_deref_mut()
1557 && cheats.pending_isolated_snapshot_journal.is_some()
1558 {
1559 cheats.pending_isolated_snapshot_journal = Some(ecx.journal_inner().journal.clone());
1560 }
1561 }
1562}
1563
1564impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>>
1565 for InspectorStackRefMut<'_, FEN>
1566{
1567 fn initialize_interp(
1568 &mut self,
1569 interpreter: &mut Interpreter,
1570 ecx: &mut FoundryContextFor<'_, FEN>,
1571 ) {
1572 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
1573 && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
1574 && frame.is_create
1575 && let Some(offset) = ecx.journal_inner().journal[frame.checkpoint.journal_i..]
1576 .iter()
1577 .position(|entry| matches!(entry, JournalEntry::AccountCreated { .. }))
1578 {
1579 frame.checkpoint.journal_i += offset;
1580 }
1581
1582 let address = interpreter.input.target_address();
1583 let should_mark_created_locally = self.locally_created_accounts.contains(&address)
1584 || self
1585 .inner_context_data
1586 .as_ref()
1587 .is_some_and(|ctx| ctx.locally_created_accounts.contains(&address));
1588 if should_mark_created_locally
1589 && let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&address)
1590 && account.is_created()
1591 {
1592 account.mark_created_locally();
1593 }
1594
1595 call_inspectors!(
1596 [
1597 &mut self.line_coverage,
1598 &mut self.tracer,
1599 &mut self.cheatcodes,
1600 &mut self.script_execution_inspector,
1601 &mut self.printer
1602 ],
1603 |inspector| inspector.initialize_interp(interpreter, ecx),
1604 );
1605 }
1606
1607 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1608 self.step_inlined(interpreter, ecx);
1609 }
1610
1611 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1612 self.step_end_inlined(interpreter, ecx);
1613 }
1614
1615 #[allow(clippy::redundant_clone)]
1616 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1617 call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1618 inspector.log(ecx, log.clone())
1619 });
1620 if let Some(inspector) = &mut self.cheatcodes
1621 && inspector.has_log_hooks()
1622 {
1623 crate::utils::cold_path();
1624 inspector.log(ecx, log.clone());
1625 }
1626 call_inspectors!([&mut self.printer], |inspector| { inspector.log(ecx, log.clone()) });
1627 }
1628
1629 #[allow(clippy::redundant_clone)]
1630 fn log_full(
1631 &mut self,
1632 interpreter: &mut Interpreter,
1633 ecx: &mut FoundryContextFor<'_, FEN>,
1634 log: Log,
1635 ) {
1636 call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1637 inspector.log_full(interpreter, ecx, log.clone())
1638 });
1639 if let Some(inspector) = &mut self.cheatcodes
1640 && inspector.has_log_hooks()
1641 {
1642 crate::utils::cold_path();
1643 inspector.log_full(interpreter, ecx, log.clone());
1644 }
1645 call_inspectors!([&mut self.printer], |inspector| {
1646 inspector.log_full(interpreter, ecx, log.clone())
1647 });
1648 }
1649
1650 fn frame_start(
1651 &mut self,
1652 ecx: &mut FoundryContextFor<'_, FEN>,
1653 frame_input: &mut FrameInput,
1654 ) -> Option<FrameResult> {
1655 if !self.enable_isolation
1657 && !self.in_inner_context
1658 && ecx.journal().depth() == 0
1659 && let Some(cheats) = self.cheatcodes.as_deref_mut()
1660 {
1661 cheats.pending_isolated_snapshot_journal = None;
1662 cheats.track_isolated_snapshots = true;
1663 cheats.isolated_snapshot_restores.clear();
1664 self.inner.isolated_frame_checkpoints.clear();
1665 }
1666 if let Some(cheats) = self.cheatcodes.as_deref_mut()
1667 && cheats.track_isolated_snapshots
1668 {
1669 let journal = ecx.journal_inner();
1670 let restore_len = cheats.isolated_snapshot_restores.len();
1671 cheats.capture_isolated_snapshot_restore = self
1675 .inner
1676 .isolated_frame_checkpoints
1677 .last()
1678 .is_none_or(|caller| caller.restore_len == restore_len);
1679 self.inner.isolated_frame_checkpoints.push(IsolatedFrameCheckpoint {
1680 checkpoint: JournalCheckpoint {
1681 log_i: journal.logs.len(),
1682 journal_i: journal.journal.len(),
1683 selfdestructed_i: journal.selfdestructed_addresses.len(),
1684 },
1685 restore_len,
1686 return_depth: journal.depth,
1687 is_create: matches!(frame_input, FrameInput::Create(_)),
1688 failed: false,
1689 });
1690 }
1691
1692 if let FrameInput::Create(inputs) = frame_input
1693 && self.should_use_create2_factory(ecx.journal().depth(), inputs)
1694 {
1695 let salt = match inputs.scheme() {
1700 CreateScheme::Create2 { salt } => salt,
1701 CreateScheme::Create => {
1704 if !self.inner.batch_rewrite_warned {
1705 let _ = sh_warn!(
1706 "--batch rewrites CREATE → CREATE2 via the Arachnid factory; \
1707 deployed addresses follow the CREATE2 formula and constructor \
1708 msg.sender is the factory, not the EOA."
1709 );
1710 self.inner.batch_rewrite_warned = true;
1711 }
1712 let chain_id = ecx.cfg().chain_id();
1713 let nonce = ecx.journal_mut().load_account(inputs.caller()).ok()?.info.nonce;
1714 self.inner.next_batch_create_salt(chain_id, nonce)
1715 }
1716 _ => return None,
1717 };
1718
1719 let gas_limit = inputs.gas_limit();
1720 let create2_deployer = self.create2_deployer();
1721
1722 let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash;
1724 if code_hash == KECCAK_EMPTY {
1725 self.inner.pending_create2_error = Some(CreateOutcome {
1728 result: InterpreterResult {
1729 result: InstructionResult::Revert,
1730 output: Bytes::from(
1731 format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(),
1732 ),
1733 gas: Gas::new(gas_limit),
1734 },
1735 address: None,
1736 charged_create_state_gas: inputs.charged_create_state_gas(),
1737 });
1738 return None;
1739 } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH {
1740 self.inner.pending_create2_error = Some(CreateOutcome {
1741 result: InterpreterResult {
1742 result: InstructionResult::Revert,
1743 output: "invalid CREATE2 deployer bytecode".into(),
1744 gas: Gas::new(gas_limit),
1745 },
1746 address: None,
1747 charged_create_state_gas: inputs.charged_create_state_gas(),
1748 });
1749 return None;
1750 }
1751
1752 let call_inputs =
1753 get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut())
1754 .ok()?;
1755
1756 self.inner.pending_create2_redirects.push(PendingCreate2Redirect {
1758 depth: ecx.journal().depth(),
1759 charged_create_state_gas: inputs.charged_create_state_gas(),
1760 });
1761
1762 *frame_input = FrameInput::Call(Box::new(call_inputs));
1764 }
1765
1766 None
1767 }
1768
1769 fn frame_end(
1770 &mut self,
1771 ecx: &mut FoundryContextFor<'_, FEN>,
1772 _frame_input: &FrameInput,
1773 frame_result: &mut FrameResult,
1774 ) {
1775 let depth = ecx.journal().depth();
1776 self.finish_create2_redirect(depth, frame_result);
1777 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots) {
1778 self.finish_isolated_snapshot_frame(ecx);
1779 }
1780
1781 let result = frame_result.instruction_result();
1782 if !self.in_inner_context && depth == 0 {
1783 let failed = std::mem::take(&mut self.inner.top_level_frame_failed_before_rewrite)
1784 || !result.is_ok();
1785 self.top_level_frame_end(ecx, failed);
1786 if !self.enable_isolation {
1787 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1788 cheats.track_isolated_snapshots = false;
1789 cheats.isolated_snapshot_restores.clear();
1790 }
1791 self.inner.isolated_frame_checkpoints.clear();
1792 }
1793 }
1794 }
1795
1796 fn call(
1797 &mut self,
1798 ecx: &mut FoundryContextFor<'_, FEN>,
1799 call: &mut CallInputs,
1800 ) -> Option<CallOutcome> {
1801 if self.is_inner_context_root(ecx.journal().depth()) {
1802 self.adjust_evm_data_for_inner_context(ecx);
1803 warm_arbitrum_system_contract::<FEN>(ecx);
1804 if let Some(cheatcodes) = self.cheatcodes.as_deref() {
1806 apply_mocked_function(cheatcodes, ecx, call);
1807 }
1808 return None;
1809 }
1810
1811 if ecx.journal().depth() == 0 {
1812 self.top_level_frame_start(ecx);
1813 }
1814
1815 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1816 revert_diag.frame_start();
1817 }
1818
1819 let storage_hook_callback = self
1820 .cheatcodes
1821 .as_deref()
1822 .is_some_and(|cheatcodes| cheatcodes.is_storage_hook_callback(ecx, call));
1823 let storage_hook_active =
1824 self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
1825
1826 if !storage_hook_active {
1827 call_inspectors!(
1828 #[ret]
1829 [&mut self.fuzzer],
1830 |inspector| {
1831 let mut out = None;
1832 if let Some(output) = inspector.call(ecx, call) {
1833 out = Some(Some(output));
1834 }
1835 out
1836 }
1837 );
1838 }
1839
1840 if self.tracer.is_some() {
1841 crate::utils::cold_path();
1842 let (output, trace_idx) = {
1843 let tracer = self.tracer.as_deref_mut().unwrap();
1844 let output = tracer.call(ecx, call);
1845 (output, tracer.traces().nodes().len() - 1)
1846 };
1847 self.pending_call_traces.push(PendingCallTrace { trace_idx, executed_address: None });
1848 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1849 revert_diag.set_trace_node(trace_idx);
1850 }
1851 if output.is_some() {
1852 return output;
1853 }
1854 }
1855 if let Some(recorder) = self.debug_recorder.as_deref_mut() {
1856 recorder.call(ecx, call);
1857 }
1858
1859 call_inspectors!(
1860 #[ret]
1861 [
1862 &mut self.log_collector,
1863 &mut self.printer,
1864 &mut self.revert_diag,
1865 &mut self.tempo_labels
1866 ],
1867 |inspector| inspector.call(ecx, call).map(Some),
1868 );
1869
1870 if storage_hook_callback {
1873 if let Some(pending) = self.pending_call_traces.last_mut() {
1874 pending.executed_address = Some(call.bytecode_address);
1875 }
1876 return None;
1877 }
1878
1879 let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
1883 let isolate = self.enable_isolation
1886 && !self.in_inner_context
1887 && (ecx.journal().depth() == 1
1888 || self.inner.synthetic_create_depth == Some(ecx.journal().depth()));
1889 let mut cheatcode_outcome = None;
1890 if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
1891 apply_mocked_function(cheatcodes, ecx, call);
1892
1893 let execution_disable_fee_charge = ecx.cfg_env().disable_fee_charge;
1894 if let Some(disable_fee_charge) = self.inner.outer_disable_fee_charge {
1895 ecx.cfg_env_mut().disable_fee_charge = disable_fee_charge;
1896 }
1897 cheatcode_outcome = cheatcodes.call_with_executor(
1898 ecx,
1899 call,
1900 self.inner,
1901 isolate && call.scheme.is_call(),
1902 );
1903 ecx.cfg_env_mut().disable_fee_charge = execution_disable_fee_charge;
1904 }
1905
1906 if let Some(trace_idx) = trace_idx
1907 && let Some(tracer) = self.tracer.as_deref_mut()
1908 {
1909 let caller = match call.scheme {
1910 CallScheme::DelegateCall | CallScheme::CallCode => call.transfer_to(),
1911 CallScheme::Call | CallScheme::StaticCall => call.transfer_from(),
1912 };
1913 let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
1914 debug_assert_eq!(node.trace.depth, ecx.journal().depth());
1915 node.trace.caller = caller;
1916 }
1917
1918 if let Some(output) = cheatcode_outcome {
1919 return Some(output);
1920 }
1921
1922 if let Some(outcome) = handle_arbitrum_system_call::<FEN>(ecx, call) {
1923 return Some(outcome);
1924 }
1925
1926 if let Some(pending) = self.pending_call_traces.last_mut() {
1927 pending.executed_address = Some(call.bytecode_address);
1928 }
1929
1930 if isolate {
1931 match call.scheme {
1932 CallScheme::Call => {
1934 let input = call.input.bytes(ecx);
1935 let precharged_state = call
1936 .charged_new_account_state_gas
1937 .then_some(ecx.cfg().gas_params().new_account_state_gas());
1938 let (result, _, was_precompile_called) = self.transact_inner(
1939 ecx,
1940 TxKind::Call(call.transfer_to()),
1941 call.transfer_from(),
1942 input,
1943 IsolatedGas {
1944 regular_limit: call.gas_limit,
1945 reservoir: call.reservoir,
1946 precharged_state,
1947 },
1948 call.value.get(),
1949 );
1950 return Some(CallOutcome {
1951 result,
1952 memory_offset: call.return_memory_offset.clone(),
1953 was_precompile_called,
1954 precompile_call_logs: vec![],
1955 charged_new_account_state_gas: call.charged_new_account_state_gas,
1956 });
1957 }
1958 CallScheme::StaticCall => {
1960 let (_, journal_inner) = ecx.db_journal_inner_mut();
1961 let JournaledState { state, warm_addresses, .. } = journal_inner;
1962 for (addr, acc_mut) in state {
1963 if let Some(cheatcodes) = &self.cheatcodes
1965 && cheatcodes.has_arbitrary_storage(addr)
1966 {
1967 continue;
1968 }
1969
1970 if warm_addresses.is_cold(addr) {
1971 acc_mut.mark_cold();
1972 }
1973
1974 for slot_mut in acc_mut.storage.values_mut() {
1975 slot_mut.is_cold = true;
1976 }
1977 }
1978 }
1979 CallScheme::CallCode | CallScheme::DelegateCall => {}
1981 }
1982 }
1983
1984 None
1985 }
1986
1987 fn call_end(
1988 &mut self,
1989 ecx: &mut FoundryContextFor<'_, FEN>,
1990 inputs: &CallInputs,
1991 outcome: &mut CallOutcome,
1992 ) {
1993 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
1994 && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
1995 {
1996 frame.failed |= !outcome.result.result.is_ok();
1997 }
1998
1999 if self.is_inner_context_root(ecx.journal().depth()) {
2002 self.isolated_call_was_precompile = Some(outcome.was_precompile_called);
2003 return;
2004 }
2005
2006 if ecx.journal().depth() == 0 {
2007 self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
2008 }
2009
2010 if let Some(pending) = self.pending_call_traces.pop()
2011 && let Some(tracer) = self.tracer.as_deref_mut()
2012 {
2013 let trace = &mut tracer.traces_mut().nodes_mut()[pending.trace_idx].trace;
2014 if trace.address == P256_VERIFY {
2015 trace.maybe_precompile = Some(
2016 pending.executed_address == Some(P256_VERIFY) && outcome.was_precompile_called,
2017 );
2018 }
2019 }
2020
2021 self.do_call_end(ecx, inputs, outcome);
2022
2023 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2024 revert_diag.frame_end();
2025 }
2026 }
2027
2028 fn create(
2029 &mut self,
2030 ecx: &mut FoundryContextFor<'_, FEN>,
2031 create: &mut CreateInputs,
2032 ) -> Option<CreateOutcome> {
2033 if self.is_inner_context_root(ecx.journal().depth()) {
2034 self.adjust_evm_data_for_inner_context(ecx);
2035 warm_arbitrum_system_contract::<FEN>(ecx);
2036 return None;
2037 }
2038
2039 if ecx.journal().depth() == 0 {
2040 self.top_level_frame_start(ecx);
2041 }
2042
2043 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2044 revert_diag.frame_start();
2045 }
2046
2047 if self.tracer.is_some() {
2048 crate::utils::cold_path();
2049 let (output, trace_idx) = {
2050 let tracer = self.tracer.as_deref_mut().unwrap();
2051 let output = tracer.create(ecx, create);
2052 (output, tracer.traces().nodes().len() - 1)
2053 };
2054 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2055 revert_diag.set_trace_node(trace_idx);
2056 }
2057 if output.is_some() {
2058 return output;
2059 }
2060 }
2061 if let Some(recorder) = self.debug_recorder.as_deref_mut() {
2062 recorder.create(ecx, create);
2063 }
2064
2065 call_inspectors!(
2066 #[ret]
2067 [&mut self.line_coverage],
2068 |inspector| inspector.create(ecx, create).map(Some),
2069 );
2070
2071 let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
2072 let mut cheatcode_outcome = None;
2073 if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
2074 cheatcode_outcome = cheatcodes.create(ecx, create);
2075 }
2076
2077 if let Some(trace_idx) = trace_idx
2078 && let Some(tracer) = self.tracer.as_deref_mut()
2079 {
2080 let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
2081 debug_assert_eq!(node.trace.depth, ecx.journal().depth());
2082 node.trace.caller = create.caller();
2083 }
2084
2085 if let Some(output) = cheatcode_outcome {
2086 return Some(output);
2087 }
2088
2089 if let Some(error) = self.inner.pending_create2_error.take() {
2092 return Some(error);
2093 }
2094
2095 if !matches!(create.scheme(), CreateScheme::Create2 { .. })
2096 && self.enable_isolation
2097 && !self.in_inner_context
2098 && (ecx.journal().depth() == 1
2099 || self.synthetic_create_depth == Some(ecx.journal().depth()))
2100 {
2101 let precomputed_address = ecx
2105 .journal()
2106 .evm_state()
2107 .get(&create.caller())
2108 .map(|acc| create.caller().create(acc.info.nonce));
2109 let precharged_state = create
2110 .charged_create_state_gas()
2111 .then_some(ecx.cfg().gas_params().create_state_gas());
2112
2113 let (result, address, _) = self.transact_inner(
2114 ecx,
2115 TxKind::Create,
2116 create.caller(),
2117 create.init_code().clone(),
2118 IsolatedGas {
2119 regular_limit: create.gas_limit(),
2120 reservoir: create.reservoir(),
2121 precharged_state,
2122 },
2123 create.value(),
2124 );
2125 let address =
2126 address.or_else(|| if result.is_revert() { precomputed_address } else { None });
2127 return Some(CreateOutcome {
2128 result,
2129 address,
2130 charged_create_state_gas: create.charged_create_state_gas(),
2131 });
2132 }
2133
2134 None
2135 }
2136
2137 fn create_end(
2138 &mut self,
2139 ecx: &mut FoundryContextFor<'_, FEN>,
2140 call: &CreateInputs,
2141 outcome: &mut CreateOutcome,
2142 ) {
2143 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
2144 && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
2145 {
2146 frame.failed |= !outcome.result.result.is_ok();
2147 }
2148
2149 if outcome.result.result.is_ok()
2150 && let Some(address) = outcome.address
2151 {
2152 self.locally_created_accounts.insert(address);
2153
2154 if self.in_inner_context
2155 && let Some(inner_context) = &mut self.inner_context_data
2156 {
2157 inner_context.locally_created_accounts.insert(address);
2158 }
2159 }
2160
2161 if self.is_inner_context_root(ecx.journal().depth()) {
2164 return;
2165 }
2166
2167 if ecx.journal().depth() == 0 {
2168 self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
2169 }
2170
2171 self.do_create_end(ecx, call, outcome);
2172
2173 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2174 revert_diag.frame_end();
2175 }
2176 }
2177
2178 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
2179 call_inspectors!([&mut self.printer], |inspector| {
2180 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
2181 inspector, contract, target, value,
2182 )
2183 });
2184 }
2185}
2186
2187fn warm_arbitrum_system_contract<FEN: FoundryEvmNetwork>(ecx: &mut FoundryContextFor<'_, FEN>) {
2188 if !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id()) {
2189 return;
2190 }
2191
2192 let mut access_list = ecx.journal_inner().warm_addresses.access_list().clone();
2195 access_list.entry(arbitrum::ARB_SYS_ADDRESS).or_default();
2196 ecx.journal_mut().warm_access_list(access_list);
2197}
2198
2199fn handle_arbitrum_system_call<FEN: FoundryEvmNetwork>(
2200 ecx: &mut FoundryContextFor<'_, FEN>,
2201 call: &CallInputs,
2202) -> Option<CallOutcome> {
2203 if call.transfer_to() != arbitrum::ARB_SYS_ADDRESS
2204 || call.bytecode_address != arbitrum::ARB_SYS_ADDRESS
2205 || !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id())
2206 {
2207 return None;
2208 }
2209
2210 let input = call.input.bytes(ecx);
2211 if input.get(..4) != Some(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR) {
2212 return None;
2213 }
2214
2215 let block_number = match ecx.db().active_fork_block_number() {
2218 Some(block_number) => block_number,
2219 None => ecx.block().number().saturating_to(),
2220 };
2221 let Some((gas_cost, output)) = arbitrum::arb_block_number_call(call.gas_limit, block_number)
2222 else {
2223 return Some(arbitrum_call_outcome(
2224 call,
2225 InstructionResult::PrecompileOOG,
2226 0,
2227 Bytes::new(),
2228 ));
2229 };
2230
2231 Some(arbitrum_call_outcome(call, InstructionResult::Return, gas_cost, output))
2232}
2233
2234fn arbitrum_call_outcome(
2235 call: &CallInputs,
2236 result: InstructionResult,
2237 gas_used: u64,
2238 output: Bytes,
2239) -> CallOutcome {
2240 let mut gas = Gas::new(call.gas_limit);
2241 if result.is_ok() {
2242 let _ = gas.record_regular_cost(gas_used);
2243 } else {
2244 gas.spend_all();
2245 }
2246
2247 CallOutcome {
2248 result: InterpreterResult { result, output, gas },
2249 memory_offset: call.return_memory_offset.clone(),
2250 was_precompile_called: true,
2251 precompile_call_logs: vec![],
2252 charged_new_account_state_gas: call.charged_new_account_state_gas,
2253 }
2254}
2255
2256impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStackRefMut<'_, FEN> {
2257 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2258 call_inspectors!(
2259 #[ret]
2260 [&mut self.cheatcodes],
2261 |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) },
2262 );
2263
2264 false
2265 }
2266
2267 fn console_log(&mut self, msg: &str) {
2268 call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log(
2269 inspector, msg
2270 ));
2271 }
2272
2273 fn get_networks(&self) -> NetworkConfigs {
2274 self.inner.networks
2275 }
2276
2277 fn create2_deployer(&self) -> Address {
2278 self.inner.create2_deployer
2279 }
2280}
2281
2282impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for InspectorStack<FEN> {
2283 #[inline(always)]
2284 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2285 self.as_mut().step_inlined(interpreter, ecx)
2286 }
2287
2288 #[inline(always)]
2289 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2290 self.as_mut().step_end_inlined(interpreter, ecx)
2291 }
2292
2293 fn call(
2294 &mut self,
2295 context: &mut FoundryContextFor<'_, FEN>,
2296 inputs: &mut CallInputs,
2297 ) -> Option<CallOutcome> {
2298 self.as_mut().call(context, inputs)
2299 }
2300
2301 fn call_end(
2302 &mut self,
2303 context: &mut FoundryContextFor<'_, FEN>,
2304 inputs: &CallInputs,
2305 outcome: &mut CallOutcome,
2306 ) {
2307 self.as_mut().call_end(context, inputs, outcome)
2308 }
2309
2310 fn create(
2311 &mut self,
2312 context: &mut FoundryContextFor<'_, FEN>,
2313 create: &mut CreateInputs,
2314 ) -> Option<CreateOutcome> {
2315 self.as_mut().create(context, create)
2316 }
2317
2318 fn create_end(
2319 &mut self,
2320 context: &mut FoundryContextFor<'_, FEN>,
2321 call: &CreateInputs,
2322 outcome: &mut CreateOutcome,
2323 ) {
2324 self.as_mut().create_end(context, call, outcome)
2325 }
2326
2327 fn initialize_interp(
2328 &mut self,
2329 interpreter: &mut Interpreter,
2330 ecx: &mut FoundryContextFor<'_, FEN>,
2331 ) {
2332 self.as_mut().initialize_interp(interpreter, ecx)
2333 }
2334
2335 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
2336 self.as_mut().log(ecx, log)
2337 }
2338
2339 fn log_full(
2340 &mut self,
2341 interpreter: &mut Interpreter,
2342 ecx: &mut FoundryContextFor<'_, FEN>,
2343 log: Log,
2344 ) {
2345 self.as_mut().log_full(interpreter, ecx, log)
2346 }
2347
2348 fn frame_start(
2349 &mut self,
2350 context: &mut FoundryContextFor<'_, FEN>,
2351 frame_input: &mut FrameInput,
2352 ) -> Option<FrameResult> {
2353 self.as_mut().frame_start(context, frame_input)
2354 }
2355
2356 fn frame_end(
2357 &mut self,
2358 context: &mut FoundryContextFor<'_, FEN>,
2359 frame_input: &FrameInput,
2360 frame_result: &mut FrameResult,
2361 ) {
2362 self.as_mut().frame_end(context, frame_input, frame_result)
2363 }
2364
2365 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
2366 call_inspectors!([&mut self.inner.printer], |inspector| {
2367 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
2368 inspector, contract, target, value,
2369 )
2370 });
2371 }
2372}
2373
2374impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStack<FEN> {
2375 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2376 self.as_mut().should_use_create2_factory(depth, inputs)
2377 }
2378
2379 fn get_networks(&self) -> NetworkConfigs {
2380 self.networks
2381 }
2382
2383 fn create2_deployer(&self) -> Address {
2384 self.create2_deployer
2385 }
2386}
2387
2388impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> {
2389 type Target = &'a mut InspectorStackInner;
2390
2391 fn deref(&self) -> &Self::Target {
2392 &self.inner
2393 }
2394}
2395
2396impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStackRefMut<'_, FEN> {
2397 fn deref_mut(&mut self) -> &mut Self::Target {
2398 &mut self.inner
2399 }
2400}
2401
2402impl<FEN: FoundryEvmNetwork> Deref for InspectorStack<FEN> {
2403 type Target = InspectorStackInner;
2404
2405 fn deref(&self) -> &Self::Target {
2406 &self.inner
2407 }
2408}
2409
2410impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStack<FEN> {
2411 fn deref_mut(&mut self) -> &mut Self::Target {
2412 &mut self.inner
2413 }
2414}
2415
2416impl InspectorStackInner {
2417 #[inline]
2418 const fn refresh_static_opcode_dispatch(&mut self) {
2419 self.refresh_static_step_dispatch();
2420 self.refresh_static_step_end_dispatch();
2421 }
2422
2423 #[inline]
2424 const fn refresh_static_step_dispatch(&mut self) {
2425 self.static_step_dispatch = if self.edge_coverage.is_none()
2426 && self.line_coverage.is_none()
2427 && self.printer.is_none()
2428 && self.revert_diag.is_none()
2429 && self.script_execution_inspector.is_none()
2430 && self.tracer.is_none()
2431 && self.debug_recorder.is_none()
2432 {
2433 if self.fuzzer.is_some() {
2434 OpcodeStepDispatch::FuzzerOnly
2435 } else {
2436 OpcodeStepDispatch::None
2437 }
2438 } else {
2439 OpcodeStepDispatch::General
2440 };
2441 }
2442
2443 #[inline]
2444 const fn refresh_static_step_end_dispatch(&mut self) {
2445 self.has_static_step_end_inspectors = self.chisel_state.is_some()
2446 || self.printer.is_some()
2447 || self.revert_diag.is_some()
2448 || self.tracer.is_some()
2449 || self.debug_recorder.is_some();
2450 }
2451
2452 fn next_batch_create_salt(&mut self, chain_id: u64, nonce: u64) -> U256 {
2455 let process_salt = *self.batch_rewrite_process_salt.get_or_insert_with(rand::random);
2456 let counter = self.batch_create_counter;
2457 self.batch_create_counter = counter.wrapping_add(1);
2458 compute_batch_create_salt(process_salt, chain_id, nonce, counter)
2459 }
2460
2461 fn is_inner_context_root(&self, depth: usize) -> bool {
2463 self.in_inner_context
2464 && self.inner_context_data.as_ref().is_some_and(|inner| depth == inner.root_depth)
2465 }
2466
2467 fn with_snapshot_tracking<FEN: FoundryEvmNetwork, R>(
2469 &mut self,
2470 cheats: &mut Cheatcodes<FEN>,
2471 f: impl FnOnce(&mut Self, &mut Cheatcodes<FEN>) -> R,
2472 ) -> R {
2473 let tracking = std::mem::replace(&mut cheats.track_isolated_snapshots, true);
2474 let isolation = std::mem::replace(&mut cheats.in_isolation_context, false);
2475 let capture = std::mem::replace(&mut cheats.capture_isolated_snapshot_restore, true);
2476 let restores = std::mem::take(&mut cheats.isolated_snapshot_restores);
2477 let pending = cheats.pending_isolated_snapshot_journal.take();
2478 let frames = std::mem::take(&mut self.isolated_frame_checkpoints);
2479 let result = f(self, cheats);
2480 self.isolated_frame_checkpoints = frames;
2481 cheats.pending_isolated_snapshot_journal = pending;
2482 cheats.isolated_snapshot_restores = restores;
2483 cheats.capture_isolated_snapshot_restore = capture;
2484 cheats.in_isolation_context = isolation;
2485 cheats.track_isolated_snapshots = tracking;
2486 result
2487 }
2488}
2489
2490fn compute_batch_create_salt(process_salt: u64, chain_id: u64, nonce: u64, counter: u64) -> U256 {
2496 let mut buf = [0u8; 32];
2497 buf[0..8].copy_from_slice(&process_salt.to_be_bytes());
2498 buf[8..16].copy_from_slice(&chain_id.to_be_bytes());
2499 buf[16..24].copy_from_slice(&nonce.to_be_bytes());
2500 buf[24..32].copy_from_slice(&counter.to_be_bytes());
2501 keccak256(buf).into()
2502}
2503
2504fn apply_mocked_function<FEN: FoundryEvmNetwork>(
2506 cheatcodes: &Cheatcodes<FEN>,
2507 ecx: &mut FoundryContextFor<'_, FEN>,
2508 call: &mut CallInputs,
2509) {
2510 if let Some(mocks) = cheatcodes.core.mocked_functions.get(&call.bytecode_address) {
2511 let input_bytes = call.input.bytes(ecx);
2512 if let Some(target) = mocks
2515 .get(&input_bytes)
2516 .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector)))
2517 {
2518 call.bytecode_address = *target;
2519
2520 let target =
2521 ecx.journal_mut().load_account_with_code(*target).expect("failed to load account");
2522 call.known_bytecode =
2523 (target.info.code_hash(), target.info.code.clone().unwrap_or_default());
2524 }
2525 }
2526}
2527
2528#[cfg(test)]
2529mod tests {
2530 use super::*;
2531
2532 #[test]
2533 fn opcode_dispatch_defaults_to_no_static_inspectors() {
2534 let stack = InspectorStackInner::default();
2535
2536 assert_eq!(stack.static_step_dispatch, OpcodeStepDispatch::None);
2537 assert!(!stack.has_static_step_end_inspectors);
2538 }
2539
2540 #[test]
2541 fn opcode_dispatch_uses_fuzzer_fast_path_when_fuzzer_is_only_static_step_inspector() {
2542 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2543 stack.set_fuzzer(Fuzzer::new(16, None));
2544
2545 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2546 assert!(!stack.inner.has_static_step_end_inspectors);
2547
2548 stack.collect_line_coverage(true);
2549 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2550
2551 stack.collect_line_coverage(false);
2552 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2553 }
2554
2555 #[test]
2556 fn opcode_dispatch_tracks_general_step_and_step_end_inspectors() {
2557 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2558
2559 stack.print(true);
2560 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2561 assert!(stack.inner.has_static_step_end_inspectors);
2562
2563 stack.print(false);
2564 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2565 assert!(!stack.inner.has_static_step_end_inspectors);
2566
2567 stack.tracing_requirements(TraceRequirements::none().with_calls(true));
2568 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2569 assert!(stack.inner.has_static_step_end_inspectors);
2570
2571 stack.tracing_requirements(TraceRequirements::none());
2572 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2573 assert!(!stack.inner.has_static_step_end_inspectors);
2574
2575 stack.set_chisel(0);
2576 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2577 assert!(stack.inner.has_static_step_end_inspectors);
2578 }
2579
2580 #[test]
2581 fn opcode_dispatch_tracks_script_and_edge_coverage_inspectors() {
2582 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2583
2584 stack.script(Address::with_last_byte(1));
2585 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2586 assert!(!stack.inner.has_static_step_end_inspectors);
2587
2588 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2589 stack.collect_edge_coverage(true);
2590 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2591 stack.collect_edge_coverage(false);
2592 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2593 }
2594
2595 #[test]
2596 fn revert_diagnostic_frames_remain_balanced() {
2597 let mut inspector = RevertDiagnostic::default();
2598 inspector.frame_start();
2599 inspector.set_trace_node(0);
2600 inspector.frame_start();
2601 inspector.set_trace_node(1);
2602 inspector.frame_end();
2603 inspector.frame_end();
2604
2605 assert!(inspector.into_diagnostics().is_empty());
2606 }
2607
2608 #[test]
2609 fn distinct_salts_across_simulations_at_same_nonce() {
2610 let a = compute_batch_create_salt(0xabcd_ef01_2345_6789, 1, 5, 0);
2612 let b = compute_batch_create_salt(0x1122_3344_5566_7788, 1, 5, 0);
2613 assert_ne!(a, b);
2614 }
2615
2616 #[test]
2617 fn counter_changes_salt() {
2618 let a = compute_batch_create_salt(1, 1, 5, 0);
2619 let b = compute_batch_create_salt(1, 1, 5, 1);
2620 assert_ne!(a, b);
2621 }
2622
2623 #[test]
2624 fn chain_id_changes_salt() {
2625 let a = compute_batch_create_salt(1, 1, 5, 0);
2626 let b = compute_batch_create_salt(1, 2, 5, 0);
2627 assert_ne!(a, b);
2628 }
2629
2630 #[test]
2631 fn deterministic_for_same_inputs() {
2632 let a = compute_batch_create_salt(42, 1, 5, 7);
2633 let b = compute_batch_create_salt(42, 1, 5, 7);
2634 assert_eq!(a, b);
2635 }
2636
2637 #[test]
2638 fn distinct_create2_addresses_across_inspector_instances_at_same_onchain_state() {
2639 let factory = Address::with_last_byte(0x42);
2642 let init_code = b"\x60\x80\x60\x40".as_slice();
2643
2644 let mut a = InspectorStackInner::default();
2645 let mut b = InspectorStackInner::default();
2646 let salt_a = a.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2647 let salt_b = b.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2648
2649 let addr_a = factory.create2_from_code(salt_a, init_code);
2650 let addr_b = factory.create2_from_code(salt_b, init_code);
2651 assert_ne!(addr_a, addr_b);
2652 }
2653}