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