Skip to main content

foundry_cheatcodes/
evm.rs

1//! Implementations of [`Evm`](spec::Group::Evm) cheatcodes.
2
3use crate::{
4    BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result,
5    Vm::*, inspector::RecordDebugStepInfo,
6};
7use alloy_consensus::transaction::SignerRecoverable;
8use alloy_evm::FromRecoveredTx;
9use alloy_genesis::{Genesis, GenesisAccount};
10use alloy_network::eip2718::EIP4844_TX_TYPE_ID;
11use alloy_primitives::{
12    Address, B256, U256, hex, keccak256,
13    map::{B256Map, HashMap},
14};
15use alloy_rlp::Decodable;
16use alloy_sol_types::SolValue;
17use foundry_common::{
18    TransactionMaybeSigned,
19    fs::{read_json_file, write_json_file},
20    slot_identifier::{
21        ENCODING_BYTES, ENCODING_DYN_ARRAY, ENCODING_INPLACE, ENCODING_MAPPING, SlotIdentifier,
22        SlotInfo,
23    },
24};
25use foundry_compilers::artifacts::EvmVersion;
26use foundry_evm_core::{
27    FoundryBlock, FoundryTransaction,
28    backend::{DatabaseError, DatabaseExt, RevertStateSnapshotAction},
29    constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
30    env::FoundryContextExt,
31    evm::{FoundryEvmNetwork, TxEnvFor, TxEnvelopeFor},
32    utils::get_blob_base_fee_update_fraction_by_spec_id,
33};
34use foundry_evm_traces::TraceMode;
35use itertools::Itertools;
36use rand::Rng;
37use revm::{
38    Database,
39    bytecode::Bytecode,
40    context::{Block, Cfg, ContextTr, Host, JournalTr, Transaction, result::ExecutionResult},
41    inspector::JournalExt,
42    primitives::{KECCAK_EMPTY, hardfork::SpecId},
43    state::{Account, AccountStatus},
44};
45use std::{
46    collections::{BTreeMap, HashSet, btree_map::Entry},
47    fmt::Display,
48    path::Path,
49    str::FromStr,
50};
51
52mod record_debug_step;
53use foundry_common::fmt::format_token_raw;
54use foundry_config::evm_spec_id;
55use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
56use serde::Serialize;
57
58mod fork;
59pub(crate) mod mapping;
60pub(crate) mod mock;
61pub(crate) mod prank;
62
63/// JSON-serializable log entry for `getRecordedLogsJson`.
64#[derive(Serialize)]
65#[serde(rename_all = "camelCase")]
66struct LogJson {
67    /// The topics of the log, including the signature, if any.
68    topics: Vec<String>,
69    /// The raw data of the log, hex-encoded with 0x prefix.
70    data: String,
71    /// The address of the log's emitter.
72    emitter: String,
73}
74
75/// Records storage slots reads and writes.
76#[derive(Clone, Debug, Default)]
77pub struct RecordAccess {
78    /// Storage slots reads.
79    pub reads: HashMap<Address, Vec<U256>>,
80    /// Storage slots writes.
81    pub writes: HashMap<Address, Vec<U256>>,
82}
83
84impl RecordAccess {
85    /// Records a read access to a storage slot.
86    pub fn record_read(&mut self, target: Address, slot: U256) {
87        self.reads.entry(target).or_default().push(slot);
88    }
89
90    /// Records a write access to a storage slot.
91    ///
92    /// This also records a read internally as `SSTORE` does an implicit `SLOAD`.
93    pub fn record_write(&mut self, target: Address, slot: U256) {
94        self.record_read(target, slot);
95        self.writes.entry(target).or_default().push(slot);
96    }
97
98    /// Clears the recorded reads and writes.
99    pub fn clear(&mut self) {
100        // Also frees memory.
101        *self = Default::default();
102    }
103}
104
105/// Records the `snapshotGas*` cheatcodes.
106#[derive(Clone, Debug)]
107pub struct GasRecord {
108    /// The group name of the gas snapshot.
109    pub group: String,
110    /// The name of the gas snapshot.
111    pub name: String,
112    /// The total gas used in the gas snapshot.
113    pub gas_used: u64,
114    /// Depth at which the gas snapshot was taken.
115    pub depth: usize,
116}
117
118/// Records `deal` cheatcodes
119#[derive(Clone, Debug)]
120pub struct DealRecord {
121    /// Target of the deal.
122    pub address: Address,
123    /// The balance of the address before deal was applied
124    pub old_balance: U256,
125    /// Balance after deal was applied
126    pub new_balance: U256,
127}
128
129/// Storage slot diff info.
130#[derive(Serialize, Default)]
131#[serde(rename_all = "camelCase")]
132struct SlotStateDiff {
133    /// Initial storage value.
134    previous_value: B256,
135    /// Current storage value.
136    new_value: B256,
137    /// Storage layout metadata (variable name, type, offset).
138    /// Only present when contract has storage layout output.
139    /// This includes decoded values when available.
140    #[serde(skip_serializing_if = "Option::is_none", flatten)]
141    slot_info: Option<SlotInfo>,
142}
143
144/// Balance diff info.
145#[derive(Serialize, Default)]
146#[serde(rename_all = "camelCase")]
147struct BalanceDiff {
148    /// Initial storage value.
149    previous_value: U256,
150    /// Current storage value.
151    new_value: U256,
152}
153
154/// Nonce diff info.
155#[derive(Serialize, Default)]
156#[serde(rename_all = "camelCase")]
157struct NonceDiff {
158    /// Initial nonce value.
159    previous_value: u64,
160    /// Current nonce value.
161    new_value: u64,
162}
163
164/// Account state diff info.
165#[derive(Serialize, Default)]
166#[serde(rename_all = "camelCase")]
167struct AccountStateDiffs {
168    /// Address label, if any set.
169    label: Option<String>,
170    /// Contract identifier from artifact. e.g "src/Counter.sol:Counter"
171    contract: Option<String>,
172    /// Account balance changes.
173    balance_diff: Option<BalanceDiff>,
174    /// Account nonce changes.
175    nonce_diff: Option<NonceDiff>,
176    /// State changes, per slot.
177    state_diff: BTreeMap<B256, SlotStateDiff>,
178}
179
180impl Display for AccountStateDiffs {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
182        // Print changed account.
183        if let Some(label) = &self.label {
184            writeln!(f, "label: {label}")?;
185        }
186        if let Some(contract) = &self.contract {
187            writeln!(f, "contract: {contract}")?;
188        }
189        // Print balance diff if changed.
190        if let Some(balance_diff) = &self.balance_diff
191            && balance_diff.previous_value != balance_diff.new_value
192        {
193            writeln!(
194                f,
195                "- balance diff: {} → {}",
196                balance_diff.previous_value, balance_diff.new_value
197            )?;
198        }
199        // Print nonce diff if changed.
200        if let Some(nonce_diff) = &self.nonce_diff
201            && nonce_diff.previous_value != nonce_diff.new_value
202        {
203            writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?;
204        }
205        // Print state diff if any.
206        if !&self.state_diff.is_empty() {
207            writeln!(f, "- state diff:")?;
208            for (slot, slot_changes) in &self.state_diff {
209                match &slot_changes.slot_info {
210                    Some(slot_info) => {
211                        if let Some(decoded) = &slot_info.decoded {
212                            // Have slot info with decoded values - show decoded values
213                            writeln!(
214                                f,
215                                "@ {slot} ({}, {}): {} → {}",
216                                slot_info.label,
217                                slot_info.slot_type.dyn_sol_type,
218                                format_token_raw(&decoded.previous_value),
219                                format_token_raw(&decoded.new_value)
220                            )?;
221                        } else {
222                            // Have slot info but no decoded values - show raw hex values
223                            writeln!(
224                                f,
225                                "@ {slot} ({}, {}): {} → {}",
226                                slot_info.label,
227                                slot_info.slot_type.dyn_sol_type,
228                                slot_changes.previous_value,
229                                slot_changes.new_value
230                            )?;
231                        }
232                    }
233                    None => {
234                        // No slot info - show raw hex values
235                        writeln!(
236                            f,
237                            "@ {slot}: {} → {}",
238                            slot_changes.previous_value, slot_changes.new_value
239                        )?;
240                    }
241                }
242            }
243        }
244
245        Ok(())
246    }
247}
248
249impl Cheatcode for addrCall {
250    fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
251        let Self { privateKey } = self;
252        let wallet = super::crypto::parse_wallet(privateKey)?;
253        Ok(wallet.address().abi_encode())
254    }
255}
256
257impl Cheatcode for getNonce_0Call {
258    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
259        let Self { account } = self;
260        get_nonce(ccx, account)
261    }
262}
263
264impl Cheatcode for getNonce_1Call {
265    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
266        let Self { wallet } = self;
267        get_nonce(ccx, &wallet.addr)
268    }
269}
270
271impl Cheatcode for loadCall {
272    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
273        let Self { target, slot } = *self;
274        ccx.ensure_not_precompile(&target)?;
275
276        ccx.ecx.journal_mut().load_account(target)?;
277        let mut val = ccx
278            .ecx
279            .journal_mut()
280            .sload(target, slot.into())
281            .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?;
282
283        if val.is_cold && val.data.is_zero() {
284            if ccx.state.has_arbitrary_storage(&target) {
285                // If storage slot is untouched and load from a target with arbitrary storage,
286                // then set random value for current slot.
287                let rand_value = ccx.state.rng().random();
288                ccx.state.arbitrary_storage.as_mut().unwrap().save(
289                    ccx.ecx,
290                    target,
291                    slot.into(),
292                    rand_value,
293                );
294                val.data = rand_value;
295            } else if ccx.state.is_arbitrary_storage_copy(&target) {
296                // If storage slot is untouched and load from a target that copies storage from
297                // a source address with arbitrary storage, then copy existing arbitrary value.
298                // If no arbitrary value generated yet, then the random one is saved and set.
299                let rand_value = ccx.state.rng().random();
300                val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
301                    ccx.ecx,
302                    target,
303                    slot.into(),
304                    rand_value,
305                );
306            }
307        }
308
309        Ok(val.abi_encode())
310    }
311}
312
313impl Cheatcode for loadAllocsCall {
314    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
315        let Self { pathToAllocsJson } = self;
316
317        let path = Path::new(pathToAllocsJson);
318        ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
319
320        // Let's first assume we're reading a file with only the allocs.
321        let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
322            Ok(allocs) => allocs,
323            Err(_) => {
324                // Let's try and read from a genesis file, and extract allocs.
325                let genesis = read_json_file::<Genesis>(path)?;
326                genesis.alloc
327            }
328        };
329
330        // Then, load the allocs into the database.
331        let (db, inner) = ccx.ecx.db_journal_inner_mut();
332        db.load_allocs(&allocs, inner)
333            .map(|()| Vec::default())
334            .map_err(|e| fmt_err!("failed to load allocs: {e}"))
335    }
336}
337
338impl Cheatcode for cloneAccountCall {
339    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
340        let Self { source, target } = self;
341
342        let account = ccx.ecx.journal_mut().load_account(*source)?;
343        let genesis = genesis_account(account.data);
344        let (db, inner) = ccx.ecx.db_journal_inner_mut();
345        db.clone_account(&genesis, target, inner)?;
346        // Cloned account should persist in forked envs.
347        ccx.ecx.db_mut().add_persistent_account(*target);
348        Ok(Default::default())
349    }
350}
351
352impl Cheatcode for dumpStateCall {
353    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
354        let Self { pathToStateJson } = self;
355        let path = Path::new(pathToStateJson);
356
357        // Do not include system account or empty accounts in the dump.
358        let skip = |key: &Address, val: &Account| {
359            key == &CHEATCODE_ADDRESS
360                || key == &CALLER
361                || key == &HARDHAT_CONSOLE_ADDRESS
362                || key == &TEST_CONTRACT_ADDRESS
363                || key == &ccx.caller
364                || key == &ccx.state.config.evm_opts.sender
365                || val.is_empty()
366        };
367
368        let alloc = ccx
369            .ecx
370            .journal_mut()
371            .evm_state_mut()
372            .iter_mut()
373            .filter(|(key, val)| !skip(key, val))
374            .map(|(key, val)| (key, genesis_account(val)))
375            .collect::<BTreeMap<_, _>>();
376
377        write_json_file(path, &alloc)?;
378        Ok(Default::default())
379    }
380}
381
382impl Cheatcode for recordCall {
383    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
384        let Self {} = self;
385        state.recording_accesses = true;
386        state.accesses.clear();
387        Ok(Default::default())
388    }
389}
390
391impl Cheatcode for stopRecordCall {
392    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
393        state.recording_accesses = false;
394        Ok(Default::default())
395    }
396}
397
398impl Cheatcode for accessesCall {
399    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
400        let Self { target } = *self;
401        let result = (
402            state.accesses.reads.entry(target).or_default().as_slice(),
403            state.accesses.writes.entry(target).or_default().as_slice(),
404        );
405        Ok(result.abi_encode_params())
406    }
407}
408
409impl Cheatcode for recordLogsCall {
410    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
411        let Self {} = self;
412        state.recorded_logs = Some(Default::default());
413        Ok(Default::default())
414    }
415}
416
417impl Cheatcode for getRecordedLogsCall {
418    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
419        let Self {} = self;
420        Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
421    }
422}
423
424impl Cheatcode for getRecordedLogsJsonCall {
425    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
426        let Self {} = self;
427        let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default();
428        let json_logs: Vec<_> = logs
429            .into_iter()
430            .map(|log| LogJson {
431                topics: log.topics.iter().map(|t| format!("{t}")).collect(),
432                data: hex::encode_prefixed(&log.data),
433                emitter: format!("{}", log.emitter),
434            })
435            .collect();
436        Ok(serde_json::to_string(&json_logs)?.abi_encode())
437    }
438}
439
440impl Cheatcode for pauseGasMeteringCall {
441    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
442        let Self {} = self;
443        state.gas_metering.paused = true;
444        Ok(Default::default())
445    }
446}
447
448impl Cheatcode for resumeGasMeteringCall {
449    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
450        let Self {} = self;
451        state.gas_metering.resume();
452        Ok(Default::default())
453    }
454}
455
456impl Cheatcode for resetGasMeteringCall {
457    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
458        let Self {} = self;
459        state.gas_metering.reset();
460        Ok(Default::default())
461    }
462}
463
464impl Cheatcode for lastCallGasCall {
465    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
466        let Self {} = self;
467        let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
468            bail!("no external call was made yet");
469        };
470        Ok(last_call_gas.abi_encode())
471    }
472}
473
474impl Cheatcode for getChainIdCall {
475    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
476        let Self {} = self;
477        Ok(U256::from(ccx.ecx.cfg().chain_id()).abi_encode())
478    }
479}
480
481impl Cheatcode for chainIdCall {
482    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
483        let Self { newChainId } = self;
484        ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64");
485        ccx.ecx.cfg_mut().chain_id = newChainId.to();
486        Ok(Default::default())
487    }
488}
489
490impl Cheatcode for coinbaseCall {
491    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
492        let Self { newCoinbase } = self;
493        ccx.ecx.block_mut().set_beneficiary(*newCoinbase);
494        Ok(Default::default())
495    }
496}
497
498impl Cheatcode for difficultyCall {
499    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
500        let Self { newDifficulty } = self;
501        ensure!(
502            (*ccx.ecx.cfg().spec()).into() < SpecId::MERGE,
503            "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
504             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
505        );
506        ccx.ecx.block_mut().set_difficulty(*newDifficulty);
507        Ok(Default::default())
508    }
509}
510
511impl Cheatcode for feeCall {
512    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
513        let Self { newBasefee } = self;
514        ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64");
515        ccx.ecx.block_mut().set_basefee(newBasefee.saturating_to());
516        Ok(Default::default())
517    }
518}
519
520impl Cheatcode for prevrandao_0Call {
521    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
522        let Self { newPrevrandao } = self;
523        ensure!(
524            (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE,
525            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
526             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
527        );
528        ccx.ecx.block_mut().set_prevrandao(Some(*newPrevrandao));
529        Ok(Default::default())
530    }
531}
532
533impl Cheatcode for prevrandao_1Call {
534    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
535        let Self { newPrevrandao } = self;
536        ensure!(
537            (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE,
538            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
539             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
540        );
541        ccx.ecx.block_mut().set_prevrandao(Some((*newPrevrandao).into()));
542        Ok(Default::default())
543    }
544}
545
546impl Cheatcode for blobhashesCall {
547    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
548        let Self { hashes } = self;
549        ensure!(
550            (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN,
551            "`blobhashes` is not supported before the Cancun hard fork; \
552             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
553        );
554        ccx.ecx.tx_mut().set_blob_hashes(hashes.clone());
555        // force this as 4844 txtype
556        ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
557        Ok(Default::default())
558    }
559}
560
561impl Cheatcode for getBlobhashesCall {
562    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
563        let Self {} = self;
564        ensure!(
565            (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN,
566            "`getBlobhashes` is not supported before the Cancun hard fork; \
567             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
568        );
569        Ok(ccx.ecx.tx().blob_versioned_hashes().to_vec().abi_encode())
570    }
571}
572
573impl Cheatcode for rollCall {
574    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
575        let Self { newHeight } = self;
576        ccx.ecx.block_mut().set_number(*newHeight);
577        Ok(Default::default())
578    }
579}
580
581impl Cheatcode for getBlockNumberCall {
582    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
583        let Self {} = self;
584        Ok(ccx.ecx.block().number().abi_encode())
585    }
586}
587
588impl Cheatcode for txGasPriceCall {
589    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
590        let Self { newGasPrice } = self;
591        ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64");
592        ccx.ecx.tx_mut().set_gas_price(newGasPrice.saturating_to());
593        Ok(Default::default())
594    }
595}
596
597impl Cheatcode for warpCall {
598    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
599        let Self { newTimestamp } = self;
600        ccx.ecx.block_mut().set_timestamp(*newTimestamp);
601        Ok(Default::default())
602    }
603}
604
605impl Cheatcode for getBlockTimestampCall {
606    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
607        let Self {} = self;
608        Ok(ccx.ecx.block().timestamp().abi_encode())
609    }
610}
611
612impl Cheatcode for blobBaseFeeCall {
613    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
614        let Self { newBlobBaseFee } = self;
615        ensure!(
616            (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN,
617            "`blobBaseFee` is not supported before the Cancun hard fork; \
618             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
619        );
620
621        let spec: SpecId = (*ccx.ecx.cfg().spec()).into();
622        ccx.ecx.block_mut().set_blob_excess_gas_and_price(
623            (*newBlobBaseFee).to(),
624            get_blob_base_fee_update_fraction_by_spec_id(spec),
625        );
626        Ok(Default::default())
627    }
628}
629
630impl Cheatcode for getBlobBaseFeeCall {
631    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
632        let Self {} = self;
633        Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode())
634    }
635}
636
637impl Cheatcode for dealCall {
638    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
639        let Self { account: address, newBalance: new_balance } = *self;
640        let account = journaled_account(ccx.ecx, address)?;
641        let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
642        let record = DealRecord { address, old_balance, new_balance };
643        ccx.state.eth_deals.push(record);
644        Ok(Default::default())
645    }
646}
647
648impl Cheatcode for etchCall {
649    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
650        let Self { target, newRuntimeBytecode } = self;
651        ccx.ensure_not_precompile(target)?;
652        ccx.ecx.journal_mut().load_account(*target)?;
653        let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone())
654            .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
655        ccx.ecx.journal_mut().set_code(*target, bytecode);
656        Ok(Default::default())
657    }
658}
659
660impl Cheatcode for resetNonceCall {
661    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
662        let Self { account } = self;
663        let account = journaled_account(ccx.ecx, *account)?;
664        // Per EIP-161, EOA nonces start at 0, but contract nonces
665        // start at 1. Comparing by code_hash instead of code
666        // to avoid hitting the case where account's code is None.
667        let empty = account.info.code_hash == KECCAK_EMPTY;
668        let nonce = if empty { 0 } else { 1 };
669        account.info.nonce = nonce;
670        debug!(target: "cheatcodes", nonce, "reset");
671        Ok(Default::default())
672    }
673}
674
675impl Cheatcode for setNonceCall {
676    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
677        let Self { account, newNonce } = *self;
678        let account = journaled_account(ccx.ecx, account)?;
679        // nonce must increment only
680        let current = account.info.nonce;
681        ensure!(
682            newNonce >= current,
683            "new nonce ({newNonce}) must be strictly equal to or higher than the \
684             account's current nonce ({current})"
685        );
686        account.info.nonce = newNonce;
687        Ok(Default::default())
688    }
689}
690
691impl Cheatcode for setNonceUnsafeCall {
692    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
693        let Self { account, newNonce } = *self;
694        let account = journaled_account(ccx.ecx, account)?;
695        account.info.nonce = newNonce;
696        Ok(Default::default())
697    }
698}
699
700impl Cheatcode for storeCall {
701    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
702        let Self { target, slot, value } = *self;
703        ccx.ensure_not_precompile(&target)?;
704        ensure_loaded_account(ccx.ecx, target)?;
705        ccx.ecx
706            .journal_mut()
707            .sstore(target, slot.into(), value.into())
708            .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?;
709        Ok(Default::default())
710    }
711}
712
713impl Cheatcode for coolCall {
714    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
715        let Self { target } = self;
716        if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) {
717            account.unmark_touch();
718            account.storage.values_mut().for_each(|slot| slot.mark_cold());
719        }
720        Ok(Default::default())
721    }
722}
723
724impl Cheatcode for accessListCall {
725    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
726        let Self { access } = self;
727        let access_list = access
728            .iter()
729            .map(|item| {
730                let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
731                alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
732            })
733            .collect_vec();
734        state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
735        Ok(Default::default())
736    }
737}
738
739impl Cheatcode for noAccessListCall {
740    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
741        let Self {} = self;
742        // Set to empty option in order to override previous applied access list.
743        if state.access_list.is_some() {
744            state.access_list = Some(alloy_rpc_types::AccessList::default());
745        }
746        Ok(Default::default())
747    }
748}
749
750impl Cheatcode for warmSlotCall {
751    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
752        let Self { target, slot } = *self;
753        set_cold_slot(ccx, target, slot.into(), false);
754        Ok(Default::default())
755    }
756}
757
758impl Cheatcode for coolSlotCall {
759    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
760        let Self { target, slot } = *self;
761        set_cold_slot(ccx, target, slot.into(), true);
762        Ok(Default::default())
763    }
764}
765
766impl Cheatcode for readCallersCall {
767    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
768        let Self {} = self;
769        read_callers(ccx.state, &ccx.ecx.tx().caller(), ccx.ecx.journal().depth())
770    }
771}
772
773impl Cheatcode for snapshotValue_0Call {
774    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
775        let Self { name, value } = self;
776        inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
777    }
778}
779
780impl Cheatcode for snapshotValue_1Call {
781    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
782        let Self { group, name, value } = self;
783        inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
784    }
785}
786
787impl Cheatcode for snapshotGasLastCall_0Call {
788    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
789        let Self { name } = self;
790        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
791            bail!("no external call was made yet");
792        };
793        inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed)
794    }
795}
796
797impl Cheatcode for snapshotGasLastCall_1Call {
798    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
799        let Self { name, group } = self;
800        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
801            bail!("no external call was made yet");
802        };
803        inner_last_gas_snapshot(
804            ccx,
805            Some(group.clone()),
806            Some(name.clone()),
807            last_call_gas.gasTotalUsed,
808        )
809    }
810}
811
812impl Cheatcode for startSnapshotGas_0Call {
813    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
814        let Self { name } = self;
815        inner_start_gas_snapshot(ccx, None, Some(name.clone()))
816    }
817}
818
819impl Cheatcode for startSnapshotGas_1Call {
820    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
821        let Self { group, name } = self;
822        inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
823    }
824}
825
826impl Cheatcode for stopSnapshotGas_0Call {
827    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
828        let Self {} = self;
829        inner_stop_gas_snapshot(ccx, None, None)
830    }
831}
832
833impl Cheatcode for stopSnapshotGas_1Call {
834    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
835        let Self { name } = self;
836        inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
837    }
838}
839
840impl Cheatcode for stopSnapshotGas_2Call {
841    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
842        let Self { group, name } = self;
843        inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
844    }
845}
846
847// Deprecated in favor of `snapshotStateCall`
848impl Cheatcode for snapshotCall {
849    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
850        let Self {} = self;
851        inner_snapshot_state(ccx)
852    }
853}
854
855impl Cheatcode for snapshotStateCall {
856    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
857        let Self {} = self;
858        inner_snapshot_state(ccx)
859    }
860}
861
862// Deprecated in favor of `revertToStateCall`
863impl Cheatcode for revertToCall {
864    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
865        let Self { snapshotId } = self;
866        inner_revert_to_state(ccx, *snapshotId)
867    }
868}
869
870impl Cheatcode for revertToStateCall {
871    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
872        let Self { snapshotId } = self;
873        inner_revert_to_state(ccx, *snapshotId)
874    }
875}
876
877// Deprecated in favor of `revertToStateAndDeleteCall`
878impl Cheatcode for revertToAndDeleteCall {
879    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
880        let Self { snapshotId } = self;
881        inner_revert_to_state_and_delete(ccx, *snapshotId)
882    }
883}
884
885impl Cheatcode for revertToStateAndDeleteCall {
886    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
887        let Self { snapshotId } = self;
888        inner_revert_to_state_and_delete(ccx, *snapshotId)
889    }
890}
891
892// Deprecated in favor of `deleteStateSnapshotCall`
893impl Cheatcode for deleteSnapshotCall {
894    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
895        let Self { snapshotId } = self;
896        let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
897        Ok(result.abi_encode())
898    }
899}
900
901impl Cheatcode for deleteStateSnapshotCall {
902    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
903        let Self { snapshotId } = self;
904        let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
905        Ok(result.abi_encode())
906    }
907}
908
909// Deprecated in favor of `deleteStateSnapshotsCall`
910impl Cheatcode for deleteSnapshotsCall {
911    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
912        let Self {} = self;
913        ccx.ecx.db_mut().delete_state_snapshots();
914        Ok(Default::default())
915    }
916}
917
918impl Cheatcode for deleteStateSnapshotsCall {
919    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
920        let Self {} = self;
921        ccx.ecx.db_mut().delete_state_snapshots();
922        Ok(Default::default())
923    }
924}
925
926impl Cheatcode for startStateDiffRecordingCall {
927    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
928        let Self {} = self;
929        state.recorded_account_diffs_stack = Some(Default::default());
930        // Enable mapping recording to track mapping slot accesses
931        state.mapping_slots.get_or_insert_default();
932        Ok(Default::default())
933    }
934}
935
936impl Cheatcode for stopAndReturnStateDiffCall {
937    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
938        let Self {} = self;
939        get_state_diff(state)
940    }
941}
942
943impl Cheatcode for getStateDiffCall {
944    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
945        let mut diffs = String::new();
946        let state_diffs = get_recorded_state_diffs(ccx);
947        for (address, state_diffs) in state_diffs {
948            diffs.push_str(&format!("{address}\n"));
949            diffs.push_str(&format!("{state_diffs}\n"));
950        }
951        Ok(diffs.abi_encode())
952    }
953}
954
955impl Cheatcode for getStateDiffJsonCall {
956    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
957        let state_diffs = get_recorded_state_diffs(ccx);
958        Ok(serde_json::to_string(&state_diffs)?.abi_encode())
959    }
960}
961
962impl Cheatcode for getStorageSlotsCall {
963    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
964        let Self { target, variableName } = self;
965
966        let storage_layout = get_contract_data(ccx, *target)
967            .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone()))
968            .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?;
969
970        trace!(storage = ?storage_layout.storage, "fetched storage");
971
972        let variable_name_lower = variableName.to_lowercase();
973        let storage = storage_layout
974            .storage
975            .iter()
976            .find(|s| s.label.to_lowercase() == variable_name_lower)
977            .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
978
979        let storage_type = storage_layout
980            .types
981            .get(&storage.storage_type)
982            .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
983
984        if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
985        {
986            return Err(fmt_err!(
987                "cannot get storage slots for variables with mapping or dynamic array types"
988            ));
989        }
990
991        let slot = U256::from_str(&storage.slot).map_err(|_| {
992            fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
993        })?;
994
995        let mut slots = Vec::new();
996
997        // Always push the base slot
998        slots.push(slot);
999
1000        if storage_type.encoding == ENCODING_INPLACE {
1001            // For inplace encoding, calculate the number of slots needed
1002            let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
1003                fmt_err!(
1004                    "invalid number_of_bytes {} for variable {variableName}",
1005                    storage_type.number_of_bytes
1006                )
1007            })?;
1008            let num_slots = num_bytes.div_ceil(U256::from(32));
1009
1010            // Start from 1 since base slot is already added
1011            for i in 1..num_slots.to::<usize>() {
1012                slots.push(slot + U256::from(i));
1013            }
1014        }
1015
1016        if storage_type.encoding == ENCODING_BYTES {
1017            // Try to check if it's a long bytes/string by reading the current storage
1018            // value
1019            if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) {
1020                let value_bytes = value.data.to_be_bytes::<32>();
1021                let length_byte = value_bytes[31];
1022                // Check if it's a long bytes/string (LSB is 1)
1023                if length_byte & 1 == 1 {
1024                    // Calculate data slots for long bytes/string
1025                    let length: U256 = value.data >> 1;
1026                    let num_data_slots = length.to::<usize>().div_ceil(32);
1027                    let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0);
1028
1029                    for i in 0..num_data_slots {
1030                        slots.push(data_start + U256::from(i));
1031                    }
1032                }
1033            }
1034        }
1035
1036        Ok(slots.abi_encode())
1037    }
1038}
1039
1040impl Cheatcode for getStorageAccessesCall {
1041    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1042        let mut storage_accesses = Vec::new();
1043
1044        if let Some(recorded_diffs) = &state.recorded_account_diffs_stack {
1045            for account_accesses in recorded_diffs.iter().flatten() {
1046                storage_accesses.extend(account_accesses.storageAccesses.clone());
1047            }
1048        }
1049
1050        Ok(storage_accesses.abi_encode())
1051    }
1052}
1053
1054impl Cheatcode for broadcastRawTransactionCall {
1055    fn apply_full<FEN: FoundryEvmNetwork>(
1056        &self,
1057        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1058        executor: &mut dyn CheatcodesExecutor<FEN>,
1059    ) -> Result {
1060        let tx = TxEnvelopeFor::<FEN>::decode(&mut self.data.as_ref())
1061            .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1062
1063        let sender =
1064            tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1065        let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1066        let from = sender;
1067
1068        executor.transact_from_tx_on_db(ccx.state, ccx.ecx, tx_env)?;
1069
1070        if ccx.state.broadcast.is_some() {
1071            ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1072                rpc: ccx.ecx.db().active_fork_url(),
1073                transaction: TransactionMaybeSigned::Signed { tx, from },
1074            });
1075        }
1076
1077        Ok(Default::default())
1078    }
1079}
1080
1081impl Cheatcode for setBlockhashCall {
1082    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1083        let Self { blockNumber, blockHash } = *self;
1084        ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1085        ensure!(
1086            blockNumber <= U256::from(ccx.ecx.block().number()),
1087            "block number must be less than or equal to the current block number"
1088        );
1089
1090        ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash);
1091
1092        Ok(Default::default())
1093    }
1094}
1095
1096impl Cheatcode for executeTransactionCall {
1097    fn apply_full<FEN: FoundryEvmNetwork>(
1098        &self,
1099        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1100        executor: &mut dyn CheatcodesExecutor<FEN>,
1101    ) -> Result {
1102        use crate::env::FORGE_CONTEXT;
1103
1104        // Block in script contexts.
1105        if let Some(ctx) = FORGE_CONTEXT.get()
1106            && *ctx == ForgeContext::ScriptGroup
1107        {
1108            return Err(fmt_err!("executeTransaction is not allowed in forge script"));
1109        }
1110
1111        // Decode the RLP-encoded signed transaction.
1112        let tx = TxEnvelopeFor::<FEN>::decode(&mut self.rawTx.as_ref())
1113            .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1114
1115        // Build TxEnv from the recovered transaction.
1116        let sender =
1117            tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1118        let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1119
1120        // Save current env for restoration after execution.
1121        let cached_evm_env = ccx.ecx.evm_clone();
1122        let cached_tx_env = ccx.ecx.tx_clone();
1123
1124        // Override env for isolated execution.
1125        ccx.ecx.block_mut().set_basefee(0);
1126        ccx.ecx.set_tx(tx_env);
1127        ccx.ecx.tx_mut().set_gas_price(0);
1128        ccx.ecx.tx_mut().set_gas_priority_fee(None);
1129
1130        // Enable nonce checks for realistic simulation.
1131        ccx.ecx.cfg_mut().disable_nonce_check = false;
1132
1133        // EIP-3860: enforce initcode size limit.
1134        ccx.ecx.cfg_mut().limit_contract_initcode_size =
1135            Some(revm::primitives::eip3860::MAX_INITCODE_SIZE);
1136
1137        // Snapshot the modified env for EVM construction.
1138        let modified_evm_env = ccx.ecx.evm_clone();
1139        let modified_tx_env = ccx.ecx.tx_clone();
1140
1141        // Mark as inner context so isolation mode doesn't trigger a nested transact_inner
1142        // when the inner EVM executes calls at depth == 1.
1143        executor.set_in_inner_context(true, Some(sender));
1144
1145        // Clone journaled state and mark all accounts/slots cold.
1146        let cold_state = {
1147            let (_, journal) = ccx.ecx.db_journal_inner_mut();
1148            let mut state = journal.state.clone();
1149            for (addr, acc_mut) in &mut state {
1150                if journal.warm_addresses.is_cold(addr) {
1151                    acc_mut.mark_cold();
1152                }
1153                for slot_mut in acc_mut.storage.values_mut() {
1154                    slot_mut.is_cold = true;
1155                    slot_mut.original_value = slot_mut.present_value;
1156                }
1157            }
1158            state
1159        };
1160
1161        let mut res = None;
1162        let mut cold_state = Some(cold_state);
1163        let mut nested_evm_env = {
1164            let (db, _) = ccx.ecx.db_journal_inner_mut();
1165            executor.with_fresh_nested_evm(ccx.state, db, modified_evm_env, &mut |evm| {
1166                // SAFETY: closure is called exactly once by the executor.
1167                evm.journal_inner_mut().state = cold_state.take().expect("called once");
1168                // Set depth to 1 for proper trace collection.
1169                evm.journal_inner_mut().depth = 1;
1170                res = Some(evm.transact_raw(modified_tx_env.clone()));
1171                Ok(())
1172            })?
1173        };
1174        let res = res.unwrap();
1175
1176        // Restore env, preserving cheatcode cfg/block changes from the nested EVM
1177        // but restoring the original tx and basefee (which we zeroed for the nested call)
1178        // as well as cfg overrides that were applied only for the nested execution.
1179        nested_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1180        nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check;
1181        nested_evm_env.cfg_env.limit_contract_initcode_size =
1182            cached_evm_env.cfg_env.limit_contract_initcode_size;
1183        ccx.ecx.set_evm(nested_evm_env);
1184        ccx.ecx.set_tx(cached_tx_env);
1185
1186        // Reset inner context flag.
1187        executor.set_in_inner_context(false, None);
1188
1189        let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?;
1190
1191        // Merge state changes back into the parent journaled state.
1192        for (addr, mut acc) in res.state {
1193            let Some(acc_mut) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&addr) else {
1194                ccx.ecx.journal_mut().evm_state_mut().insert(addr, acc);
1195                continue;
1196            };
1197
1198            // Preserve warm account status from parent context.
1199            if acc.status.contains(AccountStatus::Cold)
1200                && !acc_mut.status.contains(AccountStatus::Cold)
1201            {
1202                acc.status -= AccountStatus::Cold;
1203            }
1204            acc_mut.info = acc.info;
1205            acc_mut.status |= acc.status;
1206
1207            // Merge storage changes.
1208            for (key, val) in acc.storage {
1209                let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
1210                    acc_mut.storage.insert(key, val);
1211                    continue;
1212                };
1213                slot_mut.present_value = val.present_value;
1214                slot_mut.is_cold &= val.is_cold;
1215            }
1216        }
1217
1218        // Return output bytes.
1219        let output = match res.result {
1220            ExecutionResult::Success { output, .. } => output.into_data(),
1221            ExecutionResult::Halt { reason, .. } => {
1222                return Err(fmt_err!("transaction halted: {reason:?}"));
1223            }
1224            ExecutionResult::Revert { output, .. } => {
1225                return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output)));
1226            }
1227        };
1228
1229        Ok(output.abi_encode())
1230    }
1231}
1232
1233impl Cheatcode for startDebugTraceRecordingCall {
1234    fn apply_full<FEN: FoundryEvmNetwork>(
1235        &self,
1236        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1237        executor: &mut dyn CheatcodesExecutor<FEN>,
1238    ) -> Result {
1239        let Some(tracer) = executor.tracing_inspector() else {
1240            return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1241        };
1242
1243        let mut info = RecordDebugStepInfo {
1244            // will be updated later
1245            start_node_idx: 0,
1246            // keep the original config to revert back later
1247            original_tracer_config: *tracer.config(),
1248        };
1249
1250        // turn on tracer debug configuration for recording
1251        *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None");
1252
1253        // track where the recording starts
1254        if let Some(last_node) = tracer.traces().nodes().last() {
1255            info.start_node_idx = last_node.idx;
1256        }
1257
1258        ccx.state.record_debug_steps_info = Some(info);
1259        Ok(Default::default())
1260    }
1261}
1262
1263impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1264    fn apply_full<FEN: FoundryEvmNetwork>(
1265        &self,
1266        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1267        executor: &mut dyn CheatcodesExecutor<FEN>,
1268    ) -> Result {
1269        let Some(tracer) = executor.tracing_inspector() else {
1270            return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1271        };
1272
1273        let Some(record_info) = ccx.state.record_debug_steps_info else {
1274            return Err(Error::from("nothing recorded"));
1275        };
1276
1277        // Use the trace nodes to flatten the call trace
1278        let root = tracer.traces();
1279        let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1280
1281        let debug_steps: Vec<DebugStep> =
1282            steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1283        // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage.
1284        if !record_info.original_tracer_config.record_steps {
1285            tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1286                node.trace.steps = Vec::new();
1287                node.logs = Vec::new();
1288                node.ordering = Vec::new();
1289            });
1290        }
1291
1292        // Revert the tracer config to the one before recording
1293        tracer.update_config(|_config| record_info.original_tracer_config);
1294
1295        // Clean up the recording info
1296        ccx.state.record_debug_steps_info = None;
1297
1298        Ok(debug_steps.abi_encode())
1299    }
1300}
1301
1302impl Cheatcode for setEvmVersionCall {
1303    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1304        let Self { evm } = self;
1305        let spec_id = evm_spec_id(
1306            EvmVersion::from_str(evm)
1307                .map_err(|_| Error::from(format!("invalid evm version {evm}")))?,
1308        );
1309        ccx.state.execution_evm_version = Some(spec_id);
1310        Ok(Default::default())
1311    }
1312}
1313
1314impl Cheatcode for getEvmVersionCall {
1315    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1316        let spec = (*ccx.ecx.cfg().spec()).into();
1317        Ok(spec.to_string().to_lowercase().abi_encode())
1318    }
1319}
1320
1321pub(super) fn get_nonce<FEN: FoundryEvmNetwork>(
1322    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1323    address: &Address,
1324) -> Result {
1325    let account = ccx.ecx.journal_mut().load_account(*address)?;
1326    Ok(account.data.info.nonce.abi_encode())
1327}
1328
1329fn inner_snapshot_state<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1330    let evm_env = ccx.ecx.evm_clone();
1331    let (db, inner) = ccx.ecx.db_journal_inner_mut();
1332    let id = db.snapshot_state(inner, &evm_env);
1333    Ok(id.abi_encode())
1334}
1335
1336fn inner_revert_to_state<FEN: FoundryEvmNetwork>(
1337    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1338    snapshot_id: U256,
1339) -> Result {
1340    let mut evm_env = ccx.ecx.evm_clone();
1341    let caller = ccx.ecx.caller();
1342    let (db, inner) = ccx.ecx.db_journal_inner_mut();
1343    if let Some(restored) = db.revert_state(
1344        snapshot_id,
1345        inner,
1346        &mut evm_env,
1347        caller,
1348        RevertStateSnapshotAction::RevertKeep,
1349    ) {
1350        *inner = restored;
1351        ccx.ecx.set_evm(evm_env);
1352        Ok(true.abi_encode())
1353    } else {
1354        Ok(false.abi_encode())
1355    }
1356}
1357
1358fn inner_revert_to_state_and_delete<FEN: FoundryEvmNetwork>(
1359    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1360    snapshot_id: U256,
1361) -> Result {
1362    let mut evm_env = ccx.ecx.evm_clone();
1363    let caller = ccx.ecx.caller();
1364    let (db, inner) = ccx.ecx.db_journal_inner_mut();
1365    if let Some(restored) = db.revert_state(
1366        snapshot_id,
1367        inner,
1368        &mut evm_env,
1369        caller,
1370        RevertStateSnapshotAction::RevertRemove,
1371    ) {
1372        *inner = restored;
1373        ccx.ecx.set_evm(evm_env);
1374        Ok(true.abi_encode())
1375    } else {
1376        Ok(false.abi_encode())
1377    }
1378}
1379
1380fn inner_value_snapshot<FEN: FoundryEvmNetwork>(
1381    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1382    group: Option<String>,
1383    name: Option<String>,
1384    value: String,
1385) -> Result {
1386    let (group, name) = derive_snapshot_name(ccx, group, name);
1387
1388    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1389
1390    Ok(Default::default())
1391}
1392
1393fn inner_last_gas_snapshot<FEN: FoundryEvmNetwork>(
1394    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1395    group: Option<String>,
1396    name: Option<String>,
1397    value: u64,
1398) -> Result {
1399    let (group, name) = derive_snapshot_name(ccx, group, name);
1400
1401    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1402
1403    Ok(value.abi_encode())
1404}
1405
1406fn inner_start_gas_snapshot<FEN: FoundryEvmNetwork>(
1407    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1408    group: Option<String>,
1409    name: Option<String>,
1410) -> Result {
1411    // Revert if there is an active gas snapshot as we can only have one active snapshot at a time.
1412    if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1413        bail!("gas snapshot was already started with group: {group} and name: {name}");
1414    }
1415
1416    let (group, name) = derive_snapshot_name(ccx, group, name);
1417
1418    ccx.state.gas_metering.gas_records.push(GasRecord {
1419        group: group.clone(),
1420        name: name.clone(),
1421        gas_used: 0,
1422        depth: ccx.ecx.journal().depth(),
1423    });
1424
1425    ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1426
1427    ccx.state.gas_metering.start();
1428
1429    Ok(Default::default())
1430}
1431
1432fn inner_stop_gas_snapshot<FEN: FoundryEvmNetwork>(
1433    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1434    group: Option<String>,
1435    name: Option<String>,
1436) -> Result {
1437    // If group and name are not provided, use the last snapshot group and name.
1438    let (group, name) = group
1439        .zip(name)
1440        .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone())
1441        .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?;
1442
1443    if let Some(record) = ccx
1444        .state
1445        .gas_metering
1446        .gas_records
1447        .iter_mut()
1448        .find(|record| record.group == group && record.name == name)
1449    {
1450        // Calculate the gas used since the snapshot was started.
1451        // We subtract 171 from the gas used to account for gas used by the snapshot itself.
1452        let value = record.gas_used.saturating_sub(171);
1453
1454        ccx.state
1455            .gas_snapshots
1456            .entry(group.clone())
1457            .or_default()
1458            .insert(name.clone(), value.to_string());
1459
1460        // Stop the gas metering.
1461        ccx.state.gas_metering.stop();
1462
1463        // Remove the gas record.
1464        ccx.state
1465            .gas_metering
1466            .gas_records
1467            .retain(|record| record.group != group && record.name != name);
1468
1469        // Clear last snapshot cache if we have an exact match.
1470        if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1471            && snapshot_group == &group
1472            && snapshot_name == &name
1473        {
1474            ccx.state.gas_metering.active_gas_snapshot = None;
1475        }
1476
1477        Ok(value.abi_encode())
1478    } else {
1479        bail!("no gas snapshot was started with the name: {name} in group: {group}");
1480    }
1481}
1482
1483// Derives the snapshot group and name from the provided group and name or the running contract.
1484fn derive_snapshot_name<FEN: FoundryEvmNetwork>(
1485    ccx: &CheatsCtxt<'_, '_, FEN>,
1486    group: Option<String>,
1487    name: Option<String>,
1488) -> (String, String) {
1489    let group = group.unwrap_or_else(|| {
1490        ccx.state.config.running_artifact.clone().expect("expected running contract").name
1491    });
1492    let name = name.unwrap_or_else(|| "default".to_string());
1493    (group, name)
1494}
1495
1496/// Reads the current caller information and returns the current [CallerMode], `msg.sender` and
1497/// `tx.origin`.
1498///
1499/// Depending on the current caller mode, one of the following results will be returned:
1500/// - If there is an active prank:
1501///     - caller_mode will be equal to:
1502///         - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`.
1503///         - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`.
1504///     - `msg.sender` will be equal to the address set for the prank.
1505///     - `tx.origin` will be equal to the default sender address unless an alternative one has been
1506///       set when configuring the prank.
1507///
1508/// - If there is an active broadcast:
1509///     - caller_mode will be equal to:
1510///         - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`.
1511///         - [CallerMode::RecurrentBroadcast] if the broadcast has been set with
1512///           `vm.startBroadcast(..)`.
1513///     - `msg.sender` and `tx.origin` will be equal to the address provided when setting the
1514///       broadcast.
1515///
1516/// - If no caller modification is active:
1517///     - caller_mode will be equal to [CallerMode::None],
1518///     - `msg.sender` and `tx.origin` will be equal to the default sender address.
1519fn read_callers<FEN: FoundryEvmNetwork>(
1520    state: &Cheatcodes<FEN>,
1521    default_sender: &Address,
1522    call_depth: usize,
1523) -> Result {
1524    let mut mode = CallerMode::None;
1525    let mut new_caller = default_sender;
1526    let mut new_origin = default_sender;
1527    if let Some(prank) = state.get_prank(call_depth) {
1528        mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1529        new_caller = &prank.new_caller;
1530        if let Some(new) = &prank.new_origin {
1531            new_origin = new;
1532        }
1533    } else if let Some(broadcast) = &state.broadcast {
1534        mode = if broadcast.single_call {
1535            CallerMode::Broadcast
1536        } else {
1537            CallerMode::RecurrentBroadcast
1538        };
1539        new_caller = &broadcast.new_origin;
1540        new_origin = &broadcast.new_origin;
1541    }
1542
1543    Ok((mode, new_caller, new_origin).abi_encode_params())
1544}
1545
1546/// Ensures the `Account` is loaded and touched.
1547pub(super) fn journaled_account<
1548    CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1549>(
1550    ecx: &mut CTX,
1551    addr: Address,
1552) -> Result<&mut Account> {
1553    ensure_loaded_account(ecx, addr)?;
1554    Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded"))
1555}
1556
1557pub(super) fn ensure_loaded_account<CTX: ContextTr<Db: Database<Error = DatabaseError>>>(
1558    ecx: &mut CTX,
1559    addr: Address,
1560) -> Result<()> {
1561    ecx.journal_mut().load_account(addr)?;
1562    ecx.journal_mut().touch_account(addr);
1563    Ok(())
1564}
1565
1566/// Consumes recorded account accesses and returns them as an abi encoded
1567/// array of [AccountAccess]. If there are no accounts were
1568/// recorded as accessed, an abi encoded empty array is returned.
1569///
1570/// In the case where `stopAndReturnStateDiff` is called at a lower
1571/// depth than `startStateDiffRecording`, multiple `Vec<RecordedAccountAccesses>`
1572/// will be flattened, preserving the order of the accesses.
1573fn get_state_diff<FEN: FoundryEvmNetwork>(state: &mut Cheatcodes<FEN>) -> Result {
1574    let res = state
1575        .recorded_account_diffs_stack
1576        .replace(Default::default())
1577        .unwrap_or_default()
1578        .into_iter()
1579        .flatten()
1580        .collect::<Vec<_>>();
1581    Ok(res.abi_encode())
1582}
1583
1584/// Helper function that creates a `GenesisAccount` from a regular `Account`.
1585fn genesis_account(account: &Account) -> GenesisAccount {
1586    GenesisAccount {
1587        nonce: Some(account.info.nonce),
1588        balance: account.info.balance,
1589        code: account.info.code.as_ref().map(|o| o.original_bytes()),
1590        storage: Some(
1591            account
1592                .storage
1593                .iter()
1594                .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1595                .collect(),
1596        ),
1597        private_key: None,
1598    }
1599}
1600
1601/// Helper function to returns state diffs recorded for each changed account.
1602fn get_recorded_state_diffs<FEN: FoundryEvmNetwork>(
1603    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1604) -> BTreeMap<Address, AccountStateDiffs> {
1605    let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1606
1607    // First, collect all unique addresses we need to look up
1608    let mut addresses_to_lookup = HashSet::new();
1609    if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1610        for account_access in records.iter().flatten() {
1611            if !account_access.storageAccesses.is_empty()
1612                || account_access.oldBalance != account_access.newBalance
1613            {
1614                addresses_to_lookup.insert(account_access.account);
1615                for storage_access in &account_access.storageAccesses {
1616                    if storage_access.isWrite && !storage_access.reverted {
1617                        addresses_to_lookup.insert(storage_access.account);
1618                    }
1619                }
1620            }
1621        }
1622    }
1623
1624    // Look up contract names and storage layouts for all addresses
1625    let mut contract_names = HashMap::new();
1626    let mut storage_layouts = HashMap::new();
1627    for address in addresses_to_lookup {
1628        if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) {
1629            contract_names.insert(address, artifact_id.identifier());
1630
1631            // Also get storage layout if available
1632            if let Some(storage_layout) = &contract_data.storage_layout {
1633                storage_layouts.insert(address, storage_layout.clone());
1634            }
1635        }
1636    }
1637
1638    // Now process the records
1639    if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1640        records
1641            .iter()
1642            .flatten()
1643            .filter(|account_access| {
1644                !account_access.storageAccesses.is_empty()
1645                    || account_access.oldBalance != account_access.newBalance
1646                    || account_access.oldNonce != account_access.newNonce
1647            })
1648            .for_each(|account_access| {
1649                // Record account balance diffs.
1650                if account_access.oldBalance != account_access.newBalance {
1651                    let account_diff =
1652                        state_diffs.entry(account_access.account).or_insert_with(|| {
1653                            AccountStateDiffs {
1654                                label: ccx.state.labels.get(&account_access.account).cloned(),
1655                                contract: contract_names.get(&account_access.account).cloned(),
1656                                ..Default::default()
1657                            }
1658                        });
1659                    // Update balance diff. Do not overwrite the initial balance if already set.
1660                    if let Some(diff) = &mut account_diff.balance_diff {
1661                        diff.new_value = account_access.newBalance;
1662                    } else {
1663                        account_diff.balance_diff = Some(BalanceDiff {
1664                            previous_value: account_access.oldBalance,
1665                            new_value: account_access.newBalance,
1666                        });
1667                    }
1668                }
1669
1670                // Record account nonce diffs.
1671                if account_access.oldNonce != account_access.newNonce {
1672                    let account_diff =
1673                        state_diffs.entry(account_access.account).or_insert_with(|| {
1674                            AccountStateDiffs {
1675                                label: ccx.state.labels.get(&account_access.account).cloned(),
1676                                contract: contract_names.get(&account_access.account).cloned(),
1677                                ..Default::default()
1678                            }
1679                        });
1680                    // Update nonce diff. Do not overwrite the initial nonce if already set.
1681                    if let Some(diff) = &mut account_diff.nonce_diff {
1682                        diff.new_value = account_access.newNonce;
1683                    } else {
1684                        account_diff.nonce_diff = Some(NonceDiff {
1685                            previous_value: account_access.oldNonce,
1686                            new_value: account_access.newNonce,
1687                        });
1688                    }
1689                }
1690
1691                // Collect all storage accesses for this account
1692                let raw_changes_by_slot = account_access
1693                    .storageAccesses
1694                    .iter()
1695                    .filter_map(|access| {
1696                        (access.isWrite && !access.reverted)
1697                            .then_some((access.slot, (access.previousValue, access.newValue)))
1698                    })
1699                    .collect::<BTreeMap<_, _>>();
1700
1701                // Record account state diffs.
1702                for storage_access in &account_access.storageAccesses {
1703                    if storage_access.isWrite && !storage_access.reverted {
1704                        let account_diff = state_diffs
1705                            .entry(storage_access.account)
1706                            .or_insert_with(|| AccountStateDiffs {
1707                                label: ccx.state.labels.get(&storage_access.account).cloned(),
1708                                contract: contract_names.get(&storage_access.account).cloned(),
1709                                ..Default::default()
1710                            });
1711                        let layout = storage_layouts.get(&storage_access.account);
1712                        // Update state diff. Do not overwrite the initial value if already set.
1713                        let entry = match account_diff.state_diff.entry(storage_access.slot) {
1714                            Entry::Vacant(slot_state_diff) => {
1715                                // Get storage layout info for this slot
1716                                // Include mapping slots if available for the account
1717                                let mapping_slots = ccx
1718                                    .state
1719                                    .mapping_slots
1720                                    .as_ref()
1721                                    .and_then(|slots| slots.get(&storage_access.account));
1722
1723                                let slot_info = layout.and_then(|layout| {
1724                                    let decoder = SlotIdentifier::new(layout.clone());
1725                                    decoder.identify(&storage_access.slot, mapping_slots).or_else(
1726                                        || {
1727                                            // Create a map of new values for bytes/string
1728                                            // identification. These values are used to determine
1729                                            // the length of the data which helps determine how many
1730                                            // slots to search
1731                                            let current_base_slot_values = raw_changes_by_slot
1732                                                .iter()
1733                                                .map(|(slot, (_, new_val))| (*slot, *new_val))
1734                                                .collect::<B256Map<_>>();
1735                                            decoder.identify_bytes_or_string(
1736                                                &storage_access.slot,
1737                                                &current_base_slot_values,
1738                                            )
1739                                        },
1740                                    )
1741                                });
1742
1743                                slot_state_diff.insert(SlotStateDiff {
1744                                    previous_value: storage_access.previousValue,
1745                                    new_value: storage_access.newValue,
1746                                    slot_info,
1747                                })
1748                            }
1749                            Entry::Occupied(slot_state_diff) => {
1750                                let entry = slot_state_diff.into_mut();
1751                                entry.new_value = storage_access.newValue;
1752                                entry
1753                            }
1754                        };
1755
1756                        // Update decoded values if we have slot info
1757                        if let Some(slot_info) = &mut entry.slot_info {
1758                            slot_info.decode_values(entry.previous_value, storage_access.newValue);
1759                            if slot_info.is_bytes_or_string() {
1760                                slot_info.decode_bytes_or_string_values(
1761                                    &storage_access.slot,
1762                                    &raw_changes_by_slot,
1763                                );
1764                            }
1765                        }
1766                    }
1767                }
1768            });
1769    }
1770    state_diffs
1771}
1772
1773/// EIP-1967 implementation storage slot
1774const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
1775
1776/// EIP-1822 UUPS implementation storage slot: keccak256("PROXIABLE")
1777const EIP1822_PROXIABLE_SLOT: &str =
1778    "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
1779
1780/// Helper function to get the contract data from the deployed code at an address.
1781fn get_contract_data<'a, FEN: FoundryEvmNetwork>(
1782    ccx: &'a mut CheatsCtxt<'_, '_, FEN>,
1783    address: Address,
1784) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
1785    // Check if we have available artifacts to match against
1786    let artifacts = ccx.state.config.available_artifacts.as_ref()?;
1787
1788    // Try to load the account and get its code
1789    let account = ccx.ecx.journal_mut().load_account(address).ok()?;
1790    let code = account.data.info.code.as_ref()?;
1791
1792    // Skip if code is empty
1793    if code.is_empty() {
1794        return None;
1795    }
1796
1797    // Try to find the artifact by deployed code
1798    let code_bytes = code.original_bytes();
1799    // First check for proxy patterns
1800    let hex_str = hex::encode(&code_bytes);
1801    let find_by_suffix =
1802        |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
1803    // Simple proxy detection based on storage slot patterns
1804    if hex_str.contains(EIP1967_IMPL_SLOT)
1805        && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
1806    {
1807        return Some(result);
1808    } else if hex_str.contains(EIP1822_PROXIABLE_SLOT)
1809        && let Some(result) = find_by_suffix(":UUPSUpgradeable")
1810    {
1811        return Some(result);
1812    }
1813
1814    // Try exact match
1815    if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
1816        return Some(result);
1817    }
1818
1819    // Fallback to fuzzy matching if exact match fails
1820    artifacts.find_by_deployed_code(&code_bytes)
1821}
1822
1823/// Helper function to set / unset cold storage slot of the target address.
1824fn set_cold_slot<FEN: FoundryEvmNetwork>(
1825    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1826    target: Address,
1827    slot: U256,
1828    cold: bool,
1829) {
1830    if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target)
1831        && let Some(storage_slot) = account.storage.get_mut(&slot)
1832    {
1833        storage_slot.is_cold = cold;
1834    }
1835}