1use crate::{
4 CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result,
5 Vm::{self, AccountAccess},
6 evm::{
7 DealRecord, GasRecord, RecordAccess, journaled_account,
8 mock::{MockCallDataContext, MockCallReturnData},
9 prank::Prank,
10 },
11 inspector::utils::CommonCreateInput,
12 script::{Broadcast, Wallets},
13 test::{
14 assume::AssumeNoRevert,
15 expect::{
16 self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedCreate,
17 ExpectedEmitTracker, ExpectedRevert, ExpectedRevertKind,
18 },
19 revert_handlers,
20 },
21 utils::IgnoredTraces,
22};
23use alloy_consensus::BlobTransactionSidecarVariant;
24use alloy_evm::eth::EthEvmContext;
25use alloy_network::{TransactionBuilder4844, TransactionBuilder7594};
26use alloy_primitives::{
27 Address, B256, Bytes, Log, TxKind, U256, hex,
28 map::{AddressHashMap, HashMap, HashSet},
29};
30use alloy_rpc_types::{
31 AccessList,
32 request::{TransactionInput, TransactionRequest},
33};
34use alloy_sol_types::{SolCall, SolInterface, SolValue};
35use foundry_common::{
36 SELECTOR_LEN, TransactionMaybeSigned,
37 mapping_slots::{MappingSlots, step as mapping_step},
38};
39use foundry_evm_core::{
40 Breakpoints, ContextExt, InspectorExt,
41 abi::Vm::stopExpectSafeMemoryCall,
42 backend::{DatabaseError, DatabaseExt, RevertDiagnostic},
43 constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME},
44 evm::{FoundryEvm, new_evm_with_existing_context},
45};
46use foundry_evm_traces::{
47 TracingInspector, TracingInspectorConfig, identifier::SignaturesIdentifier,
48};
49use foundry_wallets::wallet_multi::MultiWallet;
50use itertools::Itertools;
51use proptest::test_runner::{RngAlgorithm, TestRng, TestRunner};
52use rand::Rng;
53use revm::{
54 Inspector, Journal,
55 bytecode::opcode as op,
56 context::{BlockEnv, ContextTr, JournalTr, LocalContext, TransactionType, result::EVMError},
57 context_interface::{CreateScheme, transaction::SignedAuthorization},
58 handler::FrameResult,
59 inspector::JournalExt,
60 interpreter::{
61 CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, FrameInput, Gas, Host,
62 InstructionResult, Interpreter, InterpreterAction, InterpreterResult,
63 interpreter_types::{Jumps, LoopControl, MemoryTr},
64 },
65 primitives::hardfork::SpecId,
66};
67use serde_json::Value;
68use std::{
69 cmp::max,
70 collections::{BTreeMap, VecDeque},
71 fs::File,
72 io::BufReader,
73 ops::Range,
74 path::PathBuf,
75 sync::{Arc, OnceLock},
76};
77
78mod utils;
79
80pub mod analysis;
81pub use analysis::CheatcodeAnalysis;
82
83pub type Ecx<'a, 'b, 'c> = &'a mut EthEvmContext<&'b mut (dyn DatabaseExt + 'c)>;
84
85pub trait CheatcodesExecutor {
91 fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box<dyn InspectorExt + 'a>;
94
95 fn exec_create(
97 &mut self,
98 inputs: CreateInputs,
99 ccx: &mut CheatsCtxt,
100 ) -> Result<CreateOutcome, EVMError<DatabaseError>> {
101 with_evm(self, ccx, |evm| {
102 evm.journaled_state.depth += 1;
103
104 let frame = FrameInput::Create(Box::new(inputs));
105
106 let outcome = match evm.run_execution(frame)? {
107 FrameResult::Call(_) => unreachable!(),
108 FrameResult::Create(create) => create,
109 };
110
111 evm.journaled_state.depth -= 1;
112
113 Ok(outcome)
114 })
115 }
116
117 fn console_log(&mut self, ccx: &mut CheatsCtxt, msg: &str) {
118 self.get_inspector(ccx.state).console_log(msg);
119 }
120
121 fn tracing_inspector(&mut self) -> Option<&mut TracingInspector> {
123 None
124 }
125
126 fn set_in_inner_context(&mut self, _enabled: bool, _original_origin: Option<Address>) {}
130}
131
132fn with_evm<E, F, O>(
134 executor: &mut E,
135 ccx: &mut CheatsCtxt,
136 f: F,
137) -> Result<O, EVMError<DatabaseError>>
138where
139 E: CheatcodesExecutor + ?Sized,
140 F: for<'a, 'b> FnOnce(
141 &mut FoundryEvm<'a, &'b mut dyn InspectorExt>,
142 ) -> Result<O, EVMError<DatabaseError>>,
143{
144 let mut inspector = executor.get_inspector(ccx.state);
145 let error = std::mem::replace(&mut ccx.ecx.error, Ok(()));
146
147 let ctx = EthEvmContext {
148 block: ccx.ecx.block.clone(),
149 cfg: ccx.ecx.cfg.clone(),
150 tx: ccx.ecx.tx.clone(),
151 journaled_state: Journal {
152 inner: ccx.ecx.journaled_state.inner.clone(),
153 database: &mut *ccx.ecx.journaled_state.database as &mut dyn DatabaseExt,
154 },
155 local: LocalContext::default(),
156 chain: (),
157 error,
158 };
159
160 let mut evm = new_evm_with_existing_context(ctx, &mut *inspector);
161
162 let res = f(&mut evm)?;
163
164 let ctx = evm.into_context();
165 ccx.ecx.journaled_state.inner = ctx.journaled_state.inner;
166 ccx.ecx.block = ctx.block;
167 ccx.ecx.tx = ctx.tx;
168 ccx.ecx.cfg = ctx.cfg;
169 ccx.ecx.error = ctx.error;
170
171 Ok(res)
172}
173
174#[derive(Debug, Default, Clone, Copy)]
177struct TransparentCheatcodesExecutor;
178
179impl CheatcodesExecutor for TransparentCheatcodesExecutor {
180 fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box<dyn InspectorExt + 'a> {
181 Box::new(cheats)
182 }
183}
184
185macro_rules! try_or_return {
186 ($e:expr) => {
187 match $e {
188 Ok(v) => v,
189 Err(_) => return,
190 }
191 };
192}
193
194#[derive(Debug, Default)]
196pub struct TestContext {
197 pub opened_read_files: HashMap<PathBuf, BufReader<File>>,
199}
200
201impl Clone for TestContext {
203 fn clone(&self) -> Self {
204 Default::default()
205 }
206}
207
208impl TestContext {
209 pub fn clear(&mut self) {
211 self.opened_read_files.clear();
212 }
213}
214
215#[derive(Clone, Debug)]
217pub struct BroadcastableTransaction {
218 pub rpc: Option<String>,
220 pub transaction: TransactionMaybeSigned,
222}
223
224#[derive(Clone, Debug, Copy)]
225pub struct RecordDebugStepInfo {
226 pub start_node_idx: usize,
228 pub original_tracer_config: TracingInspectorConfig,
230}
231
232#[derive(Clone, Debug, Default)]
234pub struct GasMetering {
235 pub paused: bool,
237 pub touched: bool,
240 pub reset: bool,
242 pub paused_frames: Vec<Gas>,
244
245 pub active_gas_snapshot: Option<(String, String)>,
247
248 pub last_call_gas: Option<crate::Vm::Gas>,
251
252 pub recording: bool,
254 pub last_gas_used: u64,
256 pub gas_records: Vec<GasRecord>,
258}
259
260impl GasMetering {
261 pub fn start(&mut self) {
263 self.recording = true;
264 }
265
266 pub fn stop(&mut self) {
268 self.recording = false;
269 }
270
271 pub fn resume(&mut self) {
273 if self.paused {
274 self.paused = false;
275 self.touched = true;
276 }
277 self.paused_frames.clear();
278 }
279
280 pub fn reset(&mut self) {
282 self.paused = false;
283 self.touched = true;
284 self.reset = true;
285 self.paused_frames.clear();
286 }
287}
288
289#[derive(Clone, Debug, Default)]
291pub struct ArbitraryStorage {
292 pub values: HashMap<Address, HashMap<U256, U256>>,
296 pub copies: HashMap<Address, Address>,
298 pub overwrites: HashSet<Address>,
300}
301
302impl ArbitraryStorage {
303 pub fn mark_arbitrary(&mut self, address: &Address, overwrite: bool) {
305 self.values.insert(*address, HashMap::default());
306 if overwrite {
307 self.overwrites.insert(*address);
308 } else {
309 self.overwrites.remove(address);
310 }
311 }
312
313 pub fn mark_copy(&mut self, from: &Address, to: &Address) {
315 if self.values.contains_key(from) {
316 self.copies.insert(*to, *from);
317 }
318 }
319
320 pub fn save(&mut self, ecx: Ecx, address: Address, slot: U256, data: U256) {
324 self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data);
325 let (db, journal, _) = ecx.as_db_env_and_journal();
326 if journal.load_account(db, address).is_ok() {
327 journal
328 .sstore(db, address, slot, data, false)
329 .expect("could not set arbitrary storage value");
330 }
331 }
332
333 pub fn copy(&mut self, ecx: Ecx, target: Address, slot: U256, new_value: U256) -> U256 {
339 let source = self.copies.get(&target).expect("missing arbitrary copy target entry");
340 let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage");
341 let value = match storage_cache.get(&slot) {
342 Some(value) => *value,
343 None => {
344 storage_cache.insert(slot, new_value);
345 let (db, journal, _) = ecx.as_db_env_and_journal();
347 if journal.load_account(db, *source).is_ok() {
348 journal
349 .sstore(db, *source, slot, new_value, false)
350 .expect("could not copy arbitrary storage value");
351 }
352 new_value
353 }
354 };
355 let (db, journal, _) = ecx.as_db_env_and_journal();
357 if journal.load_account(db, target).is_ok() {
358 journal.sstore(db, target, slot, value, false).expect("could not set storage");
359 }
360 value
361 }
362}
363
364pub type BroadcastableTransactions = VecDeque<BroadcastableTransaction>;
366
367#[derive(Clone, Debug)]
385pub struct Cheatcodes {
386 pub analysis: Option<CheatcodeAnalysis>,
388
389 pub block: Option<BlockEnv>,
394
395 pub active_delegations: Vec<SignedAuthorization>,
399
400 pub active_blob_sidecar: Option<BlobTransactionSidecarVariant>,
402
403 pub gas_price: Option<u128>,
408
409 pub labels: AddressHashMap<String>,
411
412 pub pranks: BTreeMap<usize, Prank>,
414
415 pub expected_revert: Option<ExpectedRevert>,
417
418 pub assume_no_revert: Option<AssumeNoRevert>,
420
421 pub fork_revert_diagnostic: Option<RevertDiagnostic>,
423
424 pub accesses: RecordAccess,
426
427 pub recording_accesses: bool,
429
430 pub recorded_account_diffs_stack: Option<Vec<Vec<AccountAccess>>>,
436
437 pub record_debug_steps_info: Option<RecordDebugStepInfo>,
439
440 pub recorded_logs: Option<Vec<crate::Vm::Log>>,
442
443 pub mocked_calls: HashMap<Address, BTreeMap<MockCallDataContext, VecDeque<MockCallReturnData>>>,
446
447 pub mocked_functions: HashMap<Address, HashMap<Bytes, Address>>,
449
450 pub expected_calls: ExpectedCallTracker,
452 pub expected_emits: ExpectedEmitTracker,
454 pub expected_creates: Vec<ExpectedCreate>,
456
457 pub allowed_mem_writes: HashMap<u64, Vec<Range<u64>>>,
459
460 pub broadcast: Option<Broadcast>,
462
463 pub broadcastable_transactions: BroadcastableTransactions,
465
466 pub access_list: Option<AccessList>,
468
469 pub config: Arc<CheatsConfig>,
471
472 pub test_context: TestContext,
474
475 pub fs_commit: bool,
478
479 pub serialized_jsons: BTreeMap<String, BTreeMap<String, Value>>,
482
483 pub eth_deals: Vec<DealRecord>,
485
486 pub gas_metering: GasMetering,
488
489 pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>,
492
493 pub mapping_slots: Option<AddressHashMap<MappingSlots>>,
495
496 pub pc: usize,
498 pub breakpoints: Breakpoints,
501
502 pub intercept_next_create_call: bool,
504
505 test_runner: Option<TestRunner>,
508
509 pub ignored_traces: IgnoredTraces,
511
512 pub arbitrary_storage: Option<ArbitraryStorage>,
514
515 pub deprecated: HashMap<&'static str, Option<&'static str>>,
517 pub wallets: Option<Wallets>,
519 signatures_identifier: OnceLock<Option<SignaturesIdentifier>>,
521 pub dynamic_gas_limit: bool,
523 pub execution_evm_version: Option<SpecId>,
525}
526
527impl Default for Cheatcodes {
531 fn default() -> Self {
532 Self::new(Arc::default())
533 }
534}
535
536impl Cheatcodes {
537 pub fn new(config: Arc<CheatsConfig>) -> Self {
539 Self {
540 analysis: None,
541 fs_commit: true,
542 labels: config.labels.clone(),
543 config,
544 block: Default::default(),
545 active_delegations: Default::default(),
546 active_blob_sidecar: Default::default(),
547 gas_price: Default::default(),
548 pranks: Default::default(),
549 expected_revert: Default::default(),
550 assume_no_revert: Default::default(),
551 fork_revert_diagnostic: Default::default(),
552 accesses: Default::default(),
553 recording_accesses: Default::default(),
554 recorded_account_diffs_stack: Default::default(),
555 recorded_logs: Default::default(),
556 record_debug_steps_info: Default::default(),
557 mocked_calls: Default::default(),
558 mocked_functions: Default::default(),
559 expected_calls: Default::default(),
560 expected_emits: Default::default(),
561 expected_creates: Default::default(),
562 allowed_mem_writes: Default::default(),
563 broadcast: Default::default(),
564 broadcastable_transactions: Default::default(),
565 access_list: Default::default(),
566 test_context: Default::default(),
567 serialized_jsons: Default::default(),
568 eth_deals: Default::default(),
569 gas_metering: Default::default(),
570 gas_snapshots: Default::default(),
571 mapping_slots: Default::default(),
572 pc: Default::default(),
573 breakpoints: Default::default(),
574 intercept_next_create_call: Default::default(),
575 test_runner: Default::default(),
576 ignored_traces: Default::default(),
577 arbitrary_storage: Default::default(),
578 deprecated: Default::default(),
579 wallets: Default::default(),
580 signatures_identifier: Default::default(),
581 dynamic_gas_limit: Default::default(),
582 execution_evm_version: None,
583 }
584 }
585
586 pub fn set_analysis(&mut self, analysis: CheatcodeAnalysis) {
588 self.analysis = Some(analysis);
589 }
590
591 pub fn get_prank(&self, depth: usize) -> Option<&Prank> {
595 self.pranks.range(..=depth).last().map(|(_, prank)| prank)
596 }
597
598 pub fn wallets(&mut self) -> &Wallets {
600 self.wallets.get_or_insert_with(|| Wallets::new(MultiWallet::default(), None))
601 }
602
603 pub fn set_wallets(&mut self, wallets: Wallets) {
605 self.wallets = Some(wallets);
606 }
607
608 pub fn add_delegation(&mut self, authorization: SignedAuthorization) {
610 self.active_delegations.push(authorization);
611 }
612
613 pub fn signatures_identifier(&self) -> Option<&SignaturesIdentifier> {
615 self.signatures_identifier.get_or_init(|| SignaturesIdentifier::new(true).ok()).as_ref()
616 }
617
618 fn apply_cheatcode(
620 &mut self,
621 ecx: Ecx,
622 call: &CallInputs,
623 executor: &mut dyn CheatcodesExecutor,
624 ) -> Result {
625 let decoded = Vm::VmCalls::abi_decode(&call.input.bytes(ecx)).map_err(|e| {
627 if let alloy_sol_types::Error::UnknownSelector { name: _, selector } = e {
628 let msg = format!(
629 "unknown cheatcode with selector {selector}; \
630 you may have a mismatch between the `Vm` interface (likely in `forge-std`) \
631 and the `forge` version"
632 );
633 return alloy_sol_types::Error::Other(std::borrow::Cow::Owned(msg));
634 }
635 e
636 })?;
637
638 let caller = call.caller;
639
640 ecx.db_mut().ensure_cheatcode_access_forking_mode(&caller)?;
643
644 apply_dispatch(
645 &decoded,
646 &mut CheatsCtxt { state: self, ecx, gas_limit: call.gas_limit, caller },
647 executor,
648 )
649 }
650
651 fn allow_cheatcodes_on_create(&self, ecx: Ecx, caller: Address, created_address: Address) {
657 if ecx.journal().depth() <= 1 || ecx.db().has_cheatcode_access(&caller) {
658 ecx.db_mut().allow_cheatcode_access(created_address);
659 }
660 }
661
662 fn apply_accesslist(&mut self, ecx: Ecx) {
668 if let Some(access_list) = &self.access_list {
669 ecx.tx.access_list = access_list.clone();
670
671 if ecx.tx.tx_type == TransactionType::Legacy as u8 {
672 ecx.tx.tx_type = TransactionType::Eip2930 as u8;
673 }
674 }
675 }
676
677 pub fn on_revert(&mut self, ecx: Ecx) {
682 trace!(deals=?self.eth_deals.len(), "rolling back deals");
683
684 if self.expected_revert.is_some() {
686 return;
687 }
688
689 if ecx.journal().depth() > 0 {
691 return;
692 }
693
694 while let Some(record) = self.eth_deals.pop() {
698 if let Some(acc) = ecx.journal_mut().evm_state_mut().get_mut(&record.address) {
699 acc.info.balance = record.old_balance;
700 }
701 }
702 }
703
704 pub fn call_with_executor(
705 &mut self,
706 ecx: Ecx,
707 call: &mut CallInputs,
708 executor: &mut dyn CheatcodesExecutor,
709 ) -> Option<CallOutcome> {
710 if let Some(spec_id) = self.execution_evm_version {
712 ecx.cfg.spec = spec_id;
713 }
714
715 let gas = Gas::new(call.gas_limit);
716 let curr_depth = ecx.journal().depth();
717
718 if curr_depth == 0 {
722 let sender = ecx.tx().caller;
723 let account = match super::evm::journaled_account(ecx, sender) {
724 Ok(account) => account,
725 Err(err) => {
726 return Some(CallOutcome {
727 result: InterpreterResult {
728 result: InstructionResult::Revert,
729 output: err.abi_encode().into(),
730 gas,
731 },
732 memory_offset: call.return_memory_offset.clone(),
733 was_precompile_called: false,
734 precompile_call_logs: vec![],
735 });
736 }
737 };
738 let prev = account.info.nonce;
739 account.info.nonce = prev.saturating_sub(1);
740
741 trace!(target: "cheatcodes", %sender, nonce=account.info.nonce, prev, "corrected nonce");
742 }
743
744 if call.target_address == CHEATCODE_ADDRESS {
745 return match self.apply_cheatcode(ecx, call, executor) {
746 Ok(retdata) => Some(CallOutcome {
747 result: InterpreterResult {
748 result: InstructionResult::Return,
749 output: retdata.into(),
750 gas,
751 },
752 memory_offset: call.return_memory_offset.clone(),
753 was_precompile_called: true,
754 precompile_call_logs: vec![],
755 }),
756 Err(err) => Some(CallOutcome {
757 result: InterpreterResult {
758 result: InstructionResult::Revert,
759 output: err.abi_encode().into(),
760 gas,
761 },
762 memory_offset: call.return_memory_offset.clone(),
763 was_precompile_called: false,
764 precompile_call_logs: vec![],
765 }),
766 };
767 }
768
769 if call.target_address == HARDHAT_CONSOLE_ADDRESS {
770 return None;
771 }
772
773 if let Some(expected) = &mut self.expected_revert {
777 expected.max_depth = max(curr_depth + 1, expected.max_depth);
778 }
779
780 if let Some(expected_calls_for_target) = self.expected_calls.get_mut(&call.bytecode_address)
784 {
785 for (calldata, (expected, actual_count)) in expected_calls_for_target {
787 if calldata.len() <= call.input.len() &&
790 *calldata == call.input.bytes(ecx)[..calldata.len()] &&
792 expected
794 .value.is_none_or(|value| Some(value) == call.transfer_value()) &&
795 expected.gas.is_none_or(|gas| gas == call.gas_limit) &&
797 expected.min_gas.is_none_or(|min_gas| min_gas <= call.gas_limit)
799 {
800 *actual_count += 1;
801 }
802 }
803 }
804
805 if let Some(mocks) = self.mocked_calls.get_mut(&call.bytecode_address) {
807 let ctx = MockCallDataContext {
808 calldata: call.input.bytes(ecx),
809 value: call.transfer_value(),
810 };
811
812 if let Some(return_data_queue) = match mocks.get_mut(&ctx) {
813 Some(queue) => Some(queue),
814 None => mocks
815 .iter_mut()
816 .find(|(mock, _)| {
817 call.input.bytes(ecx).get(..mock.calldata.len()) == Some(&mock.calldata[..])
818 && mock.value.is_none_or(|value| Some(value) == call.transfer_value())
819 })
820 .map(|(_, v)| v),
821 } && let Some(return_data) = if return_data_queue.len() == 1 {
822 return_data_queue.front().map(|x| x.to_owned())
824 } else {
825 return_data_queue.pop_front()
827 } {
828 return Some(CallOutcome {
829 result: InterpreterResult {
830 result: return_data.ret_type,
831 output: return_data.data,
832 gas,
833 },
834 memory_offset: call.return_memory_offset.clone(),
835 was_precompile_called: true,
836 precompile_call_logs: vec![],
837 });
838 }
839 }
840
841 if let Some(prank) = &self.get_prank(curr_depth) {
843 if prank.delegate_call
845 && curr_depth == prank.depth
846 && let CallScheme::DelegateCall = call.scheme
847 {
848 call.target_address = prank.new_caller;
849 call.caller = prank.new_caller;
850 if let Some(new_origin) = prank.new_origin {
851 ecx.tx.caller = new_origin;
852 }
853 }
854
855 if curr_depth >= prank.depth && call.caller == prank.prank_caller {
856 let mut prank_applied = false;
857
858 if curr_depth == prank.depth {
860 let _ = journaled_account(ecx, prank.new_caller);
862 call.caller = prank.new_caller;
863 prank_applied = true;
864 }
865
866 if let Some(new_origin) = prank.new_origin {
868 ecx.tx.caller = new_origin;
869 prank_applied = true;
870 }
871
872 if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
874 self.pranks.insert(curr_depth, applied_prank);
875 }
876 }
877 }
878
879 self.apply_accesslist(ecx);
881
882 if let Some(broadcast) = &self.broadcast {
884 let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
887 self.dynamic_gas_limit = false;
888
889 if curr_depth == broadcast.depth && call.caller == broadcast.original_caller {
894 ecx.tx.caller = broadcast.new_origin;
898
899 call.caller = broadcast.new_origin;
900 if !call.is_static {
905 let (db, journal, _) = ecx.as_db_env_and_journal();
906 if let Err(err) = journal.load_account(db, broadcast.new_origin) {
907 return Some(CallOutcome {
908 result: InterpreterResult {
909 result: InstructionResult::Revert,
910 output: Error::encode(err),
911 gas,
912 },
913 memory_offset: call.return_memory_offset.clone(),
914 was_precompile_called: false,
915 precompile_call_logs: vec![],
916 });
917 }
918
919 let input = TransactionInput::new(call.input.bytes(ecx));
920
921 let chain_id = ecx.cfg().chain_id;
922 let rpc = ecx.db().active_fork_url();
923 let account =
924 ecx.journal_mut().evm_state_mut().get_mut(&broadcast.new_origin).unwrap();
925
926 let mut tx_req = TransactionRequest {
927 from: Some(broadcast.new_origin),
928 to: Some(TxKind::from(Some(call.target_address))),
929 value: call.transfer_value(),
930 input,
931 nonce: Some(account.info.nonce),
932 chain_id: Some(chain_id),
933 gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None },
934 ..Default::default()
935 };
936
937 let active_delegations = std::mem::take(&mut self.active_delegations);
938 if let Some(blob_sidecar) = self.active_blob_sidecar.take() {
940 if !active_delegations.is_empty() {
942 let msg = "both delegation and blob are active; `attachBlob` and `attachDelegation` are not compatible";
943 return Some(CallOutcome {
944 result: InterpreterResult {
945 result: InstructionResult::Revert,
946 output: Error::encode(msg),
947 gas,
948 },
949 memory_offset: call.return_memory_offset.clone(),
950 was_precompile_called: false,
951 precompile_call_logs: vec![],
952 });
953 }
954 if blob_sidecar.is_eip4844() {
955 tx_req.set_blob_sidecar(blob_sidecar.into_eip4844().unwrap());
956 } else if blob_sidecar.is_eip7594() {
957 tx_req.set_blob_sidecar_7594(blob_sidecar.into_eip7594().unwrap());
958 }
959 }
960
961 if !active_delegations.is_empty() {
963 for auth in &active_delegations {
964 let Ok(authority) = auth.recover_authority() else {
965 continue;
966 };
967 if authority == broadcast.new_origin {
968 account.info.nonce += 1;
971 }
972 }
973 tx_req.authorization_list = Some(active_delegations);
974 }
975
976 self.broadcastable_transactions
977 .push_back(BroadcastableTransaction { rpc, transaction: tx_req.into() });
978 debug!(target: "cheatcodes", tx=?self.broadcastable_transactions.back().unwrap(), "broadcastable call");
979
980 if !self.config.evm_opts.isolate {
982 let prev = account.info.nonce;
983 account.info.nonce += 1;
984 debug!(target: "cheatcodes", address=%broadcast.new_origin, nonce=prev+1, prev, "incremented nonce");
985 }
986 } else if broadcast.single_call {
987 let msg = "`staticcall`s are not allowed after `broadcast`; use `startBroadcast` instead";
988 return Some(CallOutcome {
989 result: InterpreterResult {
990 result: InstructionResult::Revert,
991 output: Error::encode(msg),
992 gas,
993 },
994 memory_offset: call.return_memory_offset.clone(),
995 was_precompile_called: false,
996 precompile_call_logs: vec![],
997 });
998 }
999 }
1000 }
1001
1002 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1004 let initialized;
1007 let old_balance;
1008 let old_nonce;
1009
1010 let (db, journal, _) = ecx.as_db_env_and_journal();
1011 if let Ok(acc) = journal.load_account(db, call.target_address) {
1012 initialized = acc.info.exists();
1013 old_balance = acc.info.balance;
1014 old_nonce = acc.info.nonce;
1015 } else {
1016 initialized = false;
1017 old_balance = U256::ZERO;
1018 old_nonce = 0;
1019 }
1020
1021 let kind = match call.scheme {
1022 CallScheme::Call => crate::Vm::AccountAccessKind::Call,
1023 CallScheme::CallCode => crate::Vm::AccountAccessKind::CallCode,
1024 CallScheme::DelegateCall => crate::Vm::AccountAccessKind::DelegateCall,
1025 CallScheme::StaticCall => crate::Vm::AccountAccessKind::StaticCall,
1026 };
1027
1028 recorded_account_diffs_stack.push(vec![AccountAccess {
1034 chainInfo: crate::Vm::ChainInfo {
1035 forkId: ecx.db().active_fork_id().unwrap_or_default(),
1036 chainId: U256::from(ecx.cfg().chain_id),
1037 },
1038 accessor: call.caller,
1039 account: call.bytecode_address,
1040 kind,
1041 initialized,
1042 oldBalance: old_balance,
1043 newBalance: U256::ZERO, oldNonce: old_nonce,
1045 newNonce: 0, value: call.call_value(),
1047 data: call.input.bytes(ecx),
1048 reverted: false,
1049 deployedCode: Bytes::new(),
1050 storageAccesses: vec![], depth: ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1052 }]);
1053 }
1054
1055 None
1056 }
1057
1058 pub fn rng(&mut self) -> &mut impl Rng {
1059 self.test_runner().rng()
1060 }
1061
1062 pub fn test_runner(&mut self) -> &mut TestRunner {
1063 self.test_runner.get_or_insert_with(|| match self.config.seed {
1064 Some(seed) => TestRunner::new_with_rng(
1065 proptest::test_runner::Config::default(),
1066 TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1067 ),
1068 None => TestRunner::new(proptest::test_runner::Config::default()),
1069 })
1070 }
1071
1072 pub fn set_seed(&mut self, seed: U256) {
1073 self.test_runner = Some(TestRunner::new_with_rng(
1074 proptest::test_runner::Config::default(),
1075 TestRng::from_seed(RngAlgorithm::ChaCha, &seed.to_be_bytes::<32>()),
1076 ));
1077 }
1078
1079 pub fn arbitrary_storage(&mut self) -> &mut ArbitraryStorage {
1082 self.arbitrary_storage.get_or_insert_with(ArbitraryStorage::default)
1083 }
1084
1085 pub fn has_arbitrary_storage(&self, address: &Address) -> bool {
1087 match &self.arbitrary_storage {
1088 Some(storage) => storage.values.contains_key(address),
1089 None => false,
1090 }
1091 }
1092
1093 pub fn should_overwrite_arbitrary_storage(
1097 &self,
1098 address: &Address,
1099 storage_slot: U256,
1100 ) -> bool {
1101 match &self.arbitrary_storage {
1102 Some(storage) => {
1103 storage.overwrites.contains(address)
1104 && storage
1105 .values
1106 .get(address)
1107 .and_then(|arbitrary_values| arbitrary_values.get(&storage_slot))
1108 .is_none()
1109 }
1110 None => false,
1111 }
1112 }
1113
1114 pub fn is_arbitrary_storage_copy(&self, address: &Address) -> bool {
1116 match &self.arbitrary_storage {
1117 Some(storage) => storage.copies.contains_key(address),
1118 None => false,
1119 }
1120 }
1121
1122 pub fn struct_defs(&self) -> Option<&foundry_common::fmt::StructDefinitions> {
1124 self.analysis.as_ref().and_then(|analysis| analysis.struct_defs().ok())
1125 }
1126}
1127
1128impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
1129 fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
1130 if let Some(block) = self.block.take() {
1133 ecx.block = block;
1134 }
1135 if let Some(gas_price) = self.gas_price.take() {
1136 ecx.tx.gas_price = gas_price;
1137 }
1138
1139 if self.gas_metering.paused {
1141 self.gas_metering.paused_frames.push(interpreter.gas);
1142 }
1143
1144 if let Some(expected) = &mut self.expected_revert {
1146 expected.max_depth = max(ecx.journal().depth(), expected.max_depth);
1147 }
1148 }
1149
1150 fn step(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
1151 self.pc = interpreter.bytecode.pc();
1152
1153 if self.broadcast.is_some() {
1154 self.set_gas_limit_type(interpreter);
1155 }
1156
1157 if self.gas_metering.paused {
1159 self.meter_gas(interpreter);
1160 }
1161
1162 if self.gas_metering.reset {
1164 self.meter_gas_reset(interpreter);
1165 }
1166
1167 if self.recording_accesses {
1169 self.record_accesses(interpreter);
1170 }
1171
1172 if self.recorded_account_diffs_stack.is_some() {
1174 self.record_state_diffs(interpreter, ecx);
1175 }
1176
1177 if !self.allowed_mem_writes.is_empty() {
1179 self.check_mem_opcodes(
1180 interpreter,
1181 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64"),
1182 );
1183 }
1184
1185 if let Some(mapping_slots) = &mut self.mapping_slots {
1187 mapping_step(mapping_slots, interpreter);
1188 }
1189
1190 if self.gas_metering.recording {
1192 self.meter_gas_record(interpreter, ecx);
1193 }
1194 }
1195
1196 fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
1197 if self.gas_metering.paused {
1198 self.meter_gas_end(interpreter);
1199 }
1200
1201 if self.gas_metering.touched {
1202 self.meter_gas_check(interpreter);
1203 }
1204
1205 if self.arbitrary_storage.is_some() {
1207 self.arbitrary_storage_end(interpreter, ecx);
1208 }
1209 }
1210
1211 fn log(&mut self, _ecx: Ecx, log: Log) {
1212 if !self.expected_emits.is_empty()
1213 && let Some(err) = expect::handle_expect_emit(self, &log, None)
1214 {
1215 let _ = sh_err!("{err:?}");
1219 }
1220
1221 record_logs(&mut self.recorded_logs, &log);
1223 }
1224
1225 fn log_full(&mut self, interpreter: &mut Interpreter, _ecx: Ecx, log: Log) {
1226 if !self.expected_emits.is_empty() {
1227 expect::handle_expect_emit(self, &log, Some(interpreter));
1228 }
1229
1230 record_logs(&mut self.recorded_logs, &log);
1232 }
1233
1234 fn call(&mut self, ecx: Ecx, inputs: &mut CallInputs) -> Option<CallOutcome> {
1235 Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor)
1236 }
1237
1238 fn call_end(&mut self, ecx: Ecx, call: &CallInputs, outcome: &mut CallOutcome) {
1239 let cheatcode_call = call.target_address == CHEATCODE_ADDRESS
1240 || call.target_address == HARDHAT_CONSOLE_ADDRESS;
1241
1242 if !cheatcode_call {
1246 let curr_depth = ecx.journal().depth();
1248 if let Some(prank) = &self.get_prank(curr_depth)
1249 && curr_depth == prank.depth
1250 {
1251 ecx.tx.caller = prank.prank_origin;
1252
1253 if prank.single_call {
1255 self.pranks.remove(&curr_depth);
1256 }
1257 }
1258
1259 if let Some(broadcast) = &self.broadcast
1261 && curr_depth == broadcast.depth
1262 {
1263 ecx.tx.caller = broadcast.original_origin;
1264
1265 if broadcast.single_call {
1267 let _ = self.broadcast.take();
1268 }
1269 }
1270 }
1271
1272 if let Some(assume_no_revert) = &mut self.assume_no_revert {
1274 if outcome.result.is_revert() && assume_no_revert.reverted_by.is_none() {
1277 assume_no_revert.reverted_by = Some(call.target_address);
1278 }
1279
1280 let curr_depth = ecx.journal().depth();
1282 if curr_depth <= assume_no_revert.depth && !cheatcode_call {
1283 if outcome.result.is_revert() {
1286 let assume_no_revert = std::mem::take(&mut self.assume_no_revert).unwrap();
1287 return match revert_handlers::handle_assume_no_revert(
1288 &assume_no_revert,
1289 outcome.result.result,
1290 &outcome.result.output,
1291 &self.config.available_artifacts,
1292 ) {
1293 Ok(_) => {
1296 outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into();
1297 }
1298 Err(error) => {
1301 trace!(expected=?assume_no_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
1302 outcome.result.result = InstructionResult::Revert;
1303 outcome.result.output = error.abi_encode().into();
1304 }
1305 };
1306 } else {
1307 self.assume_no_revert = None;
1309 }
1310 }
1311 }
1312
1313 if let Some(expected_revert) = &mut self.expected_revert {
1315 if outcome.result.is_revert() {
1318 if expected_revert.reverter.is_some()
1322 && (expected_revert.reverted_by.is_none() || expected_revert.count > 1)
1323 {
1324 expected_revert.reverted_by = Some(call.target_address);
1325 }
1326 }
1327
1328 let curr_depth = ecx.journal().depth();
1329 if curr_depth <= expected_revert.depth {
1330 let needs_processing = match expected_revert.kind {
1331 ExpectedRevertKind::Default => !cheatcode_call,
1332 ExpectedRevertKind::Cheatcode { pending_processing } => {
1335 cheatcode_call && !pending_processing
1336 }
1337 };
1338
1339 if needs_processing {
1340 let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
1341 return match revert_handlers::handle_expect_revert(
1342 cheatcode_call,
1343 false,
1344 self.config.internal_expect_revert,
1345 &expected_revert,
1346 outcome.result.result,
1347 outcome.result.output.clone(),
1348 &self.config.available_artifacts,
1349 ) {
1350 Err(error) => {
1351 trace!(expected=?expected_revert, ?error, status=?outcome.result.result, "Expected revert mismatch");
1352 outcome.result.result = InstructionResult::Revert;
1353 outcome.result.output = error.abi_encode().into();
1354 }
1355 Ok((_, retdata)) => {
1356 expected_revert.actual_count += 1;
1357 if expected_revert.actual_count < expected_revert.count {
1358 self.expected_revert = Some(expected_revert);
1359 }
1360 outcome.result.result = InstructionResult::Return;
1361 outcome.result.output = retdata;
1362 }
1363 };
1364 }
1365
1366 if let ExpectedRevertKind::Cheatcode { pending_processing } =
1369 &mut self.expected_revert.as_mut().unwrap().kind
1370 {
1371 *pending_processing = false;
1372 }
1373 }
1374 }
1375
1376 if cheatcode_call {
1379 return;
1380 }
1381
1382 let gas = outcome.result.gas;
1385 self.gas_metering.last_call_gas = Some(crate::Vm::Gas {
1386 gasLimit: gas.limit(),
1387 gasTotalUsed: gas.spent(),
1388 gasMemoryUsed: 0,
1389 gasRefunded: gas.refunded(),
1390 gasRemaining: gas.remaining(),
1391 });
1392
1393 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1396 if ecx.journal().depth() > 0
1398 && let Some(mut last_recorded_depth) = recorded_account_diffs_stack.pop()
1399 {
1400 if outcome.result.is_revert() {
1403 last_recorded_depth.iter_mut().for_each(|element| {
1404 element.reverted = true;
1405 element
1406 .storageAccesses
1407 .iter_mut()
1408 .for_each(|storage_access| storage_access.reverted = true);
1409 })
1410 }
1411
1412 if let Some(call_access) = last_recorded_depth.first_mut() {
1413 let (db, journal, _) = ecx.as_db_env_and_journal();
1418 let curr_depth = journal.depth;
1419 if call_access.depth == curr_depth as u64
1420 && let Ok(acc) = journal.load_account(db, call.target_address)
1421 {
1422 debug_assert!(access_is_call(call_access.kind));
1423 call_access.newBalance = acc.info.balance;
1424 call_access.newNonce = acc.info.nonce;
1425 }
1426 if let Some(last) = recorded_account_diffs_stack.last_mut() {
1431 last.extend(last_recorded_depth);
1432 } else {
1433 recorded_account_diffs_stack.push(last_recorded_depth);
1434 }
1435 }
1436 }
1437 }
1438
1439 let should_check_emits = self
1451 .expected_emits
1452 .iter()
1453 .any(|(expected, _)| {
1454 let curr_depth = ecx.journal().depth();
1455 expected.depth == curr_depth
1456 }) &&
1457 !call.is_static;
1459 if should_check_emits {
1460 let expected_counts = self
1461 .expected_emits
1462 .iter()
1463 .filter_map(|(expected, count_map)| {
1464 let count = match expected.address {
1465 Some(emitter) => match count_map.get(&emitter) {
1466 Some(log_count) => expected
1467 .log
1468 .as_ref()
1469 .map(|l| log_count.count(l))
1470 .unwrap_or_else(|| log_count.count_unchecked()),
1471 None => 0,
1472 },
1473 None => match &expected.log {
1474 Some(log) => count_map.values().map(|logs| logs.count(log)).sum(),
1475 None => count_map.values().map(|logs| logs.count_unchecked()).sum(),
1476 },
1477 };
1478
1479 if count != expected.count { Some((expected, count)) } else { None }
1480 })
1481 .collect::<Vec<_>>();
1482
1483 if let Some((expected, _)) = self
1485 .expected_emits
1486 .iter()
1487 .find(|(expected, _)| !expected.found && expected.count > 0)
1488 {
1489 outcome.result.result = InstructionResult::Revert;
1490 let error_msg = expected.mismatch_error.as_deref().unwrap_or("log != expected log");
1491 outcome.result.output = error_msg.abi_encode().into();
1492 return;
1493 }
1494
1495 if !expected_counts.is_empty() {
1496 let msg = if outcome.result.is_ok() {
1497 let (expected, count) = expected_counts.first().unwrap();
1498 format!("log emitted {count} times, expected {}", expected.count)
1499 } else {
1500 "expected an emit, but the call reverted instead. \
1501 ensure you're testing the happy path when using `expectEmit`"
1502 .to_string()
1503 };
1504
1505 outcome.result.result = InstructionResult::Revert;
1506 outcome.result.output = Error::encode(msg);
1507 return;
1508 }
1509
1510 self.expected_emits.clear()
1514 }
1515
1516 let diag = self.fork_revert_diagnostic.take();
1519
1520 if outcome.result.is_revert()
1523 && let Some(err) = diag
1524 {
1525 outcome.result.output = Error::encode(err.to_error_msg(&self.labels));
1526 return;
1527 }
1528
1529 if let TxKind::Call(test_contract) = ecx.tx().kind {
1532 if ecx.db().is_forked_mode()
1535 && outcome.result.result == InstructionResult::Stop
1536 && call.target_address != test_contract
1537 {
1538 let journaled_state = ecx.journaled_state.clone();
1539 self.fork_revert_diagnostic =
1540 ecx.db().diagnose_revert(call.target_address, &journaled_state);
1541 }
1542 }
1543
1544 if ecx.journal().depth() == 0 {
1546 if outcome.result.is_revert() {
1550 return;
1551 }
1552
1553 for (address, calldatas) in &self.expected_calls {
1558 for (calldata, (expected, actual_count)) in calldatas {
1560 let ExpectedCallData { gas, min_gas, value, count, call_type } = expected;
1562
1563 let failed = match call_type {
1564 ExpectedCallType::Count => *count != *actual_count,
1568 ExpectedCallType::NonCount => *count > *actual_count,
1573 };
1574 if failed {
1575 let expected_values = [
1576 Some(format!("data {}", hex::encode_prefixed(calldata))),
1577 value.as_ref().map(|v| format!("value {v}")),
1578 gas.map(|g| format!("gas {g}")),
1579 min_gas.map(|g| format!("minimum gas {g}")),
1580 ]
1581 .into_iter()
1582 .flatten()
1583 .join(", ");
1584 let but = if outcome.result.is_ok() {
1585 let s = if *actual_count == 1 { "" } else { "s" };
1586 format!("was called {actual_count} time{s}")
1587 } else {
1588 "the call reverted instead; \
1589 ensure you're testing the happy path when using `expectCall`"
1590 .to_string()
1591 };
1592 let s = if *count == 1 { "" } else { "s" };
1593 let msg = format!(
1594 "expected call to {address} with {expected_values} \
1595 to be called {count} time{s}, but {but}"
1596 );
1597 outcome.result.result = InstructionResult::Revert;
1598 outcome.result.output = Error::encode(msg);
1599
1600 return;
1601 }
1602 }
1603 }
1604
1605 for (expected, _) in &mut self.expected_emits {
1609 if expected.count == 0 && !expected.found {
1610 expected.found = true;
1611 }
1612 }
1613 self.expected_emits.retain(|(expected, _)| !expected.found);
1614 if !self.expected_emits.is_empty() {
1616 let msg = if outcome.result.is_ok() {
1617 "expected an emit, but no logs were emitted afterwards. \
1618 you might have mismatched events or not enough events were emitted"
1619 } else {
1620 "expected an emit, but the call reverted instead. \
1621 ensure you're testing the happy path when using `expectEmit`"
1622 };
1623 outcome.result.result = InstructionResult::Revert;
1624 outcome.result.output = Error::encode(msg);
1625 return;
1626 }
1627
1628 if let Some(expected_create) = self.expected_creates.first() {
1630 let msg = format!(
1631 "expected {} call by address {} for bytecode {} but not found",
1632 expected_create.create_scheme,
1633 hex::encode_prefixed(expected_create.deployer),
1634 hex::encode_prefixed(&expected_create.bytecode),
1635 );
1636 outcome.result.result = InstructionResult::Revert;
1637 outcome.result.output = Error::encode(msg);
1638 }
1639 }
1640 }
1641
1642 fn create(&mut self, ecx: Ecx, mut input: &mut CreateInputs) -> Option<CreateOutcome> {
1643 if let Some(spec_id) = self.execution_evm_version {
1645 ecx.cfg.spec = spec_id;
1646 }
1647
1648 let gas = Gas::new(input.gas_limit());
1649 if self.intercept_next_create_call {
1651 self.intercept_next_create_call = false;
1653
1654 let output = input.init_code();
1656
1657 return Some(CreateOutcome {
1659 result: InterpreterResult { result: InstructionResult::Revert, output, gas },
1660 address: None,
1661 });
1662 }
1663
1664 let curr_depth = ecx.journal().depth();
1665
1666 if let Some(prank) = &self.get_prank(curr_depth)
1668 && curr_depth >= prank.depth
1669 && input.caller() == prank.prank_caller
1670 {
1671 let mut prank_applied = false;
1672
1673 if curr_depth == prank.depth {
1675 let _ = journaled_account(ecx, prank.new_caller);
1677 input.set_caller(prank.new_caller);
1678 prank_applied = true;
1679 }
1680
1681 if let Some(new_origin) = prank.new_origin {
1683 ecx.tx.caller = new_origin;
1684 prank_applied = true;
1685 }
1686
1687 if prank_applied && let Some(applied_prank) = prank.first_time_applied() {
1689 self.pranks.insert(curr_depth, applied_prank);
1690 }
1691 }
1692
1693 self.apply_accesslist(ecx);
1695
1696 if let Some(broadcast) = &mut self.broadcast
1698 && curr_depth >= broadcast.depth
1699 && input.caller() == broadcast.original_caller
1700 {
1701 let (db, journal, _) = ecx.as_db_env_and_journal();
1702 if let Err(err) = journal.load_account(db, broadcast.new_origin) {
1703 return Some(CreateOutcome {
1704 result: InterpreterResult {
1705 result: InstructionResult::Revert,
1706 output: Error::encode(err),
1707 gas,
1708 },
1709 address: None,
1710 });
1711 }
1712
1713 ecx.tx.caller = broadcast.new_origin;
1714
1715 if curr_depth == broadcast.depth || broadcast.deploy_from_code {
1716 broadcast.deploy_from_code = false;
1718
1719 input.set_caller(broadcast.new_origin);
1720
1721 let account = &ecx.journal().evm_state()[&broadcast.new_origin];
1722 self.broadcastable_transactions.push_back(BroadcastableTransaction {
1723 rpc: ecx.db().active_fork_url(),
1724 transaction: TransactionRequest {
1725 from: Some(broadcast.new_origin),
1726 to: None,
1727 value: Some(input.value()),
1728 input: TransactionInput::new(input.init_code()),
1729 nonce: Some(account.info.nonce),
1730 ..Default::default()
1731 }
1732 .into(),
1733 });
1734
1735 input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create));
1736 }
1737 }
1738
1739 let address = input.allow_cheatcodes(self, ecx);
1741
1742 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1744 recorded_account_diffs_stack.push(vec![AccountAccess {
1745 chainInfo: crate::Vm::ChainInfo {
1746 forkId: ecx.db().active_fork_id().unwrap_or_default(),
1747 chainId: U256::from(ecx.cfg().chain_id),
1748 },
1749 accessor: input.caller(),
1750 account: address,
1751 kind: crate::Vm::AccountAccessKind::Create,
1752 initialized: true,
1753 oldBalance: U256::ZERO, newBalance: U256::ZERO, oldNonce: 0, newNonce: 1, value: input.value(),
1758 data: input.init_code(),
1759 reverted: false,
1760 deployedCode: Bytes::new(), storageAccesses: vec![], depth: curr_depth as u64,
1763 }]);
1764 }
1765
1766 None
1767 }
1768
1769 fn create_end(&mut self, ecx: Ecx, call: &CreateInputs, outcome: &mut CreateOutcome) {
1770 let call = Some(call);
1771 let curr_depth = ecx.journal().depth();
1772
1773 if let Some(prank) = &self.get_prank(curr_depth)
1775 && curr_depth == prank.depth
1776 {
1777 ecx.tx.caller = prank.prank_origin;
1778
1779 if prank.single_call {
1781 std::mem::take(&mut self.pranks);
1782 }
1783 }
1784
1785 if let Some(broadcast) = &self.broadcast
1787 && curr_depth == broadcast.depth
1788 {
1789 ecx.tx.caller = broadcast.original_origin;
1790
1791 if broadcast.single_call {
1793 std::mem::take(&mut self.broadcast);
1794 }
1795 }
1796
1797 if let Some(expected_revert) = &self.expected_revert
1799 && curr_depth <= expected_revert.depth
1800 && matches!(expected_revert.kind, ExpectedRevertKind::Default)
1801 {
1802 let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap();
1803 return match revert_handlers::handle_expect_revert(
1804 false,
1805 true,
1806 self.config.internal_expect_revert,
1807 &expected_revert,
1808 outcome.result.result,
1809 outcome.result.output.clone(),
1810 &self.config.available_artifacts,
1811 ) {
1812 Ok((address, retdata)) => {
1813 expected_revert.actual_count += 1;
1814 if expected_revert.actual_count < expected_revert.count {
1815 self.expected_revert = Some(expected_revert.clone());
1816 }
1817
1818 outcome.result.result = InstructionResult::Return;
1819 outcome.result.output = retdata;
1820 outcome.address = address;
1821 }
1822 Err(err) => {
1823 outcome.result.result = InstructionResult::Revert;
1824 outcome.result.output = err.abi_encode().into();
1825 }
1826 };
1827 }
1828
1829 if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack {
1832 if curr_depth > 0
1834 && let Some(last_depth) = &mut recorded_account_diffs_stack.pop()
1835 {
1836 if outcome.result.is_revert() {
1839 last_depth.iter_mut().for_each(|element| {
1840 element.reverted = true;
1841 element
1842 .storageAccesses
1843 .iter_mut()
1844 .for_each(|storage_access| storage_access.reverted = true);
1845 })
1846 }
1847
1848 if let Some(create_access) = last_depth.first_mut() {
1849 let depth = ecx.journal().depth();
1854 if create_access.depth == depth as u64 {
1855 debug_assert_eq!(
1856 create_access.kind as u8,
1857 crate::Vm::AccountAccessKind::Create as u8
1858 );
1859 let (db, journal, _) = ecx.as_db_env_and_journal();
1860 if let Some(address) = outcome.address
1861 && let Ok(created_acc) = journal.load_account(db, address)
1862 {
1863 create_access.newBalance = created_acc.info.balance;
1864 create_access.newNonce = created_acc.info.nonce;
1865 create_access.deployedCode =
1866 created_acc.info.code.clone().unwrap_or_default().original_bytes();
1867 }
1868 }
1869 if let Some(last) = recorded_account_diffs_stack.last_mut() {
1874 last.append(last_depth);
1875 } else {
1876 recorded_account_diffs_stack.push(last_depth.clone());
1877 }
1878 }
1879 }
1880 }
1881
1882 let (db, journal, _) = ecx.as_db_env_and_journal();
1884 if !self.expected_creates.is_empty()
1885 && let (Some(address), Some(call)) = (outcome.address, call)
1886 && let Ok(created_acc) = journal.load_account(db, address)
1887 {
1888 let bytecode = created_acc.info.code.clone().unwrap_or_default().original_bytes();
1889 if let Some((index, _)) =
1890 self.expected_creates.iter().find_position(|expected_create| {
1891 expected_create.deployer == call.caller()
1892 && expected_create.create_scheme.eq(call.scheme().into())
1893 && expected_create.bytecode == bytecode
1894 })
1895 {
1896 self.expected_creates.swap_remove(index);
1897 }
1898 }
1899 }
1900}
1901
1902impl InspectorExt for Cheatcodes {
1903 fn should_use_create2_factory(&mut self, ecx: Ecx, inputs: &CreateInputs) -> bool {
1904 if let CreateScheme::Create2 { .. } = inputs.scheme() {
1905 let depth = ecx.journal().depth();
1906 let target_depth = if let Some(prank) = &self.get_prank(depth) {
1907 prank.depth
1908 } else if let Some(broadcast) = &self.broadcast {
1909 broadcast.depth
1910 } else {
1911 1
1912 };
1913
1914 depth == target_depth
1915 && (self.broadcast.is_some() || self.config.always_use_create_2_factory)
1916 } else {
1917 false
1918 }
1919 }
1920
1921 fn create2_deployer(&self) -> Address {
1922 self.config.evm_opts.create2_deployer
1923 }
1924}
1925
1926impl Cheatcodes {
1927 #[cold]
1928 fn meter_gas(&mut self, interpreter: &mut Interpreter) {
1929 if let Some(paused_gas) = self.gas_metering.paused_frames.last() {
1930 let memory = *interpreter.gas.memory();
1933 interpreter.gas = *paused_gas;
1934 interpreter.gas.memory_mut().words_num = memory.words_num;
1935 interpreter.gas.memory_mut().expansion_cost = memory.expansion_cost;
1936 } else {
1937 self.gas_metering.paused_frames.push(interpreter.gas);
1939 }
1940 }
1941
1942 #[cold]
1943 fn meter_gas_record(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
1944 if interpreter.bytecode.action.as_ref().and_then(|i| i.instruction_result()).is_none() {
1945 self.gas_metering.gas_records.iter_mut().for_each(|record| {
1946 let curr_depth = ecx.journal().depth();
1947 if curr_depth == record.depth {
1948 if self.gas_metering.last_gas_used != 0 {
1951 let gas_diff =
1952 interpreter.gas.spent().saturating_sub(self.gas_metering.last_gas_used);
1953 record.gas_used = record.gas_used.saturating_add(gas_diff);
1954 }
1955
1956 self.gas_metering.last_gas_used = interpreter.gas.spent();
1959 }
1960 });
1961 }
1962 }
1963
1964 #[cold]
1965 fn meter_gas_end(&mut self, interpreter: &mut Interpreter) {
1966 if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
1968 && will_exit(interpreter_action)
1969 {
1970 self.gas_metering.paused_frames.pop();
1971 }
1972 }
1973
1974 #[cold]
1975 fn meter_gas_reset(&mut self, interpreter: &mut Interpreter) {
1976 let mut gas = Gas::new(interpreter.gas.limit());
1977 gas.memory_mut().words_num = interpreter.gas.memory().words_num;
1978 gas.memory_mut().expansion_cost = interpreter.gas.memory().expansion_cost;
1979 interpreter.gas = gas;
1980 self.gas_metering.reset = false;
1981 }
1982
1983 #[cold]
1984 fn meter_gas_check(&mut self, interpreter: &mut Interpreter) {
1985 if let Some(interpreter_action) = interpreter.bytecode.action.as_ref()
1986 && will_exit(interpreter_action)
1987 {
1988 if interpreter.gas.spent()
1992 < u64::try_from(interpreter.gas.refunded()).unwrap_or_default()
1993 {
1994 interpreter.gas = Gas::new(interpreter.gas.limit());
1995 }
1996 }
1997 }
1998
1999 #[cold]
2007 fn arbitrary_storage_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
2008 let (key, target_address) = if interpreter.bytecode.opcode() == op::SLOAD {
2009 (try_or_return!(interpreter.stack.peek(0)), interpreter.input.target_address)
2010 } else {
2011 return;
2012 };
2013
2014 let Some(value) = ecx.sload(target_address, key) else {
2015 return;
2016 };
2017
2018 if (value.is_cold && value.data.is_zero())
2019 || self.should_overwrite_arbitrary_storage(&target_address, key)
2020 {
2021 if self.has_arbitrary_storage(&target_address) {
2022 let arbitrary_value = self.rng().random();
2023 self.arbitrary_storage.as_mut().unwrap().save(
2024 ecx,
2025 target_address,
2026 key,
2027 arbitrary_value,
2028 );
2029 } else if self.is_arbitrary_storage_copy(&target_address) {
2030 let arbitrary_value = self.rng().random();
2031 self.arbitrary_storage.as_mut().unwrap().copy(
2032 ecx,
2033 target_address,
2034 key,
2035 arbitrary_value,
2036 );
2037 }
2038 }
2039 }
2040
2041 #[cold]
2043 fn record_accesses(&mut self, interpreter: &mut Interpreter) {
2044 let access = &mut self.accesses;
2045 match interpreter.bytecode.opcode() {
2046 op::SLOAD => {
2047 let key = try_or_return!(interpreter.stack.peek(0));
2048 access.record_read(interpreter.input.target_address, key);
2049 }
2050 op::SSTORE => {
2051 let key = try_or_return!(interpreter.stack.peek(0));
2052 access.record_write(interpreter.input.target_address, key);
2053 }
2054 _ => {}
2055 }
2056 }
2057
2058 #[cold]
2059 fn record_state_diffs(&mut self, interpreter: &mut Interpreter, ecx: Ecx) {
2060 let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return };
2061 match interpreter.bytecode.opcode() {
2062 op::SELFDESTRUCT => {
2063 let Some(last) = account_accesses.last_mut() else { return };
2065
2066 let target = try_or_return!(interpreter.stack.peek(0));
2068 let target = Address::from_word(B256::from(target));
2069 let (db, journal, _) = ecx.as_db_env_and_journal();
2070 let (initialized, old_balance, old_nonce) = journal
2071 .load_account(db, target)
2072 .map(|account| {
2073 (account.info.exists(), account.info.balance, account.info.nonce)
2074 })
2075 .unwrap_or_default();
2076
2077 let value = ecx
2079 .balance(interpreter.input.target_address)
2080 .map(|b| b.data)
2081 .unwrap_or(U256::ZERO);
2082
2083 last.push(crate::Vm::AccountAccess {
2085 chainInfo: crate::Vm::ChainInfo {
2086 forkId: ecx.db().active_fork_id().unwrap_or_default(),
2087 chainId: U256::from(ecx.cfg().chain_id),
2088 },
2089 accessor: interpreter.input.target_address,
2090 account: target,
2091 kind: crate::Vm::AccountAccessKind::SelfDestruct,
2092 initialized,
2093 oldBalance: old_balance,
2094 newBalance: old_balance + value,
2095 oldNonce: old_nonce,
2096 newNonce: old_nonce, value,
2098 data: Bytes::new(),
2099 reverted: false,
2100 deployedCode: Bytes::new(),
2101 storageAccesses: vec![],
2102 depth: ecx
2103 .journal()
2104 .depth()
2105 .try_into()
2106 .expect("journaled state depth exceeds u64"),
2107 });
2108 }
2109
2110 op::SLOAD => {
2111 let Some(last) = account_accesses.last_mut() else { return };
2112
2113 let key = try_or_return!(interpreter.stack.peek(0));
2114 let address = interpreter.input.target_address;
2115
2116 let mut present_value = U256::ZERO;
2119 let (db, journal, _) = ecx.as_db_env_and_journal();
2121 if journal.load_account(db, address).is_ok()
2122 && let Some(previous) = ecx.sload(address, key)
2123 {
2124 present_value = previous.data;
2125 }
2126 let access = crate::Vm::StorageAccess {
2127 account: interpreter.input.target_address,
2128 slot: key.into(),
2129 isWrite: false,
2130 previousValue: present_value.into(),
2131 newValue: present_value.into(),
2132 reverted: false,
2133 };
2134 let curr_depth =
2135 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2136 append_storage_access(last, access, curr_depth);
2137 }
2138 op::SSTORE => {
2139 let Some(last) = account_accesses.last_mut() else { return };
2140
2141 let key = try_or_return!(interpreter.stack.peek(0));
2142 let value = try_or_return!(interpreter.stack.peek(1));
2143 let address = interpreter.input.target_address;
2144 let mut previous_value = U256::ZERO;
2147 let (db, journal, _) = ecx.as_db_env_and_journal();
2148 if journal.load_account(db, address).is_ok()
2149 && let Some(previous) = ecx.sload(address, key)
2150 {
2151 previous_value = previous.data;
2152 }
2153
2154 let access = crate::Vm::StorageAccess {
2155 account: address,
2156 slot: key.into(),
2157 isWrite: true,
2158 previousValue: previous_value.into(),
2159 newValue: value.into(),
2160 reverted: false,
2161 };
2162 let curr_depth =
2163 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2164 append_storage_access(last, access, curr_depth);
2165 }
2166
2167 op::EXTCODECOPY | op::EXTCODESIZE | op::EXTCODEHASH | op::BALANCE => {
2169 let kind = match interpreter.bytecode.opcode() {
2170 op::EXTCODECOPY => crate::Vm::AccountAccessKind::Extcodecopy,
2171 op::EXTCODESIZE => crate::Vm::AccountAccessKind::Extcodesize,
2172 op::EXTCODEHASH => crate::Vm::AccountAccessKind::Extcodehash,
2173 op::BALANCE => crate::Vm::AccountAccessKind::Balance,
2174 _ => unreachable!(),
2175 };
2176 let address =
2177 Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0))));
2178 let initialized;
2179 let balance;
2180 let nonce;
2181 let (db, journal, _) = ecx.as_db_env_and_journal();
2182 if let Ok(acc) = journal.load_account(db, address) {
2183 initialized = acc.info.exists();
2184 balance = acc.info.balance;
2185 nonce = acc.info.nonce;
2186 } else {
2187 initialized = false;
2188 balance = U256::ZERO;
2189 nonce = 0;
2190 }
2191 let curr_depth =
2192 ecx.journal().depth().try_into().expect("journaled state depth exceeds u64");
2193 let account_access = crate::Vm::AccountAccess {
2194 chainInfo: crate::Vm::ChainInfo {
2195 forkId: ecx.db().active_fork_id().unwrap_or_default(),
2196 chainId: U256::from(ecx.cfg().chain_id),
2197 },
2198 accessor: interpreter.input.target_address,
2199 account: address,
2200 kind,
2201 initialized,
2202 oldBalance: balance,
2203 newBalance: balance,
2204 oldNonce: nonce,
2205 newNonce: nonce, value: U256::ZERO,
2207 data: Bytes::new(),
2208 reverted: false,
2209 deployedCode: Bytes::new(),
2210 storageAccesses: vec![],
2211 depth: curr_depth,
2212 };
2213 if let Some(last) = account_accesses.last_mut() {
2216 last.push(account_access);
2217 } else {
2218 account_accesses.push(vec![account_access]);
2219 }
2220 }
2221 _ => {}
2222 }
2223 }
2224
2225 #[cold]
2230 fn check_mem_opcodes(&self, interpreter: &mut Interpreter, depth: u64) {
2231 let Some(ranges) = self.allowed_mem_writes.get(&depth) else {
2232 return;
2233 };
2234
2235 macro_rules! mem_opcode_match {
2244 ($(($opcode:ident, $offset_depth:expr, $size_depth:expr, $writes:expr)),* $(,)?) => {
2245 match interpreter.bytecode.opcode() {
2246 op::MSTORE => {
2251 let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2253
2254 if !ranges.iter().any(|range| {
2257 range.contains(&offset) && range.contains(&(offset + 31))
2258 }) {
2259 let value = try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>();
2264 if value[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
2265 return
2266 }
2267
2268 disallowed_mem_write(offset, 32, interpreter, ranges);
2269 return
2270 }
2271 }
2272 op::MSTORE8 => {
2273 let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2275
2276 if !ranges.iter().any(|range| range.contains(&offset)) {
2279 disallowed_mem_write(offset, 1, interpreter, ranges);
2280 return
2281 }
2282 }
2283
2284 op::MLOAD => {
2289 let offset = try_or_return!(interpreter.stack.peek(0)).saturating_to::<u64>();
2291
2292 if offset >= interpreter.memory.size() as u64 && !ranges.iter().any(|range| {
2296 range.contains(&offset) && range.contains(&(offset + 31))
2297 }) {
2298 disallowed_mem_write(offset, 32, interpreter, ranges);
2299 return
2300 }
2301 }
2302
2303 op::CALL => {
2308 let dest_offset = try_or_return!(interpreter.stack.peek(5)).saturating_to::<u64>();
2310
2311 let size = try_or_return!(interpreter.stack.peek(6)).saturating_to::<u64>();
2313
2314 let fail_cond = !ranges.iter().any(|range| {
2318 range.contains(&dest_offset) &&
2319 range.contains(&(dest_offset + size.saturating_sub(1)))
2320 });
2321
2322 if fail_cond {
2325 let to = Address::from_word(try_or_return!(interpreter.stack.peek(1)).to_be_bytes::<32>().into());
2329 if to == CHEATCODE_ADDRESS {
2330 let args_offset = try_or_return!(interpreter.stack.peek(3)).saturating_to::<usize>();
2331 let args_size = try_or_return!(interpreter.stack.peek(4)).saturating_to::<usize>();
2332 let memory_word = interpreter.memory.slice_len(args_offset, args_size);
2333 if memory_word[..SELECTOR_LEN] == stopExpectSafeMemoryCall::SELECTOR {
2334 return
2335 }
2336 }
2337
2338 disallowed_mem_write(dest_offset, size, interpreter, ranges);
2339 return
2340 }
2341 }
2342
2343 $(op::$opcode => {
2344 let dest_offset = try_or_return!(interpreter.stack.peek($offset_depth)).saturating_to::<u64>();
2346
2347 let size = try_or_return!(interpreter.stack.peek($size_depth)).saturating_to::<u64>();
2349
2350 let fail_cond = !ranges.iter().any(|range| {
2354 range.contains(&dest_offset) &&
2355 range.contains(&(dest_offset + size.saturating_sub(1)))
2356 }) && ($writes ||
2357 [dest_offset, (dest_offset + size).saturating_sub(1)].into_iter().any(|offset| {
2358 offset >= interpreter.memory.size() as u64
2359 })
2360 );
2361
2362 if fail_cond {
2365 disallowed_mem_write(dest_offset, size, interpreter, ranges);
2366 return
2367 }
2368 })*
2369
2370 _ => {}
2371 }
2372 }
2373 }
2374
2375 mem_opcode_match!(
2378 (CALLDATACOPY, 0, 2, true),
2379 (CODECOPY, 0, 2, true),
2380 (RETURNDATACOPY, 0, 2, true),
2381 (EXTCODECOPY, 1, 3, true),
2382 (CALLCODE, 5, 6, true),
2383 (STATICCALL, 4, 5, true),
2384 (DELEGATECALL, 4, 5, true),
2385 (KECCAK256, 0, 1, false),
2386 (LOG0, 0, 1, false),
2387 (LOG1, 0, 1, false),
2388 (LOG2, 0, 1, false),
2389 (LOG3, 0, 1, false),
2390 (LOG4, 0, 1, false),
2391 (CREATE, 1, 2, false),
2392 (CREATE2, 1, 2, false),
2393 (RETURN, 0, 1, false),
2394 (REVERT, 0, 1, false),
2395 );
2396 }
2397
2398 #[cold]
2399 fn set_gas_limit_type(&mut self, interpreter: &mut Interpreter) {
2400 match interpreter.bytecode.opcode() {
2401 op::CREATE2 => self.dynamic_gas_limit = true,
2402 op::CALL => {
2403 self.dynamic_gas_limit =
2406 try_or_return!(interpreter.stack.peek(0)) >= interpreter.gas.remaining() - 100
2407 }
2408 _ => self.dynamic_gas_limit = false,
2409 }
2410 }
2411}
2412
2413fn disallowed_mem_write(
2419 dest_offset: u64,
2420 size: u64,
2421 interpreter: &mut Interpreter,
2422 ranges: &[Range<u64>],
2423) {
2424 let revert_string = format!(
2425 "memory write at offset 0x{:02X} of size 0x{:02X} not allowed; safe range: {}",
2426 dest_offset,
2427 size,
2428 ranges.iter().map(|r| format!("(0x{:02X}, 0x{:02X}]", r.start, r.end)).join(" U ")
2429 );
2430
2431 interpreter.bytecode.set_action(InterpreterAction::new_return(
2432 InstructionResult::Revert,
2433 Bytes::from(revert_string.into_bytes()),
2434 interpreter.gas,
2435 ));
2436}
2437
2438fn access_is_call(kind: crate::Vm::AccountAccessKind) -> bool {
2440 matches!(
2441 kind,
2442 crate::Vm::AccountAccessKind::Call
2443 | crate::Vm::AccountAccessKind::StaticCall
2444 | crate::Vm::AccountAccessKind::CallCode
2445 | crate::Vm::AccountAccessKind::DelegateCall
2446 )
2447}
2448
2449fn record_logs(recorded_logs: &mut Option<Vec<Vm::Log>>, log: &Log) {
2451 if let Some(storage_recorded_logs) = recorded_logs {
2452 storage_recorded_logs.push(Vm::Log {
2453 topics: log.data.topics().to_vec(),
2454 data: log.data.data.clone(),
2455 emitter: log.address,
2456 });
2457 }
2458}
2459
2460fn append_storage_access(
2462 last: &mut Vec<AccountAccess>,
2463 storage_access: crate::Vm::StorageAccess,
2464 storage_depth: u64,
2465) {
2466 if !last.is_empty() && last.first().unwrap().depth < storage_depth {
2468 if last.len() == 1 {
2474 last.first_mut().unwrap().storageAccesses.push(storage_access);
2475 } else {
2476 let last_record = last.last_mut().unwrap();
2477 if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 {
2478 last_record.storageAccesses.push(storage_access);
2479 } else {
2480 let entry = last.first().unwrap();
2481 let resume_record = crate::Vm::AccountAccess {
2482 chainInfo: crate::Vm::ChainInfo {
2483 forkId: entry.chainInfo.forkId,
2484 chainId: entry.chainInfo.chainId,
2485 },
2486 accessor: entry.accessor,
2487 account: entry.account,
2488 kind: crate::Vm::AccountAccessKind::Resume,
2489 initialized: entry.initialized,
2490 storageAccesses: vec![storage_access],
2491 reverted: entry.reverted,
2492 oldBalance: U256::ZERO,
2494 newBalance: U256::ZERO,
2495 oldNonce: 0,
2496 newNonce: 0,
2497 value: U256::ZERO,
2498 data: Bytes::new(),
2499 deployedCode: Bytes::new(),
2500 depth: entry.depth,
2501 };
2502 last.push(resume_record);
2503 }
2504 }
2505 }
2506}
2507
2508fn apply_dispatch(
2510 calls: &Vm::VmCalls,
2511 ccx: &mut CheatsCtxt,
2512 executor: &mut dyn CheatcodesExecutor,
2513) -> Result {
2514 let cheat = calls_as_dyn_cheatcode(calls);
2515
2516 let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheat.id()).entered();
2517 trace!(target: "cheatcodes", ?cheat, "applying");
2518
2519 if let spec::Status::Deprecated(replacement) = *cheat.status() {
2520 ccx.state.deprecated.insert(cheat.signature(), replacement);
2521 }
2522
2523 let mut result = cheat.dyn_apply(ccx, executor);
2525
2526 if let Err(e) = &mut result
2528 && e.is_str()
2529 {
2530 let name = cheat.name();
2531 if !name.contains("assert") && name != "rpcUrl" {
2535 *e = fmt_err!("vm.{name}: {e}");
2536 }
2537 }
2538
2539 trace!(
2540 target: "cheatcodes",
2541 return = %match &result {
2542 Ok(b) => hex::encode(b),
2543 Err(e) => e.to_string(),
2544 }
2545 );
2546
2547 result
2548}
2549
2550fn calls_as_dyn_cheatcode(calls: &Vm::VmCalls) -> &dyn DynCheatcode {
2551 macro_rules! as_dyn {
2552 ($($variant:ident),*) => {
2553 match calls {
2554 $(Vm::VmCalls::$variant(cheat) => cheat,)*
2555 }
2556 };
2557 }
2558 vm_calls!(as_dyn)
2559}
2560
2561fn will_exit(action: &InterpreterAction) -> bool {
2563 match action {
2564 InterpreterAction::Return(result) => {
2565 result.result.is_ok_or_revert() || result.result.is_error()
2566 }
2567 _ => false,
2568 }
2569}