1use crate::{
4 Cheatcode, CheatsConfig, CheatsCtxt, Error, Result,
5 Vm::{self, AccountAccess},
6 evm::{
7 DealRecord, GasRecord, RecordAccess, append_storage_access, journaled_account,
8 mark_account_accesses_reverted, merge_recorded_frame,
9 mock::{self, MockCallDataContext, MockCallReturnData},
10 prank::Prank,
11 },
12 expected_emit::{self, ExpectedEmitTracker},
13 inspector::utils::CommonCreateInput,
14 script::{Broadcast, Wallets},
15 test::{
16 assume::AssumeNoRevert,
17 expect::{self, ExpectedCallTracker, ExpectedCreate, ExpectedRevert, ExpectedRevertKind},
18 revert_handlers,
19 },
20 utils::IgnoredTraces,
21};
22use alloy_consensus::BlobTransactionSidecarVariant;
23use alloy_network::{Ethereum, Network, TransactionBuilder};
24use alloy_primitives::{
25 Address, B256, Bytes, Log, TxKind, U256, hex,
26 map::{AddressHashMap, HashMap, HashSet},
27};
28use alloy_rpc_types::AccessList;
29use alloy_signer_local::PrivateKeySigner;
30use alloy_sol_types::{SolCall, SolInterface};
31use foundry_common::{
32 FoundryTransactionBuilder, SELECTOR_LEN, TransactionMaybeSigned,
33 mapping_slots::{
34 MappingSlots, PendingMappingHash, capture_hash as capture_mapping_hash,
35 record_hash as record_mapping_hash, step as mapping_step,
36 },
37};
38use foundry_evm_core::{
39 Breakpoints, EvmEnv, FoundryTransaction, InspectorExt,
40 abi::Vm::stopExpectSafeMemoryCall,
41 backend::{
42 ContextUpdateFor, DatabaseError, DatabaseExt, JournaledState, LocalForkId, RevertDiagnostic,
43 },
44 constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME},
45 env::FoundryContextExt,
46 evm::{
47 BlockEnvFor, ChainFor, EthEvmNetwork, EvmFactoryFor, FoundryContextFor, FoundryEvmFactory,
48 FoundryEvmNetwork, NestedEvmClosureFor, SpecFor, TransactionRequestFor, TxEnvFor,
49 with_inherited_evm,
50 },
51};
52use foundry_evm_traces::{
53 TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier,
54};
55use foundry_wallets::wallet_multi::MultiWallet;
56use itertools::Itertools;
57use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner};
58use rand::Rng;
59use revm::{
60 Inspector, JournalEntry,
61 bytecode::opcode as op,
62 context::{Cfg, ContextTr, Host, JournalTr, Transaction, TransactionType, result::EVMError},
63 context_interface::{CreateScheme, transaction::SignedAuthorization},
64 handler::FrameResult,
65 interpreter::{
66 CallInput, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome,
67 FrameInput, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
68 interpreter_types::{Jumps, LoopControl, MemoryTr, ReturnData},
69 },
70};
71use serde_json::Value;
72use std::{
73 cmp::max,
74 collections::{BTreeMap, VecDeque},
75 fmt::Debug,
76 fs::File,
77 io::BufReader,
78 ops::Range,
79 path::PathBuf,
80 sync::{Arc, OnceLock},
81};
82
83mod env_overrides;
84pub use env_overrides::EnvOverrideState;
85
86mod utils;
87
88pub mod analysis;
89pub use analysis::CheatcodeAnalysis;
90
91pub trait CheatcodesExecutor<FEN: FoundryEvmNetwork> {
93 fn with_nested_evm(
96 &mut self,
97 cheats: &mut Cheatcodes<FEN>,
98 ecx: &mut FoundryContextFor<'_, FEN>,
99 f: NestedEvmClosureFor<'_, FEN>,
100 ) -> Result<(), EVMError<DatabaseError>>;
101
102 fn transact_on_db(
104 &mut self,
105 cheats: &mut Cheatcodes<FEN>,
106 ecx: &mut FoundryContextFor<'_, FEN>,
107 fork_id: Option<U256>,
108 transaction: B256,
109 ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>>;
110
111 fn transact_from_tx_on_db(
113 &mut self,
114 cheats: &mut Cheatcodes<FEN>,
115 ecx: &mut FoundryContextFor<'_, FEN>,
116 tx: TxEnvFor<FEN>,
117 ) -> eyre::Result<()>;
118
119 #[allow(clippy::type_complexity)]
124 fn with_fresh_nested_evm(
125 &mut self,
126 cheats: &mut Cheatcodes<FEN>,
127 ecx: &mut FoundryContextFor<'_, FEN>,
128 chain_context: ChainFor<FEN>,
129 f: NestedEvmClosureFor<'_, FEN>,
130 ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>>;
131
132 fn console_log(&mut self, msg: &str);
134
135 fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
137 None
138 }
139}
140
141pub(crate) fn exec_create<FEN: FoundryEvmNetwork>(
143 executor: &mut dyn CheatcodesExecutor<FEN>,
144 inputs: CreateInputs,
145 ccx: &mut CheatsCtxt<'_, '_, FEN>,
146) -> std::result::Result<CreateOutcome, EVMError<DatabaseError>> {
147 let fee_token = ccx.tx_fee_token();
148 let tx_origin = ccx.tx_caller();
149 let mut inputs = Some(inputs);
150 let mut outcome = None;
151 executor.with_nested_evm(ccx.state, ccx.ecx, &mut |evm| {
152 evm.tx_mut().set_fee_token(fee_token);
153 evm.tx_mut().set_caller(tx_origin);
154 let inputs = inputs.take().unwrap();
155 evm.journal_inner_mut().depth += 1;
156
157 let frame = FrameInput::Create(Box::new(inputs));
158
159 let result = match evm.run_execution(frame)? {
160 FrameResult::Call(_) => unreachable!(),
161 FrameResult::Create(create) => create,
162 };
163
164 evm.journal_inner_mut().depth -= 1;
165
166 outcome = Some(result);
167 Ok(())
168 })?;
169 Ok(outcome.unwrap())
170}
171
172#[derive(Debug, Default, Clone, Copy)]
175struct TransparentCheatcodesExecutor;
176
177impl<FEN: FoundryEvmNetwork> CheatcodesExecutor<FEN> for TransparentCheatcodesExecutor {
178 fn with_nested_evm(
179 &mut self,
180 cheats: &mut Cheatcodes<FEN>,
181 ecx: &mut FoundryContextFor<'_, FEN>,
182 f: NestedEvmClosureFor<'_, FEN>,
183 ) -> Result<(), EVMError<DatabaseError>> {
184 with_inherited_evm::<FEN::EvmFactory, _>(ecx, cheats, f)
185 }
186
187 fn with_fresh_nested_evm(
188 &mut self,
189 cheats: &mut Cheatcodes<FEN>,
190 ecx: &mut FoundryContextFor<'_, FEN>,
191 chain_context: ChainFor<FEN>,
192 f: NestedEvmClosureFor<'_, FEN>,
193 ) -> Result<EvmEnv<SpecFor<FEN>, BlockEnvFor<FEN>>, EVMError<DatabaseError>> {
194 let depth = ecx.journal().depth();
195 let evm_env = ecx.evm_clone();
196 let (db, _) = ecx.db_journal_inner_mut();
197 let mut evm =
198 FEN::EvmFactory::default().create_nested_evm_with_inspector(db, evm_env, cheats);
199 evm.journal_inner_mut().depth = depth;
200 *evm.chain_mut() = chain_context;
201 f(&mut *evm)?;
202 Ok(evm.to_evm_env())
203 }
204
205 fn transact_on_db(
206 &mut self,
207 cheats: &mut Cheatcodes<FEN>,
208 ecx: &mut FoundryContextFor<'_, FEN>,
209 fork_id: Option<U256>,
210 transaction: B256,
211 ) -> eyre::Result<ContextUpdateFor<EvmFactoryFor<FEN>>> {
212 let evm_env = ecx.evm_clone();
213 let outer_tx_env = ecx.tx_clone();
214 let (db, inner) = ecx.db_journal_inner_mut();
215 db.transact(fork_id, transaction, evm_env, &outer_tx_env, inner, cheats)
216 }
217
218 fn transact_from_tx_on_db(
219 &mut self,
220 cheats: &mut Cheatcodes<FEN>,
221 ecx: &mut FoundryContextFor<'_, FEN>,
222 tx: TxEnvFor<FEN>,
223 ) -> eyre::Result<()> {
224 let evm_env = ecx.evm_clone();
225 let (db, inner) = ecx.db_journal_inner_mut();
226 db.transact_from_tx(tx, evm_env, inner, cheats)
227 }
228
229 fn console_log(&mut self, _msg: &str) {}
230}
231
232macro_rules! try_or_return {
233 ($e:expr) => {
234 match $e {
235 Ok(v) => v,
236 Err(_) => return,
237 }
238 };
239}
240
241#[derive(Debug, Default)]
243pub struct TestContext {
244 pub opened_read_files: HashMap<PathBuf, BufReader<File>>,
246}
247
248impl Clone for TestContext {
250 fn clone(&self) -> Self {
251 Default::default()
252 }
253}
254
255impl TestContext {
256 pub fn clear(&mut self) {
258 self.opened_read_files.clear();
259 }
260}
261
262#[derive(Clone, Debug)]
264pub struct BroadcastableTransaction<N: Network = Ethereum> {
265 pub rpc: Option<String>,
267 pub transaction: TransactionMaybeSigned<N>,
269}
270
271#[derive(Clone, Debug, Copy)]
272pub struct RecordDebugStepInfo {
273 pub start_node_idx: usize,
275 pub original_tracer_config: TracingInspectorConfig,
277}
278
279#[derive(Clone, Copy, Debug, PartialEq, Eq)]
281pub struct StorageHook {
282 pub callback_target: Address,
284 pub callback_selector: [u8; 4],
286}
287
288#[derive(Clone, Debug)]
289enum PendingStorageHook {
290 Load {
291 account: Address,
292 slot: U256,
293 hook: StorageHook,
294 },
295 Store {
296 account: Address,
297 slot: U256,
298 old_value: U256,
299 mapping: Option<(B256, Vec<B256>)>,
300 hook: StorageHook,
301 },
302}
303
304#[derive(Clone, Debug)]
305struct ActiveStorageHook {
306 parent_depth: usize,
307 callback_target: Address,
308 callback_input: Bytes,
309 saved_gas: Gas,
310 saved_return_data: Bytes,
311 saved_stack_item: Option<U256>,
312 journal_start: usize,
313 inspector_state: StorageHookInspectorState,
314 outcome: Option<(InstructionResult, Bytes)>,
315}
316
317#[derive(Clone, Debug)]
318struct StorageHookInspectorState {
319 accesses: RecordAccess,
320 recording_accesses: bool,
321 mapping_slots: Option<AddressHashMap<MappingSlots>>,
322 recorded_logs: Option<Vec<Vm::Log>>,
323 mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
324 mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
325 expected_revert: Option<ExpectedRevert>,
326 assume_no_revert: Option<AssumeNoRevert>,
327 expected_calls: ExpectedCallTracker,
328 expected_emits: ExpectedEmitTracker,
329 expected_creates: Vec<ExpectedCreate>,
330}
331
332#[derive(Clone, Debug, Default)]
334pub struct GasMetering {
335 pub paused: bool,
337 pub touched: bool,
340 pub reset: bool,
342 pub paused_frames: Vec<Gas>,
344
345 pub active_gas_snapshot: Option<(String, String)>,
347
348 pub last_call_gas: Option<crate::Vm::Gas>,
351 pub(crate) last_call_snapshot_gas_used: u64,
353
354 pub last_frame_gas: Option<crate::Vm::Gas>,
357 pub(crate) last_frame_snapshot_gas_used: u64,
359
360 isolated_snapshot_gas_used: Option<(u64, u64)>,
363
364 pending_isolated_region_gas: Option<(usize, u64, u64)>,
367
368 pub recording: bool,
370 pub last_gas_used: u64,
372 pub gas_records: Vec<GasRecord>,
374}
375
376impl GasMetering {
377 pub const fn start(&mut self) {
379 self.recording = true;
380 self.last_gas_used = 0;
381 self.pending_isolated_region_gas = None;
382 }
383
384 pub const fn stop(&mut self) {
386 self.recording = false;
387 }
388
389 pub fn resume(&mut self) {
391 if self.paused {
392 self.paused = false;
393 self.touched = true;
394 }
395 self.paused_frames.clear();
396 }
397
398 pub fn reset(&mut self) {
400 self.paused = false;
401 self.touched = true;
402 self.reset = true;
403 self.paused_frames.clear();
404 }
405
406 pub const fn set_isolated_snapshot_gas_used(&mut self, gas_used: u64, precharged_state: u64) {
411 self.isolated_snapshot_gas_used = Some((gas_used, precharged_state));
412 }
413
414 const fn take_isolated_snapshot_gas_used(&mut self, depth: usize, gas: &Gas) -> Option<u64> {
420 let Some((gas_used, precharged_state)) = self.isolated_snapshot_gas_used.take() else {
421 return None;
422 };
423 if self.recording {
424 self.pending_isolated_region_gas =
426 Some((depth, gas.total_gas_spent(), gas_used.saturating_sub(precharged_state)));
427 }
428 Some(gas_used)
429 }
430}
431
432#[derive(Clone, Debug, Default)]
434pub struct ArbitraryStorage {
435 values: HashMap<Address, HashMap<U256, U256>>,
439 copies: HashMap<Address, Address>,
441 overwrites: HashSet<Address>,
443 explicit_slots: HashMap<Address, HashSet<U256>>,
445}
446
447impl ArbitraryStorage {
448 pub fn mark_arbitrary(&mut self, address: &Address, overwrite: bool) {
450 self.values.insert(*address, HashMap::default());
451 self.explicit_slots.remove(address);
452 if overwrite {
453 self.overwrites.insert(*address);
454 } else {
455 self.overwrites.remove(address);
456 }
457 }
458
459 pub fn mark_copy(&mut self, from: &Address, to: &Address) {
461 if self.values.contains_key(from) {
462 self.copies.insert(*to, *from);
463 if let Some(slots) = self.explicit_slots.get(from).cloned() {
464 self.explicit_slots.insert(*to, slots);
465 } else {
466 self.explicit_slots.remove(to);
467 }
468 }
469 }
470
471 fn mark_explicit(&mut self, address: Address, slot: U256) {
473 if self.values.contains_key(&address) || self.copies.contains_key(&address) {
474 self.explicit_slots.entry(address).or_default().insert(slot);
475 }
476 }
477
478 fn is_explicit(&self, address: Address, slot: U256) -> bool {
480 self.explicit_slots.get(&address).is_some_and(|slots| slots.contains(&slot))
481 }
482
483 fn targets(&self) -> impl Iterator<Item = Address> + '_ {
485 self.values.keys().copied()
486 }
487
488 fn target_overwrite_modes(&self) -> impl Iterator<Item = (Address, bool)> + '_ {
491 self.values.keys().map(|address| (*address, self.overwrites.contains(address)))
492 }
493
494 fn copied_targets(&self) -> impl Iterator<Item = Address> + '_ {
496 self.copies.keys().copied()
497 }
498
499 fn copied_target_sources(&self) -> impl Iterator<Item = (Address, Address)> + '_ {
501 self.copies.iter().map(|(target, source)| (*target, *source))
502 }
503
504 fn cache_value(&mut self, address: Address, slot: U256, data: U256) {
506 if let Some(values) = self.values.get_mut(&address) {
507 values.insert(slot, data);
508 return;
509 }
510
511 let Some(source) = self.copies.get(&address).copied() else {
512 return;
513 };
514 if let Some(values) = self.values.get_mut(&source) {
515 values.insert(slot, data);
516 }
517 }
518
519 fn cached_value(&self, address: Address, slot: U256) -> Option<U256> {
521 self.values.get(&address).and_then(|values| values.get(&slot)).copied()
522 }
523
524 pub fn save<CTX: ContextTr>(
528 &mut self,
529 ecx: &mut CTX,
530 address: Address,
531 slot: U256,
532 data: U256,
533 ) {
534 self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data);
535 if ecx.journal_mut().load_account(address).is_ok() {
536 ecx.journal_mut()
537 .sstore(address, slot, data)
538 .expect("could not set arbitrary storage value");
539 }
540 }
541
542 pub fn copy<CTX: ContextTr>(
548 &mut self,
549 ecx: &mut CTX,
550 target: Address,
551 slot: U256,
552 new_value: U256,
553 ) -> U256 {
554 let source = self.copies.get(&target).expect("missing arbitrary copy target entry");
555 let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage");
556 let value = match storage_cache.get(&slot) {
557 Some(value) => *value,
558 None => {
559 storage_cache.insert(slot, new_value);
560 if ecx.journal_mut().load_account(*source).is_ok() {
562 ecx.journal_mut()
563 .sstore(*source, slot, new_value)
564 .expect("could not copy arbitrary storage value");
565 }
566 new_value
567 }
568 };
569 if ecx.journal_mut().load_account(target).is_ok() {
571 ecx.journal_mut().sstore(target, slot, value).expect("could not set storage");
572 }
573 value
574 }
575}
576
577pub type BroadcastableTransactions<N> = VecDeque<BroadcastableTransaction<N>>;
579
580#[derive(Clone, Copy, Debug, PartialEq, Eq)]
581enum CreatedAccountsFrameKind {
582 Call,
583 Create,
584}
585
586#[derive(Clone, Copy, Debug)]
587struct CreatedAccountsFrame {
588 kind: CreatedAccountsFrameKind,
589 depth: usize,
590 checkpoint: usize,
591}
592
593#[derive(Clone, Copy, Debug)]
594struct CreatedAccountChange {
595 fork_id: Option<LocalForkId>,
596 address: Address,
597 creation: usize,
598 previous: Option<usize>,
599 committed: bool,
600}
601
602#[derive(Clone, Debug)]
603struct CreatedAccountsSnapshot {
604 fork_id: Option<LocalForkId>,
605 bindings: AddressHashMap<usize>,
606}
607
608#[derive(Clone, Debug)]
626pub struct Cheatcodes<FEN: FoundryEvmNetwork = EthEvmNetwork> {
627 pub analysis: Option<CheatcodeAnalysis>,
629
630 pub block: Option<BlockEnvFor<FEN>>,
635
636 pub fork_block_number_override: Option<u64>,
640
641 pub active_delegations: Vec<SignedAuthorization>,
645
646 pub active_blob_sidecar: Option<BlobTransactionSidecarVariant>,
648
649 pub gas_price: Option<u128>,
654
655 pub labels: AddressHashMap<String>,
657
658 pub pranks: BTreeMap<usize, Prank>,
660
661 pub expected_revert: Option<ExpectedRevert>,
663
664 pub assume_no_revert: Option<AssumeNoRevert>,
666
667 pub fork_revert_diagnostic: Option<RevertDiagnostic>,
669
670 pub accesses: RecordAccess,
672
673 pub recording_accesses: bool,
675
676 pub recorded_account_diffs_stack: Option<Vec<Vec<AccountAccess>>>,
682
683 pending_account_diffs: Option<Arc<[AccountAccess]>>,
685
686 recorded_account_diffs_prefix: Option<Arc<[AccountAccess]>>,
688
689 created_accounts: Vec<Address>,
691
692 created_account_bindings: HashMap<(Option<LocalForkId>, Address), usize>,
694
695 created_account_changes: Vec<CreatedAccountChange>,
697
698 created_accounts_frames: Vec<CreatedAccountsFrame>,
700
701 created_accounts_snapshots: HashMap<U256, CreatedAccountsSnapshot>,
703
704 pub record_debug_steps_info: Option<RecordDebugStepInfo>,
706
707 pub recorded_logs: Option<Vec<crate::Vm::Log>>,
709
710 pub mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
713
714 pub mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
716
717 pub expected_calls: ExpectedCallTracker,
719 pub expected_emits: ExpectedEmitTracker,
721 pub expected_creates: Vec<ExpectedCreate>,
723
724 pub allowed_mem_writes: HashMap<u64, Vec<Range<u64>>>,
726
727 pub broadcast: Option<Broadcast>,
729
730 pub broadcastable_transactions: BroadcastableTransactions<FEN::Network>,
732
733 pub access_list: Option<AccessList>,
735
736 pub config: Arc<CheatsConfig>,
738
739 pub extra_cheatcode_addresses: &'static [Address],
741
742 pub test_context: TestContext,
744
745 pub skip_payloads: Vec<Bytes>,
750
751 pub fs_commit: bool,
754
755 pub serialized_jsons: BTreeMap<String, BTreeMap<String, Value>>,
758
759 pub eth_deals: Vec<DealRecord>,
761
762 pub gas_metering: GasMetering,
764
765 pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>,
768
769 pub mapping_slots: Option<AddressHashMap<MappingSlots>>,
771
772 pub pc: usize,
774 pub breakpoints: Breakpoints,
777
778 pub intercept_next_create_call: bool,
780
781 test_runner: Option<TestRunner>,
784
785 pub ignored_traces: IgnoredTraces,
787
788 pub arbitrary_storage: Option<ArbitraryStorage>,
790
791 storage_load_hooks: AddressHashMap<StorageHook>,
793 storage_store_hooks: AddressHashMap<StorageHook>,
795 mapping_storage_store_hooks: AddressHashMap<HashMap<B256, StorageHook>>,
797 storage_hook_mapping_slots: AddressHashMap<MappingSlots>,
799 pending_mapping_hash: Option<PendingMappingHash>,
801 storage_hooks_registered: bool,
803 pending_storage_hook: Option<PendingStorageHook>,
805 active_storage_hook: Option<ActiveStorageHook>,
807
808 pub deprecated: HashMap<&'static str, Option<&'static str>>,
810 pub script_address: Option<Address>,
812 pub wallets: Option<Wallets>,
814 pub private_key_signers: HashMap<U256, PrivateKeySigner>,
816 signatures_identifier: OnceLock<Option<SignaturesIdentifier>>,
818 pub dynamic_gas_limit: bool,
820 pub execution_evm_version: Option<SpecFor<FEN>>,
822
823 pub env_overrides: EnvOverrideState,
825
826 pub fork_block_number_override_snapshots: HashMap<U256, Option<u64>>,
828
829 #[cfg(feature = "monad")]
832 pub context_snapshots:
833 HashMap<U256, (ChainFor<FEN>, monad_revm::reserve_balance::tracker::ReserveBalanceTracker)>,
834
835 pub in_isolation_context: bool,
845
846 pub pending_isolated_snapshot_journal: Option<Vec<JournalEntry>>,
849
850 pub track_isolated_snapshots: bool,
853
854 pub isolated_snapshot_restores: Vec<JournaledState>,
857
858 pub capture_isolated_snapshot_restore: bool,
861
862 pub deploy_code_depth: Option<usize>,
864}
865
866impl Default for Cheatcodes {
870 fn default() -> Self {
871 Self::new(Arc::default())
872 }
873}
874
875impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
876 pub fn new(config: Arc<CheatsConfig>) -> Self {
878 Self {
879 analysis: None,
880 fs_commit: true,
881 labels: config.labels.clone(),
882 config,
883 extra_cheatcode_addresses: &[],
884 block: Default::default(),
885 fork_block_number_override: Default::default(),
886 active_delegations: Default::default(),
887 active_blob_sidecar: Default::default(),
888 gas_price: Default::default(),
889 pranks: Default::default(),
890 expected_revert: Default::default(),
891 assume_no_revert: Default::default(),
892 fork_revert_diagnostic: Default::default(),
893 accesses: Default::default(),
894 recording_accesses: Default::default(),
895 recorded_account_diffs_stack: Default::default(),
896 pending_account_diffs: Default::default(),
897 recorded_account_diffs_prefix: Default::default(),
898 created_accounts: Default::default(),
899 created_account_bindings: Default::default(),
900 created_account_changes: Default::default(),
901 created_accounts_frames: Default::default(),
902 created_accounts_snapshots: Default::default(),
903 recorded_logs: Default::default(),
904 record_debug_steps_info: Default::default(),
905 mocked_calls: Default::default(),
906 mocked_functions: Default::default(),
907 expected_calls: Default::default(),
908 expected_emits: Default::default(),
909 expected_creates: Default::default(),
910 allowed_mem_writes: Default::default(),
911 broadcast: Default::default(),
912 broadcastable_transactions: Default::default(),
913 access_list: Default::default(),
914 test_context: Default::default(),
915 skip_payloads: Default::default(),
916 serialized_jsons: Default::default(),
917 eth_deals: Default::default(),
918 gas_metering: Default::default(),
919 gas_snapshots: Default::default(),
920 mapping_slots: Default::default(),
921 pc: Default::default(),
922 breakpoints: Default::default(),
923 intercept_next_create_call: Default::default(),
924 test_runner: Default::default(),
925 ignored_traces: Default::default(),
926 arbitrary_storage: Default::default(),
927 storage_load_hooks: Default::default(),
928 storage_store_hooks: Default::default(),
929 mapping_storage_store_hooks: Default::default(),
930 storage_hook_mapping_slots: Default::default(),
931 pending_mapping_hash: Default::default(),
932 storage_hooks_registered: Default::default(),
933 pending_storage_hook: Default::default(),
934 active_storage_hook: Default::default(),
935 deprecated: Default::default(),
936 script_address: Default::default(),
937 wallets: Default::default(),
938 private_key_signers: Default::default(),
939 signatures_identifier: Default::default(),
940 dynamic_gas_limit: Default::default(),
941 execution_evm_version: None,
942 env_overrides: Default::default(),
943 fork_block_number_override_snapshots: Default::default(),
944 #[cfg(feature = "monad")]
945 context_snapshots: Default::default(),
946 in_isolation_context: false,
947 pending_isolated_snapshot_journal: None,
948 track_isolated_snapshots: false,
949 isolated_snapshot_restores: Vec::new(),
950 capture_isolated_snapshot_restore: false,
951 deploy_code_depth: None,
952 }
953 }
954
955 #[inline]
957 pub const fn set_extra_cheatcode_addresses(&mut self, addresses: &'static [Address]) {
958 self.extra_cheatcode_addresses = addresses;
959 }
960
961 pub fn set_analysis(&mut self, analysis: CheatcodeAnalysis) {
963 self.analysis = Some(analysis);
964 }
965
966 pub fn start_internal_state_diff_recording(&mut self) -> bool {
968 if self.recorded_account_diffs_stack.is_some()
969 || self.recorded_account_diffs_prefix.is_some()
970 {
971 return false;
972 }
973 self.recorded_account_diffs_stack = Some(Default::default());
974 true
975 }
976
977 pub fn stop_internal_state_diff_recording(&mut self) -> Vec<AccountAccess> {
979 self.recorded_account_diffs_stack.take().unwrap_or_default().into_iter().flatten().collect()
980 }
981
982 pub fn set_pending_account_diffs(&mut self, accesses: Vec<AccountAccess>) {
984 self.pending_account_diffs = (!accesses.is_empty()).then(|| Arc::from(accesses));
985 }
986
987 pub fn start_state_diff_recording(&mut self) {
989 self.recorded_account_diffs_prefix = self.pending_account_diffs.take();
990 self.recorded_account_diffs_stack = Some(Default::default());
991 }
992
993 pub fn recorded_account_diffs(&self) -> impl Iterator<Item = &AccountAccess> {
995 self.recorded_account_diffs_prefix
996 .iter()
997 .flat_map(|prefix| prefix.iter())
998 .chain(self.recorded_account_diffs_stack.iter().flatten().flatten())
999 }
1000
1001 pub fn take_recorded_account_diffs_prefix(&mut self) -> Vec<AccountAccess> {
1003 self.recorded_account_diffs_prefix
1004 .take()
1005 .map(|prefix| prefix.as_ref().to_vec())
1006 .unwrap_or_default()
1007 }
1008
1009 pub(crate) fn created_account_bindings(
1011 &self,
1012 fork_id: Option<LocalForkId>,
1013 ) -> AddressHashMap<usize> {
1014 self.created_account_bindings
1015 .iter()
1016 .filter_map(|(&(event_fork_id, address), &creation)| {
1017 (event_fork_id == fork_id).then_some((address, creation))
1018 })
1019 .collect()
1020 }
1021
1022 pub(crate) fn created_accounts(&self, fork_id: Option<LocalForkId>) -> Vec<Address> {
1024 let bindings = self.created_account_bindings(fork_id);
1025 self.created_accounts
1026 .iter()
1027 .enumerate()
1028 .filter_map(|(index, &address)| {
1029 (bindings.get(&address) == Some(&index)).then_some(address)
1030 })
1031 .collect()
1032 }
1033
1034 pub(crate) fn record_created_account(
1036 &mut self,
1037 fork_id: Option<LocalForkId>,
1038 address: Address,
1039 ) {
1040 let creation = self.created_accounts.len();
1041 self.created_accounts.push(address);
1042 let previous = self.created_account_bindings.insert((fork_id, address), creation);
1043 self.created_account_changes.push(CreatedAccountChange {
1044 fork_id,
1045 address,
1046 creation,
1047 previous,
1048 committed: false,
1049 });
1050 }
1051
1052 pub(crate) fn commit_created_account_changes(&mut self, fork_id: Option<LocalForkId>) {
1054 for change in &mut self.created_account_changes {
1055 if change.fork_id == fork_id {
1056 change.committed = true;
1057 }
1058 }
1059 }
1060
1061 pub(crate) fn record_initial_created_accounts(
1063 &mut self,
1064 fork_id: Option<LocalForkId>,
1065 accounts: impl IntoIterator<Item = (Address, usize)>,
1066 ) {
1067 for (address, creation) in accounts {
1068 self.created_account_bindings.entry((fork_id, address)).or_insert(creation);
1069 }
1070 }
1071
1072 pub(crate) fn record_propagated_accounts(
1074 &mut self,
1075 fork_id: Option<LocalForkId>,
1076 accounts: impl IntoIterator<Item = (Address, usize)>,
1077 ) {
1078 self.created_account_bindings
1079 .extend(accounts.into_iter().map(|(address, creation)| ((fork_id, address), creation)));
1080 }
1081
1082 pub(crate) fn snapshot_created_accounts(
1084 &mut self,
1085 snapshot_id: U256,
1086 fork_id: Option<LocalForkId>,
1087 ) {
1088 let bindings = self.created_account_bindings(fork_id);
1089 self.created_accounts_snapshots
1090 .insert(snapshot_id, CreatedAccountsSnapshot { fork_id, bindings });
1091 }
1092
1093 pub(crate) fn revert_created_accounts(&mut self, snapshot_id: U256, remove: bool) {
1095 let snapshot = if remove {
1096 self.created_accounts_snapshots.remove(&snapshot_id)
1097 } else {
1098 self.created_accounts_snapshots.get(&snapshot_id).cloned()
1099 };
1100 if let Some(snapshot) = snapshot {
1101 self.created_account_bindings.retain(|(fork_id, _), _| *fork_id != snapshot.fork_id);
1102 self.created_account_bindings.extend(
1103 snapshot
1104 .bindings
1105 .into_iter()
1106 .map(|(address, creation)| ((snapshot.fork_id, address), creation)),
1107 );
1108 }
1109 }
1110
1111 pub(crate) fn delete_created_accounts_snapshot(&mut self, snapshot_id: U256) {
1113 self.created_accounts_snapshots.remove(&snapshot_id);
1114 }
1115
1116 pub(crate) fn clear_created_accounts_snapshots(&mut self) {
1118 self.created_accounts_snapshots.clear();
1119 }
1120
1121 fn start_created_accounts_frame(
1122 &mut self,
1123 reset: bool,
1124 kind: CreatedAccountsFrameKind,
1125 depth: usize,
1126 ) {
1127 if reset {
1128 self.created_accounts.clear();
1129 self.created_account_bindings.clear();
1130 self.created_account_changes.clear();
1131 self.created_accounts_frames.clear();
1132 for snapshot in self.created_accounts_snapshots.values_mut() {
1134 snapshot.bindings.clear();
1135 }
1136 }
1137 self.created_accounts_frames.push(CreatedAccountsFrame {
1138 kind,
1139 depth,
1140 checkpoint: self.created_account_changes.len(),
1141 });
1142 }
1143
1144 fn finish_created_accounts_frame(
1145 &mut self,
1146 success: bool,
1147 kind: CreatedAccountsFrameKind,
1148 depth: usize,
1149 ) {
1150 let Some(frame) = self
1151 .created_accounts_frames
1152 .last()
1153 .copied()
1154 .filter(|frame| frame.kind == kind && frame.depth == depth)
1155 else {
1156 return;
1157 };
1158 let checkpoint = frame.checkpoint;
1159 self.created_accounts_frames.pop();
1160 if !success {
1161 while self.created_account_changes.len() > checkpoint {
1162 let change = self.created_account_changes.pop().expect("length checked");
1163 if change.committed {
1164 continue;
1165 }
1166 let key = (change.fork_id, change.address);
1167 if self.created_account_bindings.get(&key) != Some(&change.creation) {
1168 continue;
1169 }
1170 if let Some(previous) = change.previous {
1171 self.created_account_bindings.insert(key, previous);
1172 } else {
1173 self.created_account_bindings.remove(&key);
1174 }
1175 }
1176 }
1177 }
1178
1179 pub fn get_prank(&self, depth: usize) -> Option<&Prank> {
1183 self.pranks.range(..=depth).last().map(|(_, prank)| prank)
1184 }
1185
1186 pub fn wallets(&mut self) -> &Wallets {
1188 self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None))
1189 }
1190
1191 pub fn set_wallets(&mut self, wallets: Wallets) {
1193 self.wallets = Some(wallets);
1194 }
1195
1196 pub fn add_delegation(&mut self, authorization: SignedAuthorization) {
1198 self.active_delegations.push(authorization);
1199 }
1200
1201 pub fn signatures_identifier(&self) -> Option<&SignaturesIdentifier> {
1203 self.signatures_identifier
1204 .get_or_init(|| {
1205 if let Some(artifacts) = &self.config.available_artifacts {
1206 return SignaturesIdentifier::new_offline_with_abis(
1207 artifacts.values().map(|contract| &contract.abi),
1208 )
1209 .ok();
1210 }
1211 SignaturesIdentifier::new(true).ok()
1212 })
1213 .as_ref()
1214 }
1215
1216 fn apply_cheatcode(
1218 &mut self,
1219 ecx: &mut FoundryContextFor<'_, FEN>,
1220 call: &CallInputs,
1221 executor: &mut dyn CheatcodesExecutor<FEN>,
1222 ) -> Result {
1223 let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| {
1225 if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e {
1226 let msg = format!(
1227 "unknown cheatcode with selector {selector}; \
1228 you may have a mismatch between the `Vm` interface (likely in `forge-std`) \
1229 and the `forge` version"
1230 );
1231 return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg));
1232 }
1233 e
1234 })?;
1235
1236 let caller = call.transfer_from();
1237
1238 ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
1241
1242 apply_dispatch(
1243 &decoded,
1244 &mut CheatsCtxt {
1245 state: self,
1246 ecx,
1247 gas_limit: call.gas_limit,
1248 caller,
1249 is_static: call.is_static,
1250 },
1251 executor,
1252 )
1253 }
1254
1255 #[cfg(feature = "monad")]
1257 fn apply_monad_cheatcode(
1258 &mut self,
1259 ecx: &mut FoundryContextFor<'_, FEN>,
1260 call: &CallInputs,
1261 ) -> Result {
1262 let input = call.input.bytes(ecx);
1263 let caller = call.transfer_from();
1264
1265 ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
1268
1269 crate::monad::apply_monad_cheatcode(
1270 &mut CheatsCtxt {
1271 state: self,
1272 ecx,
1273 gas_limit: call.gas_limit,
1274 caller,
1275 is_static: call.is_static,
1276 },
1277 &input,
1278 )
1279 }
1280
1281 fn allow_cheatcodes_on_create(
1287 &self,
1288 ecx: &mut FoundryContextFor<FEN>,
1289 caller: Address,
1290 created_address: Address,
1291 ) {
1292 if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) {
1293 ecx.db_mut().allow_cheatcode_access(created_address);
1294 }
1295 }
1296
1297 fn apply_accesslist(&mut self, ecx: &mut FoundryContextFor<FEN>) {
1303 if let Some(access_list) = &self.access_list {
1304 ecx.tx_mut().set_access_list(access_list.clone());
1305
1306 if ecx.tx().tx_type() == TransactionType::Legacy as u8 {
1307 ecx.tx_mut().set_tx_type(TransactionType::Eip2930 as u8);
1308 }
1309 }
1310 }
1311
1312 pub fn on_revert(&mut self, ecx: &mut FoundryContextFor<FEN>) {
1317 trace!(deals=?self.eth_deals.len(), "rolling back deals");
1318
1319 if self.expected_revert.is_some() {
1321 return;
1322 }
1323
1324 if ecx.journal().depth() > 0 {
1326 return;
1327 }
1328
1329 while let Some(record) = self.eth_deals.pop() {
1333 if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) {
1334 acc.info.balance = record.old_balance;
1335 }
1336 }
1337 }
1338
1339 pub fn call_with_executor(
1344 &mut self,
1345 ecx: &mut FoundryContextFor<'_, FEN>,
1346 call: &mut CallInputs,
1347 executor: &mut dyn CheatcodesExecutor<FEN>,
1348 isolate_call: bool,
1349 ) -> Option<CallOutcome> {
1350 if let Some(spec_id) = self.execution_evm_version {
1352 EvmFactoryFor::<FEN>::set_execution_spec(ecx, spec_id);
1353 }
1354
1355 let gas = Gas::new(call.gas_limit);
1356 let curr_depth = ecx.journal().depth();
1357 self.start_created_accounts_frame(
1358 curr_depth == 0,
1359 CreatedAccountsFrameKind::Call,
1360 curr_depth,
1361 );
1362
1363 if curr_depth == 0 {
1367 let sender = ecx.tx().caller();
1368 let account = match super::evm::journaled_account(ecx, sender) {
1369 Ok(account) => account,
1370 Err(err) => {
1371 return Some(CallOutcome {
1372 result: InterpreterResult {
1373 result: InstructionResult::Revert,
1374 output: err.abi_encode().into(),
1375 gas,
1376 },
1377 memory_offset: call.return_memory_offset.clone(),
1378 was_precompile_called: false,
1379 precompile_call_logs: vec![],
1380 charged_new_account_state_gas: call.charged_new_account_state_gas,
1381 });
1382 }
1383 };
1384 let prev = account.info.nonce;
1385 account.info.nonce = prev.saturating_sub(1);
1386
1387 trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
1388 }
1389
1390 if call.transfer_to() == CHEATCODE_ADDRESS {
1391 return match self.apply_cheatcode(ecx, call, executor) {
1392 Ok(retdata) => Some(CallOutcome {
1393 result: InterpreterResult {
1394 result: InstructionResult::Return,
1395 output: retdata.into(),
1396 gas,
1397 },
1398 memory_offset: call.return_memory_offset.clone(),
1399 was_precompile_called: true,
1400 precompile_call_logs: vec![],
1401 charged_new_account_state_gas: call.charged_new_account_state_gas,
1402 }),
1403 Err(err) => Some(CallOutcome {
1404 result: InterpreterResult {
1405 result: InstructionResult::Revert,
1406 output: err.abi_encode().into(),
1407 gas,
1408 },
1409 memory_offset: call.return_memory_offset.clone(),
1410 was_precompile_called: false,
1411 precompile_call_logs: vec![],
1412 charged_new_account_state_gas: call.charged_new_account_state_gas,
1413 }),
1414 };
1415 }
1416
1417 #[cfg(feature = "monad")]
1418 if crate::monad::is_monad_cheatcode_call(self.extra_cheatcode_addresses, call.transfer_to())
1419 {
1420 let checkpoint = ecx.journal_mut().checkpoint();
1421 return match self.apply_monad_cheatcode(ecx, call) {
1422 Ok(retdata) => {
1423 ecx.journal_mut().checkpoint_commit();
1424 Some(CallOutcome {
1425 result: InterpreterResult {
1426 result: InstructionResult::Return,
1427 output: retdata.into(),
1428 gas,
1429 },
1430 memory_offset: call.return_memory_offset.clone(),
1431 was_precompile_called: true,
1432 precompile_call_logs: vec![],
1433 charged_new_account_state_gas: call.charged_new_account_state_gas,
1434 })
1435 }
1436 Err(err) => {
1437 ecx.journal_mut().checkpoint_revert(checkpoint);
1438 Some(CallOutcome {
1439 result: InterpreterResult {
1440 result: InstructionResult::Revert,
1441 output: err.abi_encode().into(),
1442 gas,
1443 },
1444 memory_offset: call.return_memory_offset.clone(),
1445 was_precompile_called: false,
1446 precompile_call_logs: vec![],
1447 charged_new_account_state_gas: call.charged_new_account_state_gas,
1448 })
1449 }
1450 };
1451 }
1452
1453 if call.transfer_to() == HARDHAT_CONSOLE_ADDRESS {
1454 return None;
1455 }
1456
1457 if let Some(expected) = &mut self.expected_revert {
1461 expected.max_depth = max(curr_depth + 1, expected.max_depth);
1462 }
1463
1464 if let Some(expected) = self.expected_calls.get_mut(&call.bytecode_address) {
1466 let input = call.input.as_bytes(ecx);
1467 expect::observe_call(
1468 expected,
1469 &input,
1470 call.transfer_value(),
1471 call.gas_limit,
1472 call.scheme,
1473 );
1474 }
1475
1476 if let Some(prank) = self.get_prank(curr_depth).copied() {
1478 if prank.delegate_call && curr_depth == prank.depth && call.scheme.is_delegate_call() {
1480 call.target_address = prank.new_caller;
1481 call.caller = prank.new_caller;
1482 if let Some(new_origin) = prank.new_origin {
1483 ecx.tx_mut().set_caller(new_origin);
1484 }
1485 if let Some(used) = prank.first_time_applied() {
1486 self.pranks.insert(curr_depth, used);
1487 }
1488 }
1489
1490 if let Some(changes) = prank.changes_for(curr_depth, call.transfer_from()) {
1491 if let Some(new_caller) = changes.caller {
1492 let _ = journaled_account(ecx, new_caller);
1494 call.caller = new_caller;
1495 }
1496 if let Some(new_origin) = changes.origin {
1497 ecx.tx_mut().set_caller(new_origin);
1498 }
1499 if let Some(used) = changes.used {
1500 self.pranks.insert(curr_depth, used);
1501 }
1502 }
1503 }
1504
1505 if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
1507 let input = call.input.bytes(ecx);
1508 if let Some(return_data_queue) =
1509 mock::find_mock_returns(mocks, &input, call.transfer_value())
1510 && let Some(return_data) = return_data_queue.front().map(|x| x.to_owned())
1511 {
1512 if let Some(value) = call.transfer_value() {
1513 let checkpoint = ecx.journal_mut().checkpoint();
1514 match ecx.journal_mut().transfer_loaded(
1515 call.transfer_from(),
1516 call.transfer_to(),
1517 value,
1518 ) {
1519 None => {
1520 if return_data.ret_type.is_ok() {
1521 ecx.journal_mut().checkpoint_commit();
1522 } else {
1523 ecx.journal_mut().checkpoint_revert(checkpoint);
1524 }
1525 }
1526 Some(err) => {
1527 ecx.journal_mut().checkpoint_revert(checkpoint);
1528 return Some(CallOutcome {
1529 result: InterpreterResult {
1530 result: err.into(),
1531 output: Bytes::new(),
1532 gas,
1533 },
1534 memory_offset: call.return_memory_offset.clone(),
1535 was_precompile_called: false,
1536 precompile_call_logs: vec![],
1537 charged_new_account_state_gas: call.charged_new_account_state_gas,
1538 });
1539 }
1540 }
1541 }
1542
1543 mock::advance_mock_returns(return_data_queue);
1544
1545 return Some(CallOutcome {
1546 result: InterpreterResult {
1547 result: return_data.ret_type,
1548 output: return_data.data,
1549 gas,
1550 },
1551 memory_offset: call.return_memory_offset.clone(),
1552 was_precompile_called: true,
1553 precompile_call_logs: vec![],
1554 charged_new_account_state_gas: call.charged_new_account_state_gas,
1555 });
1556 }
1557 }
1558
1559 self.apply_accesslist(ecx);
1561
1562 if let Some(broadcast) = &mut self.broadcast {
1564 let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
1567 self.dynamic_gas_limit = false;
1568
1569 if (curr_depth == broadcast.depth || broadcast.deploy_from_code)
1575 && call.transfer_from() == broadcast.original_caller
1576 {
1577 broadcast.deploy_from_code = false;
1579
1580 ecx.tx_mut().set_caller(broadcast.new_origin);
1584
1585 call.caller = broadcast.new_origin;
1586 if !call.is_static {
1591 if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
1592 return Some(CallOutcome {
1593 result: InterpreterResult {
1594 result: InstructionResult::Revert,
1595 output: Error::encode(err),
1596 gas,
1597 },
1598 memory_offset: call.return_memory_offset.clone(),
1599 was_precompile_called: false,
1600 precompile_call_logs: vec![],
1601 charged_new_account_state_gas: call.charged_new_account_state_gas,
1602 });
1603 }
1604
1605 let input = call.input.bytes(ecx);
1606 let chain_id = ecx.cfg().chain_id();
1607 let rpc = ecx.db().active_fork_url();
1608 let fee_token = ecx.tx().fee_token();
1609 let nonce =
1610 ecx.journal().evm_state().get(&broadcast.new_origin).unwrap().info.nonce;
1611
1612 let mut tx_req = TransactionRequestFor::<FEN>::default()
1613 .with_from(broadcast.new_origin)
1614 .with_to(call.transfer_to())
1615 .with_value(call.transfer_value().unwrap_or_default())
1616 .with_input(input)
1617 .with_nonce(nonce)
1618 .with_chain_id(chain_id);
1619 if is_fixed_gas_limit {
1620 tx_req.set_gas_limit(call.gas_limit)
1621 }
1622
1623 let active_delegations = std::mem::take(&mut self.active_delegations);
1624 if let Some(blob_sidecar) = self.active_blob_sidecar.take() {
1626 if !active_delegations.is_empty() {
1628 let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible";
1629 return Some(CallOutcome {
1630 result: InterpreterResult {
1631 result: InstructionResult::Revert,
1632 output: Error::encode(msg),
1633 gas,
1634 },
1635 memory_offset: call.return_memory_offset.clone(),
1636 was_precompile_called: false,
1637 precompile_call_logs: vec![],
1638 charged_new_account_state_gas: call.charged_new_account_state_gas,
1639 });
1640 }
1641 tx_req.set_blob_sidecar(blob_sidecar);
1642 }
1643
1644 if !active_delegations.is_empty() {
1646 if let Err(err) = apply_authorization_nonces::<FEN>(
1647 ecx,
1648 &active_delegations,
1649 broadcast.new_origin,
1650 chain_id,
1651 ) {
1652 return Some(CallOutcome {
1653 result: InterpreterResult {
1654 result: InstructionResult::Revert,
1655 output: err.abi_encode().into(),
1656 gas,
1657 },
1658 memory_offset: call.return_memory_offset.clone(),
1659 was_precompile_called: false,
1660 precompile_call_logs: vec![],
1661 charged_new_account_state_gas: call.charged_new_account_state_gas,
1662 });
1663 }
1664 tx_req.set_authorization_list(active_delegations);
1665 }
1666 if let Some(fee_token) = fee_token {
1667 tx_req.set_fee_token(fee_token);
1668 }
1669 self.broadcastable_transactions.push_back(BroadcastableTransaction {
1670 rpc,
1671 transaction: TransactionMaybeSigned::new(tx_req),
1672 });
1673 debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call");
1674
1675 if !isolate_call {
1678 let account = ecx
1679 .journal_mut()
1680 .evm_state_mut()
1681 .get_mut(&broadcast.new_origin)
1682 .unwrap();
1683 let prev = account.info.nonce;
1684 account.info.nonce += 1;
1685 debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce");
1686 }
1687 } else if broadcast.single_call {
1688 let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead";
1689 return Some(CallOutcome {
1690 result: InterpreterResult {
1691 result: InstructionResult::Revert,
1692 output: Error::encode(msg),
1693 gas,
1694 },
1695 memory_offset: call.return_memory_offset.clone(),
1696 was_precompile_called: false,
1697 precompile_call_logs: vec![],
1698 charged_new_account_state_gas: call.charged_new_account_state_gas,
1699 });
1700 }
1701 }
1702 }
1703
1704 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1706 let (initialized, old_balance, old_nonce) =
1709 if let Ok(acc) = ecx.journal_mut().load_account(call.transfer_to()) {
1710 (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce)
1711 } else {
1712 (false, U256::ZERO, 0)
1713 };
1714
1715 let kind = match call.scheme {
1716 CallScheme::Call => crate::Vm::AccountAccessKind::Call,
1717 CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode,
1718 CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall,
1719 CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall,
1720 };
1721
1722 recorded_account_diffs_stack.push(vec![AccountAccess {
1728 chainInfo: crate::Vm::ChainInfo {
1729 forkId: ecx.db().active_fork_id().unwrap_or_default(),
1730 chainId: U256::from(ecx.cfg().chain_id()),
1731 },
1732 accessor: call.transfer_from(),
1733 account: call.bytecode_address,
1734 kind,
1735 initialized,
1736 oldBalance: old_balance,
1737 newBalance: U256::ZERO, oldNonce: old_nonce,
1739 newNonce: 0, value: call.call_value(),
1741 data: call.input.bytes(ecx),
1742 reverted: false,
1743 deployedCode: Bytes::new(),
1744 storageAccesses: vec![], depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1746 }]);
1747 }
1748
1749 None
1750 }
1751
1752 pub fn rng(&mut self) -> &mut impl Rng {
1753 self.test_runner().rng()
1754 }
1755
1756 pub fn test_runner(&mut self) -> &mut TestRunner {
1757 self.test_runner.get_or_insert_with(|| match self.config.seed {
1758 Some(seed) => TestRunner::new_with_rng(
1759 proptest::test_runner::Config::default(),
1760 TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1761 ),
1762 None => TestRunner::new(proptest::test_runner::Config::default()),
1763 })
1764 }
1765
1766 pub fn set_seed(&mut self, seed: U256) {
1767 self.test_runner = Some(TestRunner::new_with_rng(
1768 proptest::test_runner::Config::default(),
1769 TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1770 ));
1771 }
1772
1773 pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage {
1776 self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default)
1777 }
1778
1779 pub fn arbitrary_storage_targets(&self) -> impl Iterator<Item = Address> + '_ {
1781 self.arbitrary_storage.as_ref().into_iter().flat_map(ArbitraryStorage::targets)
1782 }
1783
1784 pub fn arbitrary_storage_target_overwrite_modes(
1787 &self,
1788 ) -> impl Iterator<Item = (Address, bool)> + '_ {
1789 self.arbitrary_storage
1790 .as_ref()
1791 .into_iter()
1792 .flat_map(ArbitraryStorage::target_overwrite_modes)
1793 }
1794
1795 pub fn arbitrary_storage_copied_targets(&self) -> impl Iterator<Item = Address> + '_ {
1797 self.arbitrary_storage.as_ref().into_iter().flat_map(ArbitraryStorage::copied_targets)
1798 }
1799
1800 pub fn arbitrary_storage_copied_target_sources(
1802 &self,
1803 ) -> impl Iterator<Item = (Address, Address)> + '_ {
1804 self.arbitrary_storage
1805 .as_ref()
1806 .into_iter()
1807 .flat_map(ArbitraryStorage::copied_target_sources)
1808 }
1809
1810 pub fn cache_arbitrary_storage_value(&mut self, address: Address, slot: U256, value: U256) {
1812 if let Some(storage) = &mut self.arbitrary_storage {
1813 storage.cache_value(address, slot, value);
1814 }
1815 }
1816
1817 pub fn mark_arbitrary_storage_slot_explicit(&mut self, address: Address, slot: U256) {
1819 if let Some(storage) = &mut self.arbitrary_storage {
1820 storage.mark_explicit(address, slot);
1821 }
1822 }
1823
1824 pub fn is_arbitrary_storage_slot_explicit(&self, address: Address, slot: U256) -> bool {
1826 self.arbitrary_storage.as_ref().is_some_and(|storage| storage.is_explicit(address, slot))
1827 }
1828
1829 pub fn cached_arbitrary_storage_value(&self, address: Address, slot: U256) -> Option<U256> {
1831 self.arbitrary_storage.as_ref().and_then(|storage| storage.cached_value(address, slot))
1832 }
1833
1834 pub fn has_arbitrary_storage(&self, address: &Address) -> bool {
1836 match &self.arbitrary_storage {
1837 Some(storage) => storage.values.contains_key(address),
1838 None => false,
1839 }
1840 }
1841
1842 pub fn should_overwrite_arbitrary_storage(
1846 &self,
1847 address: &Address,
1848 storage_slot: U256,
1849 ) -> bool {
1850 match &self.arbitrary_storage {
1851 Some(storage) => {
1852 storage.overwrites.contains(address)
1853 && storage
1854 .values
1855 .get(address)
1856 .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot))
1857 .is_none()
1858 }
1859 None => false,
1860 }
1861 }
1862
1863 pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool {
1865 match &self.arbitrary_storage {
1866 Some(storage) => storage.copies.contains_key(address),
1867 None => false,
1868 }
1869 }
1870
1871 pub fn register_storage_load_hook(
1873 &mut self,
1874 target: Address,
1875 callback_target: Address,
1876 callback_selector: [u8; 4],
1877 ) {
1878 self.storage_load_hooks.insert(target, StorageHook { callback_target, callback_selector });
1879 self.storage_hooks_registered = true;
1880 }
1881
1882 pub fn register_storage_store_hook(
1884 &mut self,
1885 target: Address,
1886 callback_target: Address,
1887 callback_selector: [u8; 4],
1888 ) {
1889 self.storage_store_hooks.insert(target, StorageHook { callback_target, callback_selector });
1890 self.storage_hooks_registered = true;
1891 }
1892
1893 pub fn register_mapping_storage_store_hook(
1895 &mut self,
1896 target: Address,
1897 root_slot: B256,
1898 callback_target: Address,
1899 callback_selector: [u8; 4],
1900 ) -> bool {
1901 if self.storage_store_hooks.contains_key(&target) {
1902 return false;
1903 }
1904 self.storage_hook_mapping_slots.remove(&target);
1905 self.mapping_storage_store_hooks
1906 .entry(target)
1907 .or_default()
1908 .insert(root_slot, StorageHook { callback_target, callback_selector });
1909 self.storage_hooks_registered = true;
1910 true
1911 }
1912
1913 pub fn mapping_storage_store_hooks(
1915 &self,
1916 ) -> impl Iterator<Item = (Address, B256, StorageHook)> + '_ {
1917 self.mapping_storage_store_hooks
1918 .iter()
1919 .flat_map(|(target, hooks)| hooks.iter().map(|(root, hook)| (*target, *root, *hook)))
1920 }
1921
1922 pub fn has_mapping_storage_store_hooks(&self, target: Address) -> bool {
1924 self.mapping_storage_store_hooks.get(&target).is_some_and(|hooks| !hooks.is_empty())
1925 }
1926
1927 pub fn storage_load_hooks(&self) -> impl Iterator<Item = (Address, StorageHook)> + '_ {
1929 self.storage_load_hooks.iter().map(|(target, hook)| (*target, *hook))
1930 }
1931
1932 pub fn storage_store_hooks(&self) -> impl Iterator<Item = (Address, StorageHook)> + '_ {
1934 self.storage_store_hooks.iter().map(|(target, hook)| (*target, *hook))
1935 }
1936
1937 #[inline]
1939 pub const fn has_storage_hooks(&self) -> bool {
1940 self.storage_hooks_registered
1941 }
1942
1943 pub fn clear_storage_hook_mapping_slots(&mut self) {
1945 self.storage_hook_mapping_slots.clear();
1946 }
1947
1948 #[inline]
1950 pub const fn is_storage_hook_active(&self) -> bool {
1951 self.active_storage_hook.is_some()
1952 }
1953
1954 pub fn is_storage_hook_callback(
1956 &self,
1957 ecx: &FoundryContextFor<'_, FEN>,
1958 call: &CallInputs,
1959 ) -> bool {
1960 self.active_storage_hook.as_ref().is_some_and(|active| {
1961 active.outcome.is_none()
1962 && ecx.journal().depth() == active.parent_depth
1963 && call.transfer_from() == CHEATCODE_ADDRESS
1964 && call.transfer_to() == active.callback_target
1965 && call.input.bytes(ecx) == active.callback_input
1966 })
1967 }
1968
1969 fn finish_storage_hook_call(
1970 &mut self,
1971 ecx: &FoundryContextFor<'_, FEN>,
1972 call: &CallInputs,
1973 outcome: &CallOutcome,
1974 ) -> bool {
1975 let Some(active) = self.active_storage_hook.as_mut() else { return false };
1976 if active.outcome.is_some()
1977 || ecx.journal().depth() != active.parent_depth
1978 || call.transfer_from() != CHEATCODE_ADDRESS
1979 || call.transfer_to() != active.callback_target
1980 || call.input.bytes(ecx) != active.callback_input
1981 {
1982 return false;
1983 }
1984 active.outcome = Some((outcome.result.result, outcome.result.output.clone()));
1985 true
1986 }
1987
1988 #[inline(always)]
1989 pub fn has_step_hooks(&self) -> bool {
1990 self.broadcast.is_some()
1991 || self.gas_metering.paused
1992 || self.gas_metering.reset
1993 || self.recording_accesses
1994 || self.recorded_account_diffs_stack.is_some()
1995 || !self.allowed_mem_writes.is_empty()
1996 || self.mapping_slots.is_some()
1997 || self.gas_metering.recording
1998 || self.has_active_env_overrides()
1999 || self.has_storage_hooks()
2000 }
2001
2002 #[inline(always)]
2003 pub fn has_step_end_hooks(&self) -> bool {
2004 self.gas_metering.paused
2005 || self.gas_metering.touched
2006 || self.arbitrary_storage.is_some()
2007 || self.mapping_slots.is_some()
2008 || self.has_active_env_overrides()
2009 || self.has_storage_hooks()
2010 }
2011
2012 #[inline(always)]
2013 pub fn has_log_hooks(&self) -> bool {
2014 !self.expected_emits.is_empty() || self.recorded_logs.is_some()
2015 }
2016
2017 #[inline(always)]
2018 pub fn has_recording_accesses_only_step_hook(&self) -> bool {
2019 self.recording_accesses
2020 && self.broadcast.is_none()
2021 && !self.gas_metering.paused
2022 && !self.gas_metering.reset
2023 && self.recorded_account_diffs_stack.is_none()
2024 && self.allowed_mem_writes.is_empty()
2025 && self.mapping_slots.is_none()
2026 && !self.has_storage_hooks()
2027 && !self.gas_metering.recording
2028 && !self.has_active_env_overrides()
2029 }
2030
2031 #[inline(always)]
2032 fn has_active_env_overrides(&self) -> bool {
2033 self.env_overrides.is_any_set()
2034 }
2035
2036 pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> {
2038 self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok())
2039 }
2040}
2041
2042const fn frame_gas(result: &InterpreterResult) -> Vm::Gas {
2043 let gas = &result.gas;
2044 let regular_gas_spent = if result.is_halt() {
2046 gas.total_gas_spent()
2047 } else {
2048 gas.total_gas_spent().saturating_sub(gas.state_gas_spilled())
2049 };
2050 Vm::Gas {
2051 gasLimit: gas.limit(),
2052 gasTotalUsed: regular_gas_spent,
2053 gasMemoryUsed: 0,
2054 gasRefunded: gas.refunded(),
2055 gasRemaining: gas.remaining(),
2056 gasStateUsed: if result.is_ok() { gas.state_gas_spent() } else { 0 },
2057 }
2058}
2059
2060impl<FEN: FoundryEvmNetwork> Inspector<FoundryContextFor<'_, FEN>> for Cheatcodes<FEN> {
2061 fn initialize_interp(
2062 &mut self,
2063 interpreter: &mut Interpreter,
2064 ecx: &mut FoundryContextFor<'_, FEN>,
2065 ) {
2066 if let Some(block) = self.block.take() {
2069 ecx.set_block(block);
2070 }
2071 if let Some(gas_price) = self.gas_price.take() {
2072 ecx.tx_mut().set_gas_price(gas_price);
2073 }
2074
2075 if self.gas_metering.paused {
2077 self.gas_metering.paused_frames.push(interpreter.gas);
2078 }
2079
2080 if let Some(expected) = &mut self.expected_revert {
2082 expected.max_depth = max(ecx.journal().depth(), expected.max_depth);
2083 }
2084 }
2085
2086 fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2087 self.pc = interpreter.bytecode.pc();
2088
2089 if !self.has_step_hooks() {
2090 return;
2091 }
2092
2093 if self.finish_storage_hook_callback(interpreter, ecx) {
2094 return;
2095 }
2096
2097 if self.broadcast.is_some() {
2098 self.set_gas_limit_type(interpreter);
2099 }
2100
2101 if interpreter.bytecode.opcode() == op::CALLER
2104 && let Some(broadcast) = &self.broadcast
2105 && let Some(script_address) = self.script_address
2106 && ecx.journal().depth() == broadcast.depth
2107 && interpreter.input.target_address == script_address
2108 && interpreter.input.bytecode_address == Some(script_address)
2109 && interpreter.input.caller_address != broadcast.new_origin
2110 {
2111 interpreter.bytecode.set_action(InterpreterAction::new_return(
2112 InstructionResult::Revert,
2113 Bytes::from(
2114 format!(
2115 "Usage of `msg.sender` inside a `broadcast` in script contract detected. \
2116 `msg.sender` is `{:#x}`, not the broadcast sender `{:#x}`. \
2117 Use the `--sender` flag or pass the deployer address directly instead.",
2118 interpreter.input.caller_address, broadcast.new_origin,
2119 )
2120 .into_bytes(),
2121 ),
2122 interpreter.gas,
2123 ));
2124 return;
2125 }
2126
2127 if self.gas_metering.paused {
2129 self.meter_gas(interpreter);
2130 }
2131
2132 if self.gas_metering.reset {
2134 self.meter_gas_reset(interpreter);
2135 }
2136
2137 if self.recording_accesses {
2139 self.record_accesses(interpreter);
2140 }
2141
2142 if self.recorded_account_diffs_stack.is_some() {
2144 self.record_state_diffs(interpreter, ecx);
2145 }
2146
2147 if !self.allowed_mem_writes.is_empty() {
2149 self.check_mem_opcodes(
2150 interpreter,
2151 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
2152 );
2153 }
2154
2155 if self.mapping_slots.is_some() || !self.mapping_storage_store_hooks.is_empty() {
2156 if let Some(mapping_slots) = &mut self.mapping_slots {
2158 mapping_step(mapping_slots, interpreter);
2159 }
2160
2161 let account = interpreter.input.target_address;
2162 let mapping_hook_active = self.active_storage_hook.is_none()
2163 && self
2164 .mapping_storage_store_hooks
2165 .get(&account)
2166 .is_some_and(|hooks| !hooks.is_empty());
2167 if mapping_hook_active {
2168 mapping_step(&mut self.storage_hook_mapping_slots, interpreter);
2169 }
2170 self.pending_mapping_hash = if self.mapping_slots.is_some() || mapping_hook_active {
2171 capture_mapping_hash(interpreter)
2172 } else {
2173 None
2174 };
2175 }
2176
2177 if self.gas_metering.recording {
2179 self.meter_gas_record(interpreter, ecx);
2180 }
2181
2182 if !self.env_overrides.is_empty() {
2187 let fork_id = ecx.db().active_fork_id();
2188 if let Some(env_overrides) =
2189 self.env_overrides.get_mut(fork_id).filter(|o| o.is_any_set())
2190 {
2191 env_overrides.pending_opcode = None;
2195 env_overrides.pending_blobhash_index = None;
2196
2197 let opcode = interpreter.bytecode.opcode();
2198 match opcode {
2199 op::BASEFEE | op::GASPRICE => {
2200 env_overrides.pending_opcode = Some(opcode);
2201 }
2202 op::BLOBHASH => {
2203 env_overrides.pending_opcode = Some(opcode);
2204 env_overrides.pending_blobhash_index =
2205 interpreter.stack.peek(0).ok().and_then(|index| index.try_into().ok());
2206 }
2207 _ => {}
2208 }
2209 }
2210 }
2211
2212 if self.active_storage_hook.is_none() {
2213 self.capture_storage_hook(interpreter, ecx);
2214 }
2215 }
2216
2217 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut FoundryContextFor<'_, FEN>) {
2218 if !self.has_step_end_hooks() {
2219 return;
2220 }
2221
2222 if self.gas_metering.paused {
2223 self.meter_gas_end(interpreter);
2224 }
2225
2226 if self.gas_metering.touched {
2227 self.meter_gas_check(interpreter);
2228 }
2229
2230 if self.arbitrary_storage.is_some() {
2232 self.arbitrary_storage_end(interpreter, ecx);
2233 }
2234
2235 if let Some(pending) = self.pending_mapping_hash.take()
2236 && interpreter
2237 .bytecode
2238 .action
2239 .as_ref()
2240 .and_then(InterpreterAction::instruction_result)
2241 .is_none()
2242 {
2243 if let Some(mapping_slots) = &mut self.mapping_slots {
2244 record_mapping_hash(mapping_slots, interpreter, pending);
2245 }
2246 if self
2247 .mapping_storage_store_hooks
2248 .get(&pending.address)
2249 .is_some_and(|hooks| !hooks.is_empty())
2250 && self.active_storage_hook.is_none()
2251 {
2252 record_mapping_hash(&mut self.storage_hook_mapping_slots, interpreter, pending);
2253 }
2254 }
2255
2256 if self.active_storage_hook.is_none() {
2257 self.invoke_pending_storage_hook(interpreter, ecx);
2258 }
2259
2260 if !self.env_overrides.is_empty() {
2270 let fork_id = ecx.db().active_fork_id();
2271 if self.env_overrides.get(fork_id).is_some_and(|o| o.is_any_set()) {
2272 let opcode_failed = interpreter
2278 .bytecode
2279 .action
2280 .as_ref()
2281 .and_then(|a| a.instruction_result())
2282 .is_some();
2283 if opcode_failed {
2284 if let Some(env_overrides) = self.env_overrides.get_mut(fork_id) {
2285 env_overrides.pending_opcode = None;
2286 env_overrides.pending_blobhash_index = None;
2287 }
2288 } else {
2289 self.apply_env_overrides(interpreter, fork_id);
2290 }
2291 }
2292 }
2293 }
2294
2295 fn log(&mut self, _ecx: &mut FoundryContextFor<'_, FEN>, log: Log) {
2296 if !self.expected_emits.is_empty()
2297 && let Some(err) = expect::handle_expect_emit(self, &log, None)
2298 {
2299 let _ = sh_err!("{err:?}");
2303 }
2304
2305 record_logs(&mut self.recorded_logs, &log);
2307 }
2308
2309 fn log_full(
2310 &mut self,
2311 interpreter: &mut Interpreter,
2312 _ecx: &mut FoundryContextFor<'_, FEN>,
2313 log: Log,
2314 ) {
2315 if !self.expected_emits.is_empty() {
2316 expect::handle_expect_emit(self, &log, Some(interpreter));
2317 }
2318
2319 record_logs(&mut self.recorded_logs, &log);
2321 }
2322
2323 fn call(
2324 &mut self,
2325 ecx: &mut FoundryContextFor<'_, FEN>,
2326 inputs: &mut CallInputs,
2327 ) -> Option<CallOutcome> {
2328 if self.is_storage_hook_callback(ecx, inputs) {
2329 return None;
2330 }
2331 Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor, false)
2332 }
2333
2334 fn call_end(
2335 &mut self,
2336 ecx: &mut FoundryContextFor<'_, FEN>,
2337 call: &CallInputs,
2338 outcome: &mut CallOutcome,
2339 ) {
2340 let isolated_snapshot_gas_used = self
2341 .gas_metering
2342 .take_isolated_snapshot_gas_used(ecx.journal().depth(), &outcome.result.gas);
2343 if self.finish_storage_hook_call(ecx, call, outcome) {
2344 return;
2345 }
2346
2347 let cheatcode_call = call.transfer_to() == CHEATCODE_ADDRESS
2348 || call.transfer_to() == HARDHAT_CONSOLE_ADDRESS;
2349 #[cfg(feature = "monad")]
2350 let cheatcode_call = cheatcode_call
2351 || crate::monad::is_monad_cheatcode_call(
2352 self.extra_cheatcode_addresses,
2353 call.transfer_to(),
2354 );
2355 let curr_depth = ecx.journal().depth();
2356
2357 self.finish_created_accounts_frame(
2358 outcome.result.is_ok(),
2359 CreatedAccountsFrameKind::Call,
2360 curr_depth,
2361 );
2362
2363 if !cheatcode_call {
2367 if let Some(prank) = &self.get_prank(curr_depth)
2369 && curr_depth == prank.depth
2370 {
2371 ecx.tx_mut().set_caller(prank.prank_origin);
2372
2373 if prank.single_call {
2375 self.pranks.remove(&curr_depth);
2376 }
2377 }
2378
2379 if let Some(broadcast) = &self.broadcast
2381 && curr_depth == broadcast.depth
2382 {
2383 ecx.tx_mut().set_caller(broadcast.original_origin);
2384
2385 if broadcast.single_call {
2387 let _ = self.broadcast.take();
2388 }
2389 }
2390 }
2391
2392 if let Some(assume_no_revert) = &mut self.assume_no_revert {
2394 if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() {
2397 assume_no_revert.reverted_by = Some(call.transfer_to());
2398 }
2399
2400 let curr_depth = ecx.journal().depth();
2402 if curr_depth <= assume_no_revert.depth && !cheatcode_call {
2403 if outcome.result.is_revert() {
2406 let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap();
2407 return match revert_handlers::handle_assume_no_revert(
2408 &assume_no_revert,
2409 outcome.result.result,
2410 &outcome.result.output,
2411 &self.config.available_artifacts,
2412 ) {
2413 Ok(_) => {
2416 outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into();
2417 }
2418 Err(error) => {
2421 trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
2422 outcome.result.result = InstructionResult::Revert;
2423 outcome.result.output = error.abi_encode().into();
2424 }
2425 };
2426 }
2427 self.assume_no_revert = None;
2429 }
2430 }
2431
2432 if let Some(expected_revert) = &mut self.expected_revert {
2434 let call_failed = !outcome.result.result.is_ok();
2437 if call_failed {
2438 if expected_revert.reverter.is_some()
2442 && (expected_revert.reverted_by.is_none() || expected_revert.count > 1)
2443 {
2444 expected_revert.reverted_by = Some(call.transfer_to());
2445 }
2446 }
2447
2448 let curr_depth = ecx.journal().depth();
2449 if curr_depth <= expected_revert.depth {
2450 let needs_processing = expected_revert.needs_processing(
2451 cheatcode_call,
2452 call_failed,
2453 curr_depth,
2454 self.config.internal_expect_revert,
2455 );
2456
2457 if needs_processing {
2458 let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
2459 let clear_last_frame_gas =
2460 matches!(expected_revert.kind, ExpectedRevertKind::Default);
2461 return match revert_handlers::handle_expect_revert(
2462 cheatcode_call,
2463 false,
2464 self.config.internal_expect_revert,
2465 &expected_revert,
2466 outcome.result.result,
2467 outcome.result.output.clone(),
2468 &self.config.available_artifacts,
2469 ) {
2470 Err(error) => {
2471 trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
2472 outcome.result.result = InstructionResult::Revert;
2473 outcome.result.output = error.abi_encode().into();
2474 }
2475 Ok((_, retdata)) => {
2476 expected_revert.actual_count += 1;
2477 if expected_revert.actual_count < expected_revert.count {
2478 self.expected_revert = Some(expected_revert);
2479 }
2480 if clear_last_frame_gas {
2481 self.gas_metering.last_frame_gas = None;
2482 }
2483 outcome.result.result = InstructionResult::Return;
2484 outcome.result.output = retdata;
2485 }
2486 };
2487 }
2488
2489 if let ExpectedRevertKind::Cheatcode { pending_processing } =
2492 &mut self.expected_revert.as_mut().unwrap().kind
2493 {
2494 *pending_processing = false;
2495 }
2496 }
2497 }
2498
2499 if cheatcode_call {
2502 return;
2503 }
2504
2505 let frame_gas = frame_gas(&outcome.result);
2508 let snapshot_gas_used =
2509 isolated_snapshot_gas_used.unwrap_or_else(|| outcome.result.gas.total_gas_spent());
2510 self.gas_metering.last_call_gas = Some(frame_gas.clone());
2511 self.gas_metering.last_frame_gas = Some(frame_gas);
2512 self.gas_metering.last_call_snapshot_gas_used = snapshot_gas_used;
2513 self.gas_metering.last_frame_snapshot_gas_used = snapshot_gas_used;
2514
2515 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2518 if ecx.journal().depth() > 0
2520 && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop()
2521 {
2522 if outcome.result.is_revert() {
2525 mark_account_accesses_reverted(&mut last_recorded_depth);
2526 }
2527
2528 if let Some(call_access) = last_recorded_depth.first_mut() {
2529 let curr_depth = ecx.journal().depth();
2534 if call_access.depth == curr_depth as u64
2535 && let Ok(acc) = ecx.journal_mut().load_account(call.transfer_to())
2536 {
2537 debug_assert!(access_is_call(call_access.kind));
2538 call_access.newBalance = acc.data.info.balance;
2539 call_access.newNonce = acc.data.info.nonce;
2540 }
2541 merge_recorded_frame(recorded_account_diffs_stack, last_recorded_depth);
2542 }
2543 }
2544 }
2545
2546 let diag = self.fork_revert_diagnostic.take();
2549
2550 if outcome.result.is_revert() {
2553 if let Some(err) = diag {
2556 outcome.result.output = Error::encode(err.to_error_msg(&self.labels));
2557 }
2558 return;
2559 }
2560
2561 if let Some(unmet) = expected_emit::check_call_emits(
2562 &mut self.expected_emits,
2563 ecx.journal().depth(),
2564 call.is_static,
2565 outcome.result.is_ok(),
2566 ) {
2567 outcome.result.result = InstructionResult::Revert;
2568 outcome.result.output = unmet.encode(|| self.signatures_identifier());
2569 return;
2570 }
2571
2572 if let TxKind::Call(test_contract) = ecx.tx().kind() {
2575 if ecx.db().is_forked_mode()
2578 && outcome.result.result == InstructionResult::Stop
2579 && call.transfer_to() != test_contract
2580 {
2581 self.fork_revert_diagnostic =
2582 ecx.db().diagnose_revert(call.transfer_to(), ecx.journal().evm_state());
2583 }
2584 }
2585
2586 if ecx.journal().depth() == 0 {
2588 if outcome.result.is_revert() {
2592 return;
2593 }
2594
2595 if let Some(msg) =
2600 expect::first_unmet_call(&self.expected_calls, outcome.result.is_ok())
2601 {
2602 outcome.result.result = InstructionResult::Revert;
2603 outcome.result.output = Error::encode(msg);
2604 return;
2605 }
2606
2607 if let Some(msg) = expected_emit::first_unmet_root_emit(
2609 &mut self.expected_emits,
2610 outcome.result.is_ok(),
2611 ) {
2612 outcome.result.result = InstructionResult::Revert;
2613 outcome.result.output = Error::encode(msg);
2614 return;
2615 }
2616
2617 if let Some(msg) = expect::first_unmet_create(&self.expected_creates) {
2619 outcome.result.result = InstructionResult::Revert;
2620 outcome.result.output = Error::encode(msg);
2621 }
2622 }
2623 }
2624
2625 fn create(
2626 &mut self,
2627 ecx: &mut FoundryContextFor<'_, FEN>,
2628 mut input: &mut CreateInputs,
2629 ) -> Option<CreateOutcome> {
2630 if let Some(spec_id) = self.execution_evm_version {
2632 EvmFactoryFor::<FEN>::set_execution_spec(ecx, spec_id);
2633 }
2634
2635 let gas = Gas::new(input.gas_limit());
2636 let curr_depth = ecx.journal().depth();
2637 self.start_created_accounts_frame(
2638 curr_depth == 0,
2639 CreatedAccountsFrameKind::Create,
2640 curr_depth,
2641 );
2642
2643 if self.intercept_next_create_call {
2645 self.intercept_next_create_call = false;
2647
2648 let output = input.init_code();
2650
2651 return Some(CreateOutcome {
2653 result: InterpreterResult { result: InstructionResult::Revert, output, gas },
2654 address: None,
2655 charged_create_state_gas: input.charged_create_state_gas(),
2656 });
2657 }
2658
2659 if let Some(prank) = self.get_prank(curr_depth)
2661 && let Some(changes) = prank.changes_for(curr_depth, input.caller())
2662 {
2663 if let Some(new_caller) = changes.caller {
2664 let _ = journaled_account(ecx, new_caller);
2666 input.set_caller(new_caller);
2667 }
2668 if let Some(new_origin) = changes.origin {
2669 ecx.tx_mut().set_caller(new_origin);
2670 }
2671 if let Some(used) = changes.used {
2672 self.pranks.insert(curr_depth, used);
2673 }
2674 }
2675
2676 self.apply_accesslist(ecx);
2678
2679 if let Some(broadcast) = &mut self.broadcast
2681 && curr_depth >= broadcast.depth
2682 && input.caller() == broadcast.original_caller
2683 {
2684 if let Err(err) = ecx.journal_mut().load_account(broadcast.new_origin) {
2685 return Some(CreateOutcome {
2686 result: InterpreterResult {
2687 result: InstructionResult::Revert,
2688 output: Error::encode(err),
2689 gas,
2690 },
2691 address: None,
2692 charged_create_state_gas: input.charged_create_state_gas(),
2693 });
2694 }
2695
2696 ecx.tx_mut().set_caller(broadcast.new_origin);
2697
2698 if curr_depth == broadcast.depth || broadcast.deploy_from_code {
2699 broadcast.deploy_from_code = false;
2701
2702 input.set_caller(broadcast.new_origin);
2703
2704 let rpc = ecx.db().active_fork_url();
2705 let fee_token = ecx.tx().fee_token();
2706 let account = &ecx.journal().evm_state()[&broadcast.new_origin];
2707 let mut tx_req = TransactionRequestFor::<FEN>::default()
2708 .with_from(broadcast.new_origin)
2709 .with_kind(TxKind::Create)
2710 .with_value(input.value())
2711 .with_input(input.init_code())
2712 .with_nonce(account.info.nonce);
2713 if let Some(fee_token) = fee_token {
2714 tx_req.set_fee_token(fee_token);
2715 }
2716 self.broadcastable_transactions.push_back(BroadcastableTransaction {
2717 rpc,
2718 transaction: TransactionMaybeSigned::new(tx_req),
2719 });
2720
2721 input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create));
2722 }
2723 }
2724
2725 let address = input.allow_cheatcodes(self, ecx);
2727
2728 self.record_created_account(ecx.db().active_fork_id(), address);
2729
2730 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
2732 recorded_account_diffs_stack.push(vec![AccountAccess {
2733 chainInfo: crate::Vm::ChainInfo {
2734 forkId: ecx.db().active_fork_id().unwrap_or_default(),
2735 chainId: U256::from(ecx.cfg().chain_id()),
2736 },
2737 accessor: input.caller(),
2738 account: address,
2739 kind: crate::Vm::AccountAccessKind::Create,
2740 initialized: true,
2741 oldBalance: U256::ZERO, newBalance: U256::ZERO, oldNonce: 0, newNonce: 1, value: input.value(),
2746 data: input.init_code(),
2747 reverted: false,
2748 deployedCode: Bytes::new(), storageAccesses: vec![], depth: curr_depth as u64,
2751 }]);
2752 }
2753
2754 None
2755 }
2756
2757 fn create_end(
2758 &mut self,
2759 ecx: &mut FoundryContextFor<'_, FEN>,
2760 call: &CreateInputs,
2761 outcome: &mut CreateOutcome,
2762 ) {
2763 let isolated_snapshot_gas_used = self
2764 .gas_metering
2765 .take_isolated_snapshot_gas_used(ecx.journal().depth(), &outcome.result.gas);
2766 let call = Some(call);
2767 let curr_depth = ecx.journal().depth();
2768
2769 self.finish_created_accounts_frame(
2770 outcome.result.is_ok(),
2771 CreatedAccountsFrameKind::Create,
2772 curr_depth,
2773 );
2774
2775 if let Some(prank) = &self.get_prank(curr_depth)
2777 && curr_depth == prank.depth
2778 {
2779 ecx.tx_mut().set_caller(prank.prank_origin);
2780
2781 if prank.single_call {
2783 self.pranks.remove(&curr_depth);
2784 }
2785 }
2786
2787 if let Some(broadcast) = &self.broadcast
2789 && curr_depth == broadcast.depth
2790 {
2791 ecx.tx_mut().set_caller(broadcast.original_origin);
2792
2793 if broadcast.single_call {
2795 std::mem::take(&mut self.broadcast);
2796 }
2797 }
2798
2799 if let Some(expected_revert) = &mut self.expected_revert {
2801 if outcome.result.is_revert()
2813 && expected_revert.reverter.is_some()
2814 && expected_revert.reverted_by.is_none()
2815 && let Some(addr) = outcome.address
2816 {
2817 expected_revert.reverted_by = Some(addr);
2818 }
2819
2820 if curr_depth <= expected_revert.depth
2821 && matches!(expected_revert.kind, ExpectedRevertKind::Default)
2822 {
2823 let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
2824 return match revert_handlers::handle_expect_revert(
2825 false,
2826 true,
2827 self.config.internal_expect_revert,
2828 &expected_revert,
2829 outcome.result.result,
2830 outcome.result.output.clone(),
2831 &self.config.available_artifacts,
2832 ) {
2833 Ok((address, retdata)) => {
2834 expected_revert.actual_count += 1;
2835 if expected_revert.actual_count < expected_revert.count {
2836 expected_revert.reverted_by = None;
2838 self.expected_revert = Some(expected_revert.clone());
2839 }
2840
2841 outcome.result.result = InstructionResult::Return;
2842 outcome.result.output = retdata;
2843 outcome.address = address;
2844 self.gas_metering.last_frame_gas = None;
2845 }
2846 Err(err) => {
2847 outcome.result.result = InstructionResult::Revert;
2848 outcome.result.output = err.abi_encode().into();
2849 }
2850 };
2851 }
2852 }
2853
2854 if curr_depth > 0 {
2855 self.gas_metering.last_frame_gas = Some(frame_gas(&outcome.result));
2858 self.gas_metering.last_frame_snapshot_gas_used =
2859 isolated_snapshot_gas_used.unwrap_or_else(|| outcome.result.gas.total_gas_spent());
2860 }
2861
2862 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack
2865 && let Some(mut last_depth) = recorded_account_diffs_stack.pop()
2866 {
2867 if outcome.result.is_revert() {
2870 mark_account_accesses_reverted(&mut last_depth);
2871 }
2872
2873 if let Some(create_access) = last_depth.first_mut() {
2874 if create_access.depth == curr_depth as u64 {
2876 debug_assert_eq!(
2877 create_access.kind as u8,
2878 crate::Vm::AccountAccessKind::Create as u8
2879 );
2880 if let Some(address) = outcome.address
2881 && let Ok(created_acc) = ecx.journal_mut().load_account(address)
2882 {
2883 create_access.newBalance = created_acc.data.info.balance;
2884 create_access.newNonce = created_acc.data.info.nonce;
2885 create_access.deployedCode =
2886 created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
2887 }
2888 }
2889 }
2890 merge_recorded_frame(recorded_account_diffs_stack, last_depth);
2891 }
2892
2893 if !self.expected_creates.is_empty()
2895 && let (Some(address), Some(call)) = (outcome.address, call)
2896 && let Ok(created_acc) = ecx.journal_mut().load_account(address)
2897 {
2898 let bytecode = created_acc.data.info.code.clone().unwrap_or_default().original_bytes();
2899 expect::observe_create(
2900 &mut self.expected_creates,
2901 call.caller(),
2902 || call.scheme().into(),
2903 &bytecode,
2904 );
2905 }
2906 }
2907}
2908
2909impl<FEN: FoundryEvmNetwork> InspectorExt for Cheatcodes<FEN> {
2910 fn should_use_create2_factory(&mut self, depth: usize, inputs: &CreateInputs) -> bool {
2911 let depth =
2914 if self.deploy_code_depth.is_some_and(|d| d + 1 == depth) { depth - 1 } else { depth };
2915 let target_depth = if let Some(prank) = &self.get_prank(depth) {
2916 prank.depth
2917 } else if let Some(broadcast) = &self.broadcast {
2918 broadcast.depth
2919 } else {
2920 1
2921 };
2922
2923 if depth != target_depth {
2924 return false;
2925 }
2926
2927 match inputs.scheme() {
2928 CreateScheme::Create2 { .. } => {
2929 self.broadcast.is_some() || self.config.always_use_create_2_factory
2930 }
2931 CreateScheme::Create => self.config.batch_rewrite_creates && self.broadcast.is_some(),
2932 _ => false,
2933 }
2934 }
2935
2936 fn create2_deployer(&self) -> Address {
2937 self.config.evm_opts.create2_deployer
2938 }
2939}
2940
2941impl<FEN: FoundryEvmNetwork> Cheatcodes<FEN> {
2942 #[cold]
2943 fn meter_gas(&mut self, interpreter: &mut Interpreter) {
2944 if let Some(paused_gas) = self.gas_metering.paused_frames.last() {
2945 let memory = *interpreter.gas.memory();
2948 interpreter.gas = *paused_gas;
2949 interpreter.gas.memory_mut().words_num = memory.words_num;
2950 interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost;
2951 } else {
2952 self.gas_metering.paused_frames.push(interpreter.gas);
2954 }
2955 }
2956
2957 #[cold]
2958 fn meter_gas_record(
2959 &mut self,
2960 interpreter: &mut Interpreter,
2961 ecx: &mut FoundryContextFor<'_, FEN>,
2962 ) {
2963 if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() {
2964 let curr_depth = ecx.journal().depth();
2965 let isolated_region_gas = match self.gas_metering.pending_isolated_region_gas {
2966 Some((depth, charged, used)) if depth == curr_depth => {
2967 self.gas_metering.pending_isolated_region_gas = None;
2968 Some((charged, used))
2969 }
2970 _ => None,
2971 };
2972 self.gas_metering.gas_records.iter_mut().for_each(|record| {
2973 if curr_depth == record.depth {
2974 if self.gas_metering.last_gas_used != 0 {
2977 let mut gas_diff = interpreter
2978 .gas
2979 .total_gas_spent()
2980 .saturating_sub(self.gas_metering.last_gas_used);
2981 if let Some((charged, used)) = isolated_region_gas {
2982 gas_diff = gas_diff.saturating_sub(charged).saturating_add(used);
2983 }
2984 record.gas_used = record.gas_used.saturating_add(gas_diff);
2985 }
2986
2987 self.gas_metering.last_gas_used = interpreter.gas.total_gas_spent();
2990 }
2991 });
2992 }
2993 }
2994
2995 #[cold]
2996 fn meter_gas_end(&mut self, interpreter: &mut Interpreter) {
2997 if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
2999 && will_exit(interpreter_action)
3000 {
3001 self.gas_metering.paused_frames.pop();
3002 }
3003 }
3004
3005 #[cold]
3006 const fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) {
3007 let mut gas = Gas::new(interpreter.gas.limit());
3008 gas.memory_mut().words_num = interpreter.gas.memory().words_num;
3009 gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost;
3010 interpreter.gas = gas;
3011 self.gas_metering.reset = false;
3012 }
3013
3014 #[cold]
3015 fn meter_gas_check(&mut self, interpreter: &mut Interpreter) {
3016 if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
3017 && will_exit(interpreter_action)
3018 {
3019 if interpreter.gas.total_gas_spent()
3023 < u64::try_from(interpreter.gas.refunded()).unwrap_or_default()
3024 {
3025 interpreter.gas = Gas::new(interpreter.gas.limit());
3026 }
3027 }
3028 }
3029
3030 #[cold]
3044 fn apply_env_overrides(&mut self, interpreter: &mut Interpreter, fork_id: Option<U256>) {
3045 let Some(env_overrides) = self.env_overrides.get_mut(fork_id) else { return };
3046 let Some(opcode) = env_overrides.pending_opcode.take() else { return };
3047 let value = match opcode {
3049 op::BASEFEE => env_overrides.basefee_override().map(U256::from),
3050 op::GASPRICE => env_overrides.gas_price_override().map(U256::from),
3051 op::BLOBHASH => env_overrides
3053 .pending_blobhash_index
3054 .take()
3055 .and_then(|index| env_overrides.blob_hash_override(index))
3056 .map(Into::into),
3057 _ => None,
3058 };
3059 if let Some(value) = value {
3060 Self::replace_top_of_stack(interpreter, value);
3061 }
3062 }
3063
3064 fn replace_top_of_stack(interpreter: &mut Interpreter, value: U256) {
3072 if interpreter.stack.pop().is_err() {
3073 debug_assert!(false, "env override expected opcode result on stack");
3074 return;
3075 }
3076 let _ = interpreter.stack.push(value);
3077 }
3078
3079 #[cold]
3087 fn arbitrary_storage_end(
3088 &mut self,
3089 interpreter: &mut Interpreter,
3090 ecx: &mut FoundryContextFor<'_, FEN>,
3091 ) {
3092 let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD {
3093 (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address)
3094 } else {
3095 return;
3096 };
3097
3098 if self.is_arbitrary_storage_slot_explicit(target_address, key) {
3099 return;
3100 }
3101
3102 let Some(value) = ecx.sload(target_address, key) else {
3103 return;
3104 };
3105
3106 if (value.is_cold && value.data.is_zero())
3107 || self.should_overwrite_arbitrary_storage(&target_address, key)
3108 {
3109 if self.has_arbitrary_storage(&target_address) {
3110 let arbitrary_value = self
3111 .cached_arbitrary_storage_value(target_address, key)
3112 .unwrap_or_else(|| self.rng().random());
3113 self.arbitrary_storage.as_mut().unwrap().save(
3114 ecx,
3115 target_address,
3116 key,
3117 arbitrary_value,
3118 );
3119 } else if self.is_arbitrary_storage_copy(&target_address) {
3120 let arbitrary_value = self.rng().random();
3121 self.arbitrary_storage.as_mut().unwrap().copy(
3122 ecx,
3123 target_address,
3124 key,
3125 arbitrary_value,
3126 );
3127 }
3128 }
3129 }
3130
3131 #[inline]
3135 pub fn finish_storage_hook_callback(
3136 &mut self,
3137 interpreter: &mut Interpreter,
3138 ecx: &mut FoundryContextFor<'_, FEN>,
3139 ) -> bool {
3140 let Some(active) = self.active_storage_hook.as_ref() else { return false };
3141 let Some((result, output)) = active.outcome.clone() else { return false };
3142
3143 let active = self.active_storage_hook.take().expect("active storage hook exists");
3144 Self::restore_storage_hook_access(ecx, active.journal_start);
3145 self.restore_storage_hook_inspector_state(active.inspector_state);
3146 let _ = interpreter.stack.pop();
3147 if let Some(item) = active.saved_stack_item {
3148 let result = interpreter.stack.push(item);
3149 debug_assert!(result, "reserved storage-hook stack slot must be available");
3150 }
3151 interpreter.gas = active.saved_gas;
3152 interpreter.return_data.set_buffer(active.saved_return_data);
3153
3154 if result.is_ok() {
3155 false
3156 } else {
3157 interpreter.bytecode.set_action(InterpreterAction::new_return(
3158 InstructionResult::Revert,
3159 output,
3160 interpreter.gas,
3161 ));
3162 true
3163 }
3164 }
3165
3166 fn take_storage_hook_inspector_state(&mut self) -> StorageHookInspectorState {
3167 StorageHookInspectorState {
3168 accesses: std::mem::take(&mut self.accesses),
3169 recording_accesses: std::mem::replace(&mut self.recording_accesses, false),
3170 mapping_slots: self.mapping_slots.take(),
3171 recorded_logs: self.recorded_logs.take(),
3172 mocked_calls: std::mem::take(&mut self.mocked_calls),
3173 mocked_functions: std::mem::take(&mut self.mocked_functions),
3174 expected_revert: self.expected_revert.take(),
3175 assume_no_revert: self.assume_no_revert.take(),
3176 expected_calls: std::mem::take(&mut self.expected_calls),
3177 expected_emits: std::mem::take(&mut self.expected_emits),
3178 expected_creates: std::mem::take(&mut self.expected_creates),
3179 }
3180 }
3181
3182 fn restore_storage_hook_inspector_state(&mut self, state: StorageHookInspectorState) {
3183 self.accesses = state.accesses;
3184 self.recording_accesses = state.recording_accesses;
3185 self.mapping_slots = state.mapping_slots;
3186 self.recorded_logs = state.recorded_logs;
3187 self.mocked_calls = state.mocked_calls;
3188 self.mocked_functions = state.mocked_functions;
3189 self.expected_revert = state.expected_revert;
3190 self.assume_no_revert = state.assume_no_revert;
3191 self.expected_calls = state.expected_calls;
3192 self.expected_emits = state.expected_emits;
3193 self.expected_creates = state.expected_creates;
3194 }
3195
3196 fn restore_storage_hook_access(ecx: &mut FoundryContextFor<'_, FEN>, journal_start: usize) {
3197 let (_, journal) = ecx.db_journal_inner_mut();
3198 let entries =
3199 journal.journal.drain(journal_start.min(journal.journal.len())..).collect_vec();
3200 for entry in entries {
3201 match entry {
3202 JournalEntry::AccountWarmed { address } => {
3203 journal.state.get_mut(&address).expect("warmed account exists").mark_cold();
3204 }
3205 JournalEntry::StorageWarmed { address, key } => {
3206 journal
3210 .state
3211 .get_mut(&address)
3212 .expect("warmed account exists")
3213 .storage
3214 .get_mut(&key)
3215 .expect("warmed storage slot exists")
3216 .mark_cold();
3217 }
3218 entry => journal.journal.push(entry),
3219 }
3220 }
3221 }
3222
3223 fn capture_storage_hook(
3224 &mut self,
3225 interpreter: &Interpreter,
3226 ecx: &mut FoundryContextFor<'_, FEN>,
3227 ) {
3228 self.pending_storage_hook = None;
3229 if self.active_storage_hook.is_some() {
3230 return;
3231 }
3232 let account = interpreter.input.target_address;
3233 match interpreter.bytecode.opcode() {
3234 op::SLOAD => {
3235 let slot = try_or_return!(interpreter.stack.peek(0));
3236 let Some(hook) = self.storage_load_hooks.get(&account).copied() else { return };
3237 self.pending_storage_hook = Some(PendingStorageHook::Load { account, slot, hook });
3238 }
3239 op::SSTORE => {
3240 let slot = try_or_return!(interpreter.stack.peek(0));
3241 let (hook, mapping) = if let Some(hook) = self.storage_store_hooks.get(&account) {
3242 (*hook, None)
3243 } else {
3244 let Some(provenance) = self
3245 .storage_hook_mapping_slots
3246 .get(&account)
3247 .and_then(|slots| slots.resolve(slot.into()))
3248 else {
3249 return;
3250 };
3251 let Some(hook) = self
3252 .mapping_storage_store_hooks
3253 .get(&account)
3254 .and_then(|hooks| hooks.get(&provenance.root_slot))
3255 .copied()
3256 else {
3257 return;
3258 };
3259 (hook, Some((provenance.root_slot, provenance.keys)))
3260 };
3261 let checkpoint = ecx.journal_mut().checkpoint();
3262 let old_value =
3263 ecx.sload(account, slot).map(|value| value.data).unwrap_or_default();
3264 ecx.journal_mut().checkpoint_revert(checkpoint);
3265 self.pending_storage_hook =
3266 Some(PendingStorageHook::Store { account, slot, old_value, mapping, hook });
3267 }
3268 _ => {}
3269 }
3270 }
3271
3272 fn invoke_pending_storage_hook(
3273 &mut self,
3274 interpreter: &mut Interpreter,
3275 ecx: &mut FoundryContextFor<'_, FEN>,
3276 ) {
3277 let Some(pending) = self.pending_storage_hook.take() else { return };
3278 if interpreter
3279 .bytecode
3280 .action
3281 .as_ref()
3282 .and_then(InterpreterAction::instruction_result)
3283 .is_some()
3284 {
3285 return;
3286 }
3287
3288 let (hook, input, saved_stack_item) = match pending {
3289 PendingStorageHook::Load { account, slot, hook } => {
3290 let value = try_or_return!(interpreter.stack.peek(0));
3291 let mut input = Vec::with_capacity(4 + 32 * 3);
3292 input.extend_from_slice(&hook.callback_selector);
3293 input.extend_from_slice(account.into_word().as_slice());
3294 input.extend_from_slice(&slot.to_be_bytes::<32>());
3295 input.extend_from_slice(&value.to_be_bytes::<32>());
3296 (hook, Bytes::from(input), Some(value))
3297 }
3298 PendingStorageHook::Store { account, slot, old_value, mapping, hook } => {
3299 let new_value =
3300 ecx.sload(account, slot).map(|value| value.data).unwrap_or_default();
3301 let mut input = Vec::with_capacity(4 + 32 * 4);
3302 input.extend_from_slice(&hook.callback_selector);
3303 input.extend_from_slice(account.into_word().as_slice());
3304 input.extend_from_slice(&slot.to_be_bytes::<32>());
3305 if let Some((root, keys)) = mapping {
3306 input.extend_from_slice(root.as_slice());
3307 input.extend_from_slice(&U256::from(32 * 6).to_be_bytes::<32>());
3308 input.extend_from_slice(&old_value.to_be_bytes::<32>());
3309 input.extend_from_slice(&new_value.to_be_bytes::<32>());
3310 input.extend_from_slice(&U256::from(keys.len()).to_be_bytes::<32>());
3311 for key in keys {
3312 input.extend_from_slice(key.as_slice());
3313 }
3314 } else {
3315 input.extend_from_slice(&old_value.to_be_bytes::<32>());
3316 input.extend_from_slice(&new_value.to_be_bytes::<32>());
3317 }
3318 (hook, Bytes::from(input), None)
3319 }
3320 };
3321
3322 let journal_start = ecx.db_journal_inner_mut().1.journal.len();
3323 let account = match ecx.journal_mut().load_account_with_code(hook.callback_target) {
3324 Ok(account) => account,
3325 Err(err) => {
3326 interpreter.bytecode.set_action(InterpreterAction::new_return(
3327 InstructionResult::Revert,
3328 Error::encode(err),
3329 interpreter.gas,
3330 ));
3331 return;
3332 }
3333 };
3334 let known_bytecode =
3335 (account.info.code_hash(), account.info.code.clone().unwrap_or_default());
3336 let saved_gas = interpreter.gas;
3337 let saved_return_data = Bytes::copy_from_slice(interpreter.return_data.buffer());
3338 let gas_limit = interpreter.gas.remaining();
3339 let parent_depth = ecx.journal().depth();
3340 if saved_stack_item.is_some() {
3341 let result = interpreter.stack.pop();
3342 debug_assert!(result.is_ok(), "captured SLOAD result must be on the stack");
3343 }
3344 let inspector_state = self.take_storage_hook_inspector_state();
3345
3346 self.active_storage_hook = Some(ActiveStorageHook {
3347 parent_depth,
3348 callback_target: hook.callback_target,
3349 callback_input: input.clone(),
3350 saved_gas,
3351 saved_return_data,
3352 saved_stack_item,
3353 journal_start,
3354 inspector_state,
3355 outcome: None,
3356 });
3357 interpreter.bytecode.set_action(InterpreterAction::NewFrame(FrameInput::Call(Box::new(
3358 CallInputs {
3359 input: CallInput::Bytes(input),
3360 return_memory_offset: 0..0,
3361 gas_limit,
3362 reservoir: 0,
3363 bytecode_address: hook.callback_target,
3364 known_bytecode,
3365 target_address: hook.callback_target,
3366 caller: CHEATCODE_ADDRESS,
3367 value: CallValue::Transfer(U256::ZERO),
3368 scheme: CallScheme::Call,
3369 is_static: false,
3370 charged_new_account_state_gas: false,
3371 },
3372 ))));
3373 }
3374
3375 #[cold]
3377 fn record_accesses(&mut self, interpreter: &mut Interpreter) {
3378 let access = &mut self.accesses;
3379 match interpreter.bytecode.opcode() {
3380 op::SLOAD => {
3381 let key = try_or_return!(interpreter.stack.peek(0));
3382 access.record_read(interpreter.input.target_address, key);
3383 }
3384 op::SSTORE => {
3385 let key = try_or_return!(interpreter.stack.peek(0));
3386 access.record_write(interpreter.input.target_address, key);
3387 }
3388 _ => {}
3389 }
3390 }
3391
3392 #[cold]
3393 fn record_state_diffs(
3394 &mut self,
3395 interpreter: &mut Interpreter,
3396 ecx: &mut FoundryContextFor<'_, FEN>,
3397 ) {
3398 let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return };
3399 match interpreter.bytecode.opcode() {
3400 op::SELFDESTRUCT => {
3401 let Some(last) = account_accesses.last_mut() else { return };
3403
3404 let target = try_or_return!(interpreter.stack.peek(0));
3406 let target = Address::from_word(B256::from(target));
3407 let (initialized, old_balance, old_nonce) = ecx
3408 .journal_mut()
3409 .load_account(target)
3410 .map(|account| {
3411 (
3412 account.data.info.exists(),
3413 account.data.info.balance,
3414 account.data.info.nonce,
3415 )
3416 })
3417 .unwrap_or_default();
3418
3419 let value = ecx
3421 .balance(interpreter.input.target_address)
3422 .map(|b| b.data)
3423 .unwrap_or(U256::ZERO);
3424
3425 last.push(crate::Vm::AccountAccess {
3427 chainInfo: crate::Vm::ChainInfo {
3428 forkId: ecx.db().active_fork_id().unwrap_or_default(),
3429 chainId: U256::from(ecx.cfg().chain_id()),
3430 },
3431 accessor: interpreter.input.target_address,
3432 account: target,
3433 kind: crate::Vm::AccountAccessKind::SelfDestruct,
3434 initialized,
3435 oldBalance: old_balance,
3436 newBalance: old_balance + value,
3437 oldNonce: old_nonce,
3438 newNonce: old_nonce, value,
3440 data: Bytes::new(),
3441 reverted: false,
3442 deployedCode: Bytes::new(),
3443 storageAccesses: vec![],
3444 depth: ecx
3445 .journal()
3446 .depth()
3447 .try_into()
3448 .expect("journaled state depth exceeds u64"),
3449 });
3450 }
3451
3452 op::SLOAD => {
3453 let Some(last) = account_accesses.last_mut() else { return };
3454
3455 let key = try_or_return!(interpreter.stack.peek(0));
3456 let address = interpreter.input.target_address;
3457
3458 let checkpoint = ecx.journal_mut().checkpoint();
3462 let present_value =
3463 ecx.sload(address, key).map(|previous| previous.data).unwrap_or_default();
3464 ecx.journal_mut().checkpoint_revert(checkpoint);
3465 let access = crate::Vm::StorageAccess {
3466 account: interpreter.input.target_address,
3467 slot: key.into(),
3468 isWrite: false,
3469 previousValue: present_value.into(),
3470 newValue: present_value.into(),
3471 reverted: false,
3472 };
3473 let curr_depth =
3474 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3475 append_storage_access(last, access, curr_depth);
3476 }
3477 op::SSTORE => {
3478 let Some(last) = account_accesses.last_mut() else { return };
3479
3480 let key = try_or_return!(interpreter.stack.peek(0));
3481 let value = try_or_return!(interpreter.stack.peek(1));
3482 let address = interpreter.input.target_address;
3483 let checkpoint = ecx.journal_mut().checkpoint();
3487 let previous_value =
3488 ecx.sload(address, key).map(|previous| previous.data).unwrap_or_default();
3489 ecx.journal_mut().checkpoint_revert(checkpoint);
3490
3491 let access = crate::Vm::StorageAccess {
3492 account: address,
3493 slot: key.into(),
3494 isWrite: true,
3495 previousValue: previous_value.into(),
3496 newValue: value.into(),
3497 reverted: false,
3498 };
3499 let curr_depth =
3500 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3501 append_storage_access(last, access, curr_depth);
3502 }
3503
3504 op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => {
3506 let kind = match interpreter.bytecode.opcode() {
3507 op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy,
3508 op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize,
3509 op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash,
3510 op::BALANCE => crate::Vm::AccountAccessKind::Balance,
3511 _ => unreachable!(),
3512 };
3513 let address =
3514 Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0))));
3515 let checkpoint = ecx.journal_mut().checkpoint();
3516 let (initialized, balance, nonce) = ecx
3517 .journal_mut()
3518 .load_account(address)
3519 .map(|acc| (acc.data.info.exists(), acc.data.info.balance, acc.data.info.nonce))
3520 .unwrap_or_default();
3521 ecx.journal_mut().checkpoint_revert(checkpoint);
3522 let curr_depth =
3523 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
3524 let account_access = crate::Vm::AccountAccess {
3525 chainInfo: crate::Vm::ChainInfo {
3526 forkId: ecx.db().active_fork_id().unwrap_or_default(),
3527 chainId: U256::from(ecx.cfg().chain_id()),
3528 },
3529 accessor: interpreter.input.target_address,
3530 account: address,
3531 kind,
3532 initialized,
3533 oldBalance: balance,
3534 newBalance: balance,
3535 oldNonce: nonce,
3536 newNonce: nonce, value: U256::ZERO,
3538 data: Bytes::new(),
3539 reverted: false,
3540 deployedCode: Bytes::new(),
3541 storageAccesses: vec![],
3542 depth: curr_depth,
3543 };
3544 if let Some(last) = account_accesses.last_mut() {
3547 last.push(account_access);
3548 } else {
3549 account_accesses.push(vec![account_access]);
3550 }
3551 }
3552 _ => {}
3553 }
3554 }
3555
3556 #[cold]
3561 fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) {
3562 let Some(ranges) = self.allowed_mem_writes.get(&depth) else {
3563 return;
3564 };
3565
3566 macro_rules! mem_opcode_match {
3575 ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => {
3576 match interpreter.bytecode.opcode() {
3577 op::MSTORE => {
3582 let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3584
3585 if !ranges.iter().any(|range| {
3588 range.contains(&offset) && range.contains(&(offset + 31))
3589 }) {
3590 let value = try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>();
3595 if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
3596 return
3597 }
3598
3599 disallowed_mem_write(offset, 32, interpreter, ranges);
3600 return
3601 }
3602 }
3603 op::MSTORE8 => {
3604 let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3606
3607 if !ranges.iter().any(|range| range.contains(&offset)) {
3610 disallowed_mem_write(offset, 1, interpreter, ranges);
3611 return
3612 }
3613 }
3614
3615 op::MLOAD => {
3620 let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
3622
3623 if offset >= interpreter.memory.size() as u64 && !ranges.iter().any(|range| {
3627 range.contains(&offset) && range.contains(&(offset + 31))
3628 }) {
3629 disallowed_mem_write(offset, 32, interpreter, ranges);
3630 return
3631 }
3632 }
3633
3634 op::CALL => {
3639 let dest_offset = try_or_return!(interpreter.stack.peek(5)).saturating_to::<u64>();
3641
3642 let size = try_or_return!(interpreter.stack.peek(6)).saturating_to::<u64>();
3644
3645 let fail_cond = !ranges.iter().any(|range| {
3649 range.contains(&dest_offset) &&
3650 range.contains(&(dest_offset + size.saturating_sub(1)))
3651 });
3652
3653 if fail_cond {
3656 let to = Address::from_word(try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>().into());
3660 if to == CHEATCODE_ADDRESS {
3661 let args_offset = try_or_return!(interpreter.stack.peek(3)).saturating_to::<usize>();
3662 let args_size = try_or_return!(interpreter.stack.peek(4)).saturating_to::<usize>();
3663 if args_size >= SELECTOR_LEN
3665 && args_offset.saturating_add(args_size) <= interpreter.memory.size()
3666 {
3667 let memory_word = interpreter.memory.slice_len(args_offset, args_size);
3668 if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
3669 return
3670 }
3671 }
3672 }
3673
3674 disallowed_mem_write(dest_offset, size, interpreter, ranges);
3675 return
3676 }
3677 }
3678
3679 $(op::$opcode => {
3680 let dest_offset = try_or_return!(interpreter.stack.peek($offset_depth)).saturating_to::<u64>();
3682
3683 let size = try_or_return!(interpreter.stack.peek($size_depth)).saturating_to::<u64>();
3685
3686 let fail_cond = !ranges.iter().any(|range| {
3690 range.contains(&dest_offset) &&
3691 range.contains(&(dest_offset + size.saturating_sub(1)))
3692 }) && ($writes ||
3693 [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| {
3694 offset >= interpreter.memory.size() as u64
3695 })
3696 );
3697
3698 if fail_cond {
3701 disallowed_mem_write(dest_offset, size, interpreter, ranges);
3702 return
3703 }
3704 })*
3705
3706 _ => {}
3707 }
3708 }
3709 }
3710
3711 mem_opcode_match!(
3714 (CALLDATACOPY, 0, 2, true),
3715 (CODECOPY, 0, 2, true),
3716 (RETURNDATACOPY, 0, 2, true),
3717 (EXTCODECOPY, 1, 3, true),
3718 (CALLCODE, 5, 6, true),
3719 (STATICCALL, 4, 5, true),
3720 (DELEGATECALL, 4, 5, true),
3721 (KECCAK256, 0, 1, false),
3722 (LOG0, 0, 1, false),
3723 (LOG1, 0, 1, false),
3724 (LOG2, 0, 1, false),
3725 (LOG3, 0, 1, false),
3726 (LOG4, 0, 1, false),
3727 (CREATE, 1, 2, false),
3728 (CREATE2, 1, 2, false),
3729 (RETURN, 0, 1, false),
3730 (REVERT, 0, 1, false),
3731 );
3732 }
3733
3734 #[cold]
3735 fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
3736 match interpreter.bytecode.opcode() {
3737 op::CREATE2 => self.dynamic_gas_limit = true,
3738 op::CALL => {
3739 self.dynamic_gas_limit =
3742 try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100
3743 }
3744 _ => self.dynamic_gas_limit = false,
3745 }
3746 }
3747}
3748
3749fn disallowed_mem_write(
3755 dest_offset: u64,
3756 size: u64,
3757 interpreter: &mut Interpreter,
3758 ranges: &[Range<u64>],
3759) {
3760 let revert_string = format!(
3761 "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}",
3762 dest_offset,
3763 size,
3764 ranges.iter().map(|r| format!("[0x{:02X}, 0x{:02X})", r.start, r.end)).join(" U ")
3765 );
3766
3767 interpreter.bytecode.set_action(InterpreterAction::new_return(
3768 InstructionResult::Revert,
3769 Bytes::from(revert_string.into_bytes()),
3770 interpreter.gas,
3771 ));
3772}
3773
3774const fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool {
3776 matches!(
3777 kind,
3778 crate::Vm::AccountAccessKind::Call
3779 | crate::Vm::AccountAccessKind::StaticCall
3780 | crate::Vm::AccountAccessKind::CallCode
3781 | crate::Vm::AccountAccessKind::DelegateCall
3782 )
3783}
3784
3785fn record_logs(recorded_logs: &mut Option<Vec<Vm::Log>>, log: &Log) {
3787 if let Some(storage_recorded_logs) = recorded_logs {
3788 storage_recorded_logs.push(Vm::Log {
3789 topics: log.data.topics().to_vec(),
3790 data: log.data.data.clone(),
3791 emitter: log.address,
3792 });
3793 }
3794}
3795
3796const fn cheatcode_of<T: spec::CheatcodeDef>(_: &T) -> &'static spec::Cheatcode<'static> {
3798 T::CHEATCODE
3799}
3800
3801fn cheatcode_name(cheat: &spec::Cheatcode<'static>) -> &'static str {
3802 cheatcode_signature(cheat).split('(').next().unwrap()
3803}
3804
3805const fn cheatcode_id(cheat: &spec::Cheatcode<'static>) -> &'static str {
3806 cheat.func.id
3807}
3808
3809const fn cheatcode_signature(cheat: &spec::Cheatcode<'static>) -> &'static str {
3810 cheat.func.signature
3811}
3812
3813fn apply_dispatch<FEN: FoundryEvmNetwork>(
3815 calls: &Vm::VmCalls,
3816 ccx: &mut CheatsCtxt<'_, '_, FEN>,
3817 executor: &mut dyn CheatcodesExecutor<FEN>,
3818) -> Result {
3819 macro_rules! get_cheatcode {
3821 ($($variant:ident),*) => {
3822 match calls {
3823 $(Vm::VmCalls::$variant(cheat) => cheatcode_of(cheat),)*
3824 }
3825 };
3826 }
3827 let cheat = vm_calls!(get_cheatcode);
3828
3829 let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheatcode_id(cheat)).entered();
3830 trace!(target: "cheatcodes", cheat = %cheatcode_signature(cheat), "applying");
3831
3832 if let spec::Status::Deprecated(replacement) = cheat.status {
3833 ccx.state.deprecated.insert(cheatcode_signature(cheat), replacement);
3834 }
3835
3836 macro_rules! dispatch {
3838 ($($variant:ident),*) => {
3839 match calls {
3840 $(Vm::VmCalls::$variant(cheat) => Cheatcode::apply_full(cheat, ccx, executor),)*
3841 }
3842 };
3843 }
3844 let mut result = if ccx.state.config.blocked_cheatcodes.contains(&cheat.func.selector_bytes) {
3845 Err(fmt_err!("disabled during restricted execution"))
3846 } else {
3847 vm_calls!(dispatch)
3848 };
3849
3850 if let Err(e) = &mut result
3852 && e.is_str()
3853 {
3854 let name = cheatcode_name(cheat);
3855 if !name.contains("assert") && name != "rpcUrl" {
3859 *e = fmt_err!("vm.{name}: {e}");
3860 }
3861 }
3862
3863 trace!(
3864 target: "cheatcodes",
3865 return = %match &result {
3866 Ok(b) => hex::encode(b),
3867 Err(e) => e.to_string(),
3868 }
3869 );
3870
3871 result
3872}
3873
3874fn apply_authorization_nonces<FEN: FoundryEvmNetwork>(
3880 ecx: &mut FoundryContextFor<'_, FEN>,
3881 authorizations: &[SignedAuthorization],
3882 sender: Address,
3883 chain_id: u64,
3884) -> Result<()> {
3885 for auth in authorizations {
3886 if (!auth.chain_id.is_zero() && auth.chain_id != U256::from(chain_id))
3887 || auth.nonce() == u64::MAX
3888 {
3889 continue;
3890 }
3891 let Ok(authority) = auth.recover_authority() else { continue };
3892 let account = journaled_account(ecx, authority)?;
3895 if auth.nonce() == account.info.nonce + u64::from(authority == sender) {
3897 account.info.nonce += 1;
3898 }
3899 }
3900 Ok(())
3901}
3902
3903const fn will_exit(action: &InterpreterAction) -> bool {
3905 match action {
3906 InterpreterAction::Return(result) => {
3907 result.result.is_ok_or_revert() || result.result.is_halt()
3908 }
3909 _ => false,
3910 }
3911}
3912
3913#[cfg(test)]
3914mod tests {
3915 use super::*;
3916 use env_overrides::EnvOverrides;
3917
3918 fn cheats(flag: bool, broadcast: Option<Broadcast>) -> Cheatcodes {
3919 let config = CheatsConfig { batch_rewrite_creates: flag, ..Default::default() };
3920 let mut cheats = Cheatcodes::new(Arc::new(config));
3921 cheats.broadcast = broadcast;
3922 cheats
3923 }
3924
3925 fn create_inputs() -> CreateInputs {
3926 CreateInputs::new(Address::ZERO, CreateScheme::Create, U256::ZERO, Bytes::new(), 100_000, 0)
3927 }
3928
3929 fn broadcast_at(depth: usize) -> Broadcast {
3930 Broadcast { depth, ..Default::default() }
3931 }
3932
3933 #[test]
3934 fn flag_off_with_broadcast_returns_false() {
3935 let mut cheats = cheats(false, Some(broadcast_at(1)));
3936 assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
3937 }
3938
3939 #[test]
3940 fn flag_on_without_broadcast_returns_false() {
3941 let mut cheats = cheats(true, None);
3942 assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
3943 }
3944
3945 #[test]
3946 fn flag_on_with_broadcast_depth_mismatch_returns_false() {
3947 let mut cheats = cheats(true, Some(broadcast_at(2)));
3948 assert!(!cheats.should_use_create2_factory(1, &create_inputs()));
3949 }
3950
3951 #[test]
3952 fn flag_on_with_broadcast_depth_match_returns_true() {
3953 let mut cheats = cheats(true, Some(broadcast_at(1)));
3954 assert!(cheats.should_use_create2_factory(1, &create_inputs()));
3955 }
3956
3957 #[test]
3958 fn default_cheatcodes_have_no_opcode_hooks() {
3959 let cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
3960 assert!(!cheats.has_step_hooks());
3961 assert!(!cheats.has_step_end_hooks());
3962 assert!(!cheats.has_log_hooks());
3963 }
3964
3965 #[test]
3966 fn active_cheatcode_state_enables_opcode_hooks() {
3967 let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
3968
3969 cheats.recording_accesses = true;
3970 assert!(cheats.has_step_hooks());
3971 assert!(!cheats.has_step_end_hooks());
3972 assert!(cheats.has_recording_accesses_only_step_hook());
3973
3974 cheats.recording_accesses = false;
3975 cheats.gas_metering.touched = true;
3976 assert!(!cheats.has_step_hooks());
3977 assert!(cheats.has_step_end_hooks());
3978 assert!(!cheats.has_recording_accesses_only_step_hook());
3979
3980 cheats.gas_metering.touched = false;
3981 cheats.register_storage_load_hook(Address::ZERO, Address::ZERO, [0; 4]);
3982 assert!(cheats.has_step_hooks());
3983 assert!(cheats.has_step_end_hooks());
3984 assert!(!cheats.has_recording_accesses_only_step_hook());
3985 }
3986
3987 #[test]
3988 fn mixed_step_hooks_disable_record_access_fast_path() {
3989 let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
3990 cheats.recording_accesses = true;
3991
3992 cheats.gas_metering.reset = true;
3993 assert!(!cheats.has_recording_accesses_only_step_hook());
3994
3995 cheats.gas_metering.reset = false;
3996 cheats.env_overrides.update(None, |o| o.basefee = Some(1));
3997 assert!(!cheats.has_recording_accesses_only_step_hook());
3998 }
3999
4000 #[test]
4001 fn env_override_hook_predicates() {
4002 fn assert_hooks(cheats: &Cheatcodes, active: bool, case: &str) {
4003 assert_eq!(cheats.has_step_hooks(), active, "step hooks: {case}");
4004 assert_eq!(cheats.has_step_end_hooks(), active, "step_end hooks: {case}");
4005 }
4006
4007 let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4008 assert_hooks(&cheats, false, "empty map");
4009 let snapshot_id = U256::from(7);
4010 cheats.env_overrides.save_snapshot(snapshot_id, None, 0, 0, &[]);
4011
4012 cheats.env_overrides.update(None, |_| {});
4013 assert_hooks(&cheats, false, "inactive entry");
4014
4015 let active = [
4016 EnvOverrides { basefee: Some(1), ..Default::default() },
4017 EnvOverrides { implicit_basefee: Some(1), ..Default::default() },
4018 EnvOverrides { gas_price: Some(1), ..Default::default() },
4019 EnvOverrides { blob_hashes: Some(vec![B256::ZERO]), ..Default::default() },
4020 ];
4021 for overrides in active {
4022 let case = format!("{overrides:?}");
4023 cheats.env_overrides.update(None, |o| *o = overrides);
4024 assert_hooks(&cheats, true, &case);
4025 }
4026
4027 cheats.env_overrides.restore_snapshot(snapshot_id, false);
4029 cheats.env_overrides.update(Some(U256::from(1)), |o| o.basefee = Some(1));
4030 assert_hooks(&cheats, true, "override on another fork");
4031
4032 cheats.env_overrides.restore_snapshot(snapshot_id, false);
4034 assert_hooks(&cheats, false, "after restoring an empty snapshot");
4035 }
4036
4037 #[test]
4038 fn active_log_state_enables_log_hooks() {
4039 let mut cheats = Cheatcodes::<EthEvmNetwork>::new(Arc::default());
4040
4041 cheats.recorded_logs = Some(Default::default());
4042 assert!(cheats.has_log_hooks());
4043
4044 cheats.recorded_logs = None;
4045 cheats.expected_emits.push_back((
4046 crate::expected_emit::ExpectedEmit {
4047 depth: 0,
4048 log: None,
4049 checks: [false; 5],
4050 address: None,
4051 anonymous: false,
4052 found: false,
4053 count: 1,
4054 mismatch_error: None,
4055 },
4056 Default::default(),
4057 ));
4058 assert!(cheats.has_log_hooks());
4059 }
4060
4061 #[test]
4062 fn frame_gas_reports_settled_components() {
4063 for mut gas in [Gas::new(100_000), Gas::new_with_regular_gas_and_reservoir(100_000, 50_000)]
4064 {
4065 assert!(gas.record_regular_cost(1_000));
4066 assert!(gas.record_state_cost(20_000));
4067
4068 let mut result = InterpreterResult::new(InstructionResult::Stop, Bytes::new(), gas);
4069 let reported = frame_gas(&result);
4070 assert_eq!(reported.gasTotalUsed, 1_000);
4071 assert_eq!(reported.gasStateUsed, 20_000);
4072
4073 result.result = InstructionResult::Revert;
4074 assert_eq!(frame_gas(&result).gasStateUsed, 0);
4075 }
4076
4077 let mut gas = Gas::new(100_000);
4078 gas.refill_reservoir(20_000);
4079 let result = InterpreterResult::new(InstructionResult::Stop, Bytes::new(), gas);
4080 assert_eq!(frame_gas(&result).gasStateUsed, -20_000);
4081
4082 let mut gas = Gas::new(100_000);
4083 assert!(gas.record_state_cost(20_000));
4084 gas.spend_all();
4085 let result = InterpreterResult::new(InstructionResult::OutOfGas, Bytes::new(), gas);
4086 let reported = frame_gas(&result);
4087 assert_eq!(reported.gasTotalUsed, 100_000);
4088 assert_eq!(reported.gasStateUsed, 0);
4089 }
4090
4091 #[test]
4092 fn arbitrary_storage_cache_value_routes_copied_targets_to_source() {
4093 let mut storage = ArbitraryStorage::default();
4094 let source = Address::repeat_byte(0x11);
4095 let copied = Address::repeat_byte(0x22);
4096 let slot = U256::from(7);
4097
4098 storage.mark_arbitrary(&source, false);
4099 storage.mark_copy(&source, &copied);
4100 storage.cache_value(copied, slot, U256::ZERO);
4101
4102 assert_eq!(storage.cached_value(source, slot), Some(U256::ZERO));
4103 }
4104}