1use super::{
2 Cheatcodes, CheatsConfig, ChiselState, CmpOperands, CustomPrintTracer, EdgeCovConfig,
3 EdgeCovInspector, EdgeCoverage, Fuzzer, LineCoverageCollector, LogCollector, RevertDiagnostic,
4 ScriptExecutionInspector, TempoLabels, TracingInspector,
5};
6use alloy_primitives::{
7 Address, B256, Bytes, Log, TxKind, U256, keccak256,
8 map::{AddressHashMap, AddressMap},
9};
10
11use foundry_cheatcodes::{CheatcodeAnalysis, CheatcodesExecutor, NestedEvmClosure, Wallets};
12use foundry_common::{compile::Analysis, sh_warn};
13use foundry_config::FuzzCorpusConfig;
14use foundry_evm_core::{
15 FoundryBlock, FoundryTransaction, InspectorExt,
16 backend::{DatabaseError, DatabaseExt, JournaledState},
17 constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH,
18 env::FoundryContextExt,
19 evm::{
20 BlockEnvFor, EthEvmNetwork, EvmEnvFor, FoundryContextFor, FoundryEvmFactory,
21 FoundryEvmNetwork, SpecFor, TxEnvFor, get_create2_factory_call_inputs, with_cloned_context,
22 },
23};
24use foundry_evm_coverage::HitMaps;
25use foundry_evm_networks::NetworkConfigs;
26use foundry_evm_traces::{SparsedTraceArena, TraceMode};
27use revm::{
28 Inspector,
29 context::{
30 Block, Cfg, ContextTr, JournalTr, Transaction, TransactionType,
31 result::{EVMError, ExecutionResult, Output},
32 },
33 context_interface::CreateScheme,
34 handler::FrameResult,
35 interpreter::{
36 CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas,
37 InstructionResult, Interpreter, InterpreterResult, return_ok,
38 },
39 primitives::KECCAK_EMPTY,
40 state::{Account, AccountStatus},
41};
42use std::{
43 ops::{Deref, DerefMut},
44 sync::Arc,
45};
46
47#[derive(Clone, Debug)]
48#[must_use = "builders do nothing unless you call `build` on them"]
49pub struct InspectorStackBuilder<BLOCK: Clone> {
50 pub analysis: Option<Analysis>,
52 pub block: Option<BLOCK>,
57 pub gas_price: Option<u128>,
62 pub cheatcodes: Option<Arc<CheatsConfig>>,
64 pub fuzzer: Option<Fuzzer>,
66 pub trace_mode: TraceMode,
68 pub logs: Option<bool>,
73 pub line_coverage: Option<bool>,
75 pub print: Option<bool>,
77 pub chisel_state: Option<usize>,
79 pub enable_isolation: bool,
83 pub networks: NetworkConfigs,
85 pub wallets: Option<Wallets>,
87 pub create2_deployer: Address,
89}
90
91impl<BLOCK: Clone> Default for InspectorStackBuilder<BLOCK> {
92 fn default() -> Self {
93 Self {
94 analysis: None,
95 block: None,
96 gas_price: None,
97 cheatcodes: None,
98 fuzzer: None,
99 trace_mode: TraceMode::None,
100 logs: None,
101 line_coverage: None,
102 print: None,
103 chisel_state: None,
104 enable_isolation: false,
105 networks: NetworkConfigs::default(),
106 wallets: None,
107 create2_deployer: Default::default(),
108 }
109 }
110}
111
112impl<BLOCK: Clone> InspectorStackBuilder<BLOCK> {
113 #[inline]
115 pub fn new() -> Self {
116 Self::default()
117 }
118
119 #[inline]
121 pub fn set_analysis(mut self, analysis: Analysis) -> Self {
122 self.analysis = Some(analysis);
123 self
124 }
125
126 #[inline]
128 pub fn block(mut self, block: BLOCK) -> Self {
129 self.block = Some(block);
130 self
131 }
132
133 #[inline]
135 pub const fn gas_price(mut self, gas_price: u128) -> Self {
136 self.gas_price = Some(gas_price);
137 self
138 }
139
140 #[inline]
142 pub fn cheatcodes(mut self, config: Arc<CheatsConfig>) -> Self {
143 self.cheatcodes = Some(config);
144 self
145 }
146
147 #[inline]
149 pub fn wallets(mut self, wallets: Wallets) -> Self {
150 self.wallets = Some(wallets);
151 self
152 }
153
154 #[inline]
156 pub fn fuzzer(mut self, fuzzer: Fuzzer) -> Self {
157 self.fuzzer = Some(fuzzer);
158 self
159 }
160
161 #[inline]
163 pub const fn chisel_state(mut self, final_pc: usize) -> Self {
164 self.chisel_state = Some(final_pc);
165 self
166 }
167
168 #[inline]
170 pub const fn logs(mut self, live_logs: bool) -> Self {
171 self.logs = Some(live_logs);
172 self
173 }
174
175 #[inline]
177 pub const fn line_coverage(mut self, yes: bool) -> Self {
178 self.line_coverage = Some(yes);
179 self
180 }
181
182 #[inline]
184 pub const fn print(mut self, yes: bool) -> Self {
185 self.print = Some(yes);
186 self
187 }
188
189 #[inline]
192 pub fn trace_mode(mut self, mode: TraceMode) -> Self {
193 if self.trace_mode < mode {
194 self.trace_mode = mode
195 }
196 self
197 }
198
199 #[inline]
202 pub const fn enable_isolation(mut self, yes: bool) -> Self {
203 self.enable_isolation = yes;
204 self
205 }
206
207 #[inline]
209 pub const fn networks(mut self, networks: NetworkConfigs) -> Self {
210 self.networks = networks;
211 self
212 }
213
214 #[inline]
215 pub const fn create2_deployer(mut self, create2_deployer: Address) -> Self {
216 self.create2_deployer = create2_deployer;
217 self
218 }
219
220 pub fn build<FEN: FoundryEvmNetwork<EvmFactory: FoundryEvmFactory<BlockEnv = BLOCK>>>(
222 self,
223 ) -> InspectorStack<FEN> {
224 let Self {
225 analysis,
226 block,
227 gas_price,
228 cheatcodes,
229 fuzzer,
230 trace_mode,
231 logs,
232 line_coverage,
233 print,
234 chisel_state,
235 enable_isolation,
236 networks,
237 wallets,
238 create2_deployer,
239 } = self;
240 let mut stack = InspectorStack::new();
241
242 if let Some(config) = cheatcodes {
244 let mut cheatcodes = Cheatcodes::new(config);
245 if let Some(analysis) = analysis {
247 stack.set_analysis(analysis.clone());
248 cheatcodes.set_analysis(CheatcodeAnalysis::new(analysis));
249 }
250 if let Some(wallets) = wallets {
252 cheatcodes.set_wallets(wallets);
253 }
254 stack.set_cheatcodes(cheatcodes);
255 }
256
257 if let Some(fuzzer) = fuzzer {
258 stack.set_fuzzer(fuzzer);
259 }
260 if let Some(chisel_state) = chisel_state {
261 stack.set_chisel(chisel_state);
262 }
263 stack.collect_line_coverage(line_coverage.unwrap_or(false));
264 stack.collect_logs(logs);
265 stack.print(print.unwrap_or(false));
266 stack.tracing(trace_mode);
267
268 stack.enable_isolation(enable_isolation);
269 stack.networks(networks);
270 stack.set_create2_deployer(create2_deployer);
271
272 if networks.is_tempo() {
273 stack.inner.tempo_labels = Some(Box::default());
274 }
275
276 if let Some(block) = block {
278 stack.set_block(block);
279 }
280 if let Some(gas_price) = gas_price {
281 stack.set_gas_price(gas_price);
282 }
283
284 stack
285 }
286}
287
288#[macro_export]
291macro_rules! call_inspectors {
292 ([$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {
293 $(
294 if let Some($id) = $inspector {
295 $crate::utils::cold_path();
296 $body;
297 }
298 )+
299 };
300 (#[ret] [$($inspector:expr),+ $(,)?], |$id:ident $(,)?| $body:expr $(,)?) => {{
301 $(
302 if let Some($id) = $inspector {
303 $crate::utils::cold_path();
304 if let Some(result) = $body {
305 return result;
306 }
307 }
308 )+
309 }};
310}
311
312pub struct InspectorData<FEN: FoundryEvmNetwork> {
314 pub logs: Vec<Log>,
315 pub labels: AddressHashMap<String>,
316 pub traces: Option<SparsedTraceArena>,
317 pub line_coverage: Option<HitMaps>,
318 pub edge_coverage: Option<EdgeCoverage>,
319 pub evm_cmp_values: Option<Vec<CmpOperands>>,
320 pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
321 pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
322 pub reverter: Option<Address>,
323}
324
325#[derive(Debug, Clone)]
331pub struct InnerContextData {
332 original_origin: Address,
334}
335
336#[derive(Clone, Debug)]
347pub struct InspectorStack<FEN: FoundryEvmNetwork = EthEvmNetwork> {
348 #[allow(clippy::type_complexity)]
349 pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
350 pub inner: InspectorStackInner,
351}
352
353#[derive(Default, Clone, Debug)]
357pub struct InspectorStackInner {
358 pub analysis: Option<Analysis>,
360
361 pub chisel_state: Option<Box<ChiselState>>,
365 pub edge_coverage: Option<Box<EdgeCovInspector>>,
366 pub fuzzer: Option<Box<Fuzzer>>,
367 pub line_coverage: Option<Box<LineCoverageCollector>>,
368 pub log_collector: Option<Box<LogCollector>>,
369 pub printer: Option<Box<CustomPrintTracer>>,
370 pub revert_diag: Option<Box<RevertDiagnostic>>,
371 pub script_execution_inspector: Option<Box<ScriptExecutionInspector>>,
372 pub tempo_labels: Option<Box<TempoLabels>>,
373 pub tracer: Option<Box<TracingInspector>>,
374
375 pub sancov_edges: bool,
378 pub sancov_trace_cmp: bool,
380 pub enable_isolation: bool,
381 pub networks: NetworkConfigs,
382 pub create2_deployer: Address,
383 pub in_inner_context: bool,
385 pub inner_context_data: Option<InnerContextData>,
386 pub top_frame_journal: AddressMap<Account>,
387 pub reverter: Option<Address>,
389 pub pending_create2_redirects: Vec<usize>,
392 pub pending_create2_error: Option<CreateOutcome>,
395 pub batch_create_counter: u64,
397 pub batch_rewrite_warned: bool,
399 pub batch_rewrite_process_salt: Option<u64>,
402}
403
404pub struct InspectorStackRefMut<'a, FEN: FoundryEvmNetwork = EthEvmNetwork> {
407 pub cheatcodes: Option<&'a mut Cheatcodes<FEN>>,
408 pub inner: &'a mut InspectorStackInner,
409}
410
411impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for InspectorStackInner {
412 fn with_nested_evm(
413 &mut self,
414 cheats: &mut Cheatcodes<FEN>,
415 ecx: &mut FoundryContextFor<'_, FEN>,
416 f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
417 ) -> Result<(), EVMError<DatabaseError>> {
418 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
419 with_cloned_context(ecx, |db, evm_env, journal_inner| {
420 let mut evm =
421 FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector);
422 *evm.journal_inner_mut() = journal_inner;
423 f(&mut *evm)?;
424 let sub_inner = evm.journal_inner_mut().clone();
425 let sub_evm_env = evm.to_evm_env();
426 Ok((sub_evm_env, sub_inner))
427 })
428 }
429
430 fn with_fresh_nested_evm(
431 &mut self,
432 cheats: &mut Cheatcodes<FEN>,
433 db: &mut <FoundryContextFor<'_, FEN> as ContextTr>::Db,
434 evm_env: EvmEnvFor<FEN>,
435 f: NestedEvmClosure<'_, SpecFor<FEN>, BlockEnvFor<FEN>, TxEnvFor<FEN>>,
436 ) -> Result<EvmEnvFor<FEN>, EVMError<DatabaseError>> {
437 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
438 let mut evm =
439 FEN::EvmFactory::default().create_foundry_nested_evm(db, evm_env, &mut inspector);
440 f(&mut *evm)?;
441 Ok(evm.to_evm_env())
442 }
443
444 fn transact_on_db(
445 &mut self,
446 cheats: &mut Cheatcodes<FEN>,
447 ecx: &mut FoundryContextFor<'_, FEN>,
448 fork_id: Option<U256>,
449 transaction: B256,
450 ) -> eyre::Result<()> {
451 let evm_env = ecx.evm_clone();
452 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
453 let (db, inner) = ecx.db_journal_inner_mut();
454 db.transact(fork_id, transaction, evm_env, inner, &mut inspector)
455 }
456
457 fn transact_from_tx_on_db(
458 &mut self,
459 cheats: &mut Cheatcodes<FEN>,
460 ecx: &mut FoundryContextFor<'_, FEN>,
461 tx_env: TxEnvFor<FEN>,
462 ) -> eyre::Result<()> {
463 let evm_env = ecx.evm_clone();
464 let mut inspector = InspectorStackRefMut { cheatcodes: Some(cheats), inner: self };
465 let (db, inner) = ecx.db_journal_inner_mut();
466 db.transact_from_tx(tx_env, evm_env, inner, &mut inspector)
467 }
468
469 fn console_log(&mut self, msg: &str) {
470 if let Some(ref mut collector) = self.log_collector {
471 InspectorExt::console_log(&mut **collector, msg);
472 }
473 }
474
475 fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
476 self.tracer.as_deref_mut()
477 }
478
479 fn set_in_inner_context(&mut self, enabled: bool, original_origin: Option<Address>) {
480 self.in_inner_context = enabled;
481 self.inner_context_data = enabled.then(|| InnerContextData {
482 original_origin: original_origin.expect("origin required when enabling inner ctx"),
483 });
484 }
485}
486
487impl<FEN: FoundryEvmNetwork> Default for InspectorStack<FEN> {
488 fn default() -> Self {
489 Self::new()
490 }
491}
492
493impl<FEN: FoundryEvmNetwork> InspectorStack<FEN> {
494 #[inline]
500 pub fn new() -> Self {
501 Self { cheatcodes: None, inner: InspectorStackInner::default() }
502 }
503
504 #[inline]
506 pub fn set_analysis(&mut self, analysis: Analysis) {
507 self.analysis = Some(analysis);
508 }
509
510 #[inline]
512 pub fn set_block(&mut self, block: BlockEnvFor<FEN>) {
513 if let Some(cheatcodes) = &mut self.cheatcodes {
514 cheatcodes.block = Some(block);
515 }
516 }
517
518 #[inline]
520 pub fn set_gas_price(&mut self, gas_price: u128) {
521 if let Some(cheatcodes) = &mut self.cheatcodes {
522 cheatcodes.gas_price = Some(gas_price);
523 }
524 }
525
526 #[inline]
528 pub fn set_cheatcodes(&mut self, cheatcodes: Cheatcodes<FEN>) {
529 self.cheatcodes = Some(cheatcodes.into());
530 }
531
532 #[inline]
534 pub fn set_fuzzer(&mut self, fuzzer: Fuzzer) {
535 self.fuzzer = Some(fuzzer.into());
536 }
537
538 #[inline]
540 pub fn set_chisel(&mut self, final_pc: usize) {
541 self.chisel_state = Some(ChiselState::new(final_pc).into());
542 }
543
544 #[inline]
546 pub fn collect_line_coverage(&mut self, yes: bool) {
547 self.line_coverage = yes.then(Default::default);
548 }
549
550 #[inline]
552 pub fn collect_edge_coverage(&mut self, yes: bool) {
553 self.edge_coverage =
554 yes.then(|| EdgeCovInspector::with_config(EdgeCovConfig::default()).into());
555 }
556
557 #[inline]
561 pub fn collect_edge_coverage_with_config(&mut self, corpus: &FuzzCorpusConfig) {
562 self.edge_coverage = corpus
563 .collect_evm_edge_coverage()
564 .then(|| EdgeCovInspector::with_config(corpus.into()).into());
565 }
566
567 #[inline]
569 pub fn collect_evm_cmp_log(&mut self, yes: bool) {
570 if yes {
571 self.edge_coverage
572 .get_or_insert_with(|| EdgeCovInspector::new().into())
573 .enable_cmp_log(true);
574 } else if let Some(edge_coverage) = &mut self.edge_coverage {
575 edge_coverage.enable_cmp_log(false);
576 }
577 }
578
579 #[inline]
581 pub const fn collect_sancov_edges(&mut self, yes: bool) {
582 self.inner.sancov_edges = yes;
583 }
584
585 #[inline]
587 pub const fn collect_sancov_trace_cmp(&mut self, yes: bool) {
588 self.inner.sancov_trace_cmp = yes;
589 }
590
591 #[inline]
593 pub const fn enable_isolation(&mut self, yes: bool) {
594 self.inner.enable_isolation = yes;
595 }
596
597 #[inline]
599 pub const fn networks(&mut self, networks: NetworkConfigs) {
600 self.inner.networks = networks;
601 }
602
603 #[inline]
605 pub fn set_create2_deployer(&mut self, deployer: Address) {
606 self.create2_deployer = deployer;
607 }
608
609 #[inline]
614 pub fn collect_logs(&mut self, live_logs: Option<bool>) {
615 self.log_collector = live_logs.map(|live_logs| {
616 Box::new(if live_logs {
617 LogCollector::LiveLogs
618 } else {
619 LogCollector::Capture { logs: Vec::new() }
620 })
621 });
622 }
623
624 #[inline]
626 pub fn print(&mut self, yes: bool) {
627 self.printer = yes.then(Default::default);
628 }
629
630 #[inline]
633 pub fn tracing(&mut self, mode: TraceMode) {
634 self.revert_diag = (!mode.is_none()).then(RevertDiagnostic::default).map(Into::into);
635
636 if let Some(config) = mode.into_config() {
637 *self.tracer.get_or_insert_with(Default::default).config_mut() = config;
638 } else {
639 self.tracer = None;
640 }
641 }
642
643 #[inline]
645 pub fn script(&mut self, script_address: Address) {
646 self.script_execution_inspector.get_or_insert_with(Default::default).script_address =
647 script_address;
648 }
649
650 #[inline(always)]
651 fn as_mut(&mut self) -> InspectorStackRefMut<'_, FEN> {
652 InspectorStackRefMut { cheatcodes: self.cheatcodes.as_deref_mut(), inner: &mut self.inner }
653 }
654
655 pub fn collect(self) -> InspectorData<FEN> {
657 let Self {
658 mut cheatcodes,
659 inner:
660 InspectorStackInner {
661 chisel_state,
662 line_coverage,
663 edge_coverage,
664 log_collector,
665 tempo_labels,
666 tracer,
667 reverter,
668 ..
669 },
670 } = self;
671
672 let traces = tracer.map(|tracer| tracer.into_traces()).map(|arena| {
673 let ignored = cheatcodes
674 .as_mut()
675 .map(|cheatcodes| {
676 let mut ignored = std::mem::take(&mut cheatcodes.ignored_traces.ignored);
677
678 if let Some(last_pause_call) = cheatcodes.ignored_traces.last_pause_call {
680 ignored.insert(last_pause_call, (arena.nodes().len(), 0));
681 }
682
683 ignored
684 })
685 .unwrap_or_default();
686
687 SparsedTraceArena { arena, ignored }
688 });
689
690 let (edge_coverage, evm_cmp_values) = edge_coverage
691 .map(|edge_coverage| {
692 let (hitcount, cmp_values) = edge_coverage.into_parts();
693 (Some(hitcount), (!cmp_values.is_empty()).then_some(cmp_values))
694 })
695 .unwrap_or_default();
696
697 InspectorData {
698 logs: log_collector.and_then(|logs| logs.into_captured_logs()).unwrap_or_default(),
699 labels: {
700 let mut labels = cheatcodes.as_ref().map(|c| c.labels.clone()).unwrap_or_default();
701 if let Some(tempo_labels) = tempo_labels {
702 labels.extend(tempo_labels.labels);
703 }
704 labels
705 },
706 traces,
707 line_coverage: line_coverage.map(|line_coverage| line_coverage.finish()),
708 edge_coverage,
709 evm_cmp_values,
710 cheatcodes,
711 chisel_state: chisel_state.and_then(|state| state.state),
712 reverter,
713 }
714 }
715}
716
717impl<FEN: FoundryEvmNetwork> InspectorStackRefMut<'_, FEN> {
718 fn adjust_evm_data_for_inner_context<CTX: FoundryContextExt>(&mut self, ecx: &mut CTX) {
723 let inner_context_data =
724 self.inner_context_data.as_ref().expect("should be called in inner context");
725 ecx.tx_mut().set_caller(inner_context_data.original_origin);
726 }
727
728 fn do_call_end(
729 &mut self,
730 ecx: &mut FoundryContextFor<'_, FEN>,
731 inputs: &CallInputs,
732 outcome: &mut CallOutcome,
733 ) {
734 let result = outcome.result.result;
735 call_inspectors!(
736 #[ret]
737 [
738 &mut self.fuzzer,
739 &mut self.tracer,
740 &mut self.cheatcodes,
741 &mut self.printer,
742 &mut self.revert_diag
743 ],
744 |inspector| {
745 let previous_outcome = outcome.clone();
746 inspector.call_end(ecx, inputs, outcome);
747
748 let different = outcome.result.result != result
751 || (outcome.result.result == InstructionResult::Revert
752 && outcome.output() != previous_outcome.output());
753 different.then_some(())
754 },
755 );
756
757 if result.is_revert() && self.reverter.is_none() {
759 self.reverter = Some(inputs.target_address);
760 }
761 }
762
763 fn do_create_end(
764 &mut self,
765 ecx: &mut FoundryContextFor<'_, FEN>,
766 call: &CreateInputs,
767 outcome: &mut CreateOutcome,
768 ) {
769 let result = outcome.result.result;
770 call_inspectors!(
771 #[ret]
772 [&mut self.tracer, &mut self.cheatcodes, &mut self.printer],
773 |inspector| {
774 let previous_outcome = outcome.clone();
775 inspector.create_end(ecx, call, outcome);
776
777 let different = outcome.result.result != result
780 || (outcome.result.result == InstructionResult::Revert
781 && outcome.output() != previous_outcome.output());
782 different.then_some(())
783 },
784 );
785 }
786
787 fn transact_inner(
788 &mut self,
789 ecx: &mut FoundryContextFor<'_, FEN>,
790 kind: TxKind,
791 caller: Address,
792 input: Bytes,
793 gas_limit: u64,
794 value: U256,
795 ) -> (InterpreterResult, Option<Address>) {
796 let cached_evm_env = ecx.evm_clone();
797 let cached_tx_env = ecx.tx_clone();
798
799 ecx.block_mut().set_basefee(0);
800
801 let chain_id = ecx.cfg().chain_id();
802 ecx.tx_mut().set_chain_id(Some(chain_id));
803 ecx.tx_mut().set_caller(caller);
804 ecx.tx_mut().set_kind(kind);
805 ecx.tx_mut().set_data(input);
806 ecx.tx_mut().set_value(value);
807 ecx.tx_mut().set_gas_limit(gas_limit + 21000);
809
810 if !ecx.cfg().is_block_gas_limit_disabled() {
813 let gas_limit = std::cmp::min(ecx.tx().gas_limit(), ecx.block().gas_limit());
814 ecx.tx_mut().set_gas_limit(gas_limit);
815 }
816 ecx.tx_mut().set_gas_price(0);
817 if ecx.tx().tx_type() == TransactionType::Eip4844 as u8 {
825 ecx.tx_mut().set_tx_type(TransactionType::Eip1559 as u8);
826 ecx.tx_mut().set_blob_hashes(Vec::new());
827 }
828
829 self.inner_context_data =
830 Some(InnerContextData { original_origin: cached_tx_env.caller() });
831 self.in_inner_context = true;
832
833 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
837 cheats.in_isolation_context = true;
838 }
839
840 let evm_env = ecx.evm_clone();
841 let tx_env = ecx.tx_clone();
842
843 let res = self.with_inspector(|mut inspector| {
844 let (res, nested_env) = {
845 let (db, journal) = ecx.db_journal_inner_mut();
846 let mut evm = FEN::EvmFactory::default().create_foundry_nested_evm(
847 db,
848 evm_env,
849 &mut inspector,
850 );
851
852 evm.journal_inner_mut().state = {
853 let mut state = journal.state.clone();
854
855 for (addr, acc_mut) in &mut state {
856 if journal.warm_addresses.is_cold(addr) {
858 acc_mut.mark_cold();
859 }
860
861 for slot_mut in acc_mut.storage.values_mut() {
863 slot_mut.is_cold = true;
864 slot_mut.original_value = slot_mut.present_value;
865 }
866 }
867
868 state
869 };
870
871 evm.journal_inner_mut().depth = 1;
873
874 let res = evm.transact_raw(tx_env);
875 let nested_evm_env = evm.to_evm_env();
876 (res, nested_evm_env)
877 };
878
879 let mut restored_evm_env = nested_env;
882 restored_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
883 ecx.set_evm(restored_evm_env);
884 ecx.set_tx(cached_tx_env);
885
886 res
887 });
888
889 self.in_inner_context = false;
890 self.inner_context_data = None;
891
892 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
895 cheats.in_isolation_context = false;
896 }
897
898 let mut gas = Gas::new(gas_limit);
899
900 let Ok(res) = res else {
901 let result =
903 InterpreterResult { result: InstructionResult::Revert, output: Bytes::new(), gas };
904 return (result, None);
905 };
906
907 for (addr, mut acc) in res.state {
908 let Some(acc_mut) = ecx.journal_mut().evm_state_mut().get_mut(&addr) else {
909 ecx.journal_mut().evm_state_mut().insert(addr, acc);
910 continue;
911 };
912
913 if acc.status.contains(AccountStatus::Cold)
915 && !acc_mut.status.contains(AccountStatus::Cold)
916 {
917 acc.status -= AccountStatus::Cold;
918 }
919 acc_mut.info = acc.info;
920 acc_mut.status |= acc.status;
921
922 for (key, val) in acc.storage {
923 let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
924 acc_mut.storage.insert(key, val);
925 continue;
926 };
927 slot_mut.present_value = val.present_value;
928 slot_mut.is_cold &= val.is_cold;
929 }
930 }
931
932 let (result, address, output) = match res.result {
933 ExecutionResult::Success { reason, gas: result_gas, logs: _, output } => {
934 gas.set_refund(result_gas.final_refunded() as i64);
935 let _ = gas.record_regular_cost(result_gas.tx_gas_used());
936 let address = match output {
937 Output::Create(_, address) => address,
938 Output::Call(_) => None,
939 };
940 (reason.into(), address, output.into_data())
941 }
942 ExecutionResult::Halt { reason, gas: result_gas, .. } => {
943 let _ = gas.record_regular_cost(result_gas.tx_gas_used());
944 (InstructionResult::from(reason), None, Bytes::new())
945 }
946 ExecutionResult::Revert { gas: result_gas, output, .. } => {
947 let _ = gas.record_regular_cost(result_gas.tx_gas_used());
948 (InstructionResult::Revert, None, output)
949 }
950 };
951 (InterpreterResult { result, output, gas }, address)
952 }
953
954 fn with_inspector<O>(&mut self, f: impl FnOnce(InspectorStackRefMut<'_, FEN>) -> O) -> O {
957 let mut cheatcodes = self
958 .cheatcodes
959 .as_deref_mut()
960 .map(|cheats| core::mem::replace(cheats, Cheatcodes::new(cheats.config.clone())));
961 let mut inner = std::mem::take(self.inner);
962
963 let saved_create2_redirects = std::mem::take(&mut inner.pending_create2_redirects);
966
967 let out = f(InspectorStackRefMut { cheatcodes: cheatcodes.as_mut(), inner: &mut inner });
968
969 if let Some(cheats) = self.cheatcodes.as_deref_mut() {
970 *cheats = cheatcodes.unwrap();
971 }
972
973 inner.pending_create2_redirects = saved_create2_redirects;
974 *self.inner = inner;
975
976 out
977 }
978
979 fn top_level_frame_start(&mut self, ecx: &mut FoundryContextFor<'_, FEN>) {
981 if self.enable_isolation {
982 self.top_frame_journal.clone_from(ecx.journal().evm_state());
985 }
986 }
987
988 fn top_level_frame_end(
990 &mut self,
991 ecx: &mut FoundryContextFor<'_, FEN>,
992 result: InstructionResult,
993 ) {
994 if !result.is_revert() {
995 return;
996 }
997 if let Some(cheats) = self.cheatcodes.as_mut() {
1001 cheats.on_revert(ecx);
1002 }
1003
1004 if self.enable_isolation {
1008 *ecx.journal_mut().evm_state_mut() = std::mem::take(&mut self.top_frame_journal);
1009 }
1010 }
1011
1012 #[inline(always)]
1018 fn step_inlined(
1019 &mut self,
1020 interpreter: &mut Interpreter,
1021 ecx: &mut FoundryContextFor<'_, FEN>,
1022 ) {
1023 call_inspectors!(
1024 [
1025 &mut self.edge_coverage,
1027 &mut self.fuzzer,
1028 &mut self.line_coverage,
1029 &mut self.printer,
1030 &mut self.revert_diag,
1031 &mut self.script_execution_inspector,
1032 &mut self.tracer,
1033 &mut self.cheatcodes,
1035 ],
1036 |inspector| (**inspector).step(interpreter, ecx),
1037 );
1038 }
1039
1040 #[inline(always)]
1041 fn step_end_inlined(
1042 &mut self,
1043 interpreter: &mut Interpreter,
1044 ecx: &mut FoundryContextFor<'_, FEN>,
1045 ) {
1046 call_inspectors!(
1047 [
1048 &mut self.chisel_state,
1050 &mut self.printer,
1051 &mut self.revert_diag,
1052 &mut self.tracer,
1053 &mut self.cheatcodes,
1055 ],
1056 |inspector| (**inspector).step_end(interpreter, ecx),
1057 );
1058 }
1059}
1060
1061impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>>
1062 for InspectorStackRefMut<'_, FEN>
1063{
1064 fn initialize_interp(
1065 &mut self,
1066 interpreter: &mut Interpreter,
1067 ecx: &mut FoundryContextFor<'_, FEN>,
1068 ) {
1069 call_inspectors!(
1070 [
1071 &mut self.line_coverage,
1072 &mut self.tracer,
1073 &mut self.cheatcodes,
1074 &mut self.script_execution_inspector,
1075 &mut self.printer
1076 ],
1077 |inspector| inspector.initialize_interp(interpreter, ecx),
1078 );
1079 }
1080
1081 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1082 self.step_inlined(interpreter, ecx);
1083 }
1084
1085 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1086 self.step_end_inlined(interpreter, ecx);
1087 }
1088
1089 #[allow(clippy::redundant_clone)]
1090 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1091 call_inspectors!(
1092 [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer],
1093 |inspector| inspector.log(ecx, log.clone()),
1094 );
1095 }
1096
1097 #[allow(clippy::redundant_clone)]
1098 fn log_full(
1099 &mut self,
1100 interpreter: &mut Interpreter,
1101 ecx: &mut FoundryContextFor<'_, FEN>,
1102 log: Log,
1103 ) {
1104 call_inspectors!(
1105 [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer],
1106 |inspector| inspector.log_full(interpreter, ecx, log.clone()),
1107 );
1108 }
1109
1110 fn frame_start(
1111 &mut self,
1112 ecx: &mut FoundryContextFor<'_, FEN>,
1113 frame_input: &mut FrameInput,
1114 ) -> Option<FrameResult> {
1115 if let FrameInput::Create(inputs) = frame_input
1116 && self.should_use_create2_factory(ecx.journal().depth(), inputs)
1117 {
1118 let salt = match inputs.scheme() {
1123 CreateScheme::Create2 { salt } => salt,
1124 CreateScheme::Create => {
1127 if !self.inner.batch_rewrite_warned {
1128 let _ = sh_warn!(
1129 "--batch rewrites CREATE → CREATE2 via the Arachnid factory; \
1130 deployed addresses follow the CREATE2 formula and constructor \
1131 msg.sender is the factory, not the EOA."
1132 );
1133 self.inner.batch_rewrite_warned = true;
1134 }
1135 let chain_id = ecx.cfg().chain_id();
1136 let nonce = ecx.journal_mut().load_account(inputs.caller()).ok()?.info.nonce;
1137 self.inner.next_batch_create_salt(chain_id, nonce)
1138 }
1139 _ => return None,
1140 };
1141
1142 let gas_limit = inputs.gas_limit();
1143 let create2_deployer = self.create2_deployer();
1144
1145 let code_hash = ecx.journal_mut().load_account(create2_deployer).ok()?.info.code_hash;
1147 if code_hash == KECCAK_EMPTY {
1148 self.inner.pending_create2_error = Some(CreateOutcome {
1151 result: InterpreterResult {
1152 result: InstructionResult::Revert,
1153 output: Bytes::from(
1154 format!("missing CREATE2 deployer: {create2_deployer}").into_bytes(),
1155 ),
1156 gas: Gas::new(gas_limit),
1157 },
1158 address: None,
1159 });
1160 return None;
1161 } else if code_hash != DEFAULT_CREATE2_DEPLOYER_CODEHASH {
1162 self.inner.pending_create2_error = Some(CreateOutcome {
1163 result: InterpreterResult {
1164 result: InstructionResult::Revert,
1165 output: "invalid CREATE2 deployer bytecode".into(),
1166 gas: Gas::new(gas_limit),
1167 },
1168 address: None,
1169 });
1170 return None;
1171 }
1172
1173 let call_inputs =
1174 get_create2_factory_call_inputs(salt, inputs, create2_deployer, ecx.journal_mut())
1175 .ok()?;
1176
1177 self.inner.pending_create2_redirects.push(ecx.journal().depth());
1179
1180 *frame_input = FrameInput::Call(Box::new(call_inputs));
1182 }
1183
1184 None
1185 }
1186
1187 fn frame_end(
1188 &mut self,
1189 ecx: &mut FoundryContextFor<'_, FEN>,
1190 _frame_input: &FrameInput,
1191 frame_result: &mut FrameResult,
1192 ) {
1193 let depth = ecx.journal().depth();
1194 if self.inner.pending_create2_redirects.last().copied() != Some(depth) {
1195 return;
1196 }
1197
1198 self.inner.pending_create2_redirects.pop();
1199
1200 let FrameResult::Call(call) = frame_result else {
1201 debug_assert!(false, "pending CREATE2 redirect ended with non-call result");
1202 return;
1203 };
1204
1205 let address = match call.instruction_result() {
1206 return_ok!() => Address::try_from(call.output().as_ref())
1207 .map_err(|_| {
1208 call.result = InterpreterResult {
1209 result: InstructionResult::Revert,
1210 output: "invalid CREATE2 factory output".into(),
1211 gas: Gas::new(call.result.gas.limit()),
1212 };
1213 })
1214 .ok(),
1215 _ => None,
1216 };
1217
1218 *frame_result = FrameResult::Create(CreateOutcome { result: call.result.clone(), address });
1219 }
1220
1221 fn call(
1222 &mut self,
1223 ecx: &mut FoundryContextFor<'_, FEN>,
1224 call: &mut CallInputs,
1225 ) -> Option<CallOutcome> {
1226 if self.in_inner_context && ecx.journal().depth() == 1 {
1227 self.adjust_evm_data_for_inner_context(ecx);
1228 return None;
1229 }
1230
1231 if ecx.journal().depth() == 0 {
1232 self.top_level_frame_start(ecx);
1233 }
1234
1235 call_inspectors!(
1236 #[ret]
1237 [
1238 &mut self.fuzzer,
1239 &mut self.tracer,
1240 &mut self.log_collector,
1241 &mut self.printer,
1242 &mut self.revert_diag,
1243 &mut self.tempo_labels
1244 ],
1245 |inspector| {
1246 let mut out = None;
1247 if let Some(output) = inspector.call(ecx, call) {
1248 out = Some(Some(output));
1249 }
1250 out
1251 },
1252 );
1253
1254 if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() {
1255 if let Some(mocks) = cheatcodes.mocked_functions.get(&call.bytecode_address) {
1257 let input_bytes = call.input.bytes(ecx);
1258 if let Some(target) = mocks
1261 .get(&input_bytes)
1262 .or_else(|| input_bytes.get(..4).and_then(|selector| mocks.get(selector)))
1263 {
1264 call.bytecode_address = *target;
1265
1266 let target = ecx
1267 .journal_mut()
1268 .load_account_with_code(*target)
1269 .expect("failed to load account");
1270 call.known_bytecode =
1271 (target.info.code_hash, target.info.code.clone().unwrap_or_default());
1272 }
1273 }
1274
1275 if let Some(output) = cheatcodes.call_with_executor(ecx, call, self.inner) {
1276 return Some(output);
1277 }
1278 }
1279
1280 if self.enable_isolation && !self.in_inner_context && ecx.journal().depth() == 1 {
1281 match call.scheme {
1282 CallScheme::Call => {
1284 let input = call.input.bytes(ecx);
1285 let (result, _) = self.transact_inner(
1286 ecx,
1287 TxKind::Call(call.target_address),
1288 call.caller,
1289 input,
1290 call.gas_limit,
1291 call.value.get(),
1292 );
1293 return Some(CallOutcome {
1294 result,
1295 memory_offset: call.return_memory_offset.clone(),
1296 was_precompile_called: true,
1297 precompile_call_logs: vec![],
1298 charged_new_account_state_gas: false,
1299 });
1300 }
1301 CallScheme::StaticCall => {
1303 let (_, journal_inner) = ecx.db_journal_inner_mut();
1304 let JournaledState { state, warm_addresses, .. } = journal_inner;
1305 for (addr, acc_mut) in state {
1306 if let Some(cheatcodes) = &self.cheatcodes
1308 && cheatcodes.has_arbitrary_storage(addr)
1309 {
1310 continue;
1311 }
1312
1313 if warm_addresses.is_cold(addr) {
1314 acc_mut.mark_cold();
1315 }
1316
1317 for slot_mut in acc_mut.storage.values_mut() {
1318 slot_mut.is_cold = true;
1319 }
1320 }
1321 }
1322 CallScheme::CallCode | CallScheme::DelegateCall => {}
1324 }
1325 }
1326
1327 None
1328 }
1329
1330 fn call_end(
1331 &mut self,
1332 ecx: &mut FoundryContextFor<'_, FEN>,
1333 inputs: &CallInputs,
1334 outcome: &mut CallOutcome,
1335 ) {
1336 if self.in_inner_context && ecx.journal().depth() == 1 {
1339 return;
1340 }
1341
1342 self.do_call_end(ecx, inputs, outcome);
1343
1344 if ecx.journal().depth() == 0 {
1345 self.top_level_frame_end(ecx, outcome.result.result);
1346 }
1347 }
1348
1349 fn create(
1350 &mut self,
1351 ecx: &mut FoundryContextFor<'_, FEN>,
1352 create: &mut CreateInputs,
1353 ) -> Option<CreateOutcome> {
1354 if self.in_inner_context && ecx.journal().depth() == 1 {
1355 self.adjust_evm_data_for_inner_context(ecx);
1356 return None;
1357 }
1358
1359 if ecx.journal().depth() == 0 {
1360 self.top_level_frame_start(ecx);
1361 }
1362
1363 call_inspectors!(
1364 #[ret]
1365 [&mut self.tracer, &mut self.line_coverage, &mut self.cheatcodes],
1366 |inspector| inspector.create(ecx, create).map(Some),
1367 );
1368
1369 if let Some(error) = self.inner.pending_create2_error.take() {
1372 return Some(error);
1373 }
1374
1375 if !matches!(create.scheme(), CreateScheme::Create2 { .. })
1376 && self.enable_isolation
1377 && !self.in_inner_context
1378 && ecx.journal().depth() == 1
1379 {
1380 let precomputed_address = ecx
1384 .journal()
1385 .evm_state()
1386 .get(&create.caller())
1387 .map(|acc| create.caller().create(acc.info.nonce));
1388
1389 let (result, address) = self.transact_inner(
1390 ecx,
1391 TxKind::Create,
1392 create.caller(),
1393 create.init_code().clone(),
1394 create.gas_limit(),
1395 create.value(),
1396 );
1397 let address =
1398 address.or_else(|| if result.is_revert() { precomputed_address } else { None });
1399 return Some(CreateOutcome { result, address });
1400 }
1401
1402 None
1403 }
1404
1405 fn create_end(
1406 &mut self,
1407 ecx: &mut FoundryContextFor<'_, FEN>,
1408 call: &CreateInputs,
1409 outcome: &mut CreateOutcome,
1410 ) {
1411 if self.in_inner_context && ecx.journal().depth() == 1 {
1414 return;
1415 }
1416
1417 self.do_create_end(ecx, call, outcome);
1418
1419 if ecx.journal().depth() == 0 {
1420 self.top_level_frame_end(ecx, outcome.result.result);
1421 }
1422 }
1423
1424 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
1425 call_inspectors!([&mut self.printer], |inspector| {
1426 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
1427 inspector, contract, target, value,
1428 )
1429 });
1430 }
1431}
1432
1433impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStackRefMut<'_, FEN> {
1434 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
1435 call_inspectors!(
1436 #[ret]
1437 [&mut self.cheatcodes],
1438 |inspector| { inspector.should_use_create2_factory(depth, inputs).then_some(true) },
1439 );
1440
1441 false
1442 }
1443
1444 fn console_log(&mut self, msg: &str) {
1445 call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log(
1446 inspector, msg
1447 ));
1448 }
1449
1450 fn get_networks(&self) -> NetworkConfigs {
1451 self.inner.networks
1452 }
1453
1454 fn create2_deployer(&self) -> Address {
1455 self.inner.create2_deployer
1456 }
1457}
1458
1459impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for InspectorStack<FEN> {
1460 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1461 self.as_mut().step_inlined(interpreter, ecx)
1462 }
1463
1464 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
1465 self.as_mut().step_end_inlined(interpreter, ecx)
1466 }
1467
1468 fn call(
1469 &mut self,
1470 context: &mut FoundryContextFor<'_, FEN>,
1471 inputs: &mut CallInputs,
1472 ) -> Option<CallOutcome> {
1473 self.as_mut().call(context, inputs)
1474 }
1475
1476 fn call_end(
1477 &mut self,
1478 context: &mut FoundryContextFor<'_, FEN>,
1479 inputs: &CallInputs,
1480 outcome: &mut CallOutcome,
1481 ) {
1482 self.as_mut().call_end(context, inputs, outcome)
1483 }
1484
1485 fn create(
1486 &mut self,
1487 context: &mut FoundryContextFor<'_, FEN>,
1488 create: &mut CreateInputs,
1489 ) -> Option<CreateOutcome> {
1490 self.as_mut().create(context, create)
1491 }
1492
1493 fn create_end(
1494 &mut self,
1495 context: &mut FoundryContextFor<'_, FEN>,
1496 call: &CreateInputs,
1497 outcome: &mut CreateOutcome,
1498 ) {
1499 self.as_mut().create_end(context, call, outcome)
1500 }
1501
1502 fn initialize_interp(
1503 &mut self,
1504 interpreter: &mut Interpreter,
1505 ecx: &mut FoundryContextFor<'_, FEN>,
1506 ) {
1507 self.as_mut().initialize_interp(interpreter, ecx)
1508 }
1509
1510 fn log(&mut self, ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
1511 self.as_mut().log(ecx, log)
1512 }
1513
1514 fn log_full(
1515 &mut self,
1516 interpreter: &mut Interpreter,
1517 ecx: &mut FoundryContextFor<'_, FEN>,
1518 log: Log,
1519 ) {
1520 self.as_mut().log_full(interpreter, ecx, log)
1521 }
1522
1523 fn frame_start(
1524 &mut self,
1525 context: &mut FoundryContextFor<'_, FEN>,
1526 frame_input: &mut FrameInput,
1527 ) -> Option<FrameResult> {
1528 self.as_mut().frame_start(context, frame_input)
1529 }
1530
1531 fn frame_end(
1532 &mut self,
1533 context: &mut FoundryContextFor<'_, FEN>,
1534 frame_input: &FrameInput,
1535 frame_result: &mut FrameResult,
1536 ) {
1537 self.as_mut().frame_end(context, frame_input, frame_result)
1538 }
1539
1540 fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) {
1541 call_inspectors!([&mut self.inner.printer], |inspector| {
1542 Inspector::<FoundryContextFor<'_, FEN>>::selfdestruct(
1543 inspector, contract, target, value,
1544 )
1545 });
1546 }
1547}
1548
1549impl<FEN: FoundryEvmNetwork> InspectorExt for InspectorStack<FEN> {
1550 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
1551 self.as_mut().should_use_create2_factory(depth, inputs)
1552 }
1553
1554 fn get_networks(&self) -> NetworkConfigs {
1555 self.networks
1556 }
1557
1558 fn create2_deployer(&self) -> Address {
1559 self.create2_deployer
1560 }
1561}
1562
1563impl<'a, FEN: FoundryEvmNetwork> Deref for InspectorStackRefMut<'a, FEN> {
1564 type Target = &'a mut InspectorStackInner;
1565
1566 fn deref(&self) -> &Self::Target {
1567 &self.inner
1568 }
1569}
1570
1571impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStackRefMut<'_, FEN> {
1572 fn deref_mut(&mut self) -> &mut Self::Target {
1573 &mut self.inner
1574 }
1575}
1576
1577impl<FEN: FoundryEvmNetwork> Deref for InspectorStack<FEN> {
1578 type Target = InspectorStackInner;
1579
1580 fn deref(&self) -> &Self::Target {
1581 &self.inner
1582 }
1583}
1584
1585impl<FEN: FoundryEvmNetwork> DerefMut for InspectorStack<FEN> {
1586 fn deref_mut(&mut self) -> &mut Self::Target {
1587 &mut self.inner
1588 }
1589}
1590
1591impl InspectorStackInner {
1592 fn next_batch_create_salt(&mut self, chain_id: u64, nonce: u64) -> U256 {
1595 let process_salt = *self.batch_rewrite_process_salt.get_or_insert_with(rand::random);
1596 let counter = self.batch_create_counter;
1597 self.batch_create_counter = counter.wrapping_add(1);
1598 compute_batch_create_salt(process_salt, chain_id, nonce, counter)
1599 }
1600}
1601
1602fn compute_batch_create_salt(process_salt: u64, chain_id: u64, nonce: u64, counter: u64) -> U256 {
1608 let mut buf = [0u8; 32];
1609 buf[0..8].copy_from_slice(&process_salt.to_be_bytes());
1610 buf[8..16].copy_from_slice(&chain_id.to_be_bytes());
1611 buf[16..24].copy_from_slice(&nonce.to_be_bytes());
1612 buf[24..32].copy_from_slice(&counter.to_be_bytes());
1613 U256::from_be_bytes(keccak256(buf).0)
1614}
1615
1616#[cfg(test)]
1617mod tests {
1618 use super::{Address, InspectorStackInner, compute_batch_create_salt};
1619
1620 #[test]
1621 fn distinct_salts_across_simulations_at_same_nonce() {
1622 let a = compute_batch_create_salt(0xabcd_ef01_2345_6789, 1, 5, 0);
1624 let b = compute_batch_create_salt(0x1122_3344_5566_7788, 1, 5, 0);
1625 assert_ne!(a, b);
1626 }
1627
1628 #[test]
1629 fn counter_changes_salt() {
1630 let a = compute_batch_create_salt(1, 1, 5, 0);
1631 let b = compute_batch_create_salt(1, 1, 5, 1);
1632 assert_ne!(a, b);
1633 }
1634
1635 #[test]
1636 fn chain_id_changes_salt() {
1637 let a = compute_batch_create_salt(1, 1, 5, 0);
1638 let b = compute_batch_create_salt(1, 2, 5, 0);
1639 assert_ne!(a, b);
1640 }
1641
1642 #[test]
1643 fn deterministic_for_same_inputs() {
1644 let a = compute_batch_create_salt(42, 1, 5, 7);
1645 let b = compute_batch_create_salt(42, 1, 5, 7);
1646 assert_eq!(a, b);
1647 }
1648
1649 #[test]
1650 fn distinct_create2_addresses_across_inspector_instances_at_same_onchain_state() {
1651 let factory = Address::with_last_byte(0x42);
1654 let init_code = b"\x60\x80\x60\x40".as_slice();
1655
1656 let mut a = InspectorStackInner::default();
1657 let mut b = InspectorStackInner::default();
1658 let salt_a = a.next_batch_create_salt(1, 5).to_be_bytes::<32>();
1659 let salt_b = b.next_batch_create_salt(1, 5).to_be_bytes::<32>();
1660
1661 let addr_a = factory.create2_from_code(salt_a, init_code);
1662 let addr_b = factory.create2_from_code(salt_b, init_code);
1663 assert_ne!(addr_a, addr_b);
1664 }
1665}