foundry_cheatcodes/
evm.rs

1//! Implementations of [`Evm`](spec::Group::Evm) cheatcodes.
2
3use crate::{
4    inspector::{Ecx, RecordDebugStepInfo},
5    BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result,
6    Vm::*,
7};
8use alloy_consensus::TxEnvelope;
9use alloy_genesis::{Genesis, GenesisAccount};
10use alloy_primitives::{map::HashMap, Address, Bytes, B256, U256};
11use alloy_rlp::Decodable;
12use alloy_sol_types::SolValue;
13use foundry_common::fs::{read_json_file, write_json_file};
14use foundry_evm_core::{
15    backend::{DatabaseExt, RevertStateSnapshotAction},
16    constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
17    ContextExt,
18};
19use foundry_evm_traces::StackSnapshotType;
20use itertools::Itertools;
21use rand::Rng;
22use revm::{
23    bytecode::Bytecode,
24    context::{Block, JournalTr},
25    primitives::{hardfork::SpecId, KECCAK_EMPTY},
26    state::Account,
27};
28use std::{
29    collections::{btree_map::Entry, BTreeMap},
30    fmt::Display,
31    path::Path,
32};
33
34mod record_debug_step;
35use record_debug_step::{convert_call_trace_to_debug_step, flatten_call_trace};
36use serde::Serialize;
37
38mod fork;
39pub(crate) mod mapping;
40pub(crate) mod mock;
41pub(crate) mod prank;
42
43/// Records storage slots reads and writes.
44#[derive(Clone, Debug, Default)]
45pub struct RecordAccess {
46    /// Storage slots reads.
47    pub reads: HashMap<Address, Vec<U256>>,
48    /// Storage slots writes.
49    pub writes: HashMap<Address, Vec<U256>>,
50}
51
52impl RecordAccess {
53    /// Records a read access to a storage slot.
54    pub fn record_read(&mut self, target: Address, slot: U256) {
55        self.reads.entry(target).or_default().push(slot);
56    }
57
58    /// Records a write access to a storage slot.
59    ///
60    /// This also records a read internally as `SSTORE` does an implicit `SLOAD`.
61    pub fn record_write(&mut self, target: Address, slot: U256) {
62        self.record_read(target, slot);
63        self.writes.entry(target).or_default().push(slot);
64    }
65
66    /// Clears the recorded reads and writes.
67    pub fn clear(&mut self) {
68        // Also frees memory.
69        *self = Default::default();
70    }
71}
72
73/// Records the `snapshotGas*` cheatcodes.
74#[derive(Clone, Debug)]
75pub struct GasRecord {
76    /// The group name of the gas snapshot.
77    pub group: String,
78    /// The name of the gas snapshot.
79    pub name: String,
80    /// The total gas used in the gas snapshot.
81    pub gas_used: u64,
82    /// Depth at which the gas snapshot was taken.
83    pub depth: usize,
84}
85
86/// Records `deal` cheatcodes
87#[derive(Clone, Debug)]
88pub struct DealRecord {
89    /// Target of the deal.
90    pub address: Address,
91    /// The balance of the address before deal was applied
92    pub old_balance: U256,
93    /// Balance after deal was applied
94    pub new_balance: U256,
95}
96
97/// Storage slot diff info.
98#[derive(Serialize, Default)]
99#[serde(rename_all = "camelCase")]
100struct SlotStateDiff {
101    /// Initial storage value.
102    previous_value: B256,
103    /// Current storage value.
104    new_value: B256,
105}
106
107/// Balance diff info.
108#[derive(Serialize, Default)]
109#[serde(rename_all = "camelCase")]
110struct BalanceDiff {
111    /// Initial storage value.
112    previous_value: U256,
113    /// Current storage value.
114    new_value: U256,
115}
116
117/// Account state diff info.
118#[derive(Serialize, Default)]
119#[serde(rename_all = "camelCase")]
120struct AccountStateDiffs {
121    /// Address label, if any set.
122    label: Option<String>,
123    /// Account balance changes.
124    balance_diff: Option<BalanceDiff>,
125    /// State changes, per slot.
126    state_diff: BTreeMap<B256, SlotStateDiff>,
127}
128
129impl Display for AccountStateDiffs {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
131        // Print changed account.
132        if let Some(label) = &self.label {
133            writeln!(f, "label: {label}")?;
134        }
135        // Print balance diff if changed.
136        if let Some(balance_diff) = &self.balance_diff {
137            if balance_diff.previous_value != balance_diff.new_value {
138                writeln!(
139                    f,
140                    "- balance diff: {} → {}",
141                    balance_diff.previous_value, balance_diff.new_value
142                )?;
143            }
144        }
145        // Print state diff if any.
146        if !&self.state_diff.is_empty() {
147            writeln!(f, "- state diff:")?;
148            for (slot, slot_changes) in &self.state_diff {
149                writeln!(
150                    f,
151                    "@ {slot}: {} → {}",
152                    slot_changes.previous_value, slot_changes.new_value
153                )?;
154            }
155        }
156
157        Ok(())
158    }
159}
160
161impl Cheatcode for addrCall {
162    fn apply(&self, _state: &mut Cheatcodes) -> Result {
163        let Self { privateKey } = self;
164        let wallet = super::crypto::parse_wallet(privateKey)?;
165        Ok(wallet.address().abi_encode())
166    }
167}
168
169impl Cheatcode for getNonce_0Call {
170    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
171        let Self { account } = self;
172        get_nonce(ccx, account)
173    }
174}
175
176impl Cheatcode for getNonce_1Call {
177    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
178        let Self { wallet } = self;
179        get_nonce(ccx, &wallet.addr)
180    }
181}
182
183impl Cheatcode for loadCall {
184    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
185        let Self { target, slot } = *self;
186        ensure_not_precompile!(&target, ccx);
187        ccx.ecx.journaled_state.load_account(target)?;
188        let mut val = ccx.ecx.journaled_state.sload(target, slot.into())?;
189
190        if val.is_cold && val.data.is_zero() {
191            if ccx.state.has_arbitrary_storage(&target) {
192                // If storage slot is untouched and load from a target with arbitrary storage,
193                // then set random value for current slot.
194                let rand_value = ccx.state.rng().random();
195                ccx.state.arbitrary_storage.as_mut().unwrap().save(
196                    ccx.ecx,
197                    target,
198                    slot.into(),
199                    rand_value,
200                );
201                val.data = rand_value;
202            } else if ccx.state.is_arbitrary_storage_copy(&target) {
203                // If storage slot is untouched and load from a target that copies storage from
204                // a source address with arbitrary storage, then copy existing arbitrary value.
205                // If no arbitrary value generated yet, then the random one is saved and set.
206                let rand_value = ccx.state.rng().random();
207                val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
208                    ccx.ecx,
209                    target,
210                    slot.into(),
211                    rand_value,
212                );
213            }
214        }
215
216        Ok(val.abi_encode())
217    }
218}
219
220impl Cheatcode for loadAllocsCall {
221    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
222        let Self { pathToAllocsJson } = self;
223
224        let path = Path::new(pathToAllocsJson);
225        ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
226
227        // Let's first assume we're reading a file with only the allocs.
228        let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
229            Ok(allocs) => allocs,
230            Err(_) => {
231                // Let's try and read from a genesis file, and extract allocs.
232                let genesis = read_json_file::<Genesis>(path)?;
233                genesis.alloc
234            }
235        };
236
237        // Then, load the allocs into the database.
238        let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
239        db.load_allocs(&allocs, journal)
240            .map(|()| Vec::default())
241            .map_err(|e| fmt_err!("failed to load allocs: {e}"))
242    }
243}
244
245impl Cheatcode for cloneAccountCall {
246    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
247        let Self { source, target } = self;
248
249        let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
250        let account = journal.load_account(db, *source)?;
251        let genesis = &genesis_account(account.data);
252        db.clone_account(genesis, target, journal)?;
253        // Cloned account should persist in forked envs.
254        ccx.ecx.journaled_state.database.add_persistent_account(*target);
255        Ok(Default::default())
256    }
257}
258
259impl Cheatcode for dumpStateCall {
260    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
261        let Self { pathToStateJson } = self;
262        let path = Path::new(pathToStateJson);
263
264        // Do not include system account or empty accounts in the dump.
265        let skip = |key: &Address, val: &Account| {
266            key == &CHEATCODE_ADDRESS ||
267                key == &CALLER ||
268                key == &HARDHAT_CONSOLE_ADDRESS ||
269                key == &TEST_CONTRACT_ADDRESS ||
270                key == &ccx.caller ||
271                key == &ccx.state.config.evm_opts.sender ||
272                val.is_empty()
273        };
274
275        let alloc = ccx
276            .ecx
277            .journaled_state
278            .state()
279            .iter_mut()
280            .filter(|(key, val)| !skip(key, val))
281            .map(|(key, val)| (key, genesis_account(val)))
282            .collect::<BTreeMap<_, _>>();
283
284        write_json_file(path, &alloc)?;
285        Ok(Default::default())
286    }
287}
288
289impl Cheatcode for recordCall {
290    fn apply(&self, state: &mut Cheatcodes) -> Result {
291        let Self {} = self;
292        state.recording_accesses = true;
293        state.accesses.clear();
294        Ok(Default::default())
295    }
296}
297
298impl Cheatcode for stopRecordCall {
299    fn apply(&self, state: &mut Cheatcodes) -> Result {
300        state.recording_accesses = false;
301        Ok(Default::default())
302    }
303}
304
305impl Cheatcode for accessesCall {
306    fn apply(&self, state: &mut Cheatcodes) -> Result {
307        let Self { target } = *self;
308        let result = (
309            state.accesses.reads.entry(target).or_default().as_slice(),
310            state.accesses.writes.entry(target).or_default().as_slice(),
311        );
312        Ok(result.abi_encode_params())
313    }
314}
315
316impl Cheatcode for recordLogsCall {
317    fn apply(&self, state: &mut Cheatcodes) -> Result {
318        let Self {} = self;
319        state.recorded_logs = Some(Default::default());
320        Ok(Default::default())
321    }
322}
323
324impl Cheatcode for getRecordedLogsCall {
325    fn apply(&self, state: &mut Cheatcodes) -> Result {
326        let Self {} = self;
327        Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
328    }
329}
330
331impl Cheatcode for pauseGasMeteringCall {
332    fn apply(&self, state: &mut Cheatcodes) -> Result {
333        let Self {} = self;
334        state.gas_metering.paused = true;
335        Ok(Default::default())
336    }
337}
338
339impl Cheatcode for resumeGasMeteringCall {
340    fn apply(&self, state: &mut Cheatcodes) -> Result {
341        let Self {} = self;
342        state.gas_metering.resume();
343        Ok(Default::default())
344    }
345}
346
347impl Cheatcode for resetGasMeteringCall {
348    fn apply(&self, state: &mut Cheatcodes) -> Result {
349        let Self {} = self;
350        state.gas_metering.reset();
351        Ok(Default::default())
352    }
353}
354
355impl Cheatcode for lastCallGasCall {
356    fn apply(&self, state: &mut Cheatcodes) -> Result {
357        let Self {} = self;
358        let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
359            bail!("no external call was made yet");
360        };
361        Ok(last_call_gas.abi_encode())
362    }
363}
364
365impl Cheatcode for chainIdCall {
366    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
367        let Self { newChainId } = self;
368        ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1");
369        ccx.ecx.cfg.chain_id = newChainId.to();
370        Ok(Default::default())
371    }
372}
373
374impl Cheatcode for coinbaseCall {
375    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
376        let Self { newCoinbase } = self;
377        ccx.ecx.block.beneficiary = *newCoinbase;
378        Ok(Default::default())
379    }
380}
381
382impl Cheatcode for difficultyCall {
383    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
384        let Self { newDifficulty } = self;
385        ensure!(
386            ccx.ecx.cfg.spec < SpecId::MERGE,
387            "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
388             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
389        );
390        ccx.ecx.block.difficulty = *newDifficulty;
391        Ok(Default::default())
392    }
393}
394
395impl Cheatcode for feeCall {
396    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
397        let Self { newBasefee } = self;
398        ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64 - 1");
399        ccx.ecx.block.basefee = newBasefee.saturating_to();
400        Ok(Default::default())
401    }
402}
403
404impl Cheatcode for prevrandao_0Call {
405    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
406        let Self { newPrevrandao } = self;
407        ensure!(
408            ccx.ecx.cfg.spec >= SpecId::MERGE,
409            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
410             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
411        );
412        ccx.ecx.block.prevrandao = Some(*newPrevrandao);
413        Ok(Default::default())
414    }
415}
416
417impl Cheatcode for prevrandao_1Call {
418    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
419        let Self { newPrevrandao } = self;
420        ensure!(
421            ccx.ecx.cfg.spec >= SpecId::MERGE,
422            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
423             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
424        );
425        ccx.ecx.block.prevrandao = Some((*newPrevrandao).into());
426        Ok(Default::default())
427    }
428}
429
430impl Cheatcode for blobhashesCall {
431    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
432        let Self { hashes } = self;
433        ensure!(
434            ccx.ecx.cfg.spec >= SpecId::CANCUN,
435            "`blobhashes` is not supported before the Cancun hard fork; \
436             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
437        );
438        ccx.ecx.tx.blob_hashes.clone_from(hashes);
439        Ok(Default::default())
440    }
441}
442
443impl Cheatcode for getBlobhashesCall {
444    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
445        let Self {} = self;
446        ensure!(
447            ccx.ecx.cfg.spec >= SpecId::CANCUN,
448            "`getBlobhashes` is not supported before the Cancun hard fork; \
449             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
450        );
451        Ok(ccx.ecx.tx.blob_hashes.clone().abi_encode())
452    }
453}
454
455impl Cheatcode for rollCall {
456    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
457        let Self { newHeight } = self;
458        ensure!(*newHeight <= U256::from(u64::MAX), "block height must be less than 2^64 - 1");
459        ccx.ecx.block.number = newHeight.saturating_to();
460        Ok(Default::default())
461    }
462}
463
464impl Cheatcode for getBlockNumberCall {
465    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
466        let Self {} = self;
467        Ok(ccx.ecx.block.number.abi_encode())
468    }
469}
470
471impl Cheatcode for txGasPriceCall {
472    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
473        let Self { newGasPrice } = self;
474        ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64 - 1");
475        ccx.ecx.tx.gas_price = newGasPrice.saturating_to();
476        Ok(Default::default())
477    }
478}
479
480impl Cheatcode for warpCall {
481    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
482        let Self { newTimestamp } = self;
483        ensure!(*newTimestamp <= U256::from(u64::MAX), "timestamp must be less than 2^64 - 1");
484        ccx.ecx.block.timestamp = newTimestamp.saturating_to();
485        Ok(Default::default())
486    }
487}
488
489impl Cheatcode for getBlockTimestampCall {
490    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
491        let Self {} = self;
492        Ok(ccx.ecx.block.timestamp.abi_encode())
493    }
494}
495
496impl Cheatcode for blobBaseFeeCall {
497    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
498        let Self { newBlobBaseFee } = self;
499        ensure!(
500            ccx.ecx.cfg.spec >= SpecId::CANCUN,
501            "`blobBaseFee` is not supported before the Cancun hard fork; \
502             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
503        );
504        let is_prague = ccx.ecx.cfg.spec >= SpecId::PRAGUE;
505        ccx.ecx.block.set_blob_excess_gas_and_price((*newBlobBaseFee).to(), is_prague);
506        Ok(Default::default())
507    }
508}
509
510impl Cheatcode for getBlobBaseFeeCall {
511    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
512        let Self {} = self;
513        Ok(ccx.ecx.block.blob_excess_gas().unwrap_or(0).abi_encode())
514    }
515}
516
517impl Cheatcode for dealCall {
518    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
519        let Self { account: address, newBalance: new_balance } = *self;
520        let account = journaled_account(ccx.ecx, address)?;
521        let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
522        let record = DealRecord { address, old_balance, new_balance };
523        ccx.state.eth_deals.push(record);
524        Ok(Default::default())
525    }
526}
527
528impl Cheatcode for etchCall {
529    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
530        let Self { target, newRuntimeBytecode } = self;
531        ensure_not_precompile!(target, ccx);
532        ccx.ecx.journaled_state.load_account(*target)?;
533        let bytecode = Bytecode::new_raw_checked(Bytes::copy_from_slice(newRuntimeBytecode))
534            .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
535        ccx.ecx.journaled_state.set_code(*target, bytecode);
536        Ok(Default::default())
537    }
538}
539
540impl Cheatcode for resetNonceCall {
541    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
542        let Self { account } = self;
543        let account = journaled_account(ccx.ecx, *account)?;
544        // Per EIP-161, EOA nonces start at 0, but contract nonces
545        // start at 1. Comparing by code_hash instead of code
546        // to avoid hitting the case where account's code is None.
547        let empty = account.info.code_hash == KECCAK_EMPTY;
548        let nonce = if empty { 0 } else { 1 };
549        account.info.nonce = nonce;
550        debug!(target: "cheatcodes", nonce, "reset");
551        Ok(Default::default())
552    }
553}
554
555impl Cheatcode for setNonceCall {
556    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
557        let Self { account, newNonce } = *self;
558        let account = journaled_account(ccx.ecx, account)?;
559        // nonce must increment only
560        let current = account.info.nonce;
561        ensure!(
562            newNonce >= current,
563            "new nonce ({newNonce}) must be strictly equal to or higher than the \
564             account's current nonce ({current})"
565        );
566        account.info.nonce = newNonce;
567        Ok(Default::default())
568    }
569}
570
571impl Cheatcode for setNonceUnsafeCall {
572    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
573        let Self { account, newNonce } = *self;
574        let account = journaled_account(ccx.ecx, account)?;
575        account.info.nonce = newNonce;
576        Ok(Default::default())
577    }
578}
579
580impl Cheatcode for storeCall {
581    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
582        let Self { target, slot, value } = *self;
583        ensure_not_precompile!(&target, ccx);
584        // ensure the account is touched
585        let _ = journaled_account(ccx.ecx, target)?;
586        ccx.ecx.journaled_state.sstore(target, slot.into(), value.into())?;
587        Ok(Default::default())
588    }
589}
590
591impl Cheatcode for coolCall {
592    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
593        let Self { target } = self;
594        if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) {
595            account.unmark_touch();
596            account.storage.values_mut().for_each(|slot| slot.mark_cold());
597        }
598        Ok(Default::default())
599    }
600}
601
602impl Cheatcode for accessListCall {
603    fn apply(&self, state: &mut Cheatcodes) -> Result {
604        let Self { access } = self;
605        let access_list = access
606            .iter()
607            .map(|item| {
608                let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
609                alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
610            })
611            .collect_vec();
612        state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
613        Ok(Default::default())
614    }
615}
616
617impl Cheatcode for noAccessListCall {
618    fn apply(&self, state: &mut Cheatcodes) -> Result {
619        let Self {} = self;
620        // Set to empty option in order to override previous applied access list.
621        if state.access_list.is_some() {
622            state.access_list = Some(alloy_rpc_types::AccessList::default());
623        }
624        Ok(Default::default())
625    }
626}
627
628impl Cheatcode for warmSlotCall {
629    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
630        let Self { target, slot } = *self;
631        set_cold_slot(ccx, target, slot.into(), false);
632        Ok(Default::default())
633    }
634}
635
636impl Cheatcode for coolSlotCall {
637    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
638        let Self { target, slot } = *self;
639        set_cold_slot(ccx, target, slot.into(), true);
640        Ok(Default::default())
641    }
642}
643
644impl Cheatcode for readCallersCall {
645    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
646        let Self {} = self;
647        read_callers(ccx.state, &ccx.ecx.tx.caller, ccx.ecx.journaled_state.depth())
648    }
649}
650
651impl Cheatcode for snapshotValue_0Call {
652    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
653        let Self { name, value } = self;
654        inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
655    }
656}
657
658impl Cheatcode for snapshotValue_1Call {
659    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
660        let Self { group, name, value } = self;
661        inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
662    }
663}
664
665impl Cheatcode for snapshotGasLastCall_0Call {
666    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
667        let Self { name } = self;
668        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
669            bail!("no external call was made yet");
670        };
671        inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed)
672    }
673}
674
675impl Cheatcode for snapshotGasLastCall_1Call {
676    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
677        let Self { name, group } = self;
678        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
679            bail!("no external call was made yet");
680        };
681        inner_last_gas_snapshot(
682            ccx,
683            Some(group.clone()),
684            Some(name.clone()),
685            last_call_gas.gasTotalUsed,
686        )
687    }
688}
689
690impl Cheatcode for startSnapshotGas_0Call {
691    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
692        let Self { name } = self;
693        inner_start_gas_snapshot(ccx, None, Some(name.clone()))
694    }
695}
696
697impl Cheatcode for startSnapshotGas_1Call {
698    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
699        let Self { group, name } = self;
700        inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
701    }
702}
703
704impl Cheatcode for stopSnapshotGas_0Call {
705    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
706        let Self {} = self;
707        inner_stop_gas_snapshot(ccx, None, None)
708    }
709}
710
711impl Cheatcode for stopSnapshotGas_1Call {
712    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
713        let Self { name } = self;
714        inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
715    }
716}
717
718impl Cheatcode for stopSnapshotGas_2Call {
719    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
720        let Self { group, name } = self;
721        inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
722    }
723}
724
725// Deprecated in favor of `snapshotStateCall`
726impl Cheatcode for snapshotCall {
727    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
728        let Self {} = self;
729        inner_snapshot_state(ccx)
730    }
731}
732
733impl Cheatcode for snapshotStateCall {
734    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
735        let Self {} = self;
736        inner_snapshot_state(ccx)
737    }
738}
739
740// Deprecated in favor of `revertToStateCall`
741impl Cheatcode for revertToCall {
742    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
743        let Self { snapshotId } = self;
744        inner_revert_to_state(ccx, *snapshotId)
745    }
746}
747
748impl Cheatcode for revertToStateCall {
749    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
750        let Self { snapshotId } = self;
751        inner_revert_to_state(ccx, *snapshotId)
752    }
753}
754
755// Deprecated in favor of `revertToStateAndDeleteCall`
756impl Cheatcode for revertToAndDeleteCall {
757    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
758        let Self { snapshotId } = self;
759        inner_revert_to_state_and_delete(ccx, *snapshotId)
760    }
761}
762
763impl Cheatcode for revertToStateAndDeleteCall {
764    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
765        let Self { snapshotId } = self;
766        inner_revert_to_state_and_delete(ccx, *snapshotId)
767    }
768}
769
770// Deprecated in favor of `deleteStateSnapshotCall`
771impl Cheatcode for deleteSnapshotCall {
772    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
773        let Self { snapshotId } = self;
774        inner_delete_state_snapshot(ccx, *snapshotId)
775    }
776}
777
778impl Cheatcode for deleteStateSnapshotCall {
779    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
780        let Self { snapshotId } = self;
781        inner_delete_state_snapshot(ccx, *snapshotId)
782    }
783}
784
785// Deprecated in favor of `deleteStateSnapshotsCall`
786impl Cheatcode for deleteSnapshotsCall {
787    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
788        let Self {} = self;
789        inner_delete_state_snapshots(ccx)
790    }
791}
792
793impl Cheatcode for deleteStateSnapshotsCall {
794    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
795        let Self {} = self;
796        inner_delete_state_snapshots(ccx)
797    }
798}
799
800impl Cheatcode for startStateDiffRecordingCall {
801    fn apply(&self, state: &mut Cheatcodes) -> Result {
802        let Self {} = self;
803        state.recorded_account_diffs_stack = Some(Default::default());
804        Ok(Default::default())
805    }
806}
807
808impl Cheatcode for stopAndReturnStateDiffCall {
809    fn apply(&self, state: &mut Cheatcodes) -> Result {
810        let Self {} = self;
811        get_state_diff(state)
812    }
813}
814
815impl Cheatcode for getStateDiffCall {
816    fn apply(&self, state: &mut Cheatcodes) -> Result {
817        let mut diffs = String::new();
818        let state_diffs = get_recorded_state_diffs(state);
819        for (address, state_diffs) in state_diffs {
820            diffs.push_str(&format!("{address}\n"));
821            diffs.push_str(&format!("{state_diffs}\n"));
822        }
823        Ok(diffs.abi_encode())
824    }
825}
826
827impl Cheatcode for getStateDiffJsonCall {
828    fn apply(&self, state: &mut Cheatcodes) -> Result {
829        let state_diffs = get_recorded_state_diffs(state);
830        Ok(serde_json::to_string(&state_diffs)?.abi_encode())
831    }
832}
833
834impl Cheatcode for broadcastRawTransactionCall {
835    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
836        let tx = TxEnvelope::decode(&mut self.data.as_ref())
837            .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
838
839        let (db, journal, env) = ccx.ecx.as_db_env_and_journal();
840        db.transact_from_tx(
841            &tx.clone().into(),
842            env.to_owned(),
843            journal,
844            &mut *executor.get_inspector(ccx.state),
845        )?;
846
847        if ccx.state.broadcast.is_some() {
848            ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
849                rpc: ccx.ecx.journaled_state.database.active_fork_url(),
850                transaction: tx.try_into()?,
851            });
852        }
853
854        Ok(Default::default())
855    }
856}
857
858impl Cheatcode for setBlockhashCall {
859    fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
860        let Self { blockNumber, blockHash } = *self;
861        ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64 - 1");
862        ensure!(
863            blockNumber <= U256::from(ccx.ecx.block.number),
864            "block number must be less than or equal to the current block number"
865        );
866
867        ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash);
868
869        Ok(Default::default())
870    }
871}
872
873impl Cheatcode for startDebugTraceRecordingCall {
874    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
875        let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else {
876            return Err(Error::from("no tracer initiated, consider adding -vvv flag"))
877        };
878
879        let mut info = RecordDebugStepInfo {
880            // will be updated later
881            start_node_idx: 0,
882            // keep the original config to revert back later
883            original_tracer_config: *tracer.config(),
884        };
885
886        // turn on tracer configuration for recording
887        tracer.update_config(|config| {
888            config
889                .set_steps(true)
890                .set_memory_snapshots(true)
891                .set_stack_snapshots(StackSnapshotType::Full)
892        });
893
894        // track where the recording starts
895        if let Some(last_node) = tracer.traces().nodes().last() {
896            info.start_node_idx = last_node.idx;
897        }
898
899        ccx.state.record_debug_steps_info = Some(info);
900        Ok(Default::default())
901    }
902}
903
904impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
905    fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
906        let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else {
907            return Err(Error::from("no tracer initiated, consider adding -vvv flag"))
908        };
909
910        let Some(record_info) = ccx.state.record_debug_steps_info else {
911            return Err(Error::from("nothing recorded"))
912        };
913
914        // Use the trace nodes to flatten the call trace
915        let root = tracer.traces();
916        let steps = flatten_call_trace(0, root, record_info.start_node_idx);
917
918        let debug_steps: Vec<DebugStep> =
919            steps.iter().map(|&step| convert_call_trace_to_debug_step(step)).collect();
920        // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage.
921        if !record_info.original_tracer_config.record_steps {
922            tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
923                node.trace.steps = Vec::new();
924                node.logs = Vec::new();
925                node.ordering = Vec::new();
926            });
927        }
928
929        // Revert the tracer config to the one before recording
930        tracer.update_config(|_config| record_info.original_tracer_config);
931
932        // Clean up the recording info
933        ccx.state.record_debug_steps_info = None;
934
935        Ok(debug_steps.abi_encode())
936    }
937}
938
939pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result {
940    let account = ccx.ecx.journaled_state.load_account(*address)?;
941    Ok(account.info.nonce.abi_encode())
942}
943
944fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result {
945    let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
946    Ok(db.snapshot_state(journal, &mut env).abi_encode())
947}
948
949fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
950    let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
951    let result = if let Some(journaled_state) =
952        db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep)
953    {
954        // we reset the evm's journaled_state to the state of the snapshot previous state
955        ccx.ecx.journaled_state.inner = journaled_state;
956        true
957    } else {
958        false
959    };
960    Ok(result.abi_encode())
961}
962
963fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
964    let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
965
966    let result = if let Some(journaled_state) =
967        db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove)
968    {
969        // we reset the evm's journaled_state to the state of the snapshot previous state
970        ccx.ecx.journaled_state.inner = journaled_state;
971        true
972    } else {
973        false
974    };
975    Ok(result.abi_encode())
976}
977
978fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
979    let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id);
980    Ok(result.abi_encode())
981}
982
983fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result {
984    ccx.ecx.journaled_state.database.delete_state_snapshots();
985    Ok(Default::default())
986}
987
988fn inner_value_snapshot(
989    ccx: &mut CheatsCtxt,
990    group: Option<String>,
991    name: Option<String>,
992    value: String,
993) -> Result {
994    let (group, name) = derive_snapshot_name(ccx, group, name);
995
996    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
997
998    Ok(Default::default())
999}
1000
1001fn inner_last_gas_snapshot(
1002    ccx: &mut CheatsCtxt,
1003    group: Option<String>,
1004    name: Option<String>,
1005    value: u64,
1006) -> Result {
1007    let (group, name) = derive_snapshot_name(ccx, group, name);
1008
1009    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1010
1011    Ok(value.abi_encode())
1012}
1013
1014fn inner_start_gas_snapshot(
1015    ccx: &mut CheatsCtxt,
1016    group: Option<String>,
1017    name: Option<String>,
1018) -> Result {
1019    // Revert if there is an active gas snapshot as we can only have one active snapshot at a time.
1020    if ccx.state.gas_metering.active_gas_snapshot.is_some() {
1021        let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone();
1022        bail!("gas snapshot was already started with group: {group} and name: {name}");
1023    }
1024
1025    let (group, name) = derive_snapshot_name(ccx, group, name);
1026
1027    ccx.state.gas_metering.gas_records.push(GasRecord {
1028        group: group.clone(),
1029        name: name.clone(),
1030        gas_used: 0,
1031        depth: ccx.ecx.journaled_state.depth(),
1032    });
1033
1034    ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1035
1036    ccx.state.gas_metering.start();
1037
1038    Ok(Default::default())
1039}
1040
1041fn inner_stop_gas_snapshot(
1042    ccx: &mut CheatsCtxt,
1043    group: Option<String>,
1044    name: Option<String>,
1045) -> Result {
1046    // If group and name are not provided, use the last snapshot group and name.
1047    let (group, name) = group.zip(name).unwrap_or_else(|| {
1048        let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone();
1049        (group, name)
1050    });
1051
1052    if let Some(record) = ccx
1053        .state
1054        .gas_metering
1055        .gas_records
1056        .iter_mut()
1057        .find(|record| record.group == group && record.name == name)
1058    {
1059        // Calculate the gas used since the snapshot was started.
1060        // We subtract 171 from the gas used to account for gas used by the snapshot itself.
1061        let value = record.gas_used.saturating_sub(171);
1062
1063        ccx.state
1064            .gas_snapshots
1065            .entry(group.clone())
1066            .or_default()
1067            .insert(name.clone(), value.to_string());
1068
1069        // Stop the gas metering.
1070        ccx.state.gas_metering.stop();
1071
1072        // Remove the gas record.
1073        ccx.state
1074            .gas_metering
1075            .gas_records
1076            .retain(|record| record.group != group && record.name != name);
1077
1078        // Clear last snapshot cache if we have an exact match.
1079        if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot {
1080            if snapshot_group == &group && snapshot_name == &name {
1081                ccx.state.gas_metering.active_gas_snapshot = None;
1082            }
1083        }
1084
1085        Ok(value.abi_encode())
1086    } else {
1087        bail!("no gas snapshot was started with the name: {name} in group: {group}");
1088    }
1089}
1090
1091// Derives the snapshot group and name from the provided group and name or the running contract.
1092fn derive_snapshot_name(
1093    ccx: &CheatsCtxt,
1094    group: Option<String>,
1095    name: Option<String>,
1096) -> (String, String) {
1097    let group = group.unwrap_or_else(|| {
1098        ccx.state.config.running_artifact.clone().expect("expected running contract").name
1099    });
1100    let name = name.unwrap_or_else(|| "default".to_string());
1101    (group, name)
1102}
1103
1104/// Reads the current caller information and returns the current [CallerMode], `msg.sender` and
1105/// `tx.origin`.
1106///
1107/// Depending on the current caller mode, one of the following results will be returned:
1108/// - If there is an active prank:
1109///     - caller_mode will be equal to:
1110///         - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`.
1111///         - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`.
1112///     - `msg.sender` will be equal to the address set for the prank.
1113///     - `tx.origin` will be equal to the default sender address unless an alternative one has been
1114///       set when configuring the prank.
1115///
1116/// - If there is an active broadcast:
1117///     - caller_mode will be equal to:
1118///         - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`.
1119///         - [CallerMode::RecurrentBroadcast] if the broadcast has been set with
1120///           `vm.startBroadcast(..)`.
1121///     - `msg.sender` and `tx.origin` will be equal to the address provided when setting the
1122///       broadcast.
1123///
1124/// - If no caller modification is active:
1125///     - caller_mode will be equal to [CallerMode::None],
1126///     - `msg.sender` and `tx.origin` will be equal to the default sender address.
1127fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) -> Result {
1128    let mut mode = CallerMode::None;
1129    let mut new_caller = default_sender;
1130    let mut new_origin = default_sender;
1131    if let Some(prank) = state.get_prank(call_depth) {
1132        mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1133        new_caller = &prank.new_caller;
1134        if let Some(new) = &prank.new_origin {
1135            new_origin = new;
1136        }
1137    } else if let Some(broadcast) = &state.broadcast {
1138        mode = if broadcast.single_call {
1139            CallerMode::Broadcast
1140        } else {
1141            CallerMode::RecurrentBroadcast
1142        };
1143        new_caller = &broadcast.new_origin;
1144        new_origin = &broadcast.new_origin;
1145    }
1146
1147    Ok((mode, new_caller, new_origin).abi_encode_params())
1148}
1149
1150/// Ensures the `Account` is loaded and touched.
1151pub(super) fn journaled_account<'a>(
1152    ecx: Ecx<'a, '_, '_>,
1153    addr: Address,
1154) -> Result<&'a mut Account> {
1155    ecx.journaled_state.load_account(addr)?;
1156    ecx.journaled_state.touch(addr);
1157    Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded"))
1158}
1159
1160/// Consumes recorded account accesses and returns them as an abi encoded
1161/// array of [AccountAccess]. If there are no accounts were
1162/// recorded as accessed, an abi encoded empty array is returned.
1163///
1164/// In the case where `stopAndReturnStateDiff` is called at a lower
1165/// depth than `startStateDiffRecording`, multiple `Vec<RecordedAccountAccesses>`
1166/// will be flattened, preserving the order of the accesses.
1167fn get_state_diff(state: &mut Cheatcodes) -> Result {
1168    let res = state
1169        .recorded_account_diffs_stack
1170        .replace(Default::default())
1171        .unwrap_or_default()
1172        .into_iter()
1173        .flatten()
1174        .collect::<Vec<_>>();
1175    Ok(res.abi_encode())
1176}
1177
1178/// Helper function that creates a `GenesisAccount` from a regular `Account`.
1179fn genesis_account(account: &Account) -> GenesisAccount {
1180    GenesisAccount {
1181        nonce: Some(account.info.nonce),
1182        balance: account.info.balance,
1183        code: account.info.code.as_ref().map(|o| o.original_bytes()),
1184        storage: Some(
1185            account
1186                .storage
1187                .iter()
1188                .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1189                .collect(),
1190        ),
1191        private_key: None,
1192    }
1193}
1194
1195/// Helper function to returns state diffs recorded for each changed account.
1196fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap<Address, AccountStateDiffs> {
1197    let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1198    if let Some(records) = &state.recorded_account_diffs_stack {
1199        records
1200            .iter()
1201            .flatten()
1202            .filter(|account_access| {
1203                !account_access.storageAccesses.is_empty() ||
1204                    account_access.oldBalance != account_access.newBalance
1205            })
1206            .for_each(|account_access| {
1207                // Record account balance diffs.
1208                if account_access.oldBalance != account_access.newBalance {
1209                    let account_diff =
1210                        state_diffs.entry(account_access.account).or_insert_with(|| {
1211                            AccountStateDiffs {
1212                                label: state.labels.get(&account_access.account).cloned(),
1213                                ..Default::default()
1214                            }
1215                        });
1216                    // Update balance diff. Do not overwrite the initial balance if already set.
1217                    if let Some(diff) = &mut account_diff.balance_diff {
1218                        diff.new_value = account_access.newBalance;
1219                    } else {
1220                        account_diff.balance_diff = Some(BalanceDiff {
1221                            previous_value: account_access.oldBalance,
1222                            new_value: account_access.newBalance,
1223                        });
1224                    }
1225                }
1226
1227                // Record account state diffs.
1228                for storage_access in &account_access.storageAccesses {
1229                    if storage_access.isWrite && !storage_access.reverted {
1230                        let account_diff = state_diffs
1231                            .entry(storage_access.account)
1232                            .or_insert_with(|| AccountStateDiffs {
1233                                label: state.labels.get(&storage_access.account).cloned(),
1234                                ..Default::default()
1235                            });
1236                        // Update state diff. Do not overwrite the initial value if already set.
1237                        match account_diff.state_diff.entry(storage_access.slot) {
1238                            Entry::Vacant(slot_state_diff) => {
1239                                slot_state_diff.insert(SlotStateDiff {
1240                                    previous_value: storage_access.previousValue,
1241                                    new_value: storage_access.newValue,
1242                                });
1243                            }
1244                            Entry::Occupied(mut slot_state_diff) => {
1245                                slot_state_diff.get_mut().new_value = storage_access.newValue;
1246                            }
1247                        }
1248                    }
1249                }
1250            });
1251    }
1252    state_diffs
1253}
1254
1255/// Helper function to set / unset cold storage slot of the target address.
1256fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) {
1257    if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target) {
1258        if let Some(storage_slot) = account.storage.get_mut(&slot) {
1259            storage_slot.is_cold = cold;
1260        }
1261    }
1262}