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    tempo::{TIP20_MAX_LOGO_URI_BYTES, Tip20LogoUriValidationError, validate_tip20_logo_uri},
25};
26use foundry_evm_core::{
27    FoundryBlock, FoundryTransaction,
28    backend::{DatabaseError, DatabaseExt, RevertStateSnapshotAction},
29    constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
30    eip2935::{
31        HISTORY_SERVE_WINDOW, HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE, forward_fill_start,
32        history_storage_slot, history_storage_value,
33    },
34    env::FoundryContextExt,
35    evm::{
36        FoundryEvmFactory, FoundryEvmNetwork, TxEnvFor, TxEnvelopeFor,
37        refresh_context_after_state_change,
38    },
39    utils::get_blob_base_fee_update_fraction_by_spec_id,
40};
41use foundry_evm_traces::TraceRequirements;
42use itertools::Itertools;
43use rand::Rng;
44use revm::{
45    Database,
46    bytecode::Bytecode,
47    context::{Block, Cfg, ContextTr, Host, JournalTr, Transaction, result::ExecutionResult},
48    inspector::JournalExt,
49    primitives::{KECCAK_EMPTY, hardfork::SpecId},
50    state::{Account, AccountStatus},
51};
52use std::{
53    collections::{BTreeMap, HashSet, btree_map::Entry},
54    fmt::Display,
55    path::Path,
56    str::FromStr,
57};
58
59mod record_debug_step;
60use foundry_common::fmt::format_token_raw;
61use foundry_config::{ExecutionSpec, evm_spec_id_from_str};
62use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
63use serde::{Serialize, Serializer, ser::SerializeMap};
64
65mod fork;
66pub(crate) mod mapping;
67pub(crate) mod mock;
68pub(crate) mod prank;
69
70/// JSON-serializable log entry for `getRecordedLogsJson`.
71#[derive(Serialize)]
72#[serde(rename_all = "camelCase")]
73struct LogJson {
74    /// The topics of the log, including the signature, if any.
75    topics: Vec<String>,
76    /// The raw data of the log, hex-encoded with 0x prefix.
77    data: String,
78    /// The address of the log's emitter.
79    emitter: String,
80}
81
82struct StateDump<'a>(&'a [(Address, GenesisAccount)]);
83
84impl Serialize for StateDump<'_> {
85    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
86    where
87        S: Serializer,
88    {
89        let mut map = serializer.serialize_map(Some(self.0.len()))?;
90        for (address, account) in self.0 {
91            map.serialize_entry(address, account)?;
92        }
93        map.end()
94    }
95}
96
97/// Records storage slots reads and writes.
98#[derive(Clone, Debug, Default)]
99pub struct RecordAccess {
100    /// Storage slots reads.
101    pub reads: HashMap<Address, Vec<U256>>,
102    /// Storage slots writes.
103    pub writes: HashMap<Address, Vec<U256>>,
104}
105
106impl RecordAccess {
107    /// Records a read access to a storage slot.
108    pub fn record_read(&mut self, target: Address, slot: U256) {
109        self.reads.entry(target).or_default().push(slot);
110    }
111
112    /// Records a write access to a storage slot.
113    ///
114    /// This also records a read internally as `SSTORE` does an implicit `SLOAD`.
115    pub fn record_write(&mut self, target: Address, slot: U256) {
116        self.record_read(target, slot);
117        self.writes.entry(target).or_default().push(slot);
118    }
119
120    /// Clears the recorded reads and writes.
121    pub fn clear(&mut self) {
122        // Also frees memory.
123        *self = Default::default();
124    }
125}
126
127/// Records the `snapshotGas*` cheatcodes.
128#[derive(Clone, Debug)]
129pub struct GasRecord {
130    /// The group name of the gas snapshot.
131    pub group: String,
132    /// The name of the gas snapshot.
133    pub name: String,
134    /// The total gas used in the gas snapshot.
135    pub gas_used: u64,
136    /// Depth at which the gas snapshot was taken.
137    pub depth: usize,
138}
139
140/// Records `deal` cheatcodes
141#[derive(Clone, Debug)]
142pub struct DealRecord {
143    /// Target of the deal.
144    pub address: Address,
145    /// The balance of the address before deal was applied
146    pub old_balance: U256,
147    /// Balance after deal was applied
148    pub new_balance: U256,
149}
150
151/// Storage slot diff info.
152#[derive(Serialize, Default)]
153#[serde(rename_all = "camelCase")]
154struct SlotStateDiff {
155    /// Initial storage value.
156    previous_value: B256,
157    /// Current storage value.
158    new_value: B256,
159    /// Storage layout metadata (variable name, type, offset).
160    /// Only present when contract has storage layout output.
161    /// This includes decoded values when available.
162    #[serde(skip_serializing_if = "Option::is_none", flatten)]
163    slot_info: Option<SlotInfo>,
164}
165
166/// Balance diff info.
167#[derive(Serialize, Default)]
168#[serde(rename_all = "camelCase")]
169struct BalanceDiff {
170    /// Initial storage value.
171    previous_value: U256,
172    /// Current storage value.
173    new_value: U256,
174}
175
176/// Nonce diff info.
177#[derive(Serialize, Default)]
178#[serde(rename_all = "camelCase")]
179struct NonceDiff {
180    /// Initial nonce value.
181    previous_value: u64,
182    /// Current nonce value.
183    new_value: u64,
184}
185
186/// Account state diff info.
187#[derive(Serialize, Default)]
188#[serde(rename_all = "camelCase")]
189struct AccountStateDiffs {
190    /// Address label, if any set.
191    label: Option<String>,
192    /// Contract identifier from artifact. e.g "src/Counter.sol:Counter"
193    contract: Option<String>,
194    /// Account balance changes.
195    balance_diff: Option<BalanceDiff>,
196    /// Account nonce changes.
197    nonce_diff: Option<NonceDiff>,
198    /// State changes, per slot.
199    state_diff: BTreeMap<B256, SlotStateDiff>,
200}
201
202impl Display for AccountStateDiffs {
203    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
204        // Print changed account.
205        if let Some(label) = &self.label {
206            writeln!(f, "label: {label}")?;
207        }
208        if let Some(contract) = &self.contract {
209            writeln!(f, "contract: {contract}")?;
210        }
211        // Print balance diff if changed.
212        if let Some(balance_diff) = &self.balance_diff
213            && balance_diff.previous_value != balance_diff.new_value
214        {
215            writeln!(
216                f,
217                "- balance diff: {} → {}",
218                balance_diff.previous_value, balance_diff.new_value
219            )?;
220        }
221        // Print nonce diff if changed.
222        if let Some(nonce_diff) = &self.nonce_diff
223            && nonce_diff.previous_value != nonce_diff.new_value
224        {
225            writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?;
226        }
227        // Print state diff if any.
228        if !&self.state_diff.is_empty() {
229            writeln!(f, "- state diff:")?;
230            for (slot, slot_changes) in &self.state_diff {
231                match &slot_changes.slot_info {
232                    Some(slot_info) => {
233                        if let Some(decoded) = &slot_info.decoded {
234                            // Have slot info with decoded values - show decoded values
235                            writeln!(
236                                f,
237                                "@ {slot} ({}, {}): {} → {}",
238                                slot_info.label,
239                                slot_info.slot_type.dyn_sol_type,
240                                format_token_raw(&decoded.previous_value),
241                                format_token_raw(&decoded.new_value)
242                            )?;
243                        } else {
244                            // Have slot info but no decoded values - show raw hex values
245                            writeln!(
246                                f,
247                                "@ {slot} ({}, {}): {} → {}",
248                                slot_info.label,
249                                slot_info.slot_type.dyn_sol_type,
250                                slot_changes.previous_value,
251                                slot_changes.new_value
252                            )?;
253                        }
254                    }
255                    None => {
256                        // No slot info - show raw hex values
257                        writeln!(
258                            f,
259                            "@ {slot}: {} → {}",
260                            slot_changes.previous_value, slot_changes.new_value
261                        )?;
262                    }
263                }
264            }
265        }
266
267        Ok(())
268    }
269}
270
271impl Cheatcode for addrCall {
272    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
273        let Self { privateKey } = self;
274        super::crypto::with_private_key_signer(state, privateKey, |wallet| {
275            Ok(wallet.address().abi_encode())
276        })
277    }
278}
279
280impl Cheatcode for getNonce_0Call {
281    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
282        let Self { account } = self;
283        get_nonce(ccx, account)
284    }
285}
286
287impl Cheatcode for getNonce_1Call {
288    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
289        let Self { wallet } = self;
290        get_nonce(ccx, &wallet.addr)
291    }
292}
293
294impl Cheatcode for loadCall {
295    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
296        let Self { target, slot } = *self;
297
298        ccx.ecx.journal_mut().load_account(target)?;
299        let mut val = ccx
300            .ecx
301            .journal_mut()
302            .sload(target, slot.into())
303            .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?;
304
305        if val.is_cold
306            && val.data.is_zero()
307            && !ccx.state.is_arbitrary_storage_slot_explicit(target, slot.into())
308        {
309            if ccx.state.has_arbitrary_storage(&target) {
310                // If storage slot is untouched and load from a target with arbitrary storage,
311                // then set random value for current slot.
312                let rand_value = ccx
313                    .state
314                    .cached_arbitrary_storage_value(target, slot.into())
315                    .unwrap_or_else(|| ccx.state.rng().random());
316                ccx.state.arbitrary_storage.as_mut().unwrap().save(
317                    ccx.ecx,
318                    target,
319                    slot.into(),
320                    rand_value,
321                );
322                val.data = rand_value;
323            } else if ccx.state.is_arbitrary_storage_copy(&target) {
324                // If storage slot is untouched and load from a target that copies storage from
325                // a source address with arbitrary storage, then copy existing arbitrary value.
326                // If no arbitrary value generated yet, then the random one is saved and set.
327                let rand_value = ccx.state.rng().random();
328                val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
329                    ccx.ecx,
330                    target,
331                    slot.into(),
332                    rand_value,
333                );
334            }
335        }
336
337        Ok(val.abi_encode())
338    }
339}
340
341impl Cheatcode for loadAllocsCall {
342    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
343        let Self { pathToAllocsJson } = self;
344
345        let path = Path::new(pathToAllocsJson);
346        ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
347
348        // Let's first assume we're reading a file with only the allocs.
349        let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
350            Ok(allocs) => allocs,
351            Err(_) => {
352                // Let's try and read from a genesis file, and extract allocs.
353                let genesis = read_json_file::<Genesis>(path)?;
354                genesis.alloc
355            }
356        };
357
358        // Then, load the allocs into the database.
359        let (db, inner) = ccx.ecx.db_journal_inner_mut();
360        db.load_allocs(&allocs, inner).map_err(|e| fmt_err!("failed to load allocs: {e}"))?;
361        refresh_context_after_state_change::<FEN>(ccx.ecx);
362        Ok(Default::default())
363    }
364}
365
366impl Cheatcode for cloneAccountCall {
367    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
368        let Self { source, target } = self;
369
370        let account = ccx.ecx.journal_mut().load_account(*source)?;
371        let genesis = genesis_account(account.data);
372        let (db, inner) = ccx.ecx.db_journal_inner_mut();
373        db.clone_account(&genesis, target, inner)?;
374        // Cloned account should persist in forked envs.
375        ccx.ecx.db_mut().add_persistent_account(*target);
376        refresh_context_after_state_change::<FEN>(ccx.ecx);
377        Ok(Default::default())
378    }
379}
380
381impl Cheatcode for dumpStateCall {
382    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
383        let Self { pathToStateJson } = self;
384        let path = Path::new(pathToStateJson);
385
386        let fork_id = ccx.ecx.db().active_fork_id();
387        let created_accounts = ccx
388            .state
389            .created_accounts(fork_id)
390            .into_iter()
391            .filter(|address| {
392                ccx.ecx.journal().evm_state().get(address).is_some_and(Account::is_created)
393            })
394            .collect::<Vec<_>>();
395
396        // Do not include system account or empty accounts in the dump.
397        let skip = |key: &Address, val: &Account| {
398            key == &CHEATCODE_ADDRESS
399                || key == &CALLER
400                || key == &HARDHAT_CONSOLE_ADDRESS
401                || key == &TEST_CONTRACT_ADDRESS
402                || key == &ccx.caller
403                || key == &ccx.state.config.evm_opts.sender
404                || val.is_empty()
405        };
406
407        let mut alloc = ccx
408            .ecx
409            .journal_mut()
410            .evm_state_mut()
411            .iter_mut()
412            .filter(|(key, val)| !skip(key, val))
413            .map(|(key, val)| (*key, genesis_account(val)))
414            .collect::<BTreeMap<_, _>>();
415
416        let mut ordered_alloc = Vec::with_capacity(alloc.len());
417        for address in created_accounts {
418            if let Some(account) = alloc.remove(&address) {
419                ordered_alloc.push((address, account));
420            }
421        }
422        ordered_alloc.extend(alloc);
423
424        write_json_file(path, &StateDump(&ordered_alloc))?;
425        Ok(Default::default())
426    }
427}
428
429impl Cheatcode for recordCall {
430    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
431        let Self {} = self;
432        state.recording_accesses = true;
433        state.accesses.clear();
434        Ok(Default::default())
435    }
436}
437
438impl Cheatcode for stopRecordCall {
439    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
440        state.recording_accesses = false;
441        Ok(Default::default())
442    }
443}
444
445impl Cheatcode for accessesCall {
446    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
447        let Self { target } = *self;
448        let result = (
449            state.accesses.reads.entry(target).or_default().as_slice(),
450            state.accesses.writes.entry(target).or_default().as_slice(),
451        );
452        Ok(result.abi_encode_params())
453    }
454}
455
456impl Cheatcode for registerSloadHookCall {
457    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
458        let Self { target, callback } = *self;
459        ccx.state.register_storage_load_hook(target, ccx.caller, callback.0);
460        Ok(Default::default())
461    }
462}
463
464impl Cheatcode for registerSstoreHookCall {
465    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
466        let Self { target, callback } = *self;
467        if ccx.state.has_mapping_storage_store_hooks(target) {
468            bail!("cannot register raw SSTORE hook: mapping SSTORE hooks already exist for target");
469        }
470        ccx.state.register_storage_store_hook(target, ccx.caller, callback.0);
471        Ok(Default::default())
472    }
473}
474
475impl Cheatcode for registerMappingSstoreHookCall {
476    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
477        let Self { target, rootSlot, callback } = *self;
478        if !ccx.state.register_mapping_storage_store_hook(target, rootSlot, ccx.caller, callback.0)
479        {
480            bail!("cannot register mapping SSTORE hook: raw SSTORE hook already exists for target");
481        }
482        Ok(Default::default())
483    }
484}
485
486impl Cheatcode for recordLogsCall {
487    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
488        let Self {} = self;
489        state.recorded_logs = Some(Default::default());
490        Ok(Default::default())
491    }
492}
493
494impl Cheatcode for getRecordedLogsCall {
495    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
496        let Self {} = self;
497        Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
498    }
499}
500
501impl Cheatcode for getRecordedLogsJsonCall {
502    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
503        let Self {} = self;
504        let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default();
505        let json_logs: Vec<_> = logs
506            .into_iter()
507            .map(|log| LogJson {
508                topics: log.topics.iter().map(|t| format!("{t}")).collect(),
509                data: hex::encode_prefixed(&log.data),
510                emitter: format!("{}", log.emitter),
511            })
512            .collect();
513        Ok(serde_json::to_string(&json_logs)?.abi_encode())
514    }
515}
516
517impl Cheatcode for pauseGasMeteringCall {
518    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
519        let Self {} = self;
520        state.gas_metering.paused = true;
521        Ok(Default::default())
522    }
523}
524
525impl Cheatcode for resumeGasMeteringCall {
526    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
527        let Self {} = self;
528        state.gas_metering.resume();
529        Ok(Default::default())
530    }
531}
532
533impl Cheatcode for resetGasMeteringCall {
534    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
535        let Self {} = self;
536        state.gas_metering.reset();
537        Ok(Default::default())
538    }
539}
540
541impl Cheatcode for lastCallGasCall {
542    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
543        let Self {} = self;
544        let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
545            bail!("no external call was made yet");
546        };
547        Ok(last_call_gas.abi_encode())
548    }
549}
550
551impl Cheatcode for lastFrameGasCall {
552    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
553        let Self {} = self;
554        let Some(last_frame_gas) = &state.gas_metering.last_frame_gas else {
555            bail!("no external call or create was made yet");
556        };
557        Ok(last_frame_gas.abi_encode())
558    }
559}
560
561impl Cheatcode for getChainIdCall {
562    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
563        let Self {} = self;
564        Ok(U256::from(ccx.ecx.cfg().chain_id()).abi_encode())
565    }
566}
567
568impl Cheatcode for chainIdCall {
569    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
570        let Self { newChainId } = self;
571        ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64");
572        ccx.ecx.cfg_env_mut().chain_id = newChainId.to();
573        Ok(Default::default())
574    }
575}
576
577impl Cheatcode for coinbaseCall {
578    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
579        let Self { newCoinbase } = self;
580        ccx.ecx.block_mut().set_beneficiary(*newCoinbase);
581        Ok(Default::default())
582    }
583}
584
585impl Cheatcode for difficultyCall {
586    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
587        let Self { newDifficulty } = self;
588        ensure!(
589            ccx.ecx.cfg().spec().into() < SpecId::MERGE,
590            "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
591             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
592        );
593        ccx.ecx.block_mut().set_difficulty(*newDifficulty);
594        Ok(Default::default())
595    }
596}
597
598impl Cheatcode for feeCall {
599    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
600        let Self { newBasefee } = self;
601        ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64");
602        let basefee: u64 = newBasefee.saturating_to();
603        // Always record the override so `BASEFEE` reads the cheatcode value
604        // even inside the synthetic isolation transaction (which zeroes
605        // `block.basefee` for fee-accounting). See `EnvOverrides`.
606        let fork_id = ccx.ecx.db().active_fork_id();
607        ccx.state.env_overrides_for_mut(fork_id).basefee = Some(basefee);
608        // Outside isolation, also mutate the real env to preserve the
609        // historical behavior other code paths rely on.
610        if !ccx.state.in_isolation_context {
611            ccx.ecx.block_mut().set_basefee(basefee);
612        }
613        Ok(Default::default())
614    }
615}
616
617impl Cheatcode for prevrandao_0Call {
618    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
619        let Self { newPrevrandao } = self;
620        ensure!(
621            ccx.ecx.cfg().spec().into() >= SpecId::MERGE,
622            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
623             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
624        );
625        ccx.ecx.block_mut().set_prevrandao(Some(*newPrevrandao));
626        Ok(Default::default())
627    }
628}
629
630impl Cheatcode for prevrandao_1Call {
631    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
632        let Self { newPrevrandao } = self;
633        ensure!(
634            ccx.ecx.cfg().spec().into() >= SpecId::MERGE,
635            "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
636             see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
637        );
638        ccx.ecx.block_mut().set_prevrandao(Some((*newPrevrandao).into()));
639        Ok(Default::default())
640    }
641}
642
643impl Cheatcode for blobhashesCall {
644    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
645        let Self { hashes } = self;
646        ensure!(
647            ccx.ecx.cfg().spec().into() >= SpecId::CANCUN,
648            "`blobhashes` is not supported before the Cancun hard fork; \
649             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
650        );
651        // Always record the override so `BLOBHASH` returns the cheatcode
652        // value even inside the synthetic isolation transaction (which does
653        // not propagate `tx.blob_hashes`). See `EnvOverrides`.
654        let fork_id = ccx.ecx.db().active_fork_id();
655        ccx.state.env_overrides_for_mut(fork_id).blob_hashes = Some(hashes.clone());
656        // Outside isolation, also mutate the real env to preserve the
657        // historical behavior other code paths rely on.
658        if !ccx.state.in_isolation_context {
659            ccx.ecx.tx_mut().set_blob_hashes(hashes.clone());
660            // force this as 4844 txtype
661            ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
662        }
663        Ok(Default::default())
664    }
665}
666
667impl Cheatcode for getBlobhashesCall {
668    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
669        let Self {} = self;
670        ensure!(
671            ccx.ecx.cfg().spec().into() >= SpecId::CANCUN,
672            "`getBlobhashes` is not supported before the Cancun hard fork; \
673             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
674        );
675        let fork_id = ccx.ecx.db().active_fork_id();
676        let hashes = ccx
677            .state
678            .env_overrides
679            .get(&fork_id)
680            .and_then(|o| o.blob_hashes.as_deref())
681            .unwrap_or_else(|| ccx.ecx.tx().blob_versioned_hashes());
682        Ok(hashes.to_vec().abi_encode())
683    }
684}
685
686impl Cheatcode for rollCall {
687    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
688        let Self { newHeight } = self;
689        let current_height = ccx.ecx.block().number();
690        if ccx.ecx.cfg().spec().into() >= SpecId::PRAGUE && *newHeight > current_height {
691            let mut block_number = forward_fill_start(current_height, *newHeight);
692            while block_number < *newHeight {
693                let block_hash =
694                    ccx.ecx.db_mut().block_hash(block_number.saturating_to()).unwrap_or_default();
695                set_eip2935_blockhash(ccx.ecx, block_number, block_hash)?;
696                block_number += U256::from(1);
697            }
698        }
699        ccx.ecx.block_mut().set_number(*newHeight);
700        Ok(Default::default())
701    }
702}
703
704impl Cheatcode for getBlockNumberCall {
705    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
706        let Self {} = self;
707        Ok(ccx.ecx.block().number().abi_encode())
708    }
709}
710
711impl Cheatcode for txGasPriceCall {
712    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
713        let Self { newGasPrice } = self;
714        ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64");
715        let gas_price: u128 = newGasPrice.saturating_to();
716        // Always record the override so `GASPRICE` reads the cheatcode value
717        // even inside the synthetic isolation transaction (which zeroes
718        // `tx.gas_price` for fee-accounting). See `EnvOverrides`.
719        let fork_id = ccx.ecx.db().active_fork_id();
720        ccx.state.env_overrides_for_mut(fork_id).gas_price = Some(gas_price);
721        // Outside isolation, also mutate the real env to preserve the
722        // historical behavior other code paths rely on. Inside isolation we
723        // intentionally leave `tx.gas_price` at 0 so that the pranked caller
724        // does not need to pre-fund `gas * gasPrice` (see #7277).
725        if !ccx.state.in_isolation_context {
726            ccx.ecx.tx_mut().set_gas_price(gas_price);
727        }
728        Ok(Default::default())
729    }
730}
731
732impl Cheatcode for warpCall {
733    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
734        let Self { newTimestamp } = self;
735        ccx.ecx.block_mut().set_timestamp(*newTimestamp);
736        Ok(Default::default())
737    }
738}
739
740impl Cheatcode for getBlockTimestampCall {
741    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
742        let Self {} = self;
743        Ok(ccx.ecx.block().timestamp().abi_encode())
744    }
745}
746
747impl Cheatcode for blobBaseFeeCall {
748    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
749        let Self { newBlobBaseFee } = self;
750        ensure!(
751            ccx.ecx.cfg().spec().into() >= SpecId::CANCUN,
752            "`blobBaseFee` is not supported before the Cancun hard fork; \
753             see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
754        );
755
756        let spec: SpecId = ccx.ecx.cfg().spec().into();
757        ccx.ecx.block_mut().set_blob_excess_gas_and_price(
758            (*newBlobBaseFee).to(),
759            get_blob_base_fee_update_fraction_by_spec_id(spec),
760        );
761        Ok(Default::default())
762    }
763}
764
765impl Cheatcode for getBlobBaseFeeCall {
766    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
767        let Self {} = self;
768        Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode())
769    }
770}
771
772impl Cheatcode for dealCall {
773    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
774        let Self { account: address, newBalance: new_balance } = *self;
775        let account = journaled_account(ccx.ecx, address)?;
776        let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
777        let record = DealRecord { address, old_balance, new_balance };
778        ccx.state.eth_deals.push(record);
779        refresh_context_after_state_change::<FEN>(ccx.ecx);
780        Ok(Default::default())
781    }
782}
783
784impl Cheatcode for etchCall {
785    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
786        let Self { target, newRuntimeBytecode } = self;
787        ccx.ensure_not_precompile(target)?;
788        ccx.ecx.journal_mut().load_account(*target)?;
789        let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone())
790            .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
791        if *target == HISTORY_STORAGE_ADDRESS
792            && bytecode.hash_slow() != keccak256(&HISTORY_STORAGE_CODE)
793        {
794            let account =
795                ccx.ecx.journal_mut().evm_state_mut().get_mut(target).expect("account is loaded");
796            if account.info.code_hash == keccak256(&HISTORY_STORAGE_CODE) {
797                account.storage.clear();
798                account.mark_created();
799            }
800        }
801        ccx.ecx.journal_mut().set_code(*target, bytecode);
802        Ok(Default::default())
803    }
804}
805
806impl Cheatcode for resetNonceCall {
807    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
808        let Self { account } = self;
809        let account = journaled_account(ccx.ecx, *account)?;
810        // Per EIP-161, EOA nonces start at 0, but contract nonces
811        // start at 1. Comparing by code_hash instead of code
812        // to avoid hitting the case where account's code is None.
813        let empty = account.info.code_hash == KECCAK_EMPTY;
814        let nonce = if empty { 0 } else { 1 };
815        account.info.nonce = nonce;
816        debug!(target: "cheatcodes", nonce, "reset");
817        Ok(Default::default())
818    }
819}
820
821impl Cheatcode for setNonceCall {
822    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
823        let Self { account, newNonce } = *self;
824        let account = journaled_account(ccx.ecx, account)?;
825        // nonce must increment only
826        let current = account.info.nonce;
827        ensure!(
828            newNonce >= current,
829            "new nonce ({newNonce}) must be strictly equal to or higher than the \
830             account's current nonce ({current})"
831        );
832        account.info.nonce = newNonce;
833        Ok(Default::default())
834    }
835}
836
837impl Cheatcode for setNonceUnsafeCall {
838    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
839        let Self { account, newNonce } = *self;
840        let account = journaled_account(ccx.ecx, account)?;
841        account.info.nonce = newNonce;
842        Ok(Default::default())
843    }
844}
845
846impl Cheatcode for storeCall {
847    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
848        let Self { target, slot, value } = *self;
849        ccx.ensure_not_precompile(&target)?;
850        ensure_loaded_account(ccx.ecx, target)?;
851        ccx.ecx
852            .journal_mut()
853            .sstore(target, slot.into(), value.into())
854            .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?;
855        ccx.state.mark_arbitrary_storage_slot_explicit(target, slot.into());
856        Ok(Default::default())
857    }
858}
859
860impl Cheatcode for setTip20LogoURICall {
861    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
862        let Self { token, newLogoURI } = self;
863        set_tip20_logo_uri(ccx, token, newLogoURI)
864    }
865}
866
867impl Cheatcode for setLogoURICall {
868    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
869        let Self { token, newLogoURI } = self;
870        set_tip20_logo_uri(ccx, token, newLogoURI)
871    }
872}
873
874impl Cheatcode for coolCall {
875    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
876        let Self { target } = self;
877        if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) {
878            account.unmark_touch();
879            account.storage.values_mut().for_each(|slot| slot.mark_cold());
880        }
881        Ok(Default::default())
882    }
883}
884
885impl Cheatcode for accessListCall {
886    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
887        let Self { access } = self;
888        let access_list = access
889            .iter()
890            .map(|item| {
891                let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
892                alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
893            })
894            .collect_vec();
895        state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
896        Ok(Default::default())
897    }
898}
899
900impl Cheatcode for noAccessListCall {
901    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
902        let Self {} = self;
903        // Set to empty option in order to override previous applied access list.
904        if state.access_list.is_some() {
905            state.access_list = Some(alloy_rpc_types::AccessList::default());
906        }
907        Ok(Default::default())
908    }
909}
910
911impl Cheatcode for warmSlotCall {
912    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
913        let Self { target, slot } = *self;
914        set_cold_slot(ccx, target, slot.into(), false);
915        Ok(Default::default())
916    }
917}
918
919impl Cheatcode for coolSlotCall {
920    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
921        let Self { target, slot } = *self;
922        set_cold_slot(ccx, target, slot.into(), true);
923        Ok(Default::default())
924    }
925}
926
927impl Cheatcode for isIsolateModeCall {
928    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
929        let Self {} = self;
930        Ok(state.config.isolate.abi_encode())
931    }
932}
933
934impl Cheatcode for readCallersCall {
935    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
936        let Self {} = self;
937        read_callers(ccx.state, &ccx.ecx.tx().caller(), ccx.ecx.journal().depth())
938    }
939}
940
941impl Cheatcode for snapshotValue_0Call {
942    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
943        let Self { name, value } = self;
944        inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
945    }
946}
947
948impl Cheatcode for snapshotValue_1Call {
949    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
950        let Self { group, name, value } = self;
951        inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
952    }
953}
954
955impl Cheatcode for snapshotGasLastCall_0Call {
956    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
957        let Self { name } = self;
958        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
959            bail!("no external call was made yet");
960        };
961        inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed)
962    }
963}
964
965impl Cheatcode for snapshotGasLastCall_1Call {
966    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
967        let Self { name, group } = self;
968        let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
969            bail!("no external call was made yet");
970        };
971        inner_last_gas_snapshot(
972            ccx,
973            Some(group.clone()),
974            Some(name.clone()),
975            last_call_gas.gasTotalUsed,
976        )
977    }
978}
979
980impl Cheatcode for snapshotGasLastFrame_0Call {
981    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
982        let Self { name } = self;
983        let Some(last_frame_gas) = &ccx.state.gas_metering.last_frame_gas else {
984            bail!("no external call or create was made yet");
985        };
986        inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_frame_gas.gasTotalUsed)
987    }
988}
989
990impl Cheatcode for snapshotGasLastFrame_1Call {
991    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
992        let Self { name, group } = self;
993        let Some(last_frame_gas) = &ccx.state.gas_metering.last_frame_gas else {
994            bail!("no external call or create was made yet");
995        };
996        inner_last_gas_snapshot(
997            ccx,
998            Some(group.clone()),
999            Some(name.clone()),
1000            last_frame_gas.gasTotalUsed,
1001        )
1002    }
1003}
1004
1005impl Cheatcode for startSnapshotGas_0Call {
1006    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1007        let Self { name } = self;
1008        inner_start_gas_snapshot(ccx, None, Some(name.clone()))
1009    }
1010}
1011
1012impl Cheatcode for startSnapshotGas_1Call {
1013    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1014        let Self { group, name } = self;
1015        inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
1016    }
1017}
1018
1019impl Cheatcode for stopSnapshotGas_0Call {
1020    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1021        let Self {} = self;
1022        inner_stop_gas_snapshot(ccx, None, None)
1023    }
1024}
1025
1026impl Cheatcode for stopSnapshotGas_1Call {
1027    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1028        let Self { name } = self;
1029        inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
1030    }
1031}
1032
1033impl Cheatcode for stopSnapshotGas_2Call {
1034    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1035        let Self { group, name } = self;
1036        inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
1037    }
1038}
1039
1040// Deprecated in favor of `snapshotStateCall`
1041impl Cheatcode for snapshotCall {
1042    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1043        let Self {} = self;
1044        inner_snapshot_state(ccx)
1045    }
1046}
1047
1048impl Cheatcode for snapshotStateCall {
1049    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1050        let Self {} = self;
1051        inner_snapshot_state(ccx)
1052    }
1053}
1054
1055// Deprecated in favor of `revertToStateCall`
1056impl Cheatcode for revertToCall {
1057    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1058        let Self { snapshotId } = self;
1059        inner_revert_to_state(ccx, *snapshotId)
1060    }
1061}
1062
1063impl Cheatcode for revertToStateCall {
1064    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1065        let Self { snapshotId } = self;
1066        inner_revert_to_state(ccx, *snapshotId)
1067    }
1068}
1069
1070// Deprecated in favor of `revertToStateAndDeleteCall`
1071impl Cheatcode for revertToAndDeleteCall {
1072    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1073        let Self { snapshotId } = self;
1074        inner_revert_to_state_and_delete(ccx, *snapshotId)
1075    }
1076}
1077
1078impl Cheatcode for revertToStateAndDeleteCall {
1079    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1080        let Self { snapshotId } = self;
1081        inner_revert_to_state_and_delete(ccx, *snapshotId)
1082    }
1083}
1084
1085// Deprecated in favor of `deleteStateSnapshotCall`
1086impl Cheatcode for deleteSnapshotCall {
1087    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1088        let Self { snapshotId } = self;
1089        let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
1090        ccx.state.env_overrides_snapshots.remove(snapshotId);
1091        ccx.state.fork_block_number_override_snapshots.remove(snapshotId);
1092        ccx.state.context_snapshots.remove(snapshotId);
1093        ccx.state.delete_created_accounts_snapshot(*snapshotId);
1094        Ok(result.abi_encode())
1095    }
1096}
1097
1098impl Cheatcode for deleteStateSnapshotCall {
1099    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1100        let Self { snapshotId } = self;
1101        let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
1102        ccx.state.env_overrides_snapshots.remove(snapshotId);
1103        ccx.state.fork_block_number_override_snapshots.remove(snapshotId);
1104        ccx.state.context_snapshots.remove(snapshotId);
1105        ccx.state.delete_created_accounts_snapshot(*snapshotId);
1106        Ok(result.abi_encode())
1107    }
1108}
1109
1110// Deprecated in favor of `deleteStateSnapshotsCall`
1111impl Cheatcode for deleteSnapshotsCall {
1112    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1113        let Self {} = self;
1114        ccx.ecx.db_mut().delete_state_snapshots();
1115        ccx.state.env_overrides_snapshots.clear();
1116        ccx.state.fork_block_number_override_snapshots.clear();
1117        ccx.state.context_snapshots.clear();
1118        ccx.state.clear_created_accounts_snapshots();
1119        Ok(Default::default())
1120    }
1121}
1122
1123impl Cheatcode for deleteStateSnapshotsCall {
1124    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1125        let Self {} = self;
1126        ccx.ecx.db_mut().delete_state_snapshots();
1127        ccx.state.env_overrides_snapshots.clear();
1128        ccx.state.fork_block_number_override_snapshots.clear();
1129        ccx.state.context_snapshots.clear();
1130        ccx.state.clear_created_accounts_snapshots();
1131        Ok(Default::default())
1132    }
1133}
1134
1135impl Cheatcode for startStateDiffRecordingCall {
1136    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1137        let Self {} = self;
1138        state.start_state_diff_recording();
1139        // Enable mapping recording to track mapping slot accesses
1140        state.mapping_slots.get_or_insert_default();
1141        Ok(Default::default())
1142    }
1143}
1144
1145impl Cheatcode for stopAndReturnStateDiffCall {
1146    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1147        let Self {} = self;
1148        get_state_diff(state)
1149    }
1150}
1151
1152impl Cheatcode for getStateDiffCall {
1153    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1154        let mut diffs = String::new();
1155        let state_diffs = get_recorded_state_diffs(ccx);
1156        for (address, state_diffs) in state_diffs {
1157            diffs.push_str(&format!("{address}\n"));
1158            diffs.push_str(&format!("{state_diffs}\n"));
1159        }
1160        Ok(diffs.abi_encode())
1161    }
1162}
1163
1164impl Cheatcode for getStateDiffJsonCall {
1165    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1166        let state_diffs = get_recorded_state_diffs(ccx);
1167        Ok(serde_json::to_string(&state_diffs)?.abi_encode())
1168    }
1169}
1170
1171impl Cheatcode for getStorageSlotsCall {
1172    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1173        let Self { target, variableName } = self;
1174
1175        let storage_layout = get_contract_data(ccx, *target)
1176            .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone()))
1177            .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?;
1178
1179        trace!(storage = ?storage_layout.storage, "fetched storage");
1180
1181        let variable_name_lower = variableName.to_lowercase();
1182        let storage = storage_layout
1183            .storage
1184            .iter()
1185            .find(|s| s.label.to_lowercase() == variable_name_lower)
1186            .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
1187
1188        let storage_type = storage_layout
1189            .types
1190            .get(&storage.storage_type)
1191            .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
1192
1193        if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
1194        {
1195            return Err(fmt_err!(
1196                "cannot get storage slots for variables with mapping or dynamic array types"
1197            ));
1198        }
1199
1200        let slot = U256::from_str(&storage.slot).map_err(|_| {
1201            fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
1202        })?;
1203
1204        let mut slots = Vec::new();
1205
1206        // Always push the base slot
1207        slots.push(slot);
1208
1209        if storage_type.encoding == ENCODING_INPLACE {
1210            // For inplace encoding, calculate the number of slots needed
1211            let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
1212                fmt_err!(
1213                    "invalid number_of_bytes {} for variable {variableName}",
1214                    storage_type.number_of_bytes
1215                )
1216            })?;
1217            let num_slots = num_bytes.div_ceil(U256::from(32));
1218            let num_slots = usize::try_from(num_slots).map_err(|_| {
1219                fmt_err!("number_of_bytes {} exceeds host usize", storage_type.number_of_bytes)
1220            })?;
1221
1222            // Start from 1 since base slot is already added
1223            for i in 1..num_slots {
1224                slots.push(slot + U256::from(i));
1225            }
1226        }
1227
1228        if storage_type.encoding == ENCODING_BYTES {
1229            // Try to check if it's a long bytes/string by reading the current storage
1230            // value
1231            if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) {
1232                let value_bytes = value.data.to_be_bytes::<32>();
1233                let length_byte = value_bytes[31];
1234                // Check if it's a long bytes/string (LSB is 1)
1235                if length_byte & 1 == 1 {
1236                    // Calculate data slots for long bytes/string
1237                    let length: U256 = value.data >> 1;
1238                    let length = usize::try_from(length)
1239                        .map_err(|_| fmt_err!("long bytes/string length exceeds host usize"))?;
1240                    let num_data_slots = length.div_ceil(32);
1241                    let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0);
1242
1243                    for i in 0..num_data_slots {
1244                        slots.push(data_start + U256::from(i));
1245                    }
1246                }
1247            }
1248        }
1249
1250        Ok(slots.abi_encode())
1251    }
1252}
1253
1254impl Cheatcode for getStorageAccessesCall {
1255    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1256        let mut storage_accesses = Vec::new();
1257
1258        for account_access in state.recorded_account_diffs() {
1259            storage_accesses.extend(account_access.storageAccesses.clone());
1260        }
1261
1262        Ok(storage_accesses.abi_encode())
1263    }
1264}
1265
1266impl Cheatcode for broadcastRawTransactionCall {
1267    fn apply_full<FEN: FoundryEvmNetwork>(
1268        &self,
1269        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1270        executor: &mut dyn CheatcodesExecutor<FEN>,
1271    ) -> Result {
1272        let tx = TxEnvelopeFor::<FEN>::decode(&mut self.data.as_ref())
1273            .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1274
1275        let sender =
1276            tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1277        let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1278        let from = sender;
1279
1280        executor.transact_from_tx_on_db(ccx.state, ccx.ecx, tx_env)?;
1281        refresh_context_after_state_change::<FEN>(ccx.ecx);
1282
1283        if ccx.state.broadcast.is_some() {
1284            ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1285                rpc: ccx.ecx.db().active_fork_url(),
1286                transaction: TransactionMaybeSigned::Signed { tx, from },
1287            });
1288        }
1289
1290        Ok(Default::default())
1291    }
1292}
1293
1294impl Cheatcode for setBlockhashCall {
1295    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1296        let Self { blockNumber, blockHash } = *self;
1297        ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1298        ensure!(
1299            blockNumber <= U256::from(ccx.ecx.block().number()),
1300            "block number must be less than or equal to the current block number"
1301        );
1302
1303        ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash);
1304        let current_block = U256::from(ccx.ecx.block().number());
1305        if ccx.ecx.cfg().spec().into() >= SpecId::PRAGUE
1306            && blockNumber < current_block
1307            && current_block - blockNumber <= U256::from(HISTORY_SERVE_WINDOW)
1308        {
1309            set_eip2935_blockhash(ccx.ecx, blockNumber, blockHash)?;
1310        }
1311
1312        Ok(Default::default())
1313    }
1314}
1315
1316impl Cheatcode for executeTransactionCall {
1317    fn apply_full<FEN: FoundryEvmNetwork>(
1318        &self,
1319        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1320        executor: &mut dyn CheatcodesExecutor<FEN>,
1321    ) -> Result {
1322        use crate::env::FORGE_CONTEXT;
1323
1324        // Block in script contexts.
1325        if let Some(ctx) = FORGE_CONTEXT.get()
1326            && *ctx == ForgeContext::ScriptGroup
1327        {
1328            return Err(fmt_err!("executeTransaction is not allowed in forge script"));
1329        }
1330
1331        // Decode the RLP-encoded signed transaction.
1332        let tx = TxEnvelopeFor::<FEN>::decode(&mut self.rawTx.as_ref())
1333            .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1334
1335        // Build TxEnv from the recovered transaction.
1336        let sender =
1337            tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1338        let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1339        let chain_context = ccx.ecx.db().chain_context_for_synthetic_transaction(&tx_env)?;
1340        let created_address = tx_env.kind().is_create().then(|| sender.create(tx_env.nonce()));
1341
1342        // Save current env for restoration after execution.
1343        let cached_evm_env = ccx.ecx.evm_clone();
1344        let cached_tx_env = ccx.ecx.tx_clone();
1345
1346        // Override env for isolated execution.
1347        ccx.ecx.block_mut().set_basefee(0);
1348        ccx.ecx.set_tx(tx_env);
1349        ccx.ecx.tx_mut().set_gas_price(0);
1350        ccx.ecx.tx_mut().set_gas_priority_fee(None);
1351
1352        // Enable nonce checks for realistic simulation.
1353        ccx.ecx.cfg_env_mut().disable_nonce_check = false;
1354
1355        // Enforce the active EVM's initcode size limit.
1356        ccx.ecx.cfg_env_mut().limit_contract_initcode_size =
1357            Some(FEN::EvmFactory::CONTRACT_INITCODE_SIZE_LIMIT);
1358
1359        // Reset the tx gas limit cap so revm applies the spec-defined default (EIP-7825).
1360        // Normal test execution sets `Some(u64::MAX)` to disable the cap; clearing it here
1361        // lets the nested EVM enforce the real network limit for realistic simulation.
1362        ccx.ecx.cfg_env_mut().tx_gas_limit_cap = None;
1363
1364        // Snapshot the modified env for EVM construction.
1365        let modified_evm_env = ccx.ecx.evm_clone();
1366        let modified_tx_env = ccx.ecx.tx_clone();
1367
1368        // Mark as inner context so isolation mode doesn't trigger a nested transact_inner
1369        // when the inner EVM executes calls at depth == 1.
1370        executor.set_in_inner_context(true, Some(sender));
1371        if let Some(address) = created_address {
1372            let fork_id = ccx.ecx.db().active_fork_id();
1373            ccx.state.record_created_account(fork_id, address);
1374        }
1375
1376        // Clone journaled state and mark all accounts/slots cold.
1377        let cold_state = {
1378            let (_, journal) = ccx.ecx.db_journal_inner_mut();
1379            let mut state = journal.state.clone();
1380            for (addr, acc_mut) in &mut state {
1381                if journal.warm_addresses.is_cold(addr) {
1382                    acc_mut.mark_cold();
1383                }
1384                for slot_mut in acc_mut.storage.values_mut() {
1385                    slot_mut.is_cold = true;
1386                    slot_mut.original_value = slot_mut.present_value;
1387                }
1388            }
1389            state
1390        };
1391
1392        let mut res = None;
1393        let mut cold_state = Some(cold_state);
1394        let mut nested_evm_env = {
1395            let (db, _) = ccx.ecx.db_journal_inner_mut();
1396            executor.with_fresh_nested_evm(
1397                ccx.state,
1398                db,
1399                modified_evm_env,
1400                chain_context,
1401                &mut |evm| {
1402                    // SAFETY: closure is called exactly once by the executor.
1403                    evm.journal_inner_mut().state = cold_state.take().expect("called once");
1404                    // Set depth to 1 for proper trace collection.
1405                    evm.journal_inner_mut().depth = 1;
1406                    res = Some(evm.transact_raw(modified_tx_env.clone()));
1407                    Ok(())
1408                },
1409            )?
1410        };
1411        let res = res.unwrap();
1412
1413        // Restore env, preserving cheatcode cfg/block changes from the nested EVM
1414        // but restoring the original tx and basefee (which we zeroed for the nested call)
1415        // as well as cfg overrides that were applied only for the nested execution.
1416        nested_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1417        nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check;
1418        nested_evm_env.cfg_env.limit_contract_initcode_size =
1419            cached_evm_env.cfg_env.limit_contract_initcode_size;
1420        nested_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1421        ccx.ecx.set_evm(nested_evm_env);
1422        ccx.ecx.set_tx(cached_tx_env);
1423
1424        // Reset inner context flag.
1425        executor.set_in_inner_context(false, None);
1426
1427        let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?;
1428
1429        // Merge state changes back into the parent journaled state.
1430        for (addr, mut acc) in res.state {
1431            let Some(acc_mut) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&addr) else {
1432                ccx.ecx.journal_mut().evm_state_mut().insert(addr, acc);
1433                continue;
1434            };
1435
1436            // Preserve warm account status from parent context.
1437            if acc.status.contains(AccountStatus::Cold)
1438                && !acc_mut.status.contains(AccountStatus::Cold)
1439            {
1440                acc.status -= AccountStatus::Cold;
1441            }
1442            acc_mut.info = acc.info;
1443            acc_mut.status |= acc.status;
1444
1445            // Merge storage changes.
1446            for (key, val) in acc.storage {
1447                let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
1448                    acc_mut.storage.insert(key, val);
1449                    continue;
1450                };
1451                slot_mut.present_value = val.present_value;
1452                slot_mut.is_cold &= val.is_cold;
1453            }
1454        }
1455
1456        // Keep network-specific caches aligned with the state merged from the nested EVM while
1457        // preserving the outer transaction's execution context.
1458        refresh_context_after_state_change::<FEN>(ccx.ecx);
1459
1460        // Return output bytes.
1461        let output = match res.result {
1462            ExecutionResult::Success { output, .. } => output.into_data(),
1463            ExecutionResult::Halt { reason, .. } => {
1464                return Err(fmt_err!("transaction halted: {reason:?}"));
1465            }
1466            ExecutionResult::Revert { output, .. } => {
1467                return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output)));
1468            }
1469        };
1470
1471        Ok(output.abi_encode())
1472    }
1473}
1474
1475impl Cheatcode for startDebugTraceRecordingCall {
1476    fn apply_full<FEN: FoundryEvmNetwork>(
1477        &self,
1478        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1479        executor: &mut dyn CheatcodesExecutor<FEN>,
1480    ) -> Result {
1481        let Some(tracer) = executor.tracing_inspector() else {
1482            return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1483        };
1484
1485        if ccx.state.record_debug_steps_info.is_some() {
1486            bail!("debug trace recording was already started");
1487        }
1488
1489        let mut info = RecordDebugStepInfo {
1490            // will be updated later
1491            start_node_idx: 0,
1492            // keep the original config to revert back later
1493            original_tracer_config: *tracer.config(),
1494        };
1495
1496        // turn on tracer debug configuration for recording
1497        *tracer.config_mut() =
1498            TraceRequirements::none().with_debug(true).into_config().expect("cannot be None");
1499
1500        // track where the recording starts
1501        if let Some(last_node) = tracer.traces().nodes().last() {
1502            info.start_node_idx = last_node.idx;
1503        }
1504
1505        ccx.state.record_debug_steps_info = Some(info);
1506        Ok(Default::default())
1507    }
1508}
1509
1510impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1511    fn apply_full<FEN: FoundryEvmNetwork>(
1512        &self,
1513        ccx: &mut CheatsCtxt<'_, '_, FEN>,
1514        executor: &mut dyn CheatcodesExecutor<FEN>,
1515    ) -> Result {
1516        let Some(tracer) = executor.tracing_inspector() else {
1517            return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1518        };
1519
1520        let Some(record_info) = ccx.state.record_debug_steps_info else {
1521            return Err(Error::from("nothing recorded"));
1522        };
1523
1524        // Use the trace nodes to flatten the call trace
1525        let root = tracer.traces();
1526        let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1527
1528        let debug_steps: Vec<DebugStep> =
1529            steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1530        // Free up memory by clearing the steps if they are not recorded outside of cheatcode usage.
1531        if !record_info.original_tracer_config.record_steps {
1532            tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1533                node.trace.steps = Vec::new();
1534                node.logs = Vec::new();
1535                node.ordering = Vec::new();
1536            });
1537        }
1538
1539        // Revert the tracer config to the one before recording
1540        tracer.update_config(|_config| record_info.original_tracer_config);
1541
1542        // Clean up the recording info
1543        ccx.state.record_debug_steps_info = None;
1544
1545        Ok(debug_steps.abi_encode())
1546    }
1547}
1548
1549impl Cheatcode for setEvmVersionCall {
1550    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1551        let Self { evm } = self;
1552        let spec_id = evm_spec_id_from_str(evm)
1553            .ok_or_else(|| Error::from(format!("invalid evm version {evm}")))?;
1554        ccx.state.execution_evm_version = Some(spec_id);
1555        Ok(Default::default())
1556    }
1557}
1558
1559impl Cheatcode for getEvmVersionCall {
1560    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1561        let spec = ccx.ecx.cfg().spec();
1562        Ok(spec.evm_version_name().to_lowercase().abi_encode())
1563    }
1564}
1565
1566pub(super) fn get_nonce<FEN: FoundryEvmNetwork>(
1567    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1568    address: &Address,
1569) -> Result {
1570    let account = ccx.ecx.journal_mut().load_account(*address)?;
1571    Ok(account.data.info.nonce.abi_encode())
1572}
1573
1574fn inner_snapshot_state<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1575    let evm_env = ccx.ecx.evm_clone();
1576    // Snapshot the full per-fork override map; additionally fill in the
1577    // pre-override tx values for the active fork so that
1578    // `sync_tx_after_env_override_restore` can restore them faithfully on
1579    // revert instead of falling back to hard-coded zeros.
1580    let fork_id = ccx.ecx.db().active_fork_id();
1581    let mut all_env_overrides = ccx.state.env_overrides.clone();
1582    {
1583        let active = all_env_overrides.entry(fork_id).or_default();
1584        if active.gas_price.is_none() {
1585            active.pre_override_gas_price = Some(ccx.ecx.tx().gas_price());
1586        }
1587        if active.blob_hashes.is_none() {
1588            active.pre_override_tx_type = Some(ccx.ecx.tx().tx_type());
1589            active.pre_override_blob_hashes = Some(ccx.ecx.tx().blob_versioned_hashes().to_vec());
1590        }
1591    }
1592    let journaled_state = ccx.ecx.journal_inner().clone();
1593    let id = ccx.ecx.db_mut().snapshot_state(&journaled_state, &evm_env);
1594    // Capture the cheatcode-side env overrides alongside the backend
1595    // snapshot so they can be rolled back in lockstep with `EvmEnv`. See
1596    // `Cheatcodes::env_overrides_snapshots`.
1597    ccx.state.env_overrides_snapshots.insert(id, all_env_overrides);
1598    ccx.state.fork_block_number_override_snapshots.insert(id, ccx.state.fork_block_number_override);
1599    let factory = FEN::EvmFactory::default();
1600    ccx.state.context_snapshots.insert(
1601        id,
1602        (factory.capture_chain_context(ccx.ecx), factory.capture_transaction_state(ccx.ecx)),
1603    );
1604    ccx.state.snapshot_created_accounts(id, fork_id);
1605    Ok(id.abi_encode())
1606}
1607
1608/// Syncs the real `tx` fields to match restored `env_overrides`.
1609///
1610/// `evm_clone` / `set_evm` only save/restore cfg+block, not tx. When a
1611/// cheatcode like `vm.blobhashes` or `vm.txGasPrice` mutates both the
1612/// override and the real tx, reverting to a snapshot where the override was
1613/// absent would leave the real tx field stale. This brings them back into
1614/// sync so callers that read the tx directly also see the rolled-back state.
1615fn sync_tx_after_env_override_restore<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) {
1616    let fork_id = ccx.ecx.db().active_fork_id();
1617    // Clone to avoid borrow conflicts when mutating ecx below.
1618    let env_overrides = ccx.state.env_overrides.get(&fork_id).cloned().unwrap_or_default();
1619    match env_overrides.gas_price {
1620        Some(p) if !ccx.state.in_isolation_context => ccx.ecx.tx_mut().set_gas_price(p),
1621        None => {
1622            // Restore the pre-override gas_price recorded at snapshot time.
1623            // Falling back to 0 would be wrong when the tx had a non-zero price
1624            // (e.g. --gas-price flag, foundry.toml, or fork mode).
1625            let pre = env_overrides.pre_override_gas_price.unwrap_or(0);
1626            ccx.ecx.tx_mut().set_gas_price(pre);
1627        }
1628        _ => {}
1629    }
1630    match env_overrides.blob_hashes {
1631        Some(hashes) if !ccx.state.in_isolation_context => {
1632            ccx.ecx.tx_mut().set_blob_hashes(hashes);
1633            ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
1634        }
1635        None => {
1636            // Restore the pre-override blob hashes recorded at snapshot time.
1637            let pre_hashes = env_overrides.pre_override_blob_hashes.unwrap_or_default();
1638            ccx.ecx.tx_mut().set_blob_hashes(pre_hashes);
1639            // Restore pre-override tx_type; without this it stays at EIP4844 even
1640            // after the blob hashes are cleared.
1641            let pre_type = env_overrides.pre_override_tx_type.unwrap_or(0);
1642            ccx.ecx.tx_mut().set_tx_type(pre_type);
1643        }
1644        _ => {}
1645    }
1646}
1647
1648fn inner_revert_to_state<FEN: FoundryEvmNetwork>(
1649    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1650    snapshot_id: U256,
1651) -> Result {
1652    let mut evm_env = ccx.ecx.evm_clone();
1653    let caller = ccx.ecx.caller();
1654    let journaled_state = ccx.ecx.journal_inner().clone();
1655    if let Some(restored) = ccx.ecx.db_mut().revert_state(
1656        snapshot_id,
1657        &journaled_state,
1658        &mut evm_env,
1659        caller,
1660        RevertStateSnapshotAction::RevertKeep,
1661    ) {
1662        ccx.ecx.set_journal_inner(restored);
1663        if let Some((context, state)) = ccx.state.context_snapshots.get(&snapshot_id) {
1664            FEN::EvmFactory::default().apply_context_transition(ccx.ecx, Some(context));
1665            FEN::EvmFactory::default().restore_transaction_state(ccx.ecx, state.clone());
1666        } else {
1667            refresh_context_after_state_change::<FEN>(ccx.ecx);
1668        }
1669        ccx.ecx.set_evm(evm_env);
1670        // `RevertKeep` keeps the backend snapshot alive for further
1671        // reverts, so keep our matching env-overrides copy too.
1672        if let Some(snap) = ccx.state.env_overrides_snapshots.get(&snapshot_id) {
1673            ccx.state.env_overrides = snap.clone();
1674        }
1675        if let Some(&fork_block_number) =
1676            ccx.state.fork_block_number_override_snapshots.get(&snapshot_id)
1677        {
1678            ccx.state.fork_block_number_override = fork_block_number;
1679        }
1680        ccx.state.revert_created_accounts(snapshot_id, false);
1681        sync_tx_after_env_override_restore(ccx);
1682        Ok(true.abi_encode())
1683    } else {
1684        Ok(false.abi_encode())
1685    }
1686}
1687
1688fn inner_revert_to_state_and_delete<FEN: FoundryEvmNetwork>(
1689    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1690    snapshot_id: U256,
1691) -> Result {
1692    let mut evm_env = ccx.ecx.evm_clone();
1693    let caller = ccx.ecx.caller();
1694    let journaled_state = ccx.ecx.journal_inner().clone();
1695    if let Some(restored) = ccx.ecx.db_mut().revert_state(
1696        snapshot_id,
1697        &journaled_state,
1698        &mut evm_env,
1699        caller,
1700        RevertStateSnapshotAction::RevertRemove,
1701    ) {
1702        ccx.ecx.set_journal_inner(restored);
1703        if let Some((context, state)) = ccx.state.context_snapshots.remove(&snapshot_id) {
1704            FEN::EvmFactory::default().apply_context_transition(ccx.ecx, Some(&context));
1705            FEN::EvmFactory::default().restore_transaction_state(ccx.ecx, state);
1706        } else {
1707            refresh_context_after_state_change::<FEN>(ccx.ecx);
1708        }
1709        ccx.ecx.set_evm(evm_env);
1710        if let Some(snap) = ccx.state.env_overrides_snapshots.remove(&snapshot_id) {
1711            ccx.state.env_overrides = snap;
1712        }
1713        if let Some(fork_block_number) =
1714            ccx.state.fork_block_number_override_snapshots.remove(&snapshot_id)
1715        {
1716            ccx.state.fork_block_number_override = fork_block_number;
1717        }
1718        ccx.state.revert_created_accounts(snapshot_id, true);
1719        sync_tx_after_env_override_restore(ccx);
1720        Ok(true.abi_encode())
1721    } else {
1722        Ok(false.abi_encode())
1723    }
1724}
1725
1726fn inner_value_snapshot<FEN: FoundryEvmNetwork>(
1727    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1728    group: Option<String>,
1729    name: Option<String>,
1730    value: String,
1731) -> Result {
1732    let (group, name) = derive_snapshot_name(ccx, group, name);
1733
1734    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1735
1736    Ok(Default::default())
1737}
1738
1739fn inner_last_gas_snapshot<FEN: FoundryEvmNetwork>(
1740    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1741    group: Option<String>,
1742    name: Option<String>,
1743    value: u64,
1744) -> Result {
1745    let (group, name) = derive_snapshot_name(ccx, group, name);
1746
1747    ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1748
1749    Ok(value.abi_encode())
1750}
1751
1752fn inner_start_gas_snapshot<FEN: FoundryEvmNetwork>(
1753    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1754    group: Option<String>,
1755    name: Option<String>,
1756) -> Result {
1757    // Revert if there is an active gas snapshot as we can only have one active snapshot at a time.
1758    if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1759        bail!("gas snapshot was already started with group: {group} and name: {name}");
1760    }
1761
1762    let (group, name) = derive_snapshot_name(ccx, group, name);
1763
1764    ccx.state.gas_metering.gas_records.push(GasRecord {
1765        group: group.clone(),
1766        name: name.clone(),
1767        gas_used: 0,
1768        depth: ccx.ecx.journal().depth(),
1769    });
1770
1771    ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1772
1773    ccx.state.gas_metering.start();
1774
1775    Ok(Default::default())
1776}
1777
1778fn inner_stop_gas_snapshot<FEN: FoundryEvmNetwork>(
1779    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1780    group: Option<String>,
1781    name: Option<String>,
1782) -> Result {
1783    // If group and name are not provided, use the last snapshot group and name.
1784    let (group, name) = group
1785        .zip(name)
1786        .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone())
1787        .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?;
1788
1789    if let Some(record) = ccx
1790        .state
1791        .gas_metering
1792        .gas_records
1793        .iter_mut()
1794        .find(|record| record.group == group && record.name == name)
1795    {
1796        // Calculate the gas used since the snapshot was started.
1797        // We subtract 171 from the gas used to account for gas used by the snapshot itself.
1798        let value = record.gas_used.saturating_sub(171);
1799
1800        ccx.state
1801            .gas_snapshots
1802            .entry(group.clone())
1803            .or_default()
1804            .insert(name.clone(), value.to_string());
1805
1806        // Stop the gas metering.
1807        ccx.state.gas_metering.stop();
1808
1809        // Remove the gas record.
1810        ccx.state
1811            .gas_metering
1812            .gas_records
1813            .retain(|record| record.group != group && record.name != name);
1814
1815        // Clear last snapshot cache if we have an exact match.
1816        if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1817            && snapshot_group == &group
1818            && snapshot_name == &name
1819        {
1820            ccx.state.gas_metering.active_gas_snapshot = None;
1821        }
1822
1823        Ok(value.abi_encode())
1824    } else {
1825        bail!("no gas snapshot was started with the name: {name} in group: {group}");
1826    }
1827}
1828
1829// Derives the snapshot group and name from the provided group and name or the running contract.
1830fn derive_snapshot_name<FEN: FoundryEvmNetwork>(
1831    ccx: &CheatsCtxt<'_, '_, FEN>,
1832    group: Option<String>,
1833    name: Option<String>,
1834) -> (String, String) {
1835    let group = group.unwrap_or_else(|| {
1836        ccx.state.config.running_artifact.clone().expect("expected running contract").name
1837    });
1838    let name = name.unwrap_or_else(|| "default".to_string());
1839    (group, name)
1840}
1841
1842/// Reads the current caller information and returns the current [CallerMode], `msg.sender` and
1843/// `tx.origin`.
1844///
1845/// Depending on the current caller mode, one of the following results will be returned:
1846/// - If there is an active prank:
1847///     - caller_mode will be equal to:
1848///         - [CallerMode::Prank] if the prank has been set with `vm.prank(..)`.
1849///         - [CallerMode::RecurrentPrank] if the prank has been set with `vm.startPrank(..)`.
1850///     - `msg.sender` will be equal to the address set for the prank.
1851///     - `tx.origin` will be equal to the default sender address unless an alternative one has been
1852///       set when configuring the prank.
1853///
1854/// - If there is an active broadcast:
1855///     - caller_mode will be equal to:
1856///         - [CallerMode::Broadcast] if the broadcast has been set with `vm.broadcast(..)`.
1857///         - [CallerMode::RecurrentBroadcast] if the broadcast has been set with
1858///           `vm.startBroadcast(..)`.
1859///     - `msg.sender` and `tx.origin` will be equal to the address provided when setting the
1860///       broadcast.
1861///
1862/// - If no caller modification is active:
1863///     - caller_mode will be equal to [CallerMode::None],
1864///     - `msg.sender` and `tx.origin` will be equal to the default sender address.
1865fn read_callers<FEN: FoundryEvmNetwork>(
1866    state: &Cheatcodes<FEN>,
1867    default_sender: &Address,
1868    call_depth: usize,
1869) -> Result {
1870    let mut mode = CallerMode::None;
1871    let mut new_caller = default_sender;
1872    let mut new_origin = default_sender;
1873    if let Some(prank) = state.get_prank(call_depth) {
1874        mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1875        new_caller = &prank.new_caller;
1876        if let Some(new) = &prank.new_origin {
1877            new_origin = new;
1878        }
1879    } else if let Some(broadcast) = &state.broadcast {
1880        mode = if broadcast.single_call {
1881            CallerMode::Broadcast
1882        } else {
1883            CallerMode::RecurrentBroadcast
1884        };
1885        new_caller = &broadcast.new_origin;
1886        new_origin = &broadcast.new_origin;
1887    }
1888
1889    Ok((mode, new_caller, new_origin).abi_encode_params())
1890}
1891
1892/// Ensures the `Account` is loaded and touched.
1893pub(super) fn journaled_account<
1894    CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1895>(
1896    ecx: &mut CTX,
1897    addr: Address,
1898) -> Result<&mut Account> {
1899    ensure_loaded_account(ecx, addr)?;
1900    Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded"))
1901}
1902
1903pub(super) fn ensure_loaded_account<CTX: ContextTr<Db: Database<Error = DatabaseError>>>(
1904    ecx: &mut CTX,
1905    addr: Address,
1906) -> Result<()> {
1907    ecx.journal_mut().load_account(addr)?;
1908    ecx.journal_mut().touch_account(addr);
1909    Ok(())
1910}
1911
1912fn set_eip2935_blockhash<
1913    CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1914>(
1915    ecx: &mut CTX,
1916    block_number: U256,
1917    block_hash: B256,
1918) -> Result<()> {
1919    let account_was_cold = ecx.journal_mut().load_account(HISTORY_STORAGE_ADDRESS)?.is_cold;
1920    let account =
1921        ecx.journal_mut().evm_state().get(&HISTORY_STORAGE_ADDRESS).expect("account is loaded");
1922    if account.info.code_hash != keccak256(&HISTORY_STORAGE_CODE) {
1923        restore_eip2935_cold_state(ecx, account_was_cold, None);
1924        return Ok(());
1925    }
1926
1927    let slot = history_storage_slot(block_number);
1928    let slot_was_cold = ecx
1929        .journal_mut()
1930        .sstore(HISTORY_STORAGE_ADDRESS, slot, history_storage_value(block_hash))
1931        .map_err(|e| fmt_err!("failed to store EIP-2935 history slot: {:?}", e))?
1932        .is_cold;
1933    restore_eip2935_cold_state(ecx, account_was_cold, Some((slot, slot_was_cold)));
1934    Ok(())
1935}
1936
1937fn restore_eip2935_cold_state<
1938    CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1939>(
1940    ecx: &mut CTX,
1941    account_was_cold: bool,
1942    slot_state: Option<(U256, bool)>,
1943) {
1944    let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&HISTORY_STORAGE_ADDRESS) else {
1945        return;
1946    };
1947    if account_was_cold {
1948        account.mark_cold();
1949    }
1950    if let Some((slot, slot_was_cold)) = slot_state
1951        && slot_was_cold
1952        && let Some(storage_slot) = account.storage.get_mut(&slot)
1953    {
1954        storage_slot.is_cold = true;
1955    }
1956}
1957
1958// Tempo TIP-1026 stores logoURI in TIP-20 storage slot 5, reusing the
1959// previously-unused domainSeparator slot. This mirrors Tempo's
1960// crates/precompiles/tests/storage_tests/solidity/testdata/tip20.layout.json
1961// fixture, where `logoUri` has slot "5".
1962const TIP20_LOGO_URI_SLOT_INDEX: u64 = 5;
1963
1964fn set_tip20_logo_uri<FEN: FoundryEvmNetwork>(
1965    ccx: &mut CheatsCtxt<'_, '_, FEN>,
1966    token: &Address,
1967    new_logo_uri: &str,
1968) -> Result {
1969    validate_tip20_logo_uri(new_logo_uri).map_err(|err| match err {
1970        Tip20LogoUriValidationError::LogoURITooLong => fmt_err!("LogoURITooLong"),
1971        Tip20LogoUriValidationError::InvalidLogoURI => fmt_err!("InvalidLogoURI"),
1972    })?;
1973    ccx.ensure_not_precompile(token)?;
1974    ensure_loaded_account(ccx.ecx, *token)?;
1975    store_solidity_string(
1976        ccx.ecx,
1977        *token,
1978        U256::from(TIP20_LOGO_URI_SLOT_INDEX),
1979        new_logo_uri.as_bytes(),
1980    )
1981}
1982
1983fn store_solidity_string<CTX>(
1984    ecx: &mut CTX,
1985    target: Address,
1986    base_slot: U256,
1987    bytes: &[u8],
1988) -> Result
1989where
1990    CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1991{
1992    cleanup_long_string_tail(ecx, target, base_slot, bytes.len())?;
1993
1994    if bytes.len() <= 31 {
1995        ecx.journal_mut()
1996            .sstore(target, base_slot, encode_short_string(bytes))
1997            .map_err(|e| fmt_err!("failed to store TIP-20 logo URI: {:?}", e))?;
1998        return Ok(Default::default());
1999    }
2000
2001    ecx.journal_mut()
2002        .sstore(target, base_slot, U256::from(bytes.len() * 2 + 1))
2003        .map_err(|e| fmt_err!("failed to store TIP-20 logo URI length: {:?}", e))?;
2004
2005    let slot_start = solidity_dynamic_data_slot(base_slot);
2006    for (index, chunk) in bytes.chunks(32).enumerate() {
2007        let mut chunk_bytes = [0u8; 32];
2008        chunk_bytes[..chunk.len()].copy_from_slice(chunk);
2009        ecx.journal_mut()
2010            .sstore(target, slot_start + U256::from(index), U256::from_be_bytes(chunk_bytes))
2011            .map_err(|e| fmt_err!("failed to store TIP-20 logo URI data: {:?}", e))?;
2012    }
2013
2014    Ok(Default::default())
2015}
2016
2017fn cleanup_long_string_tail<CTX>(
2018    ecx: &mut CTX,
2019    target: Address,
2020    base_slot: U256,
2021    new_len: usize,
2022) -> Result<()>
2023where
2024    CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
2025{
2026    let previous = ecx
2027        .journal_mut()
2028        .sload(target, base_slot)
2029        .map_err(|e| fmt_err!("failed to load previous TIP-20 logo URI: {:?}", e))?
2030        .data;
2031    if !is_long_string(previous) {
2032        return Ok(());
2033    }
2034
2035    let Some(previous_len) = long_string_length(previous) else {
2036        return Ok(());
2037    };
2038    let previous_chunks = string_chunks(previous_len);
2039    let new_chunks = if new_len > 31 { string_chunks(new_len) } else { 0 };
2040    if previous_chunks <= new_chunks {
2041        return Ok(());
2042    }
2043
2044    let slot_start = solidity_dynamic_data_slot(base_slot);
2045    for index in new_chunks..previous_chunks {
2046        ecx.journal_mut()
2047            .sstore(target, slot_start + U256::from(index), U256::ZERO)
2048            .map_err(|e| fmt_err!("failed to clear previous TIP-20 logo URI data: {:?}", e))?;
2049    }
2050
2051    Ok(())
2052}
2053
2054fn encode_short_string(bytes: &[u8]) -> U256 {
2055    let mut storage_bytes = [0u8; 32];
2056    storage_bytes[..bytes.len()].copy_from_slice(bytes);
2057    storage_bytes[31] =
2058        u8::try_from(bytes.len() * 2).expect("short Solidity string length tag fits in u8");
2059    U256::from_be_bytes(storage_bytes)
2060}
2061
2062fn solidity_dynamic_data_slot(base_slot: U256) -> U256 {
2063    U256::from_be_bytes(keccak256(base_slot.to_be_bytes::<32>()).0)
2064}
2065
2066const fn is_long_string(slot_value: U256) -> bool {
2067    (slot_value.to_be_bytes::<32>()[31] & 1) != 0
2068}
2069
2070fn long_string_length(slot_value: U256) -> Option<usize> {
2071    let length: U256 = (slot_value - U256::ONE) >> 1;
2072    usize::try_from(length).ok().filter(|length| *length <= TIP20_MAX_LOGO_URI_BYTES)
2073}
2074
2075const fn string_chunks(byte_length: usize) -> usize {
2076    byte_length.div_ceil(32)
2077}
2078
2079/// Consumes recorded account accesses and returns them as an abi encoded
2080/// array of [AccountAccess]. If there are no accounts were
2081/// recorded as accessed, an abi encoded empty array is returned.
2082///
2083/// In the case where `stopAndReturnStateDiff` is called at a lower
2084/// depth than `startStateDiffRecording`, multiple `Vec<RecordedAccountAccesses>`
2085/// will be flattened, preserving the order of the accesses.
2086fn get_state_diff<FEN: FoundryEvmNetwork>(state: &mut Cheatcodes<FEN>) -> Result {
2087    let mut res = state.take_recorded_account_diffs_prefix();
2088    res.extend(
2089        state
2090            .recorded_account_diffs_stack
2091            .replace(Default::default())
2092            .unwrap_or_default()
2093            .into_iter()
2094            .flatten(),
2095    );
2096    Ok(res.abi_encode())
2097}
2098
2099/// Helper function that creates a `GenesisAccount` from a regular `Account`.
2100fn genesis_account(account: &Account) -> GenesisAccount {
2101    GenesisAccount {
2102        nonce: Some(account.info.nonce),
2103        balance: account.info.balance,
2104        code: account.info.code.as_ref().map(|o| o.original_bytes()),
2105        storage: Some(
2106            account
2107                .storage
2108                .iter()
2109                .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
2110                .collect(),
2111        ),
2112        private_key: None,
2113    }
2114}
2115
2116/// Helper function to returns state diffs recorded for each changed account.
2117fn get_recorded_state_diffs<FEN: FoundryEvmNetwork>(
2118    ccx: &mut CheatsCtxt<'_, '_, FEN>,
2119) -> BTreeMap<Address, AccountStateDiffs> {
2120    let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
2121
2122    // First, collect all unique addresses we need to look up
2123    let mut addresses_to_lookup = HashSet::new();
2124    for account_access in ccx.state.recorded_account_diffs() {
2125        if !account_access.storageAccesses.is_empty()
2126            || account_access.oldBalance != account_access.newBalance
2127        {
2128            addresses_to_lookup.insert(account_access.account);
2129            for storage_access in &account_access.storageAccesses {
2130                if storage_access.isWrite && !storage_access.reverted {
2131                    addresses_to_lookup.insert(storage_access.account);
2132                }
2133            }
2134        }
2135    }
2136
2137    // Look up contract names and storage layouts for all addresses
2138    let mut contract_names = HashMap::new();
2139    let mut storage_layouts = HashMap::new();
2140    for address in addresses_to_lookup {
2141        if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) {
2142            contract_names.insert(address, artifact_id.identifier());
2143
2144            // Also get storage layout if available
2145            if let Some(storage_layout) = &contract_data.storage_layout {
2146                storage_layouts.insert(address, storage_layout.clone());
2147            }
2148        }
2149    }
2150
2151    // Now process the records
2152    ccx.state
2153        .recorded_account_diffs()
2154        .filter(|account_access| {
2155            !account_access.storageAccesses.is_empty()
2156                || account_access.oldBalance != account_access.newBalance
2157                || (!account_access.reverted && account_access.oldNonce != account_access.newNonce)
2158        })
2159        .for_each(|account_access| {
2160            // Record account balance diffs.
2161            if account_access.oldBalance != account_access.newBalance {
2162                let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| {
2163                    AccountStateDiffs {
2164                        label: ccx.state.labels.get(&account_access.account).cloned(),
2165                        contract: contract_names.get(&account_access.account).cloned(),
2166                        ..Default::default()
2167                    }
2168                });
2169                // Update balance diff. Do not overwrite the initial balance if already set.
2170                if let Some(diff) = &mut account_diff.balance_diff {
2171                    diff.new_value = account_access.newBalance;
2172                } else {
2173                    account_diff.balance_diff = Some(BalanceDiff {
2174                        previous_value: account_access.oldBalance,
2175                        new_value: account_access.newBalance,
2176                    });
2177                }
2178            }
2179
2180            // Record account nonce diffs.
2181            if account_access.oldNonce != account_access.newNonce && !account_access.reverted {
2182                let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| {
2183                    AccountStateDiffs {
2184                        label: ccx.state.labels.get(&account_access.account).cloned(),
2185                        contract: contract_names.get(&account_access.account).cloned(),
2186                        ..Default::default()
2187                    }
2188                });
2189                // Update nonce diff. Do not overwrite the initial nonce if already set.
2190                if let Some(diff) = &mut account_diff.nonce_diff {
2191                    diff.new_value = account_access.newNonce;
2192                } else {
2193                    account_diff.nonce_diff = Some(NonceDiff {
2194                        previous_value: account_access.oldNonce,
2195                        new_value: account_access.newNonce,
2196                    });
2197                }
2198            }
2199
2200            // Collect all storage accesses for this account
2201            let raw_changes_by_slot = account_access
2202                .storageAccesses
2203                .iter()
2204                .filter_map(|access| {
2205                    (access.isWrite && !access.reverted)
2206                        .then_some((access.slot, (access.previousValue, access.newValue)))
2207                })
2208                .collect::<BTreeMap<_, _>>();
2209
2210            // Record account state diffs.
2211            for storage_access in &account_access.storageAccesses {
2212                if storage_access.isWrite && !storage_access.reverted {
2213                    let account_diff =
2214                        state_diffs.entry(storage_access.account).or_insert_with(|| {
2215                            AccountStateDiffs {
2216                                label: ccx.state.labels.get(&storage_access.account).cloned(),
2217                                contract: contract_names.get(&storage_access.account).cloned(),
2218                                ..Default::default()
2219                            }
2220                        });
2221                    let layout = storage_layouts.get(&storage_access.account);
2222                    // Update state diff. Do not overwrite the initial value if already set.
2223                    let entry = match account_diff.state_diff.entry(storage_access.slot) {
2224                        Entry::Vacant(slot_state_diff) => {
2225                            // Get storage layout info for this slot
2226                            // Include mapping slots if available for the account
2227                            let mapping_slots = ccx
2228                                .state
2229                                .mapping_slots
2230                                .as_ref()
2231                                .and_then(|slots| slots.get(&storage_access.account));
2232
2233                            let slot_info = layout.and_then(|layout| {
2234                                let decoder = SlotIdentifier::new(layout.clone());
2235                                decoder.identify(&storage_access.slot, mapping_slots).or_else(
2236                                    || {
2237                                        // Create a map of new values for bytes/string
2238                                        // identification. These values are used to determine
2239                                        // the length of the data which helps determine how many
2240                                        // slots to search
2241                                        let current_base_slot_values = raw_changes_by_slot
2242                                            .iter()
2243                                            .map(|(slot, (_, new_val))| (*slot, *new_val))
2244                                            .collect::<B256Map<_>>();
2245                                        decoder.identify_bytes_or_string(
2246                                            &storage_access.slot,
2247                                            &current_base_slot_values,
2248                                        )
2249                                    },
2250                                )
2251                            });
2252
2253                            slot_state_diff.insert(SlotStateDiff {
2254                                previous_value: storage_access.previousValue,
2255                                new_value: storage_access.newValue,
2256                                slot_info,
2257                            })
2258                        }
2259                        Entry::Occupied(slot_state_diff) => {
2260                            let entry = slot_state_diff.into_mut();
2261                            entry.new_value = storage_access.newValue;
2262                            entry
2263                        }
2264                    };
2265
2266                    // Update decoded values if we have slot info
2267                    if let Some(slot_info) = &mut entry.slot_info {
2268                        slot_info.decode_values(entry.previous_value, storage_access.newValue);
2269                        if slot_info.is_bytes_or_string() {
2270                            slot_info.decode_bytes_or_string_values(
2271                                &storage_access.slot,
2272                                &raw_changes_by_slot,
2273                            );
2274                        }
2275                    }
2276                }
2277            }
2278        });
2279    state_diffs
2280}
2281
2282/// EIP-1967 implementation storage slot
2283const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
2284
2285/// EIP-1822 UUPS implementation storage slot: keccak256("PROXIABLE")
2286const EIP1822_PROXIABLE_SLOT: &str =
2287    "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
2288
2289/// Helper function to get the contract data from the deployed code at an address.
2290fn get_contract_data<'a, FEN: FoundryEvmNetwork>(
2291    ccx: &'a mut CheatsCtxt<'_, '_, FEN>,
2292    address: Address,
2293) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
2294    // Check if we have available artifacts to match against
2295    let artifacts = ccx.state.config.available_artifacts.as_ref()?;
2296
2297    // Try to load the account and get its code
2298    let account = ccx.ecx.journal_mut().load_account(address).ok()?;
2299    let code = account.data.info.code.as_ref()?;
2300
2301    // Skip if code is empty
2302    if code.is_empty() {
2303        return None;
2304    }
2305
2306    // Try to find the artifact by deployed code
2307    let code_bytes = code.original_bytes();
2308    // First check for proxy patterns
2309    let hex_str = hex::encode(&code_bytes);
2310    let find_by_suffix =
2311        |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
2312    // Simple proxy detection based on storage slot patterns
2313    if hex_str.contains(EIP1967_IMPL_SLOT)
2314        && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
2315    {
2316        return Some(result);
2317    } else if hex_str.contains(EIP1822_PROXIABLE_SLOT)
2318        && let Some(result) = find_by_suffix(":UUPSUpgradeable")
2319    {
2320        return Some(result);
2321    }
2322
2323    // Try exact match
2324    if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
2325        return Some(result);
2326    }
2327
2328    // Fallback to fuzzy matching if exact match fails
2329    artifacts.find_by_deployed_code(&code_bytes)
2330}
2331
2332/// Helper function to set / unset cold storage slot of the target address.
2333fn set_cold_slot<FEN: FoundryEvmNetwork>(
2334    ccx: &mut CheatsCtxt<'_, '_, FEN>,
2335    target: Address,
2336    slot: U256,
2337    cold: bool,
2338) {
2339    if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target)
2340        && let Some(storage_slot) = account.storage.get_mut(&slot)
2341    {
2342        storage_slot.is_cold = cold;
2343    }
2344}