1use super::{
2 Cheatcodes, CheatsConfig, ChiselState, CmpOperands, CustomPrintTracer, EdgeCovConfig,
3 EdgeCovInspector, EdgeCoverage, Fuzzer, LineCoverageCollector, LogCollector, RevertDiagnostic,
4 ScriptExecutionInspector, TempoLabels, TracingInspector,
5};
6use alloy_primitives::{
7 Address, B256, Bytes, Log, TxKind, U256, keccak256,
8 map::{AddressHashMap, AddressHashSet, AddressMap},
9};
10
11use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, NestedEvmClosureFor, Wallets};
12use foundry_common::{compile::Analysis, sh_warn};
13use foundry_config::FuzzCorpusConfig;
14use foundry_evm_core::{
15 FoundryBlock, FoundryTransaction, InspectorExt,
16 backend::{ContextUpdateFor, DatabaseError, DatabaseExt, JournaledState},
17 constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH,
18 env::FoundryContextExt,
19 evm::{
20 BlockEnvFor, ChainFor, EthEvmNetwork, EvmEnvFor, EvmFactoryFor, FoundryContextFor,
21 FoundryEvmFactory, FoundryEvmNetwork, TxEnvFor, get_create2_factory_call_inputs,
22 merge_child_state, prepare_child_state, with_inherited_evm,
23 },
24 precompiles::P256_VERIFY,
25};
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},
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.set_analysis(CheatcodeAnalysis::new(analysis));
283 }
284 if let Some(wallets) = wallets {
286 cheatcodes.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 edge_coverage: Option<Box<EdgeCovInspector>>,
450 pub fuzzer: Option<Box<Fuzzer>>,
451 pub line_coverage: Option<Box<LineCoverageCollector>>,
452 pub log_collector: Option<Box<LogCollector>>,
453 pub printer: Option<Box<CustomPrintTracer>>,
454 pub revert_diag: Option<Box<RevertDiagnostic>>,
455 pub script_execution_inspector: Option<Box<ScriptExecutionInspector>>,
456 pub tempo_labels: Option<Box<TempoLabels>>,
457 pub tracer: Option<Box<TracingInspector>>,
458
459 pub sancov_edges: bool,
462 pub sancov_trace_cmp: bool,
464 pub enable_isolation: bool,
465 pub networks: NetworkConfigs,
466 pub extra_cheatcode_addresses: &'static [Address],
468 pub create2_deployer: Address,
469 pub in_inner_context: bool,
471 pub inner_context_data: Option<InnerContextData>,
472 pub locally_created_accounts: AddressHashSet,
474 pub top_frame_journal: AddressMap<Account>,
475 top_level_frame_failed_before_rewrite: bool,
477 outer_disable_fee_charge: Option<bool>,
479 isolated_call_was_precompile: Option<bool>,
481 synthetic_create_depth: Option<usize>,
483 pub reverter: Option<Address>,
485 pending_create2_redirects: Vec<PendingCreate2Redirect>,
487 pending_call_traces: Vec<PendingCallTrace>,
489 isolated_frame_checkpoints: Vec<IsolatedFrameCheckpoint>,
492 pub pending_create2_error: Option<CreateOutcome>,
495 pub batch_create_counter: u64,
497 pub batch_rewrite_warned: bool,
499 pub batch_rewrite_process_salt: Option<u64>,
502 execution_cancellation: Option<EvmExecutionCancellation>,
504 cancellation_poll_counter: u8,
506 execution_cancelled: bool,
508 #[cfg(test)]
509 early_exit_test_gate: Option<EarlyExitTestGate>,
510 static_step_dispatch: OpcodeStepDispatch,
511 has_static_step_end_inspectors: bool,
512}
513
514pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> {
517 pub cheatcodes: Option<&'a mut Cheatcodes<FEN>>,
518 pub inner: &'a mut InspectorStackInner,
519}
520
521impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for InspectorStackInner {
522 fn with_nested_evm(
523 &mut self,
524 cheats: &mut Cheatcodes<FEN>,
525 ecx: &mut FoundryContextFor<'_, FEN>,
526 f: NestedEvmClosureFor<'_, FEN>,
527 ) -> Result<(), EVMError<DatabaseError>> {
528 let previous = self.synthetic_create_depth;
529 let create2_redirects = self.pending_create2_redirects.len();
530 let snapshot_checkpoints = self.isolated_frame_checkpoints.len();
531 let snapshot_restores = cheats.isolated_snapshot_restores.len();
532 let pending_snapshot_journal = cheats
533 .track_isolated_snapshots
534 .then(|| cheats.pending_isolated_snapshot_journal.clone());
535 let capture_snapshot_restore = cheats.capture_isolated_snapshot_restore;
536 self.synthetic_create_depth = (ecx.journal().depth() == 1).then_some(2);
537 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
538 let result = with_inherited_evm::<FEN::EvmFactory, _>(ecx, &mut inspector, f);
539 self.synthetic_create_depth = previous;
540 self.pending_create2_redirects.truncate(create2_redirects);
542 if result.is_err()
543 && let Some(pending_snapshot_journal) = pending_snapshot_journal
544 {
545 self.isolated_frame_checkpoints.truncate(snapshot_checkpoints);
546 cheats.isolated_snapshot_restores.truncate(snapshot_restores);
547 cheats.pending_isolated_snapshot_journal = pending_snapshot_journal;
548 cheats.capture_isolated_snapshot_restore = capture_snapshot_restore;
549 }
550 result
551 }
552
553 fn with_fresh_nested_evm(
554 &mut self,
555 cheats: &mut Cheatcodes<FEN>,
556 ecx: &mut FoundryContextFor<'_, FEN>,
557 chain_context: ChainFor<FEN>,
558 f: NestedEvmClosureFor<'_, FEN>,
559 ) -> Result<EvmEnvFor<FEN>, EVMError<DatabaseError>> {
560 self.with_snapshot_tracking(cheats, |inner, cheats| {
561 let depth = ecx.journal().depth();
562 let previous_inner_context = inner.in_inner_context;
563 let previous_inner_context_data = inner.inner_context_data.replace(InnerContextData {
564 original_origin: ecx.tx().caller(),
565 locally_created_accounts: AddressHashSet::default(),
566 root_depth: depth,
567 });
568 let previous_precompile = inner.isolated_call_was_precompile.take();
569 inner.in_inner_context = true;
570 let mut evm_env = ecx.evm_clone();
571 let (db, _) = ecx.db_journal_inner_mut();
572 let inherited_disable_fee_charge = evm_env.cfg_env.disable_fee_charge;
573 if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
574 evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
575 }
576 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
577 let mut evm = FEN::EvmFactory::default().create_nested_evm_with_inspector(
578 db,
579 evm_env,
580 &mut inspector,
581 );
582 *evm.chain_mut() = chain_context;
583 evm.journal_inner_mut().depth = depth;
586 let result = f(&mut *evm).map(|()| {
587 let mut evm_env = evm.to_evm_env();
588 evm_env.cfg_env.disable_fee_charge = inherited_disable_fee_charge;
589 evm_env
590 });
591 drop(evm);
592 inner.in_inner_context = previous_inner_context;
593 inner.inner_context_data = previous_inner_context_data;
594 inner.isolated_call_was_precompile = previous_precompile;
595 result
596 })
597 }
598
599 fn transact_on_db(
600 &mut self,
601 cheats: &mut Cheatcodes<FEN>,
602 ecx: &mut FoundryContextFor<'_, FEN>,
603 fork_id: Option<U256>,
604 transaction: B256,
605 ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>> {
606 self.with_snapshot_tracking(cheats, |inner, cheats| {
607 let mut evm_env = ecx.evm_clone();
608 if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
609 evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
610 }
611 let outer_tx_env = ecx.tx_clone();
612 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
613 let (db, inner) = ecx.db_journal_inner_mut();
614 db.transact(fork_id, transaction, evm_env, &outer_tx_env, inner, &mut inspector)
615 })
616 }
617
618 fn transact_from_tx_on_db(
619 &mut self,
620 cheats: &mut Cheatcodes<FEN>,
621 ecx: &mut FoundryContextFor<'_, FEN>,
622 tx_env: TxEnvFor<FEN>,
623 ) -> eyre::Result<()> {
624 self.with_snapshot_tracking(cheats, |inner, cheats| {
625 let mut evm_env = ecx.evm_clone();
626 if let Some(disable_fee_charge) = inner.outer_disable_fee_charge {
627 evm_env.cfg_env.disable_fee_charge = disable_fee_charge;
628 }
629 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner };
630 let (db, inner) = ecx.db_journal_inner_mut();
631 db.transact_from_tx(tx_env, evm_env, inner, &mut inspector)
632 })
633 }
634
635 fn console_log(&mut self, msg: &str) {
636 if let Some(ref mut collector) = self.log_collector {
637 InspectorExt::console_log(&mut **collector, msg);
638 }
639 }
640
641 fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
642 self.tracer.as_deref_mut()
643 }
644}
645
646impl<FEN: FoundryEvmNetwork> Default for InspectorStack<FEN> {
647 fn default() -> Self {
648 Self::new()
649 }
650}
651
652impl<FEN: FoundryEvmNetwork> InspectorStack<FEN> {
653 #[inline]
659 pub fn new() -> Self {
660 Self { cheatcodes: None, inner: InspectorStackInner::default() }
661 }
662
663 #[inline]
665 pub fn set_analysis(&mut self, analysis: Analysis) {
666 self.analysis = Some(analysis);
667 }
668
669 #[inline]
671 pub(crate) fn set_early_exit(&mut self, early_exit: EarlyExit) {
672 self.set_execution_cancellation(EvmExecutionCancellation::early_exit(early_exit));
673 }
674
675 #[inline]
677 pub(crate) fn set_execution_cancellation(&mut self, cancellation: EvmExecutionCancellation) {
678 self.execution_cancellation = Some(cancellation);
679 }
680
681 #[inline]
683 pub(crate) const fn execution_cancelled(&self) -> bool {
684 self.inner.execution_cancelled
685 }
686
687 #[cfg(test)]
688 pub(crate) fn set_early_exit_test_gate(
689 &mut self,
690 entered: std::sync::mpsc::Sender<()>,
691 release: std::sync::mpsc::Receiver<()>,
692 target_pc: usize,
693 ) {
694 self.early_exit_test_gate = Some(EarlyExitTestGate {
695 entered,
696 release: Arc::new(std::sync::Mutex::new(release)),
697 target_pc,
698 notified: Arc::new(std::sync::atomic::AtomicBool::new(false)),
699 });
700 }
701
702 #[inline]
704 pub fn set_block(&mut self, block: BlockEnvFor<FEN>) {
705 if let Some(cheatcodes) = &mut self.cheatcodes {
706 cheatcodes.block = Some(block);
707 }
708 }
709
710 #[inline]
712 pub fn set_gas_price(&mut self, gas_price: u128) {
713 if let Some(cheatcodes) = &mut self.cheatcodes {
714 cheatcodes.gas_price = Some(gas_price);
715 }
716 }
717
718 #[inline]
720 pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes<FEN>) {
721 self.cheatcodes = Some(cheatcodes.into());
722 }
723
724 #[inline]
726 pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) {
727 self.fuzzer = Some(fuzzer.into());
728 self.refresh_static_step_dispatch();
729 }
730
731 #[inline]
733 pub fn set_chisel(&mut self, final_pc: usize) {
734 self.chisel_state = Some(ChiselState::new(final_pc).into());
735 self.refresh_static_step_end_dispatch();
736 }
737
738 #[inline]
740 pub fn collect_line_coverage(&mut self, yes: bool) {
741 self.line_coverage = yes.then(Default::default);
742 self.refresh_static_step_dispatch();
743 }
744
745 #[inline]
747 pub fn collect_edge_coverage(&mut self, yes: bool) {
748 self.edge_coverage = yes.then(|| EdgeCovInspector::default().into());
749 self.refresh_static_step_dispatch();
750 }
751
752 #[inline]
754 pub fn collect_edge_coverage_with_edge_config(&mut self, config: EdgeCovConfig) {
755 self.edge_coverage = Some(EdgeCovInspector::with_config(config).into());
756 self.refresh_static_step_dispatch();
757 }
758
759 #[inline]
763 pub fn collect_edge_coverage_with_config(&mut self, corpus: &FuzzCorpusConfig) {
764 self.edge_coverage = corpus
765 .collect_evm_edge_coverage()
766 .then(|| EdgeCovInspector::with_config(corpus.into()).into());
767 self.refresh_static_step_dispatch();
768 }
769
770 #[inline]
772 pub fn collect_evm_cmp_log(&mut self, yes: bool) {
773 if yes {
774 self.edge_coverage
775 .get_or_insert_with(|| EdgeCovInspector::with_cmp_log_only().into())
776 .enable_cmp_log(true);
777 } else if let Some(edge_coverage) = &mut self.edge_coverage {
778 edge_coverage.enable_cmp_log(false);
779 }
780 self.refresh_static_step_dispatch();
781 }
782
783 #[inline]
785 pub const fn collect_sancov_edges(&mut self, yes: bool) {
786 self.inner.sancov_edges = yes;
787 }
788
789 #[inline]
791 pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) {
792 self.inner.sancov_trace_cmp = yes;
793 }
794
795 #[inline]
797 pub const fn enable_isolation(&mut self, yes: bool) {
798 self.inner.enable_isolation = yes;
799 }
800
801 #[inline]
803 pub const fn networks(&mut self, networks: NetworkConfigs) {
804 self.inner.networks = networks;
805 }
806
807 #[inline]
809 pub const fn extra_cheatcode_addresses(&self) -> &'static [Address] {
810 self.inner.extra_cheatcode_addresses
811 }
812
813 #[inline]
815 pub const fn set_extra_cheatcode_addresses(&mut self, addresses: &'static [Address]) {
816 self.inner.extra_cheatcode_addresses = addresses;
817 }
818
819 #[inline]
821 pub fn set_create2_deployer(&mut self, deployer: Address) {
822 self.create2_deployer = deployer;
823 }
824
825 #[inline]
830 pub fn collect_logs(&mut self, live_logs: Option<bool>) {
831 self.log_collector = live_logs.map(|live_logs| {
832 Box::new(if live_logs {
833 LogCollector::LiveLogs
834 } else {
835 LogCollector::Capture { logs: Vec::new() }
836 })
837 });
838 }
839
840 #[inline]
842 pub fn print(&mut self, yes: bool) {
843 self.printer = yes.then(Default::default);
844 self.refresh_static_opcode_dispatch();
845 }
846
847 #[inline]
849 pub fn tracing_requirements(&mut self, requirements: TraceRequirements) {
850 let config = requirements.into_config();
851 self.revert_diag = config.is_some().then(RevertDiagnostic::default).map(Into::into);
852
853 if let Some(config) = config {
854 *self.tracer.get_or_insert_with(Default::default).config_mut() = config;
855 } else {
856 self.tracer = None;
857 }
858 self.refresh_static_opcode_dispatch();
859 }
860
861 #[inline]
863 pub fn script(&mut self, script_address: Address) {
864 self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
865 script_address;
866 if let Some(cheatcodes) = &mut self.cheatcodes {
867 cheatcodes.script_address = Some(script_address);
868 }
869 self.refresh_static_step_dispatch();
870 }
871
872 #[inline(always)]
873 fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> {
874 InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner }
875 }
876
877 pub fn collect(self) -> InspectorData<FEN> {
879 let Self {
880 mut cheatcodes,
881 inner:
882 InspectorStackInner {
883 chisel_state,
884 line_coverage,
885 edge_coverage,
886 log_collector,
887 tempo_labels,
888 tracer,
889 revert_diag,
890 reverter,
891 ..
892 },
893 } = self;
894
895 let trace_diagnostics =
896 revert_diag.map(|revert_diag| revert_diag.into_diagnostics()).unwrap_or_default();
897
898 let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| {
899 let ignored = cheatcodes
900 .as_mut()
901 .map(|cheatcodes| {
902 let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored);
903
904 if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call {
906 ignored.insert(last_pause_call, (arena.nodes().len(), 0));
907 }
908
909 ignored
910 })
911 .unwrap_or_default();
912
913 SparsedTraceArena { arena, ignored, diagnostics: trace_diagnostics }
914 });
915
916 let (edge_coverage, evm_cmp_values) = edge_coverage
917 .map(|edge_coverage| {
918 let (hitcount, cmp_values) = edge_coverage.into_parts();
919 (Some(hitcount), (!cmp_values.is_empty()).then_some(cmp_values))
920 })
921 .unwrap_or_default();
922
923 InspectorData {
924 logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(),
925 labels: {
926 let mut labels = cheatcodes.as_ref().map(|c| c.labels.clone()).unwrap_or_default();
927 if let Some(tempo_labels) = tempo_labels {
928 labels.extend(tempo_labels.labels);
929 }
930 labels
931 },
932 traces,
933 line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()),
934 edge_coverage,
935 evm_cmp_values,
936 cheatcodes,
937 chisel_state: chisel_state.and_then(|state| state.state),
938 reverter,
939 }
940 }
941}
942
943impl<FEN: FoundryEvmNetwork> InspectorStackRefMut<'_, FEN> {
944 fn finish_create2_redirect(&mut self, depth: usize, frame_result: &mut FrameResult) {
945 let Some(redirect) = self
946 .inner
947 .pending_create2_redirects
948 .last()
949 .copied()
950 .filter(|redirect| redirect.depth == depth)
951 else {
952 return;
953 };
954 self.inner.pending_create2_redirects.pop();
955
956 let FrameResult::Call(call) = frame_result else {
957 debug_assert!(false, "pending CREATE2 redirect ended with non-call result");
958 return;
959 };
960
961 let address = match call.instruction_result() {
962 return_ok!() => Address::try_from(call.output().as_ref())
963 .map_err(|_| {
964 call.result = InterpreterResult {
965 result: InstructionResult::Revert,
966 output: "invalid CREATE2 factory output".into(),
967 gas: Gas::new(call.result.gas.limit()),
968 };
969 })
970 .ok(),
971 _ => None,
972 };
973
974 *frame_result = FrameResult::Create(CreateOutcome {
975 result: call.result.clone(),
976 address,
977 charged_create_state_gas: redirect.charged_create_state_gas,
978 });
979 }
980
981 fn adjust_evm_data_for_inner_context<CTX: FoundryContextExt>(&mut self, ecx: &mut CTX) {
986 let inner_context_data =
987 self.inner_context_data.as_ref().expect("should be called in inner context");
988 ecx.tx_mut().set_caller(inner_context_data.original_origin);
989 }
990
991 fn do_call_end(
992 &mut self,
993 ecx: &mut FoundryContextFor<'_, FEN>,
994 inputs: &CallInputs,
995 outcome: &mut CallOutcome,
996 ) {
997 let storage_hook_active =
998 self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
999 if !storage_hook_active && let Some(fuzzer) = &mut self.fuzzer {
1000 fuzzer.call_end(ecx, inputs, outcome);
1001 }
1002
1003 let result = outcome.result.result;
1004 call_inspectors!(
1005 #[ret]
1006 [&mut self.tracer, &mut self.cheatcodes, &mut self.printer, &mut self.revert_diag],
1007 |inspector| {
1008 let previous_output = outcome.output().clone();
1009 inspector.call_end(ecx, inputs, outcome);
1010
1011 let different = outcome.result.result != result
1014 || (outcome.result.result == InstructionResult::Revert
1015 && outcome.output() != &previous_output);
1016 different.then_some(())
1017 },
1018 );
1019
1020 if result.is_revert() && self.reverter.is_none() {
1022 self.reverter = Some(inputs.transfer_to());
1023 }
1024 }
1025
1026 fn do_create_end(
1027 &mut self,
1028 ecx: &mut FoundryContextFor<'_, FEN>,
1029 call: &CreateInputs,
1030 outcome: &mut CreateOutcome,
1031 ) {
1032 let result = outcome.result.result;
1033 call_inspectors!(
1034 #[ret]
1035 [&mut self.line_coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer],
1036 |inspector| {
1037 let previous_output = outcome.output().clone();
1038 inspector.create_end(ecx, call, outcome);
1039
1040 let different = outcome.result.result != result
1043 || (outcome.result.result == InstructionResult::Revert
1044 && outcome.output() != &previous_output);
1045 different.then_some(())
1046 },
1047 );
1048 }
1049
1050 fn transact_inner(
1051 &mut self,
1052 ecx: &mut FoundryContextFor<'_, FEN>,
1053 kind: TxKind,
1054 caller: Address,
1055 input: Bytes,
1056 gas: IsolatedGas,
1057 value: U256,
1058 ) -> (InterpreterResult, Option<Address>, bool) {
1059 let IsolatedGas { regular_limit, reservoir, precharged_state } = gas;
1060 let source_fork_id = ecx.db().active_fork_id();
1061 let cached_evm_env = ecx.evm_clone();
1062 let cached_tx_env = ecx.tx_clone();
1063 self.isolated_call_was_precompile = None;
1064
1065 ecx.block_mut().set_basefee(0);
1066 ecx.cfg_env_mut().disable_fee_charge = true;
1067
1068 let chain_id = ecx.cfg().chain_id();
1069 ecx.tx_mut().set_chain_id(Some(chain_id));
1070 ecx.tx_mut().set_caller(caller);
1071 ecx.tx_mut().set_kind(kind);
1072 ecx.tx_mut().set_data(input);
1073 ecx.tx_mut().set_enveloped_tx(Bytes::new());
1074 ecx.tx_mut().set_value(value);
1075 let initial_gas = calculate_initial_gas(ecx.tx(), ecx.cfg());
1076 let regular_gas_limit = regular_limit.saturating_add(initial_gas.initial_total_gas());
1080 ecx.cfg_env_mut().tx_gas_limit_cap = Some(regular_gas_limit);
1081 let floor_gas =
1085 (initial_gas.floor_gas() > regular_gas_limit).then_some(initial_gas.floor_gas());
1086 if floor_gas.is_some() {
1087 ecx.cfg_env_mut().disable_eip7623 = true;
1088 }
1089 let mut tx_gas_limit = regular_gas_limit.saturating_add(reservoir);
1090 if let Some(precharged_state) = precharged_state {
1091 tx_gas_limit = tx_gas_limit.saturating_add(precharged_state);
1092 }
1093 ecx.tx_mut().set_gas_limit(tx_gas_limit);
1094
1095 if !ecx.cfg().is_block_gas_limit_disabled() {
1098 let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit());
1099 ecx.tx_mut().set_gas_limit(gas_limit);
1100 }
1101 ecx.tx_mut().set_gas_price(0);
1102 if ecx.tx().tx_type() == TransactionType::Eip4844 as u8 {
1110 ecx.tx_mut().set_tx_type(TransactionType::Eip1559 as u8);
1111 ecx.tx_mut().set_blob_hashes(Vec::new());
1112 }
1113
1114 let locally_created_accounts = ecx
1115 .journal()
1116 .evm_state()
1117 .iter()
1118 .filter_map(|(addr, acc)| acc.is_created_locally().then_some(*addr))
1119 .collect();
1120 let root_depth = ecx.journal().depth();
1121 self.inner_context_data = Some(InnerContextData {
1122 original_origin: cached_tx_env.caller(),
1123 locally_created_accounts,
1124 root_depth,
1125 });
1126 self.outer_disable_fee_charge = Some(cached_evm_env.cfg_env.disable_fee_charge);
1127 self.in_inner_context = true;
1128
1129 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1133 cheats.pending_isolated_snapshot_journal = None;
1134 cheats.track_isolated_snapshots = true;
1135 cheats.isolated_snapshot_restores.clear();
1136 cheats.in_isolation_context = true;
1137 }
1138 self.inner.isolated_frame_checkpoints.clear();
1139
1140 let evm_env = ecx.evm_clone();
1141 let tx_env = ecx.tx_clone();
1142 let factory = FEN::EvmFactory::default();
1143 let chain_context = ecx.chain().clone();
1144
1145 let isolated_state = prepare_child_state(ecx.journal_inner());
1146
1147 #[cfg(feature = "monad")]
1148 let state = foundry_evm_core::FoundryJournal::capture_reserve_balance(ecx.journal());
1149 #[cfg(feature = "monad")]
1150 let mut reserve_balance = None;
1151 let mut nested_chain_context = None;
1152 let res = self.with_inspector(|mut inspector| {
1153 let (res, nested_env) = {
1154 let (db, _) = ecx.db_journal_inner_mut();
1155 let mut evm = factory.create_nested_evm_with_inspector(db, evm_env, &mut inspector);
1156 *evm.chain_mut() = chain_context;
1157 evm.journal_inner_mut().state = isolated_state;
1158 #[cfg(feature = "monad")]
1159 {
1160 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1161 evm.journal_mut(),
1162 state,
1163 );
1164 foundry_evm_core::evm::refresh_nested_chain_journal(&mut *evm);
1165 foundry_evm_core::FoundryJournal::set_preserve_reserve_balance(
1166 evm.journal_mut(),
1167 true,
1168 );
1169 }
1170 evm.journal_inner_mut().depth = root_depth;
1172 let res = evm.transact_raw(tx_env);
1173 nested_chain_context = Some(evm.chain_mut().clone());
1174 #[cfg(feature = "monad")]
1175 {
1176 reserve_balance =
1177 Some(foundry_evm_core::FoundryJournal::capture_reserve_balance(
1178 evm.journal_mut(),
1179 ));
1180 }
1181 (res, evm.to_evm_env())
1182 };
1183
1184 let mut restored_evm_env = nested_env;
1187 restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1188 restored_evm_env.cfg_env.disable_fee_charge = cached_evm_env.cfg_env.disable_fee_charge;
1189 restored_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1190 restored_evm_env.cfg_env.disable_eip7623 = cached_evm_env.cfg_env.disable_eip7623;
1191 ecx.set_evm(restored_evm_env);
1192 ecx.set_tx(cached_tx_env);
1193
1194 res
1195 });
1196 *ecx.chain_mut() = nested_chain_context.expect("nested EVM chain context was captured");
1197
1198 self.in_inner_context = false;
1199 self.inner_context_data = None;
1200 self.outer_disable_fee_charge = None;
1201
1202 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1205 cheats.in_isolation_context = false;
1206 cheats.track_isolated_snapshots = false;
1207 }
1208
1209 let mut gas = Gas::new_with_regular_gas_and_reservoir(regular_limit, reservoir);
1210 let was_precompile_called = self.isolated_call_was_precompile.take().unwrap_or(false);
1211
1212 let Ok(res) = res else {
1213 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1214 cheats.pending_isolated_snapshot_journal = None;
1215 cheats.isolated_snapshot_restores.clear();
1216 }
1217 self.inner.isolated_frame_checkpoints.clear();
1218 #[cfg(feature = "monad")]
1219 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1220 ecx.journal_mut(),
1221 reserve_balance.expect("isolated transaction state was captured"),
1222 );
1223 #[cfg(feature = "monad")]
1224 refresh_chain_journal(ecx);
1225 let result =
1227 InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas };
1228 return (result, None, was_precompile_called);
1229 };
1230
1231 let mut transaction_gas = *res.result.gas();
1232 if let Some(floor_gas) = floor_gas {
1233 transaction_gas = transaction_gas.with_floor_gas(floor_gas);
1234 }
1235 let tx_state_gas_used = transaction_gas.block_state_gas_used();
1236 let precharged_state = precharged_state.map_or(0, |gas| gas.min(tx_state_gas_used));
1239 let state_gas_used = tx_state_gas_used - precharged_state;
1240 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1241 cheats
1242 .gas_metering
1243 .set_isolated_snapshot_gas_used(transaction_gas.tx_gas_used(), precharged_state);
1244 }
1245 if !(gas.record_state_cost(state_gas_used)
1249 && gas.record_regular_cost(transaction_gas.block_regular_gas_used()))
1250 {
1251 gas.spend_all();
1252 }
1253
1254 let rolled_back = !res.result.is_success();
1255
1256 let restored_journal = self
1257 .cheatcodes
1258 .as_deref_mut()
1259 .and_then(|cheats| cheats.pending_isolated_snapshot_journal.take());
1260 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1261 cheats.isolated_snapshot_restores.clear();
1262 }
1263 self.inner.isolated_frame_checkpoints.clear();
1264 let restored_snapshot = if !rolled_back && let Some(restored_journal) = restored_journal {
1265 let (_, journaled_state) = ecx.db_journal_inner_mut();
1266 journaled_state.journal = restored_journal;
1267 true
1268 } else {
1269 false
1270 };
1271 if source_fork_id == ecx.db().active_fork_id() {
1272 merge_child_state(ecx.journal_mut().evm_state_mut(), res.state, restored_snapshot);
1273 } else {
1274 *ecx.journal_mut().evm_state_mut() = res.state;
1275 }
1276 #[cfg(feature = "monad")]
1277 foundry_evm_core::FoundryJournal::restore_reserve_balance(
1278 ecx.journal_mut(),
1279 reserve_balance.expect("isolated transaction state was captured"),
1280 );
1281 #[cfg(feature = "monad")]
1282 refresh_chain_journal(ecx);
1283
1284 let (result, address, output) = match res.result {
1285 ExecutionResult::Success { reason, gas: _, logs: _, output } => {
1286 gas.set_refund(transaction_gas.final_refunded() as i64);
1287 let address = match output {
1288 Output::Create(_, address) => address,
1289 Output::Call(_) => None,
1290 };
1291 (reason.into(), address, output.into_data())
1292 }
1293 ExecutionResult::Halt { reason, .. } => {
1294 (InstructionResult::from(reason), None, Bytes::new())
1295 }
1296 ExecutionResult::Revert { output, .. } => (InstructionResult::Revert, None, output),
1297 };
1298 #[cfg(feature = "monad")]
1299 if rolled_back {
1300 refresh_chain_journal(ecx);
1301 }
1302 (InterpreterResult { result, output, gas }, address, was_precompile_called)
1303 }
1304
1305 fn with_inspector<O>(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O {
1308 let mut cheatcodes = self
1309 .cheatcodes
1310 .as_deref_mut()
1311 .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone())));
1312 let mut inner = std::mem::take(self.inner);
1313
1314 let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects);
1317
1318 let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner });
1319
1320 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1321 *cheats = cheatcodes.unwrap();
1322 }
1323
1324 inner.pending_create2_redirects = saved_create2_redirects;
1325 *self.inner = inner;
1326
1327 out
1328 }
1329
1330 fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
1332 self.locally_created_accounts.clear();
1333 self.top_level_frame_failed_before_rewrite = false;
1334 warm_arbitrum_system_contract::<FEN>(ecx);
1335 if let Some(cheatcodes) = &mut self.cheatcodes {
1336 cheatcodes.clear_storage_hook_mapping_slots();
1337 }
1338
1339 if self.enable_isolation {
1340 self.top_frame_journal.clone_from(ecx.journal().evm_state());
1343 }
1344 }
1345
1346 fn top_level_frame_end(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, failed: bool) {
1348 if let Some(cheatcodes) = &mut self.cheatcodes {
1349 cheatcodes.clear_storage_hook_mapping_slots();
1350 }
1351 if !failed {
1352 return;
1353 }
1354 if let Some(cheats) = self.cheatcodes.as_mut() {
1357 cheats.on_revert(ecx);
1358 }
1359
1360 if self.enable_isolation {
1363 *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal);
1364 }
1365
1366 #[cfg(feature = "monad")]
1367 refresh_chain_journal(ecx);
1368 }
1369
1370 #[inline(always)]
1376 fn step_inlined(
1377 &mut self,
1378 interpreter: &mut Interpreter,
1379 ecx: &mut FoundryContextFor<'_, FEN>,
1380 ) {
1381 let storage_hook_active = if let Some(cheats) = self.cheatcodes.as_mut() {
1382 if cheats.has_storage_hooks() && cheats.finish_storage_hook_callback(interpreter, ecx) {
1383 return;
1384 }
1385 cheats.pc = interpreter.bytecode.pc();
1386 cheats.is_storage_hook_active()
1387 } else {
1388 false
1389 };
1390
1391 #[cfg(test)]
1392 if interpreter.bytecode.opcode() == op::JUMPDEST
1393 && let Some(gate) = &self.inner.early_exit_test_gate
1394 && interpreter.bytecode.pc() == gate.target_pc
1395 && !gate.notified.swap(true, std::sync::atomic::Ordering::Relaxed)
1396 {
1397 let _ = gate.entered.send(());
1398 let _ = gate
1399 .release
1400 .lock()
1401 .expect("early-exit test gate lock poisoned")
1402 .recv_timeout(std::time::Duration::from_secs(1));
1403 }
1404
1405 if let Some(cancellation) = &self.inner.execution_cancellation {
1406 let poll_deadline = self.inner.cancellation_poll_counter == 0;
1407 self.inner.cancellation_poll_counter =
1408 self.inner.cancellation_poll_counter.wrapping_add(1);
1409 if cancellation.should_stop(poll_deadline) {
1410 self.inner.execution_cancelled = true;
1411 interpreter.halt(InstructionResult::Stop);
1412 return;
1413 }
1414 }
1415
1416 match self.static_step_dispatch {
1417 OpcodeStepDispatch::None => {}
1418 OpcodeStepDispatch::FuzzerOnly => {
1419 if !storage_hook_active && let Some(inspector) = &mut self.fuzzer {
1420 inspector.step(interpreter, ecx);
1421 }
1422 }
1423 OpcodeStepDispatch::General => {
1424 if !storage_hook_active {
1425 call_inspectors!(
1426 [
1427 &mut self.edge_coverage,
1429 &mut self.fuzzer,
1430 &mut self.line_coverage,
1431 ],
1432 |inspector| (**inspector).step(interpreter, ecx),
1433 );
1434 }
1435 call_inspectors!(
1436 [
1437 &mut self.printer,
1439 &mut self.revert_diag,
1440 &mut self.script_execution_inspector,
1441 &mut self.tracer,
1442 ],
1443 |inspector| (**inspector).step(interpreter, ecx),
1444 );
1445 }
1446 }
1447
1448 if let Some(cheats) = self.cheatcodes.as_mut()
1449 && cheats.has_step_hooks()
1450 {
1451 let opcode = interpreter.bytecode.opcode();
1452 if !cheats.has_recording_accesses_only_step_hook()
1453 || matches!(opcode, op::SLOAD | op::SSTORE)
1454 {
1455 crate::utils::cold_path();
1456 cheats.step(interpreter, ecx);
1457 }
1458 }
1459 }
1460
1461 #[inline(always)]
1462 fn step_end_inlined(
1463 &mut self,
1464 interpreter: &mut Interpreter,
1465 ecx: &mut FoundryContextFor<'_, FEN>,
1466 ) {
1467 if self.has_static_step_end_inspectors {
1468 call_inspectors!(
1469 [
1470 &mut self.chisel_state,
1472 &mut self.printer,
1473 &mut self.revert_diag,
1474 &mut self.tracer,
1475 ],
1476 |inspector| (**inspector).step_end(interpreter, ecx),
1477 );
1478 }
1479
1480 if let Some(fuzzer) = &mut self.fuzzer
1481 && fuzzer.mapping_slots.is_some()
1482 {
1483 fuzzer.step_end(interpreter, ecx);
1484 }
1485
1486 if let Some(cheats) = self.cheatcodes.as_mut()
1487 && cheats.has_step_end_hooks()
1488 {
1489 crate::utils::cold_path();
1490 cheats.step_end(interpreter, ecx);
1491 }
1492 }
1493
1494 fn finish_isolated_snapshot_frame(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
1495 let Some(frame) = self.inner.isolated_frame_checkpoints.pop() else { return };
1496
1497 if frame.failed
1498 && let Some(cheats) = self.cheatcodes.as_deref_mut()
1499 && cheats.isolated_snapshot_restores.len() > frame.restore_len
1500 {
1501 cheats.isolated_snapshot_restores.truncate(frame.restore_len + 1);
1502 let mut journal = cheats.isolated_snapshot_restores.pop().unwrap();
1503 journal.checkpoint_revert(frame.checkpoint);
1504 journal.depth = frame.return_depth;
1505 if cheats.isolated_snapshot_restores.is_empty() {
1506 cheats.pending_isolated_snapshot_journal = None;
1507 }
1508 ecx.set_journal_inner(journal);
1509 }
1510
1511 if let Some(caller) = self.inner.isolated_frame_checkpoints.last()
1515 && let Some(cheats) = self.cheatcodes.as_deref_mut()
1516 {
1517 cheats.isolated_snapshot_restores.truncate(caller.restore_len + 1);
1518 }
1519
1520 if let Some(cheats) = self.cheatcodes.as_deref_mut()
1521 && cheats.pending_isolated_snapshot_journal.is_some()
1522 {
1523 cheats.pending_isolated_snapshot_journal = Some(ecx.journal_inner().journal.clone());
1524 }
1525 }
1526}
1527
1528impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>>
1529 for InspectorStackRefMut<'_, FEN>
1530{
1531 fn initialize_interp(
1532 &mut self,
1533 interpreter: &mut Interpreter,
1534 ecx: &mut FoundryContextFor<'_, FEN>,
1535 ) {
1536 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
1537 && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
1538 && frame.is_create
1539 && let Some(offset) = ecx.journal_inner().journal[frame.checkpoint.journal_i..]
1540 .iter()
1541 .position(|entry| matches!(entry, JournalEntry::AccountCreated { .. }))
1542 {
1543 frame.checkpoint.journal_i += offset;
1544 }
1545
1546 let address = interpreter.input.target_address();
1547 let should_mark_created_locally = self.locally_created_accounts.contains(&address)
1548 || self
1549 .inner_context_data
1550 .as_ref()
1551 .is_some_and(|ctx| ctx.locally_created_accounts.contains(&address));
1552 if should_mark_created_locally
1553 && let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&address)
1554 && account.is_created()
1555 {
1556 account.mark_created_locally();
1557 }
1558
1559 call_inspectors!(
1560 [
1561 &mut self.line_coverage,
1562 &mut self.tracer,
1563 &mut self.cheatcodes,
1564 &mut self.script_execution_inspector,
1565 &mut self.printer
1566 ],
1567 |inspector| inspector.initialize_interp(interpreter, ecx),
1568 );
1569 }
1570
1571 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1572 self.step_inlined(interpreter, ecx);
1573 }
1574
1575 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1576 self.step_end_inlined(interpreter, ecx);
1577 }
1578
1579 #[allow(clippy::redundant_clone)]
1580 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1581 call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1582 inspector.log(ecx, log.clone())
1583 });
1584 if let Some(inspector) = &mut self.cheatcodes
1585 && inspector.has_log_hooks()
1586 {
1587 crate::utils::cold_path();
1588 inspector.log(ecx, log.clone());
1589 }
1590 call_inspectors!([&mut self.printer], |inspector| { inspector.log(ecx, log.clone()) });
1591 }
1592
1593 #[allow(clippy::redundant_clone)]
1594 fn log_full(
1595 &mut self,
1596 interpreter: &mut Interpreter,
1597 ecx: &mut FoundryContextFor<'_, FEN>,
1598 log: Log,
1599 ) {
1600 call_inspectors!([&mut self.tracer, &mut self.log_collector], |inspector| {
1601 inspector.log_full(interpreter, ecx, log.clone())
1602 });
1603 if let Some(inspector) = &mut self.cheatcodes
1604 && inspector.has_log_hooks()
1605 {
1606 crate::utils::cold_path();
1607 inspector.log_full(interpreter, ecx, log.clone());
1608 }
1609 call_inspectors!([&mut self.printer], |inspector| {
1610 inspector.log_full(interpreter, ecx, log.clone())
1611 });
1612 }
1613
1614 fn frame_start(
1615 &mut self,
1616 ecx: &mut FoundryContextFor<'_, FEN>,
1617 frame_input: &mut FrameInput,
1618 ) -> Option<FrameResult> {
1619 if !self.enable_isolation
1621 && !self.in_inner_context
1622 && ecx.journal().depth() == 0
1623 && let Some(cheats) = self.cheatcodes.as_deref_mut()
1624 {
1625 cheats.pending_isolated_snapshot_journal = None;
1626 cheats.track_isolated_snapshots = true;
1627 cheats.isolated_snapshot_restores.clear();
1628 self.inner.isolated_frame_checkpoints.clear();
1629 }
1630 if let Some(cheats) = self.cheatcodes.as_deref_mut()
1631 && cheats.track_isolated_snapshots
1632 {
1633 let journal = ecx.journal_inner();
1634 let restore_len = cheats.isolated_snapshot_restores.len();
1635 cheats.capture_isolated_snapshot_restore = self
1639 .inner
1640 .isolated_frame_checkpoints
1641 .last()
1642 .is_none_or(|caller| caller.restore_len == restore_len);
1643 self.inner.isolated_frame_checkpoints.push(IsolatedFrameCheckpoint {
1644 checkpoint: JournalCheckpoint {
1645 log_i: journal.logs.len(),
1646 journal_i: journal.journal.len(),
1647 selfdestructed_i: journal.selfdestructed_addresses.len(),
1648 },
1649 restore_len,
1650 return_depth: journal.depth,
1651 is_create: matches!(frame_input, FrameInput::Create(_)),
1652 failed: false,
1653 });
1654 }
1655
1656 if let FrameInput::Create(inputs) = frame_input
1657 && self.should_use_create2_factory(ecx.journal().depth(), inputs)
1658 {
1659 let salt = match inputs.scheme() {
1664 CreateScheme::Create2 { salt } => salt,
1665 CreateScheme::Create => {
1668 if !self.inner.batch_rewrite_warned {
1669 let _ = sh_warn!(
1670 "--batch rewrites CREATE → CREATE2 via the Arachnid factory; \
1671 deployed addresses follow the CREATE2 formula and constructor \
1672 msg.sender is the factory, not the EOA."
1673 );
1674 self.inner.batch_rewrite_warned = true;
1675 }
1676 let chain_id = ecx.cfg().chain_id();
1677 let nonce = ecx.journal_mut().load_account(inputs.caller()).ok()?.info.nonce;
1678 self.inner.next_batch_create_salt(chain_id, nonce)
1679 }
1680 _ => return None,
1681 };
1682
1683 let gas_limit = inputs.gas_limit();
1684 let create2_deployer = self.create2_deployer();
1685
1686 let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash;
1688 if code_hash == KECCAK_EMPTY {
1689 self.inner.pending_create2_error = Some(CreateOutcome {
1692 result: InterpreterResult {
1693 result: InstructionResult::Revert,
1694 output: Bytes::from(
1695 format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(),
1696 ),
1697 gas: Gas::new(gas_limit),
1698 },
1699 address: None,
1700 charged_create_state_gas: inputs.charged_create_state_gas(),
1701 });
1702 return None;
1703 } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH {
1704 self.inner.pending_create2_error = Some(CreateOutcome {
1705 result: InterpreterResult {
1706 result: InstructionResult::Revert,
1707 output: "invalid CREATE2 deployer bytecode".into(),
1708 gas: Gas::new(gas_limit),
1709 },
1710 address: None,
1711 charged_create_state_gas: inputs.charged_create_state_gas(),
1712 });
1713 return None;
1714 }
1715
1716 let call_inputs =
1717 get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut())
1718 .ok()?;
1719
1720 self.inner.pending_create2_redirects.push(PendingCreate2Redirect {
1722 depth: ecx.journal().depth(),
1723 charged_create_state_gas: inputs.charged_create_state_gas(),
1724 });
1725
1726 *frame_input = FrameInput::Call(Box::new(call_inputs));
1728 }
1729
1730 None
1731 }
1732
1733 fn frame_end(
1734 &mut self,
1735 ecx: &mut FoundryContextFor<'_, FEN>,
1736 _frame_input: &FrameInput,
1737 frame_result: &mut FrameResult,
1738 ) {
1739 let depth = ecx.journal().depth();
1740 self.finish_create2_redirect(depth, frame_result);
1741 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots) {
1742 self.finish_isolated_snapshot_frame(ecx);
1743 }
1744
1745 let result = frame_result.instruction_result();
1746 if !self.in_inner_context && depth == 0 {
1747 let failed = std::mem::take(&mut self.inner.top_level_frame_failed_before_rewrite)
1748 || !result.is_ok();
1749 self.top_level_frame_end(ecx, failed);
1750 if !self.enable_isolation {
1751 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
1752 cheats.track_isolated_snapshots = false;
1753 cheats.isolated_snapshot_restores.clear();
1754 }
1755 self.inner.isolated_frame_checkpoints.clear();
1756 }
1757 }
1758 }
1759
1760 fn call(
1761 &mut self,
1762 ecx: &mut FoundryContextFor<'_, FEN>,
1763 call: &mut CallInputs,
1764 ) -> Option<CallOutcome> {
1765 if self.is_inner_context_root(ecx.journal().depth()) {
1766 self.adjust_evm_data_for_inner_context(ecx);
1767 warm_arbitrum_system_contract::<FEN>(ecx);
1768 if let Some(cheatcodes) = self.cheatcodes.as_deref() {
1770 apply_mocked_function(cheatcodes, ecx, call);
1771 }
1772 return None;
1773 }
1774
1775 if ecx.journal().depth() == 0 {
1776 self.top_level_frame_start(ecx);
1777 }
1778
1779 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1780 revert_diag.frame_start();
1781 }
1782
1783 let storage_hook_callback = self
1784 .cheatcodes
1785 .as_deref()
1786 .is_some_and(|cheatcodes| cheatcodes.is_storage_hook_callback(ecx, call));
1787 let storage_hook_active =
1788 self.cheatcodes.as_deref().is_some_and(Cheatcodes::is_storage_hook_active);
1789
1790 if !storage_hook_active {
1791 call_inspectors!(
1792 #[ret]
1793 [&mut self.fuzzer],
1794 |inspector| {
1795 let mut out = None;
1796 if let Some(output) = inspector.call(ecx, call) {
1797 out = Some(Some(output));
1798 }
1799 out
1800 }
1801 );
1802 }
1803
1804 if self.tracer.is_some() {
1805 crate::utils::cold_path();
1806 let (output, trace_idx) = {
1807 let tracer = self.tracer.as_deref_mut().unwrap();
1808 let output = tracer.call(ecx, call);
1809 (output, tracer.traces().nodes().len() - 1)
1810 };
1811 self.pending_call_traces.push(PendingCallTrace { trace_idx, executed_address: None });
1812 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1813 revert_diag.set_trace_node(trace_idx);
1814 }
1815 if output.is_some() {
1816 return output;
1817 }
1818 }
1819
1820 call_inspectors!(
1821 #[ret]
1822 [
1823 &mut self.log_collector,
1824 &mut self.printer,
1825 &mut self.revert_diag,
1826 &mut self.tempo_labels
1827 ],
1828 |inspector| inspector.call(ecx, call).map(Some),
1829 );
1830
1831 if storage_hook_callback {
1834 if let Some(pending) = self.pending_call_traces.last_mut() {
1835 pending.executed_address = Some(call.bytecode_address);
1836 }
1837 return None;
1838 }
1839
1840 let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
1844 let isolate = self.enable_isolation
1847 && !self.in_inner_context
1848 && (ecx.journal().depth() == 1
1849 || self.inner.synthetic_create_depth == Some(ecx.journal().depth()));
1850 let mut cheatcode_outcome = None;
1851 if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
1852 apply_mocked_function(cheatcodes, ecx, call);
1853
1854 let execution_disable_fee_charge = ecx.cfg_env().disable_fee_charge;
1855 if let Some(disable_fee_charge) = self.inner.outer_disable_fee_charge {
1856 ecx.cfg_env_mut().disable_fee_charge = disable_fee_charge;
1857 }
1858 cheatcode_outcome = cheatcodes.call_with_executor(
1859 ecx,
1860 call,
1861 self.inner,
1862 isolate && call.scheme.is_call(),
1863 );
1864 ecx.cfg_env_mut().disable_fee_charge = execution_disable_fee_charge;
1865 }
1866
1867 if let Some(trace_idx) = trace_idx
1868 && let Some(tracer) = self.tracer.as_deref_mut()
1869 {
1870 let caller = match call.scheme {
1871 CallScheme::DelegateCall | CallScheme::CallCode => call.transfer_to(),
1872 CallScheme::Call | CallScheme::StaticCall => call.transfer_from(),
1873 };
1874 let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
1875 debug_assert_eq!(node.trace.depth, ecx.journal().depth());
1876 node.trace.caller = caller;
1877 }
1878
1879 if let Some(output) = cheatcode_outcome {
1880 return Some(output);
1881 }
1882
1883 if let Some(outcome) = handle_arbitrum_system_call::<FEN>(ecx, call) {
1884 return Some(outcome);
1885 }
1886
1887 if let Some(pending) = self.pending_call_traces.last_mut() {
1888 pending.executed_address = Some(call.bytecode_address);
1889 }
1890
1891 if isolate {
1892 match call.scheme {
1893 CallScheme::Call => {
1895 let input = call.input.bytes(ecx);
1896 let precharged_state = call
1897 .charged_new_account_state_gas
1898 .then_some(ecx.cfg().gas_params().new_account_state_gas());
1899 let (result, _, was_precompile_called) = self.transact_inner(
1900 ecx,
1901 TxKind::Call(call.transfer_to()),
1902 call.transfer_from(),
1903 input,
1904 IsolatedGas {
1905 regular_limit: call.gas_limit,
1906 reservoir: call.reservoir,
1907 precharged_state,
1908 },
1909 call.value.get(),
1910 );
1911 return Some(CallOutcome {
1912 result,
1913 memory_offset: call.return_memory_offset.clone(),
1914 was_precompile_called,
1915 precompile_call_logs: vec![],
1916 charged_new_account_state_gas: call.charged_new_account_state_gas,
1917 });
1918 }
1919 CallScheme::StaticCall => {
1921 let (_, journal_inner) = ecx.db_journal_inner_mut();
1922 let JournaledState { state, warm_addresses, .. } = journal_inner;
1923 for (addr, acc_mut) in state {
1924 if let Some(cheatcodes) = &self.cheatcodes
1926 && cheatcodes.has_arbitrary_storage(addr)
1927 {
1928 continue;
1929 }
1930
1931 if warm_addresses.is_cold(addr) {
1932 acc_mut.mark_cold();
1933 }
1934
1935 for slot_mut in acc_mut.storage.values_mut() {
1936 slot_mut.is_cold = true;
1937 }
1938 }
1939 }
1940 CallScheme::CallCode | CallScheme::DelegateCall => {}
1942 }
1943 }
1944
1945 None
1946 }
1947
1948 fn call_end(
1949 &mut self,
1950 ecx: &mut FoundryContextFor<'_, FEN>,
1951 inputs: &CallInputs,
1952 outcome: &mut CallOutcome,
1953 ) {
1954 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
1955 && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
1956 {
1957 frame.failed |= !outcome.result.result.is_ok();
1958 }
1959
1960 if self.is_inner_context_root(ecx.journal().depth()) {
1963 self.isolated_call_was_precompile = Some(outcome.was_precompile_called);
1964 return;
1965 }
1966
1967 if ecx.journal().depth() == 0 {
1968 self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
1969 }
1970
1971 if let Some(pending) = self.pending_call_traces.pop()
1972 && let Some(tracer) = self.tracer.as_deref_mut()
1973 {
1974 let trace = &mut tracer.traces_mut().nodes_mut()[pending.trace_idx].trace;
1975 if trace.address == P256_VERIFY {
1976 trace.maybe_precompile = Some(
1977 pending.executed_address == Some(P256_VERIFY) && outcome.was_precompile_called,
1978 );
1979 }
1980 }
1981
1982 self.do_call_end(ecx, inputs, outcome);
1983
1984 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
1985 revert_diag.frame_end();
1986 }
1987 }
1988
1989 fn create(
1990 &mut self,
1991 ecx: &mut FoundryContextFor<'_, FEN>,
1992 create: &mut CreateInputs,
1993 ) -> Option<CreateOutcome> {
1994 if self.is_inner_context_root(ecx.journal().depth()) {
1995 self.adjust_evm_data_for_inner_context(ecx);
1996 warm_arbitrum_system_contract::<FEN>(ecx);
1997 return None;
1998 }
1999
2000 if ecx.journal().depth() == 0 {
2001 self.top_level_frame_start(ecx);
2002 }
2003
2004 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2005 revert_diag.frame_start();
2006 }
2007
2008 if self.tracer.is_some() {
2009 crate::utils::cold_path();
2010 let (output, trace_idx) = {
2011 let tracer = self.tracer.as_deref_mut().unwrap();
2012 let output = tracer.create(ecx, create);
2013 (output, tracer.traces().nodes().len() - 1)
2014 };
2015 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2016 revert_diag.set_trace_node(trace_idx);
2017 }
2018 if output.is_some() {
2019 return output;
2020 }
2021 }
2022
2023 call_inspectors!(
2024 #[ret]
2025 [&mut self.line_coverage],
2026 |inspector| inspector.create(ecx, create).map(Some),
2027 );
2028
2029 let trace_idx = self.tracer.as_ref().map(|tracer| tracer.traces().nodes().len() - 1);
2030 let mut cheatcode_outcome = None;
2031 if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
2032 cheatcode_outcome = cheatcodes.create(ecx, create);
2033 }
2034
2035 if let Some(trace_idx) = trace_idx
2036 && let Some(tracer) = self.tracer.as_deref_mut()
2037 {
2038 let node = &mut tracer.traces_mut().nodes_mut()[trace_idx];
2039 debug_assert_eq!(node.trace.depth, ecx.journal().depth());
2040 node.trace.caller = create.caller();
2041 }
2042
2043 if let Some(output) = cheatcode_outcome {
2044 return Some(output);
2045 }
2046
2047 if let Some(error) = self.inner.pending_create2_error.take() {
2050 return Some(error);
2051 }
2052
2053 if !matches!(create.scheme(), CreateScheme::Create2 { .. })
2054 && self.enable_isolation
2055 && !self.in_inner_context
2056 && (ecx.journal().depth() == 1
2057 || self.synthetic_create_depth == Some(ecx.journal().depth()))
2058 {
2059 let precomputed_address = ecx
2063 .journal()
2064 .evm_state()
2065 .get(&create.caller())
2066 .map(|acc| create.caller().create(acc.info.nonce));
2067 let precharged_state = create
2068 .charged_create_state_gas()
2069 .then_some(ecx.cfg().gas_params().create_state_gas());
2070
2071 let (result, address, _) = self.transact_inner(
2072 ecx,
2073 TxKind::Create,
2074 create.caller(),
2075 create.init_code().clone(),
2076 IsolatedGas {
2077 regular_limit: create.gas_limit(),
2078 reservoir: create.reservoir(),
2079 precharged_state,
2080 },
2081 create.value(),
2082 );
2083 let address =
2084 address.or_else(|| if result.is_revert() { precomputed_address } else { None });
2085 return Some(CreateOutcome {
2086 result,
2087 address,
2088 charged_create_state_gas: create.charged_create_state_gas(),
2089 });
2090 }
2091
2092 None
2093 }
2094
2095 fn create_end(
2096 &mut self,
2097 ecx: &mut FoundryContextFor<'_, FEN>,
2098 call: &CreateInputs,
2099 outcome: &mut CreateOutcome,
2100 ) {
2101 if self.cheatcodes.as_deref().is_some_and(|cheats| cheats.track_isolated_snapshots)
2102 && let Some(frame) = self.inner.isolated_frame_checkpoints.last_mut()
2103 {
2104 frame.failed |= !outcome.result.result.is_ok();
2105 }
2106
2107 if outcome.result.result.is_ok()
2108 && let Some(address) = outcome.address
2109 {
2110 self.locally_created_accounts.insert(address);
2111
2112 if self.in_inner_context
2113 && let Some(inner_context) = &mut self.inner_context_data
2114 {
2115 inner_context.locally_created_accounts.insert(address);
2116 }
2117 }
2118
2119 if self.is_inner_context_root(ecx.journal().depth()) {
2122 return;
2123 }
2124
2125 if ecx.journal().depth() == 0 {
2126 self.inner.top_level_frame_failed_before_rewrite |= !outcome.result.result.is_ok();
2127 }
2128
2129 self.do_create_end(ecx, call, outcome);
2130
2131 if let Some(revert_diag) = self.revert_diag.as_deref_mut() {
2132 revert_diag.frame_end();
2133 }
2134 }
2135
2136 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
2137 call_inspectors!([&mut self.printer], |inspector| {
2138 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
2139 inspector, contract, target, value,
2140 )
2141 });
2142 }
2143}
2144
2145fn warm_arbitrum_system_contract<FEN: FoundryEvmNetwork>(ecx: &mut FoundryContextFor<'_, FEN>) {
2146 if !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id()) {
2147 return;
2148 }
2149
2150 let mut access_list = ecx.journal_inner().warm_addresses.access_list().clone();
2153 access_list.entry(arbitrum::ARB_SYS_ADDRESS).or_default();
2154 ecx.journal_mut().warm_access_list(access_list);
2155}
2156
2157fn handle_arbitrum_system_call<FEN: FoundryEvmNetwork>(
2158 ecx: &mut FoundryContextFor<'_, FEN>,
2159 call: &CallInputs,
2160) -> Option<CallOutcome> {
2161 if call.transfer_to() != arbitrum::ARB_SYS_ADDRESS
2162 || call.bytecode_address != arbitrum::ARB_SYS_ADDRESS
2163 || !arbitrum::is_arbitrum_chain(ecx.cfg().chain_id())
2164 {
2165 return None;
2166 }
2167
2168 let input = call.input.bytes(ecx);
2169 if input.get(..4) != Some(&arbitrum::ARB_BLOCK_NUMBER_SELECTOR) {
2170 return None;
2171 }
2172
2173 let block_number = match ecx.db().active_fork_block_number() {
2176 Some(block_number) => block_number,
2177 None => ecx.block().number().saturating_to(),
2178 };
2179 let Some((gas_cost, output)) = arbitrum::arb_block_number_call(call.gas_limit, block_number)
2180 else {
2181 return Some(arbitrum_call_outcome(
2182 call,
2183 InstructionResult::PrecompileOOG,
2184 0,
2185 Bytes::new(),
2186 ));
2187 };
2188
2189 Some(arbitrum_call_outcome(call, InstructionResult::Return, gas_cost, output))
2190}
2191
2192fn arbitrum_call_outcome(
2193 call: &CallInputs,
2194 result: InstructionResult,
2195 gas_used: u64,
2196 output: Bytes,
2197) -> CallOutcome {
2198 let mut gas = Gas::new(call.gas_limit);
2199 if result.is_ok() {
2200 let _ = gas.record_regular_cost(gas_used);
2201 } else {
2202 gas.spend_all();
2203 }
2204
2205 CallOutcome {
2206 result: InterpreterResult { result, output, gas },
2207 memory_offset: call.return_memory_offset.clone(),
2208 was_precompile_called: true,
2209 precompile_call_logs: vec![],
2210 charged_new_account_state_gas: call.charged_new_account_state_gas,
2211 }
2212}
2213
2214impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStackRefMut<'_, FEN> {
2215 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2216 call_inspectors!(
2217 #[ret]
2218 [&mut self.cheatcodes],
2219 |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) },
2220 );
2221
2222 false
2223 }
2224
2225 fn console_log(&mut self, msg: &str) {
2226 call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log(
2227 inspector, msg
2228 ));
2229 }
2230
2231 fn get_networks(&self) -> NetworkConfigs {
2232 self.inner.networks
2233 }
2234
2235 fn create2_deployer(&self) -> Address {
2236 self.inner.create2_deployer
2237 }
2238}
2239
2240impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for InspectorStack<FEN> {
2241 #[inline(always)]
2242 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2243 self.as_mut().step_inlined(interpreter, ecx)
2244 }
2245
2246 #[inline(always)]
2247 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2248 self.as_mut().step_end_inlined(interpreter, ecx)
2249 }
2250
2251 fn call(
2252 &mut self,
2253 context: &mut FoundryContextFor<'_, FEN>,
2254 inputs: &mut CallInputs,
2255 ) -> Option<CallOutcome> {
2256 self.as_mut().call(context, inputs)
2257 }
2258
2259 fn call_end(
2260 &mut self,
2261 context: &mut FoundryContextFor<'_, FEN>,
2262 inputs: &CallInputs,
2263 outcome: &mut CallOutcome,
2264 ) {
2265 self.as_mut().call_end(context, inputs, outcome)
2266 }
2267
2268 fn create(
2269 &mut self,
2270 context: &mut FoundryContextFor<'_, FEN>,
2271 create: &mut CreateInputs,
2272 ) -> Option<CreateOutcome> {
2273 self.as_mut().create(context, create)
2274 }
2275
2276 fn create_end(
2277 &mut self,
2278 context: &mut FoundryContextFor<'_, FEN>,
2279 call: &CreateInputs,
2280 outcome: &mut CreateOutcome,
2281 ) {
2282 self.as_mut().create_end(context, call, outcome)
2283 }
2284
2285 fn initialize_interp(
2286 &mut self,
2287 interpreter: &mut Interpreter,
2288 ecx: &mut FoundryContextFor<'_, FEN>,
2289 ) {
2290 self.as_mut().initialize_interp(interpreter, ecx)
2291 }
2292
2293 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
2294 self.as_mut().log(ecx, log)
2295 }
2296
2297 fn log_full(
2298 &mut self,
2299 interpreter: &mut Interpreter,
2300 ecx: &mut FoundryContextFor<'_, FEN>,
2301 log: Log,
2302 ) {
2303 self.as_mut().log_full(interpreter, ecx, log)
2304 }
2305
2306 fn frame_start(
2307 &mut self,
2308 context: &mut FoundryContextFor<'_, FEN>,
2309 frame_input: &mut FrameInput,
2310 ) -> Option<FrameResult> {
2311 self.as_mut().frame_start(context, frame_input)
2312 }
2313
2314 fn frame_end(
2315 &mut self,
2316 context: &mut FoundryContextFor<'_, FEN>,
2317 frame_input: &FrameInput,
2318 frame_result: &mut FrameResult,
2319 ) {
2320 self.as_mut().frame_end(context, frame_input, frame_result)
2321 }
2322
2323 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
2324 call_inspectors!([&mut self.inner.printer], |inspector| {
2325 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
2326 inspector, contract, target, value,
2327 )
2328 });
2329 }
2330}
2331
2332impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStack<FEN> {
2333 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2334 self.as_mut().should_use_create2_factory(depth, inputs)
2335 }
2336
2337 fn get_networks(&self) -> NetworkConfigs {
2338 self.networks
2339 }
2340
2341 fn create2_deployer(&self) -> Address {
2342 self.create2_deployer
2343 }
2344}
2345
2346impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> {
2347 type Target = &'a mut InspectorStackInner;
2348
2349 fn deref(&self) -> &Self::Target {
2350 &self.inner
2351 }
2352}
2353
2354impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStackRefMut<'_, FEN> {
2355 fn deref_mut(&mut self) -> &mut Self::Target {
2356 &mut self.inner
2357 }
2358}
2359
2360impl<FEN: FoundryEvmNetwork> Deref for InspectorStack<FEN> {
2361 type Target = InspectorStackInner;
2362
2363 fn deref(&self) -> &Self::Target {
2364 &self.inner
2365 }
2366}
2367
2368impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStack<FEN> {
2369 fn deref_mut(&mut self) -> &mut Self::Target {
2370 &mut self.inner
2371 }
2372}
2373
2374impl InspectorStackInner {
2375 #[inline]
2376 const fn refresh_static_opcode_dispatch(&mut self) {
2377 self.refresh_static_step_dispatch();
2378 self.refresh_static_step_end_dispatch();
2379 }
2380
2381 #[inline]
2382 const fn refresh_static_step_dispatch(&mut self) {
2383 self.static_step_dispatch = if self.edge_coverage.is_none()
2384 && self.line_coverage.is_none()
2385 && self.printer.is_none()
2386 && self.revert_diag.is_none()
2387 && self.script_execution_inspector.is_none()
2388 && self.tracer.is_none()
2389 {
2390 if self.fuzzer.is_some() {
2391 OpcodeStepDispatch::FuzzerOnly
2392 } else {
2393 OpcodeStepDispatch::None
2394 }
2395 } else {
2396 OpcodeStepDispatch::General
2397 };
2398 }
2399
2400 #[inline]
2401 const fn refresh_static_step_end_dispatch(&mut self) {
2402 self.has_static_step_end_inspectors = self.chisel_state.is_some()
2403 || self.printer.is_some()
2404 || self.revert_diag.is_some()
2405 || self.tracer.is_some();
2406 }
2407
2408 fn next_batch_create_salt(&mut self, chain_id: u64, nonce: u64) -> U256 {
2411 let process_salt = *self.batch_rewrite_process_salt.get_or_insert_with(rand::random);
2412 let counter = self.batch_create_counter;
2413 self.batch_create_counter = counter.wrapping_add(1);
2414 compute_batch_create_salt(process_salt, chain_id, nonce, counter)
2415 }
2416
2417 fn is_inner_context_root(&self, depth: usize) -> bool {
2419 self.in_inner_context
2420 && self.inner_context_data.as_ref().is_some_and(|inner| depth == inner.root_depth)
2421 }
2422
2423 fn with_snapshot_tracking<FEN: FoundryEvmNetwork, R>(
2425 &mut self,
2426 cheats: &mut Cheatcodes<FEN>,
2427 f: impl FnOnce(&mut Self, &mut Cheatcodes<FEN>) -> R,
2428 ) -> R {
2429 let tracking = std::mem::replace(&mut cheats.track_isolated_snapshots, true);
2430 let isolation = std::mem::replace(&mut cheats.in_isolation_context, false);
2431 let capture = std::mem::replace(&mut cheats.capture_isolated_snapshot_restore, true);
2432 let restores = std::mem::take(&mut cheats.isolated_snapshot_restores);
2433 let pending = cheats.pending_isolated_snapshot_journal.take();
2434 let frames = std::mem::take(&mut self.isolated_frame_checkpoints);
2435 let result = f(self, cheats);
2436 self.isolated_frame_checkpoints = frames;
2437 cheats.pending_isolated_snapshot_journal = pending;
2438 cheats.isolated_snapshot_restores = restores;
2439 cheats.capture_isolated_snapshot_restore = capture;
2440 cheats.in_isolation_context = isolation;
2441 cheats.track_isolated_snapshots = tracking;
2442 result
2443 }
2444}
2445
2446fn compute_batch_create_salt(process_salt: u64, chain_id: u64, nonce: u64, counter: u64) -> U256 {
2452 let mut buf = [0u8; 32];
2453 buf[0..8].copy_from_slice(&process_salt.to_be_bytes());
2454 buf[8..16].copy_from_slice(&chain_id.to_be_bytes());
2455 buf[16..24].copy_from_slice(&nonce.to_be_bytes());
2456 buf[24..32].copy_from_slice(&counter.to_be_bytes());
2457 keccak256(buf).into()
2458}
2459
2460fn apply_mocked_function<FEN: FoundryEvmNetwork>(
2462 cheatcodes: &Cheatcodes<FEN>,
2463 ecx: &mut FoundryContextFor<'_, FEN>,
2464 call: &mut CallInputs,
2465) {
2466 if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) {
2467 let input_bytes = call.input.bytes(ecx);
2468 if let Some(target) = mocks
2471 .get(&input_bytes)
2472 .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector)))
2473 {
2474 call.bytecode_address = *target;
2475
2476 let target =
2477 ecx.journal_mut().load_account_with_code(*target).expect("failed to load account");
2478 call.known_bytecode =
2479 (target.info.code_hash(), target.info.code.clone().unwrap_or_default());
2480 }
2481 }
2482}
2483
2484#[cfg(test)]
2485mod tests {
2486 use super::*;
2487
2488 #[test]
2489 fn opcode_dispatch_defaults_to_no_static_inspectors() {
2490 let stack = InspectorStackInner::default();
2491
2492 assert_eq!(stack.static_step_dispatch, OpcodeStepDispatch::None);
2493 assert!(!stack.has_static_step_end_inspectors);
2494 }
2495
2496 #[test]
2497 fn opcode_dispatch_uses_fuzzer_fast_path_when_fuzzer_is_only_static_step_inspector() {
2498 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2499 stack.set_fuzzer(Fuzzer::new(16, None));
2500
2501 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2502 assert!(!stack.inner.has_static_step_end_inspectors);
2503
2504 stack.collect_line_coverage(true);
2505 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2506
2507 stack.collect_line_coverage(false);
2508 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::FuzzerOnly);
2509 }
2510
2511 #[test]
2512 fn opcode_dispatch_tracks_general_step_and_step_end_inspectors() {
2513 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2514
2515 stack.print(true);
2516 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2517 assert!(stack.inner.has_static_step_end_inspectors);
2518
2519 stack.print(false);
2520 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2521 assert!(!stack.inner.has_static_step_end_inspectors);
2522
2523 stack.tracing_requirements(TraceRequirements::none().with_calls(true));
2524 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2525 assert!(stack.inner.has_static_step_end_inspectors);
2526
2527 stack.tracing_requirements(TraceRequirements::none());
2528 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2529 assert!(!stack.inner.has_static_step_end_inspectors);
2530
2531 stack.set_chisel(0);
2532 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2533 assert!(stack.inner.has_static_step_end_inspectors);
2534 }
2535
2536 #[test]
2537 fn opcode_dispatch_tracks_script_and_edge_coverage_inspectors() {
2538 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2539
2540 stack.script(Address::with_last_byte(1));
2541 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2542 assert!(!stack.inner.has_static_step_end_inspectors);
2543
2544 let mut stack = InspectorStack::<EthEvmNetwork>::new();
2545 stack.collect_edge_coverage(true);
2546 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::General);
2547 stack.collect_edge_coverage(false);
2548 assert_eq!(stack.inner.static_step_dispatch, OpcodeStepDispatch::None);
2549 }
2550
2551 #[test]
2552 fn revert_diagnostic_frames_remain_balanced() {
2553 let mut inspector = RevertDiagnostic::default();
2554 inspector.frame_start();
2555 inspector.set_trace_node(0);
2556 inspector.frame_start();
2557 inspector.set_trace_node(1);
2558 inspector.frame_end();
2559 inspector.frame_end();
2560
2561 assert!(inspector.into_diagnostics().is_empty());
2562 }
2563
2564 #[test]
2565 fn distinct_salts_across_simulations_at_same_nonce() {
2566 let a = compute_batch_create_salt(0xabcd_ef01_2345_6789, 1, 5, 0);
2568 let b = compute_batch_create_salt(0x1122_3344_5566_7788, 1, 5, 0);
2569 assert_ne!(a, b);
2570 }
2571
2572 #[test]
2573 fn counter_changes_salt() {
2574 let a = compute_batch_create_salt(1, 1, 5, 0);
2575 let b = compute_batch_create_salt(1, 1, 5, 1);
2576 assert_ne!(a, b);
2577 }
2578
2579 #[test]
2580 fn chain_id_changes_salt() {
2581 let a = compute_batch_create_salt(1, 1, 5, 0);
2582 let b = compute_batch_create_salt(1, 2, 5, 0);
2583 assert_ne!(a, b);
2584 }
2585
2586 #[test]
2587 fn deterministic_for_same_inputs() {
2588 let a = compute_batch_create_salt(42, 1, 5, 7);
2589 let b = compute_batch_create_salt(42, 1, 5, 7);
2590 assert_eq!(a, b);
2591 }
2592
2593 #[test]
2594 fn distinct_create2_addresses_across_inspector_instances_at_same_onchain_state() {
2595 let factory = Address::with_last_byte(0x42);
2598 let init_code = b"\x60\x80\x60\x40".as_slice();
2599
2600 let mut a = InspectorStackInner::default();
2601 let mut b = InspectorStackInner::default();
2602 let salt_a = a.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2603 let salt_b = b.next_batch_create_salt(1, 5).to_be_bytes::<32>();
2604
2605 let addr_a = factory.create2_from_code(salt_a, init_code);
2606 let addr_b = factory.create2_from_code(salt_b, init_code);
2607 assert_ne!(addr_a, addr_b);
2608 }
2609}