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