1use crate::{
4 BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesCore, CheatcodesExecutor,
5 CheatsCtxt, Error, Result, Vm::*, env::FORGE_CONTEXT, inspector::RecordDebugStepInfo,
6};
7use alloy_consensus::{Transaction as _, Typed2718, 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, Bytes, U256, hex, keccak256,
13 map::{AddressMap, AddressSet, 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, FoundryChain, FoundryTransaction,
28 backend::{DatabaseError, DatabaseExt, JournaledState, 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, merge_child_state, prepare_child_state},
36};
37use foundry_evm_traces::TraceRequirements;
38use itertools::Itertools;
39use rand::Rng;
40use revm::{
41 Database,
42 bytecode::Bytecode,
43 context::{Block, ContextTr, JournalTr, Transaction, result::ExecutionResult},
44 inspector::JournalExt,
45 primitives::hardfork::SpecId,
46 state::Account,
47};
48use std::{
49 collections::{BTreeMap, btree_map::Entry},
50 fmt::Display,
51 path::Path,
52 str::FromStr,
53};
54
55mod record_debug_step;
56use foundry_common::fmt::format_token_raw;
57use foundry_config::{ExecutionSpec, evm_spec_id_from_str, fs_permissions::FsAccessKind};
58use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
59use serde::{Serialize, Serializer, ser::SerializeMap};
60
61#[cfg(feature = "monad")]
62use foundry_evm_core::evm::refresh_chain_journal;
63
64mod fork;
65pub(crate) mod mapping;
66pub(crate) mod mock;
67pub(crate) mod prank;
68
69#[derive(Serialize)]
71#[serde(rename_all = "camelCase")]
72struct LogJson {
73 topics: Vec<String>,
75 data: String,
77 emitter: String,
79}
80
81struct StateDump<'a>(&'a [(Address, GenesisAccount)]);
82
83impl Serialize for StateDump<'_> {
84 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
85 where
86 S: Serializer,
87 {
88 let mut map = serializer.serialize_map(Some(self.0.len()))?;
89 for (address, account) in self.0 {
90 map.serialize_entry(address, account)?;
91 }
92 map.end()
93 }
94}
95
96#[derive(Clone, Debug, Default)]
98pub struct RecordAccess {
99 pub reads: HashMap<Address, Vec<U256>>,
101 pub writes: HashMap<Address, Vec<U256>>,
103}
104
105impl RecordAccess {
106 pub fn record_read(&mut self, target: Address, slot: U256) {
108 self.reads.entry(target).or_default().push(slot);
109 }
110
111 pub fn record_write(&mut self, target: Address, slot: U256) {
115 self.record_read(target, slot);
116 self.writes.entry(target).or_default().push(slot);
117 }
118
119 pub fn clear(&mut self) {
121 *self = Default::default();
123 }
124}
125
126#[derive(Clone, Debug)]
128pub struct GasRecord {
129 pub group: String,
131 pub name: String,
133 pub gas_used: u64,
135 pub depth: usize,
137}
138
139#[derive(Clone, Debug)]
141pub struct DealRecord {
142 pub address: Address,
144 pub old_balance: U256,
146 pub new_balance: U256,
148}
149
150#[derive(Serialize, Default)]
152#[serde(rename_all = "camelCase")]
153struct SlotStateDiff {
154 previous_value: B256,
156 new_value: B256,
158 #[serde(skip_serializing_if = "Option::is_none", flatten)]
162 slot_info: Option<SlotInfo>,
163}
164
165#[derive(Serialize, Default)]
167#[serde(rename_all = "camelCase")]
168struct BalanceDiff {
169 previous_value: U256,
171 new_value: U256,
173}
174
175#[derive(Serialize, Default)]
177#[serde(rename_all = "camelCase")]
178struct NonceDiff {
179 previous_value: u64,
181 new_value: u64,
183}
184
185#[derive(Serialize, Default)]
187#[serde(rename_all = "camelCase")]
188struct AccountStateDiffs {
189 label: Option<String>,
191 contract: Option<String>,
193 balance_diff: Option<BalanceDiff>,
195 nonce_diff: Option<NonceDiff>,
197 state_diff: BTreeMap<B256, SlotStateDiff>,
199}
200
201impl Display for AccountStateDiffs {
202 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
203 if let Some(label) = &self.label {
205 writeln!(f, "label: {label}")?;
206 }
207 if let Some(contract) = &self.contract {
208 writeln!(f, "contract: {contract}")?;
209 }
210 if let Some(balance_diff) = &self.balance_diff
212 && balance_diff.previous_value != balance_diff.new_value
213 {
214 writeln!(
215 f,
216 "- balance diff: {} → {}",
217 balance_diff.previous_value, balance_diff.new_value
218 )?;
219 }
220 if let Some(nonce_diff) = &self.nonce_diff
222 && nonce_diff.previous_value != nonce_diff.new_value
223 {
224 writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?;
225 }
226 if !&self.state_diff.is_empty() {
228 writeln!(f, "- state diff:")?;
229 for (slot, slot_changes) in &self.state_diff {
230 match &slot_changes.slot_info {
231 Some(slot_info)
232 if slot_info.members.as_ref().is_some_and(|members| {
233 members.iter().any(|member| member.decoded.is_some())
234 }) =>
235 {
236 writeln!(
238 f,
239 "@ {slot} ({}, {}): {} → {}",
240 slot_info.label,
241 slot_info.slot_type.dyn_sol_type,
242 slot_changes.previous_value,
243 slot_changes.new_value
244 )?;
245 for member in slot_info.members.iter().flatten() {
246 let Some(decoded) = &member.decoded else { continue };
247 writeln!(
248 f,
249 " {} ({}): {} → {}",
250 member.label,
251 member.slot_type.dyn_sol_type,
252 format_token_raw(&decoded.previous_value),
253 format_token_raw(&decoded.new_value)
254 )?;
255 }
256 }
257 Some(slot_info) => {
258 if let Some(decoded) = &slot_info.decoded {
259 writeln!(
261 f,
262 "@ {slot} ({}, {}): {} → {}",
263 slot_info.label,
264 slot_info.slot_type.dyn_sol_type,
265 format_token_raw(&decoded.previous_value),
266 format_token_raw(&decoded.new_value)
267 )?;
268 } else {
269 writeln!(
271 f,
272 "@ {slot} ({}, {}): {} → {}",
273 slot_info.label,
274 slot_info.slot_type.dyn_sol_type,
275 slot_changes.previous_value,
276 slot_changes.new_value
277 )?;
278 }
279 }
280 None => {
281 writeln!(
283 f,
284 "@ {slot}: {} → {}",
285 slot_changes.previous_value, slot_changes.new_value
286 )?;
287 }
288 }
289 }
290 }
291
292 Ok(())
293 }
294}
295
296impl Cheatcode for addrCall {
297 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
298 let Self { privateKey } = self;
299 super::crypto::with_private_key_signer(state, privateKey, |wallet| {
300 Ok(wallet.address().abi_encode())
301 })
302 }
303}
304
305impl Cheatcode for getNonce_0Call {
306 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
307 let Self { account } = self;
308 get_nonce(ccx, account)
309 }
310}
311
312impl Cheatcode for getNonce_1Call {
313 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
314 let Self { wallet } = self;
315 get_nonce(ccx, &wallet.addr)
316 }
317}
318
319impl Cheatcode for loadCall {
320 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
321 let Self { target, slot } = *self;
322
323 ccx.ecx.journal_mut().load_account(target)?;
324 let mut val = ccx
325 .ecx
326 .journal_mut()
327 .sload(target, slot.into())
328 .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?;
329
330 if val.is_cold
331 && val.data.is_zero()
332 && !ccx.state.is_arbitrary_storage_slot_explicit(target, slot.into())
333 {
334 if ccx.state.has_arbitrary_storage(&target) {
335 let rand_value = ccx
338 .state
339 .cached_arbitrary_storage_value(target, slot.into())
340 .unwrap_or_else(|| ccx.state.core.rng().random());
341 ccx.state.core.arbitrary_storage.as_mut().unwrap().save(
342 ccx.ecx,
343 target,
344 slot.into(),
345 rand_value,
346 );
347 val.data = rand_value;
348 } else if ccx.state.is_arbitrary_storage_copy(&target) {
349 let rand_value = ccx.state.core.rng().random();
353 val.data = ccx.state.core.arbitrary_storage.as_mut().unwrap().copy(
354 ccx.ecx,
355 target,
356 slot.into(),
357 rand_value,
358 );
359 }
360 }
361
362 Ok(val.abi_encode())
363 }
364}
365
366impl Cheatcode for loadTransientCall {
367 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
368 let Self { target, slot } = *self;
369 let value = ccx.ecx.journal_mut().tload(target, slot.into());
370 Ok(B256::from(value).abi_encode())
371 }
372}
373
374impl Cheatcode for loadAllocsCall {
375 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
376 let Self { pathToAllocsJson } = self;
377
378 let path = Path::new(pathToAllocsJson);
379 ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
380
381 let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
383 Ok(allocs) => allocs,
384 Err(_) => {
385 let genesis = read_json_file::<Genesis>(path)?;
387 genesis.alloc
388 }
389 };
390
391 let (db, inner) = ccx.ecx.db_journal_inner_mut();
393 db.load_allocs(&allocs, inner).map_err(|e| fmt_err!("failed to load allocs: {e}"))?;
394 #[cfg(feature = "monad")]
395 refresh_chain_journal(ccx.ecx);
396 Ok(Default::default())
397 }
398}
399
400impl Cheatcode for cloneAccountCall {
401 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
402 let Self { source, target } = self;
403
404 let account = ccx.ecx.journal_mut().load_account(*source)?;
405 let genesis = genesis_account(account.data);
406 let (db, inner) = ccx.ecx.db_journal_inner_mut();
407 db.clone_account(&genesis, target, inner)?;
408 ccx.ecx.db_mut().add_persistent_account(*target);
410 #[cfg(feature = "monad")]
411 refresh_chain_journal(ccx.ecx);
412 Ok(Default::default())
413 }
414}
415
416impl Cheatcode for dumpStateCall {
417 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
418 let Self { pathToStateJson } = self;
419 let path =
420 ccx.state.core.config.ensure_path_allowed(pathToStateJson, FsAccessKind::Write)?;
421 ccx.state.core.config.ensure_not_foundry_toml(&path)?;
422
423 let fork_id = ccx.active_fork_id();
424 let created_accounts = ccx
425 .state
426 .created_accounts(fork_id)
427 .into_iter()
428 .filter(|address| {
429 ccx.ecx.journal().evm_state().get(address).is_some_and(Account::is_created)
430 })
431 .collect::<Vec<_>>();
432
433 let skip = |key: &Address, val: &Account| {
435 key == &CHEATCODE_ADDRESS
436 || key == &CALLER
437 || key == &HARDHAT_CONSOLE_ADDRESS
438 || key == &TEST_CONTRACT_ADDRESS
439 || key == &ccx.caller
440 || key == &ccx.state.core.config.evm_opts.sender
441 || val.is_empty()
442 };
443
444 let mut alloc = ccx
445 .ecx
446 .journal_mut()
447 .evm_state_mut()
448 .iter_mut()
449 .filter(|(key, val)| !skip(key, val))
450 .map(|(key, val)| (*key, genesis_account(val)))
451 .collect::<BTreeMap<_, _>>();
452
453 let mut ordered_alloc = Vec::with_capacity(alloc.len());
454 for address in created_accounts {
455 if let Some(account) = alloc.remove(&address) {
456 ordered_alloc.push((address, account));
457 }
458 }
459 ordered_alloc.extend(alloc);
460
461 write_json_file(&path, &StateDump(&ordered_alloc))?;
462 Ok(Default::default())
463 }
464}
465
466impl Cheatcode for recordCall {
467 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
468 let Self {} = self;
469 ccx.state.recording_accesses = true;
470 ccx.state.accesses.clear();
471 Ok(Default::default())
472 }
473}
474
475impl Cheatcode for stopRecordCall {
476 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
477 ccx.state.recording_accesses = false;
478 Ok(Default::default())
479 }
480}
481
482impl Cheatcode for accessesCall {
483 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
484 let Self { target } = *self;
485 let result = (
486 ccx.state.accesses.reads.entry(target).or_default().as_slice(),
487 ccx.state.accesses.writes.entry(target).or_default().as_slice(),
488 );
489 Ok(result.abi_encode_params())
490 }
491}
492
493impl Cheatcode for registerSloadHookCall {
494 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
495 let Self { target, callback } = *self;
496 ccx.state.register_storage_load_hook(target, ccx.caller, callback.0);
497 Ok(Default::default())
498 }
499}
500
501impl Cheatcode for registerSstoreHookCall {
502 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
503 let Self { target, callback } = *self;
504 if ccx.state.has_mapping_storage_store_hooks(target) {
505 bail!("cannot register raw SSTORE hook: mapping SSTORE hooks already exist for target");
506 }
507 ccx.state.register_storage_store_hook(target, ccx.caller, callback.0);
508 Ok(Default::default())
509 }
510}
511
512impl Cheatcode for registerMappingSstoreHookCall {
513 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
514 let Self { target, rootSlot, callback } = *self;
515 if !ccx.state.register_mapping_storage_store_hook(target, rootSlot, ccx.caller, callback.0)
516 {
517 bail!("cannot register mapping SSTORE hook: raw SSTORE hook already exists for target");
518 }
519 Ok(Default::default())
520 }
521}
522
523impl Cheatcode for recordLogsCall {
524 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
525 let Self {} = self;
526 state.recorded_logs = Some(Default::default());
527 state.recorded_logs_generation += 1;
528 Ok(Default::default())
529 }
530}
531
532impl Cheatcode for getRecordedLogsCall {
533 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
534 let Self {} = self;
535 state.recorded_logs_generation += 1;
536 Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
537 }
538}
539
540impl Cheatcode for getRecordedLogsJsonCall {
541 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
542 let Self {} = self;
543 state.recorded_logs_generation += 1;
544 let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default();
545 let json_logs: Vec<_> = logs
546 .into_iter()
547 .map(|log| LogJson {
548 topics: log.topics.iter().map(|t| format!("{t}")).collect(),
549 data: hex::encode_prefixed(&log.data),
550 emitter: format!("{}", log.emitter),
551 })
552 .collect();
553 Ok(serde_json::to_string(&json_logs)?.abi_encode())
554 }
555}
556
557impl Cheatcode for pauseGasMeteringCall {
558 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
559 let Self {} = self;
560 ccx.state.gas_metering.paused = true;
561 Ok(Default::default())
562 }
563}
564
565impl Cheatcode for resumeGasMeteringCall {
566 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
567 let Self {} = self;
568 ccx.state.gas_metering.resume();
569 Ok(Default::default())
570 }
571}
572
573impl Cheatcode for resetGasMeteringCall {
574 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
575 let Self {} = self;
576 ccx.state.gas_metering.reset();
577 Ok(Default::default())
578 }
579}
580
581impl Cheatcode for lastCallGasCall {
582 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
583 let Self {} = self;
584 let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
585 bail!("no external call was made yet");
586 };
587 Ok(last_call_gas.abi_encode())
588 }
589}
590
591impl Cheatcode for lastFrameGasCall {
592 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
593 let Self {} = self;
594 let Some(last_frame_gas) = &ccx.state.gas_metering.last_frame_gas else {
595 bail!("no external call or create was made yet");
596 };
597 Ok(last_frame_gas.abi_encode())
598 }
599}
600
601impl Cheatcode for getChainIdCall {
602 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
603 let Self {} = self;
604 Ok(U256::from(ccx.chain_id()).abi_encode())
605 }
606}
607
608impl Cheatcode for chainIdCall {
609 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
610 let Self { newChainId } = self;
611 ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64");
612 ccx.ecx.cfg_env_mut().chain_id = newChainId.to();
613 Ok(Default::default())
614 }
615}
616
617impl Cheatcode for coinbaseCall {
618 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
619 let Self { newCoinbase } = self;
620 ccx.ecx.block_mut().set_beneficiary(*newCoinbase);
621 Ok(Default::default())
622 }
623}
624
625impl Cheatcode for difficultyCall {
626 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
627 let Self { newDifficulty } = self;
628 ensure!(
629 ccx.spec().into() < SpecId::MERGE,
630 "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
631 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
632 );
633 ccx.ecx.block_mut().set_difficulty(*newDifficulty);
634 Ok(Default::default())
635 }
636}
637
638impl Cheatcode for feeCall {
639 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
640 let Self { newBasefee } = self;
641 ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64");
642 let basefee: u64 = newBasefee.saturating_to();
643 let fork_id = ccx.active_fork_id();
647 ccx.state.env_overrides.update(fork_id, |o| o.basefee = Some(basefee));
648 if !ccx.state.in_isolation_context {
651 ccx.ecx.block_mut().set_basefee(basefee);
652 }
653 Ok(Default::default())
654 }
655}
656
657impl Cheatcode for prevrandao_0Call {
658 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
659 let Self { newPrevrandao } = self;
660 ensure!(
661 ccx.spec().into() >= SpecId::MERGE,
662 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
663 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
664 );
665 ccx.ecx.block_mut().set_prevrandao(Some(*newPrevrandao));
666 Ok(Default::default())
667 }
668}
669
670impl Cheatcode for prevrandao_1Call {
671 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
672 let Self { newPrevrandao } = self;
673 ensure!(
674 ccx.spec().into() >= SpecId::MERGE,
675 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
676 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
677 );
678 ccx.ecx.block_mut().set_prevrandao(Some((*newPrevrandao).into()));
679 Ok(Default::default())
680 }
681}
682
683impl Cheatcode for blobhashesCall {
684 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
685 let Self { hashes } = self;
686 ensure!(
687 ccx.spec().into() >= SpecId::CANCUN,
688 "`blobhashes` is not supported before the Cancun hard fork; \
689 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
690 );
691 let fork_id = ccx.active_fork_id();
695 ccx.state.env_overrides.update(fork_id, |o| o.blob_hashes = Some(hashes.clone()));
696 if !ccx.state.in_isolation_context {
699 ccx.ecx.tx_mut().set_blob_hashes(hashes.clone());
700 ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
702 }
703 Ok(Default::default())
704 }
705}
706
707impl Cheatcode for getBlobhashesCall {
708 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
709 let Self {} = self;
710 ensure!(
711 ccx.spec().into() >= SpecId::CANCUN,
712 "`getBlobhashes` is not supported before the Cancun hard fork; \
713 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
714 );
715 let fork_id = ccx.active_fork_id();
716 let hashes = ccx
717 .state
718 .env_overrides
719 .get(fork_id)
720 .and_then(|o| o.blob_hashes.as_deref())
721 .unwrap_or_else(|| ccx.tx_blob_hashes());
722 Ok(hashes.to_vec().abi_encode())
723 }
724}
725
726impl Cheatcode for rollCall {
727 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
728 let Self { newHeight } = self;
729 let current_height = ccx.block_number();
730 if ccx.spec().into() >= SpecId::PRAGUE && *newHeight > current_height {
731 let mut block_number = forward_fill_start(current_height, *newHeight);
732 while block_number < *newHeight {
733 let block_hash =
734 ccx.ecx.db_mut().block_hash(block_number.saturating_to()).unwrap_or_default();
735 set_eip2935_blockhash(ccx.ecx, block_number, block_hash)?;
736 block_number += U256::ONE;
737 }
738 }
739 ccx.ecx.block_mut().set_number(*newHeight);
740 Ok(Default::default())
741 }
742}
743
744impl Cheatcode for getBlockNumberCall {
745 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
746 let Self {} = self;
747 Ok(ccx.block_number().abi_encode())
748 }
749}
750
751impl Cheatcode for rollSlotCall {
752 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
753 ensure!(
754 ccx.spec().into() >= SpecId::AMSTERDAM,
755 "`rollSlot` is not supported before the Amsterdam hard fork; \
756 see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
757 );
758 ccx.ecx.block_mut().set_slot_num(self.newSlotNumber);
759 Ok(Default::default())
760 }
761}
762
763impl Cheatcode for getSlotNumberCall {
764 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
765 ensure!(
766 ccx.spec().into() >= SpecId::AMSTERDAM,
767 "`getSlotNumber` is not supported before the Amsterdam hard fork; \
768 see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
769 );
770 Ok(ccx.slot_num().abi_encode())
771 }
772}
773
774impl Cheatcode for txGasPriceCall {
775 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
776 let Self { newGasPrice } = self;
777 ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64");
778 let gas_price: u128 = newGasPrice.saturating_to();
779 let fork_id = ccx.active_fork_id();
783 ccx.state.env_overrides.update(fork_id, |o| o.gas_price = Some(gas_price));
784 if !ccx.state.in_isolation_context {
789 ccx.ecx.tx_mut().set_gas_price(gas_price);
790 }
791 Ok(Default::default())
792 }
793}
794
795impl Cheatcode for warpCall {
796 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
797 let Self { newTimestamp } = self;
798 ccx.ecx.block_mut().set_timestamp(*newTimestamp);
799 Ok(Default::default())
800 }
801}
802
803impl Cheatcode for getBlockTimestampCall {
804 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
805 let Self {} = self;
806 Ok(ccx.timestamp().abi_encode())
807 }
808}
809
810impl Cheatcode for blobBaseFeeCall {
811 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
812 let Self { newBlobBaseFee } = self;
813 ensure!(
814 ccx.spec().into() >= SpecId::CANCUN,
815 "`blobBaseFee` is not supported before the Cancun hard fork; \
816 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
817 );
818
819 ensure!(*newBlobBaseFee <= U256::from(u128::MAX), "blob base fee must be less than 2^128");
820 ccx.ecx.block_mut().set_blob_gasprice(newBlobBaseFee.saturating_to());
821 Ok(Default::default())
822 }
823}
824
825impl Cheatcode for getBlobBaseFeeCall {
826 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
827 let Self {} = self;
828 Ok(U256::from(ccx.blob_gasprice().unwrap_or_default()).abi_encode())
829 }
830}
831
832impl Cheatcode for dealCall {
833 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
834 let Self { account: address, newBalance: new_balance } = *self;
835 let account = journaled_account(ccx.ecx, address)?;
836 let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
837 let record = DealRecord { address, old_balance, new_balance };
838 ccx.state.eth_deals.push(record);
839 #[cfg(feature = "monad")]
840 refresh_chain_journal(ccx.ecx);
841 Ok(Default::default())
842 }
843}
844
845impl Cheatcode for etchCall {
846 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
847 let Self { target, newRuntimeBytecode } = self;
848 ccx.ensure_not_precompile(target)?;
849 ccx.ecx.journal_mut().load_account(*target)?;
850 let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone())
851 .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
852 if *target == HISTORY_STORAGE_ADDRESS
856 && !ccx.is_forked_mode()
857 && bytecode.hash_slow() != keccak256(&HISTORY_STORAGE_CODE)
858 && ccx.ecx.journal_mut().evm_state()[target].info.code_hash
859 == keccak256(&HISTORY_STORAGE_CODE)
860 {
861 clear_eip2935_history_storage(ccx.ecx)?;
862 }
863 ccx.ecx.journal_mut().set_code(*target, bytecode);
864 Ok(Default::default())
865 }
866}
867
868impl Cheatcode for resetNonceCall {
869 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
870 let Self { account } = self;
871 ccx.ecx.journal_mut().load_account_with_code(*account)?;
872 let account = journaled_account(ccx.ecx, *account)?;
873 let code = account.info.code.as_ref().expect("account code is loaded");
876 let is_eoa = account.info.is_empty_code_hash() || code.is_eip7702();
877 let nonce = if is_eoa { 0 } else { 1 };
878 account.info.nonce = nonce;
879 debug!(target: "cheatcodes", nonce, "reset");
880 Ok(Default::default())
881 }
882}
883
884impl Cheatcode for setNonceCall {
885 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
886 let Self { account, newNonce } = *self;
887 let account = journaled_account(ccx.ecx, account)?;
888 let current = account.info.nonce;
890 ensure!(
891 newNonce >= current,
892 "new nonce ({newNonce}) must be strictly equal to or higher than the \
893 account's current nonce ({current})"
894 );
895 account.info.nonce = newNonce;
896 Ok(Default::default())
897 }
898}
899
900impl Cheatcode for setNonceUnsafeCall {
901 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
902 let Self { account, newNonce } = *self;
903 let account = journaled_account(ccx.ecx, account)?;
904 account.info.nonce = newNonce;
905 Ok(Default::default())
906 }
907}
908
909impl Cheatcode for storeCall {
910 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
911 let Self { target, slot, value } = *self;
912 ccx.ensure_not_precompile(&target)?;
913 ensure_loaded_account(ccx.ecx, target)?;
914 ccx.ecx
915 .journal_mut()
916 .sstore(target, slot.into(), value.into())
917 .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?;
918 ccx.state.mark_arbitrary_storage_slot_explicit(target, slot.into());
919 Ok(Default::default())
920 }
921}
922
923impl Cheatcode for storeTransientCall {
924 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
925 let Self { target, slot, value } = *self;
926 ccx.ensure_not_precompile(&target)?;
927 ccx.ecx.journal_mut().tstore(target, slot.into(), value.into());
928 Ok(Default::default())
929 }
930}
931
932impl Cheatcode for setTip20LogoURICall {
933 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
934 let Self { token, newLogoURI } = self;
935 set_tip20_logo_uri(ccx, token, newLogoURI)
936 }
937}
938
939impl Cheatcode for setLogoURICall {
940 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
941 let Self { token, newLogoURI } = self;
942 set_tip20_logo_uri(ccx, token, newLogoURI)
943 }
944}
945
946impl Cheatcode for coolCall {
947 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
948 let Self { target } = self;
949 if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) {
950 account.mark_cold();
951 account.storage.values_mut().for_each(|slot| slot.mark_cold());
952 }
953 Ok(Default::default())
954 }
955}
956
957impl Cheatcode for accessListCall {
958 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
959 let Self { access } = self;
960 let access_list = access
961 .iter()
962 .map(|item| {
963 let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
964 alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
965 })
966 .collect_vec();
967 state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
968 Ok(Default::default())
969 }
970}
971
972impl Cheatcode for noAccessListCall {
973 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
974 let Self {} = self;
975 if state.access_list.is_some() {
977 state.access_list = Some(alloy_rpc_types::AccessList::default());
978 }
979 Ok(Default::default())
980 }
981}
982
983impl Cheatcode for warmSlotCall {
984 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
985 let Self { target, slot } = *self;
986 set_cold_slot(ccx, target, slot.into(), false);
987 Ok(Default::default())
988 }
989}
990
991impl Cheatcode for coolSlotCall {
992 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
993 let Self { target, slot } = *self;
994 set_cold_slot(ccx, target, slot.into(), true);
995 Ok(Default::default())
996 }
997}
998
999impl Cheatcode for isIsolateModeCall {
1000 fn apply_context(&self, state: &mut CheatcodesCore) -> Result {
1001 let Self {} = self;
1002 Ok(state.config.isolate.abi_encode())
1003 }
1004}
1005
1006impl Cheatcode for readCallersCall {
1007 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1008 let Self {} = self;
1009 read_callers(ccx.state, &ccx.tx_caller(), ccx.depth())
1010 }
1011}
1012
1013impl Cheatcode for snapshotValue_0Call {
1014 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1015 let Self { name, value } = self;
1016 inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
1017 }
1018}
1019
1020impl Cheatcode for snapshotValue_1Call {
1021 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1022 let Self { group, name, value } = self;
1023 inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
1024 }
1025}
1026
1027impl Cheatcode for snapshotGasLastCall_0Call {
1028 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1029 let Self { name } = self;
1030 if ccx.state.gas_metering.last_call_gas.is_none() {
1031 bail!("no external call was made yet");
1032 }
1033 let gas_used = ccx.state.gas_metering.last_call_snapshot_gas_used;
1034 inner_last_gas_snapshot(ccx, None, Some(name.clone()), gas_used)
1035 }
1036}
1037
1038impl Cheatcode for snapshotGasLastCall_1Call {
1039 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1040 let Self { name, group } = self;
1041 if ccx.state.gas_metering.last_call_gas.is_none() {
1042 bail!("no external call was made yet");
1043 }
1044 let gas_used = ccx.state.gas_metering.last_call_snapshot_gas_used;
1045 inner_last_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()), gas_used)
1046 }
1047}
1048
1049impl Cheatcode for snapshotGasLastFrame_0Call {
1050 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1051 let Self { name } = self;
1052 if ccx.state.gas_metering.last_frame_gas.is_none() {
1053 bail!("no external call or create was made yet");
1054 }
1055 let gas_used = ccx.state.gas_metering.last_frame_snapshot_gas_used;
1056 inner_last_gas_snapshot(ccx, None, Some(name.clone()), gas_used)
1057 }
1058}
1059
1060impl Cheatcode for snapshotGasLastFrame_1Call {
1061 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1062 let Self { name, group } = self;
1063 if ccx.state.gas_metering.last_frame_gas.is_none() {
1064 bail!("no external call or create was made yet");
1065 }
1066 let gas_used = ccx.state.gas_metering.last_frame_snapshot_gas_used;
1067 inner_last_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()), gas_used)
1068 }
1069}
1070
1071impl Cheatcode for startSnapshotGas_0Call {
1072 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1073 let Self { name } = self;
1074 inner_start_gas_snapshot(ccx, None, Some(name.clone()))
1075 }
1076}
1077
1078impl Cheatcode for startSnapshotGas_1Call {
1079 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1080 let Self { group, name } = self;
1081 inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
1082 }
1083}
1084
1085impl Cheatcode for stopSnapshotGas_0Call {
1086 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1087 let Self {} = self;
1088 inner_stop_gas_snapshot(ccx, None, None)
1089 }
1090}
1091
1092impl Cheatcode for stopSnapshotGas_1Call {
1093 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1094 let Self { name } = self;
1095 inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
1096 }
1097}
1098
1099impl Cheatcode for stopSnapshotGas_2Call {
1100 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1101 let Self { group, name } = self;
1102 inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
1103 }
1104}
1105
1106impl Cheatcode for snapshotCall {
1108 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1109 let Self {} = self;
1110 inner_snapshot_state(ccx)
1111 }
1112}
1113
1114impl Cheatcode for snapshotStateCall {
1115 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1116 let Self {} = self;
1117 inner_snapshot_state(ccx)
1118 }
1119}
1120
1121impl Cheatcode for revertToCall {
1123 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1124 let Self { snapshotId } = self;
1125 inner_revert_to_state(ccx, *snapshotId)
1126 }
1127}
1128
1129impl Cheatcode for revertToStateCall {
1130 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1131 let Self { snapshotId } = self;
1132 inner_revert_to_state(ccx, *snapshotId)
1133 }
1134}
1135
1136impl Cheatcode for revertToAndDeleteCall {
1138 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1139 let Self { snapshotId } = self;
1140 inner_revert_to_state_and_delete(ccx, *snapshotId)
1141 }
1142}
1143
1144impl Cheatcode for revertToStateAndDeleteCall {
1145 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1146 let Self { snapshotId } = self;
1147 inner_revert_to_state_and_delete(ccx, *snapshotId)
1148 }
1149}
1150
1151impl Cheatcode for deleteSnapshotCall {
1153 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1154 let Self { snapshotId } = self;
1155 let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
1156 ccx.state.env_overrides.delete_snapshot(*snapshotId);
1157 ccx.state.fork_block_number_override_snapshots.remove(snapshotId);
1158 #[cfg(feature = "monad")]
1159 ccx.state.context_snapshots.remove(snapshotId);
1160 ccx.state.delete_created_accounts_snapshot(*snapshotId);
1161 Ok(result.abi_encode())
1162 }
1163}
1164
1165impl Cheatcode for deleteStateSnapshotCall {
1166 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1167 let Self { snapshotId } = self;
1168 let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
1169 ccx.state.env_overrides.delete_snapshot(*snapshotId);
1170 ccx.state.fork_block_number_override_snapshots.remove(snapshotId);
1171 #[cfg(feature = "monad")]
1172 ccx.state.context_snapshots.remove(snapshotId);
1173 ccx.state.delete_created_accounts_snapshot(*snapshotId);
1174 Ok(result.abi_encode())
1175 }
1176}
1177
1178impl Cheatcode for deleteSnapshotsCall {
1180 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1181 let Self {} = self;
1182 ccx.ecx.db_mut().delete_state_snapshots();
1183 ccx.state.env_overrides.clear_snapshots();
1184 ccx.state.fork_block_number_override_snapshots.clear();
1185 #[cfg(feature = "monad")]
1186 ccx.state.context_snapshots.clear();
1187 ccx.state.clear_created_accounts_snapshots();
1188 Ok(Default::default())
1189 }
1190}
1191
1192impl Cheatcode for deleteStateSnapshotsCall {
1193 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1194 let Self {} = self;
1195 ccx.ecx.db_mut().delete_state_snapshots();
1196 ccx.state.env_overrides.clear_snapshots();
1197 ccx.state.fork_block_number_override_snapshots.clear();
1198 #[cfg(feature = "monad")]
1199 ccx.state.context_snapshots.clear();
1200 ccx.state.clear_created_accounts_snapshots();
1201 Ok(Default::default())
1202 }
1203}
1204
1205impl Cheatcode for startStateDiffRecordingCall {
1206 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1207 let Self {} = self;
1208 ccx.state.start_state_diff_recording();
1209 ccx.state.core.mapping_slots.get_or_insert_default();
1211 Ok(Default::default())
1212 }
1213}
1214
1215impl Cheatcode for stopAndReturnStateDiffCall {
1216 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1217 let Self {} = self;
1218 get_state_diff(ccx.state)
1219 }
1220}
1221
1222impl Cheatcode for getStateDiffCall {
1223 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1224 let mut diffs = String::new();
1225 let state_diffs = get_recorded_state_diffs(ccx);
1226 for (address, state_diffs) in state_diffs {
1227 diffs.push_str(&format!("{address}\n"));
1228 diffs.push_str(&format!("{state_diffs}\n"));
1229 }
1230 Ok(diffs.abi_encode())
1231 }
1232}
1233
1234impl Cheatcode for getStateDiffJsonCall {
1235 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1236 let state_diffs = get_recorded_state_diffs(ccx);
1237 Ok(serde_json::to_string(&state_diffs)?.abi_encode())
1238 }
1239}
1240
1241impl Cheatcode for getStorageSlotsCall {
1242 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1243 let Self { target, variableName } = self;
1244
1245 let storage_layout = get_contract_data(ccx, *target, true)
1246 .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone()))
1247 .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?;
1248
1249 trace!(storage = ?storage_layout.storage, "fetched storage");
1250
1251 let variable_name_lower = variableName.to_lowercase();
1252 let storage = storage_layout
1253 .storage
1254 .iter()
1255 .find(|s| s.label.to_lowercase() == variable_name_lower)
1256 .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
1257
1258 let storage_type = storage_layout
1259 .types
1260 .get(&storage.storage_type)
1261 .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
1262
1263 if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
1264 {
1265 return Err(fmt_err!(
1266 "cannot get storage slots for variables with mapping or dynamic array types"
1267 ));
1268 }
1269
1270 let slot = U256::from_str(&storage.slot).map_err(|_| {
1271 fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
1272 })?;
1273
1274 let mut slots = Vec::new();
1275
1276 slots.push(slot);
1278
1279 if storage_type.encoding == ENCODING_INPLACE {
1280 let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
1282 fmt_err!(
1283 "invalid number_of_bytes {} for variable {variableName}",
1284 storage_type.number_of_bytes
1285 )
1286 })?;
1287 let num_slots = num_bytes.div_ceil(U256::from(32));
1288 let num_slots = usize::try_from(num_slots).map_err(|_| {
1289 fmt_err!("number_of_bytes {} exceeds host usize", storage_type.number_of_bytes)
1290 })?;
1291
1292 for i in 1..num_slots {
1294 slots.push(slot + U256::from(i));
1295 }
1296 }
1297
1298 if storage_type.encoding == ENCODING_BYTES {
1299 if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) {
1302 let value_bytes = value.data.to_be_bytes::<32>();
1303 let length_byte = value_bytes[31];
1304 if length_byte & 1 == 1 {
1306 let length: U256 = value.data >> 1;
1308 let length = usize::try_from(length)
1309 .map_err(|_| fmt_err!("long bytes/string length exceeds host usize"))?;
1310 let num_data_slots = length.div_ceil(32);
1311 let data_start = Into::<U256>::into(keccak256(B256::from(slot).0));
1312
1313 for i in 0..num_data_slots {
1314 slots.push(data_start + U256::from(i));
1315 }
1316 }
1317 }
1318 }
1319
1320 Ok(slots.abi_encode())
1321 }
1322}
1323
1324impl Cheatcode for getStorageAccessesCall {
1325 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1326 let mut storage_accesses = Vec::new();
1327
1328 for account_access in ccx.state.recorded_account_diffs() {
1329 storage_accesses.extend(account_access.storageAccesses.clone());
1330 }
1331
1332 Ok(storage_accesses.abi_encode())
1333 }
1334}
1335
1336impl Cheatcode for broadcastRawTransactionCall {
1337 fn apply_full<FEN: FoundryEvmNetwork>(
1338 &self,
1339 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1340 executor: &mut dyn CheatcodesExecutor<FEN>,
1341 ) -> Result {
1342 let tx = TxEnvelopeFor::<FEN>::decode(&mut self.data.as_ref())
1343 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1344
1345 let sender =
1346 tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1347 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1348 let from = sender;
1349 let contract_address = if tx.kind().is_create() {
1350 let nonce = ccx.ecx.journal_mut().load_account(sender)?.data.info.nonce;
1351 Some(sender.create(nonce))
1352 } else {
1353 None
1354 };
1355
1356 executor.transact_from_tx_on_db(ccx.state, ccx.ecx, tx_env)?;
1357 #[cfg(feature = "monad")]
1358 refresh_chain_journal(ccx.ecx);
1359
1360 if ccx.state.broadcast.is_some() {
1361 ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1362 rpc: ccx.active_fork_url(),
1363 transaction: TransactionMaybeSigned::Signed { tx, from },
1364 contract_address,
1365 });
1366 }
1367
1368 Ok(Default::default())
1369 }
1370}
1371
1372impl Cheatcode for setBlockhashCall {
1373 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1374 let Self { blockNumber, blockHash } = *self;
1375 ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1376 ensure!(
1377 blockNumber <= ccx.block_number(),
1378 "block number must be less than or equal to the current block number"
1379 );
1380
1381 ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash);
1382 let current_block = ccx.block_number();
1383 if ccx.spec().into() >= SpecId::PRAGUE
1384 && blockNumber < current_block
1385 && current_block - blockNumber <= U256::from(HISTORY_SERVE_WINDOW)
1386 {
1387 set_eip2935_blockhash(ccx.ecx, blockNumber, blockHash)?;
1388 }
1389
1390 Ok(Default::default())
1391 }
1392}
1393
1394impl Cheatcode for executeTransactionCall {
1395 fn apply_full<FEN: FoundryEvmNetwork>(
1396 &self,
1397 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1398 executor: &mut dyn CheatcodesExecutor<FEN>,
1399 ) -> Result {
1400 if let Some(ctx) = FORGE_CONTEXT.get()
1402 && *ctx == ForgeContext::ScriptGroup
1403 {
1404 return Err(fmt_err!("executeTransaction is not allowed in forge script"));
1405 }
1406
1407 let tx = TxEnvelopeFor::<FEN>::decode(&mut self.rawTx.as_ref())
1409 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1410
1411 ensure!(
1412 tx.ty() != 0x79,
1413 "EIP-8130 transactions are not supported by vm.executeTransaction"
1414 );
1415
1416 let sender =
1418 tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1419 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1420 let chain_context = ccx.ecx.db().chain_context_for_synthetic_transaction(&tx_env)?;
1421 let created_address = tx_env.kind().is_create().then(|| sender.create(tx_env.nonce()));
1422
1423 let cached_evm_env = ccx.ecx.evm_clone();
1425 let cached_tx_env = ccx.ecx.tx_clone();
1426
1427 ccx.ecx.block_mut().set_basefee(0);
1429 ccx.ecx.set_tx(tx_env);
1430 ccx.ecx.tx_mut().set_gas_price(0);
1431 ccx.ecx.tx_mut().set_gas_priority_fee(None);
1432
1433 ccx.ecx.cfg_env_mut().disable_nonce_check = false;
1435
1436 let code_size_limit = ccx.limit_contract_code_size();
1440 let configured_code_size_limit = ccx.state.core.config.evm_opts.env.code_size_limit;
1441 ccx.ecx.cfg_env_mut().limit_contract_code_size = configured_code_size_limit;
1442 let initcode_size_limit = ccx.max_initcode_size();
1443 ccx.ecx.cfg_env_mut().limit_contract_code_size = code_size_limit;
1444 ccx.ecx.cfg_env_mut().limit_contract_initcode_size = Some(initcode_size_limit);
1445
1446 ccx.ecx.cfg_env_mut().tx_gas_limit_cap = None;
1450
1451 let modified_tx_env = ccx.ecx.tx_clone();
1453
1454 if let Some(address) = created_address {
1455 let fork_id = ccx.active_fork_id();
1456 ccx.state.record_created_account(fork_id, address);
1457 }
1458
1459 let cold_state = prepare_child_state(ccx.ecx.journal_inner());
1461
1462 let mut res = None;
1463 let mut cold_state = Some(cold_state);
1464 let nested_evm_env =
1465 executor.with_fresh_nested_evm(ccx.state, ccx.ecx, chain_context, &mut |evm| {
1466 evm.journal_inner_mut().state = cold_state.take().expect("called once");
1468 res = Some(evm.transact_raw(modified_tx_env.clone()));
1469 Ok(())
1470 });
1471 let mut nested_evm_env = nested_evm_env?;
1472 let res = res.unwrap();
1473
1474 nested_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1478 nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check;
1479 nested_evm_env.cfg_env.limit_contract_initcode_size =
1480 cached_evm_env.cfg_env.limit_contract_initcode_size;
1481 nested_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1482 ccx.ecx.set_evm(nested_evm_env);
1483 ccx.ecx.set_tx(cached_tx_env);
1484
1485 let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?;
1486
1487 merge_child_state(ccx.ecx.journal_mut().evm_state_mut(), res.state, false);
1489
1490 #[cfg(feature = "monad")]
1493 refresh_chain_journal(ccx.ecx);
1494
1495 let output = match res.result {
1497 ExecutionResult::Success { output, .. } => output.into_data(),
1498 ExecutionResult::Halt { reason, .. } => {
1499 return Err(fmt_err!("transaction halted: {reason:?}"));
1500 }
1501 ExecutionResult::Revert { output, .. } => {
1502 return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output)));
1503 }
1504 };
1505
1506 Ok(output.abi_encode())
1507 }
1508}
1509
1510impl Cheatcode for startDebugTraceRecordingCall {
1511 fn apply_full<FEN: FoundryEvmNetwork>(
1512 &self,
1513 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1514 executor: &mut dyn CheatcodesExecutor<FEN>,
1515 ) -> Result {
1516 let Some(tracer) = executor.tracing_inspector() else {
1517 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1518 };
1519
1520 if ccx.state.record_debug_steps_info.is_some() {
1521 bail!("debug trace recording was already started");
1522 }
1523
1524 let mut info = RecordDebugStepInfo {
1525 start_node_idx: 0,
1527 original_tracer_config: *tracer.config(),
1529 };
1530
1531 *tracer.config_mut() =
1533 TraceRequirements::none().with_debug(true).into_config().expect("cannot be None");
1534
1535 if let Some(last_node) = tracer.traces().nodes().last() {
1537 info.start_node_idx = last_node.idx;
1538 }
1539
1540 ccx.state.record_debug_steps_info = Some(info);
1541 Ok(Default::default())
1542 }
1543}
1544
1545impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1546 fn apply_full<FEN: FoundryEvmNetwork>(
1547 &self,
1548 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1549 executor: &mut dyn CheatcodesExecutor<FEN>,
1550 ) -> Result {
1551 let Some(tracer) = executor.tracing_inspector() else {
1552 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1553 };
1554
1555 let Some(record_info) = ccx.state.record_debug_steps_info else {
1556 return Err(Error::from("nothing recorded"));
1557 };
1558
1559 let root = tracer.traces();
1561 let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1562
1563 let debug_steps: Vec<DebugStep> =
1564 steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1565 if !record_info.original_tracer_config.record_steps {
1567 tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1568 node.trace.steps = Vec::new();
1569 node.logs = Vec::new();
1570 node.ordering = Vec::new();
1571 });
1572 }
1573
1574 tracer.update_config(|_config| record_info.original_tracer_config);
1576
1577 ccx.state.record_debug_steps_info = None;
1579
1580 Ok(debug_steps.abi_encode())
1581 }
1582}
1583
1584impl Cheatcode for setEvmVersionCall {
1585 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1586 let Self { evm } = self;
1587 let spec_id = evm_spec_id_from_str(evm)
1588 .ok_or_else(|| Error::from(format!("invalid evm version {evm}")))?;
1589 ccx.state.execution_evm_version = Some(spec_id);
1590 Ok(Default::default())
1591 }
1592}
1593
1594impl Cheatcode for getEvmVersionCall {
1595 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1596 let spec = ccx.spec();
1597 Ok(spec.evm_version_name().to_lowercase().abi_encode())
1598 }
1599}
1600
1601pub(super) fn get_nonce<FEN: FoundryEvmNetwork>(
1602 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1603 address: &Address,
1604) -> Result {
1605 let account = ccx.ecx.journal_mut().load_account(*address)?;
1606 Ok(account.data.info.nonce.abi_encode())
1607}
1608
1609fn inner_snapshot_state<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1610 let evm_env = ccx.ecx.evm_clone();
1611 let journaled_state = ccx.ecx.journal_inner().clone();
1612 let id = ccx.ecx.db_mut().snapshot_state(&journaled_state, &evm_env);
1613 let fork_id = ccx.active_fork_id();
1614 let tx_gas_price = ccx.tx_gas_price();
1615 let tx_type = ccx.tx_type();
1616 let tx_blob_hashes = ccx.tx_blob_hashes().to_vec();
1617 ccx.state.env_overrides.save_snapshot(id, fork_id, tx_gas_price, tx_type, &tx_blob_hashes);
1618 ccx.state.fork_block_number_override_snapshots.insert(id, ccx.state.fork_block_number_override);
1619 #[cfg(feature = "monad")]
1620 {
1621 use foundry_evm_core::FoundryJournal as _;
1622
1623 ccx.state
1624 .context_snapshots
1625 .insert(id, (ccx.ecx.chain().clone(), ccx.ecx.journal().capture_reserve_balance()));
1626 }
1627 ccx.state.snapshot_created_accounts(id, fork_id);
1628 Ok(id.abi_encode())
1629}
1630
1631fn sync_tx_after_env_override_restore<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) {
1639 let fork_id = ccx.active_fork_id();
1640 let env_overrides = ccx.state.env_overrides.get(fork_id).cloned().unwrap_or_default();
1642 match env_overrides.gas_price {
1643 Some(p) if !ccx.state.in_isolation_context => ccx.ecx.tx_mut().set_gas_price(p),
1644 None => {
1645 let pre = env_overrides.pre_override_gas_price.unwrap_or(0);
1649 ccx.ecx.tx_mut().set_gas_price(pre);
1650 }
1651 _ => {}
1652 }
1653 match env_overrides.blob_hashes {
1654 Some(hashes) if !ccx.state.in_isolation_context => {
1655 ccx.ecx.tx_mut().set_blob_hashes(hashes);
1656 ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
1657 }
1658 None => {
1659 let pre_hashes = env_overrides.pre_override_blob_hashes.unwrap_or_default();
1661 ccx.ecx.tx_mut().set_blob_hashes(pre_hashes);
1662 let pre_type = env_overrides.pre_override_tx_type.unwrap_or(0);
1665 ccx.ecx.tx_mut().set_tx_type(pre_type);
1666 }
1667 _ => {}
1668 }
1669 ccx.state.env_overrides.remove_if_unset(fork_id);
1670}
1671
1672fn restore_isolation_fee_accounting<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) {
1673 if !ccx.state.in_isolation_context {
1674 return;
1675 }
1676
1677 let basefee = ccx.basefee();
1678 if basefee != 0 {
1679 let fork_id = ccx.active_fork_id();
1680 ccx.state.env_overrides.update(fork_id, |o| {
1681 o.implicit_basefee.get_or_insert(basefee);
1682 });
1683 ccx.ecx.block_mut().set_basefee(0);
1684 }
1685 ccx.ecx.chain_mut().clear_transaction_fee_cache();
1686}
1687
1688fn track_snapshot_restore<FEN: FoundryEvmNetwork>(
1691 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1692 journaled_state: JournaledState,
1693 restored: &JournaledState,
1694) {
1695 if !ccx.state.track_isolated_snapshots {
1696 return;
1697 }
1698 if std::mem::take(&mut ccx.state.capture_isolated_snapshot_restore) {
1699 ccx.state.isolated_snapshot_restores.push(journaled_state);
1700 }
1701 if ccx.state.in_isolation_context {
1703 ccx.state.pending_isolated_snapshot_journal = Some(restored.journal.clone());
1704 }
1705}
1706
1707fn inner_revert_to_state<FEN: FoundryEvmNetwork>(
1708 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1709 snapshot_id: U256,
1710) -> Result {
1711 let mut evm_env = ccx.ecx.evm_clone();
1712 let caller = ccx.tx_caller();
1713 let journaled_state = ccx.ecx.journal_inner().clone();
1714 if let Some(restored) = ccx.ecx.db_mut().revert_state(
1715 snapshot_id,
1716 &journaled_state,
1717 &mut evm_env,
1718 caller,
1719 RevertStateSnapshotAction::RevertKeep,
1720 ) {
1721 track_snapshot_restore(ccx, journaled_state, &restored);
1722 ccx.ecx.set_journal_inner(restored);
1723 #[cfg(feature = "monad")]
1724 {
1725 use foundry_evm_core::FoundryJournal as _;
1726
1727 if let Some((context, state)) = ccx.state.context_snapshots.get(&snapshot_id) {
1728 *ccx.ecx.chain_mut() = context.clone();
1729 ccx.ecx.journal_mut().restore_reserve_balance(state.clone());
1730 }
1731 refresh_chain_journal(ccx.ecx);
1732 }
1733 ccx.ecx.set_evm(evm_env);
1734 ccx.state.env_overrides.restore_snapshot(snapshot_id, false);
1737 if let Some(&fork_block_number) =
1738 ccx.state.fork_block_number_override_snapshots.get(&snapshot_id)
1739 {
1740 ccx.state.fork_block_number_override = fork_block_number;
1741 }
1742 ccx.state.revert_created_accounts(snapshot_id, false);
1743 sync_tx_after_env_override_restore(ccx);
1744 restore_isolation_fee_accounting(ccx);
1745 Ok(true.abi_encode())
1746 } else {
1747 Ok(false.abi_encode())
1748 }
1749}
1750
1751fn inner_revert_to_state_and_delete<FEN: FoundryEvmNetwork>(
1752 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1753 snapshot_id: U256,
1754) -> Result {
1755 let mut evm_env = ccx.ecx.evm_clone();
1756 let caller = ccx.tx_caller();
1757 let journaled_state = ccx.ecx.journal_inner().clone();
1758 if let Some(restored) = ccx.ecx.db_mut().revert_state(
1759 snapshot_id,
1760 &journaled_state,
1761 &mut evm_env,
1762 caller,
1763 RevertStateSnapshotAction::RevertRemove,
1764 ) {
1765 track_snapshot_restore(ccx, journaled_state, &restored);
1766 ccx.ecx.set_journal_inner(restored);
1767 #[cfg(feature = "monad")]
1768 {
1769 use foundry_evm_core::FoundryJournal as _;
1770
1771 if let Some((context, state)) = ccx.state.context_snapshots.remove(&snapshot_id) {
1772 *ccx.ecx.chain_mut() = context;
1773 ccx.ecx.journal_mut().restore_reserve_balance(state);
1774 }
1775 refresh_chain_journal(ccx.ecx);
1776 }
1777 ccx.ecx.set_evm(evm_env);
1778 ccx.state.env_overrides.restore_snapshot(snapshot_id, true);
1779 if let Some(fork_block_number) =
1780 ccx.state.fork_block_number_override_snapshots.remove(&snapshot_id)
1781 {
1782 ccx.state.fork_block_number_override = fork_block_number;
1783 }
1784 ccx.state.revert_created_accounts(snapshot_id, true);
1785 sync_tx_after_env_override_restore(ccx);
1786 restore_isolation_fee_accounting(ccx);
1787 Ok(true.abi_encode())
1788 } else {
1789 Ok(false.abi_encode())
1790 }
1791}
1792
1793fn inner_value_snapshot<FEN: FoundryEvmNetwork>(
1794 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1795 group: Option<String>,
1796 name: Option<String>,
1797 value: String,
1798) -> Result {
1799 let (group, name) = derive_snapshot_name(ccx, group, name);
1800
1801 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1802
1803 Ok(Default::default())
1804}
1805
1806fn inner_last_gas_snapshot<FEN: FoundryEvmNetwork>(
1807 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1808 group: Option<String>,
1809 name: Option<String>,
1810 value: u64,
1811) -> Result {
1812 let (group, name) = derive_snapshot_name(ccx, group, name);
1813
1814 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1815
1816 Ok(value.abi_encode())
1817}
1818
1819fn inner_start_gas_snapshot<FEN: FoundryEvmNetwork>(
1820 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1821 group: Option<String>,
1822 name: Option<String>,
1823) -> Result {
1824 if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1826 bail!("gas snapshot was already started with group: {group} and name: {name}");
1827 }
1828
1829 let (group, name) = derive_snapshot_name(ccx, group, name);
1830
1831 ccx.state.gas_metering.gas_records.push(GasRecord {
1832 group: group.clone(),
1833 name: name.clone(),
1834 gas_used: 0,
1835 depth: ccx.depth(),
1836 });
1837
1838 ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1839
1840 ccx.state.gas_metering.start();
1841
1842 Ok(Default::default())
1843}
1844
1845fn inner_stop_gas_snapshot<FEN: FoundryEvmNetwork>(
1846 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1847 group: Option<String>,
1848 name: Option<String>,
1849) -> Result {
1850 let (group, name) = group
1852 .zip(name)
1853 .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone())
1854 .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?;
1855
1856 if let Some(record) = ccx
1857 .state
1858 .gas_metering
1859 .gas_records
1860 .iter_mut()
1861 .find(|record| record.group == group && record.name == name)
1862 {
1863 let value = record.gas_used.saturating_sub(171);
1866
1867 ccx.state
1868 .gas_snapshots
1869 .entry(group.clone())
1870 .or_default()
1871 .insert(name.clone(), value.to_string());
1872
1873 ccx.state.gas_metering.stop();
1875
1876 ccx.state
1878 .gas_metering
1879 .gas_records
1880 .retain(|record| record.group != group && record.name != name);
1881
1882 if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1884 && snapshot_group == &group
1885 && snapshot_name == &name
1886 {
1887 ccx.state.gas_metering.active_gas_snapshot = None;
1888 }
1889
1890 Ok(value.abi_encode())
1891 } else {
1892 bail!("no gas snapshot was started with the name: {name} in group: {group}");
1893 }
1894}
1895
1896fn derive_snapshot_name<FEN: FoundryEvmNetwork>(
1898 ccx: &CheatsCtxt<'_, '_, FEN>,
1899 group: Option<String>,
1900 name: Option<String>,
1901) -> (String, String) {
1902 let group = group.unwrap_or_else(|| {
1903 ccx.state.core.config.running_artifact.clone().expect("expected running contract").name
1904 });
1905 let name = name.unwrap_or_else(|| "default".to_string());
1906 (group, name)
1907}
1908
1909fn read_callers<FEN: FoundryEvmNetwork>(
1933 state: &Cheatcodes<FEN>,
1934 default_sender: &Address,
1935 call_depth: usize,
1936) -> Result {
1937 let mut mode = CallerMode::None;
1938 let mut new_caller = default_sender;
1939 let mut new_origin = default_sender;
1940 if let Some(prank) = state.get_prank(call_depth) {
1941 mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1942 new_caller = &prank.new_caller;
1943 if let Some(new) = &prank.new_origin {
1944 new_origin = new;
1945 }
1946 } else if let Some(broadcast) = &state.broadcast {
1947 mode = if broadcast.single_call {
1948 CallerMode::Broadcast
1949 } else {
1950 CallerMode::RecurrentBroadcast
1951 };
1952 new_caller = &broadcast.new_origin;
1953 new_origin = &broadcast.new_origin;
1954 }
1955
1956 Ok((mode, new_caller, new_origin).abi_encode_params())
1957}
1958
1959pub(super) fn journaled_account<
1961 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1962>(
1963 ecx: &mut CTX,
1964 addr: Address,
1965) -> Result<&mut Account> {
1966 ensure_loaded_account(ecx, addr)?;
1967 Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded"))
1968}
1969
1970pub(super) fn ensure_loaded_account<CTX: ContextTr<Db: Database<Error = DatabaseError>>>(
1971 ecx: &mut CTX,
1972 addr: Address,
1973) -> Result<()> {
1974 ecx.journal_mut().load_account(addr)?;
1975 ecx.journal_mut().touch_account(addr);
1976 Ok(())
1977}
1978
1979fn set_eip2935_blockhash<
1980 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1981>(
1982 ecx: &mut CTX,
1983 block_number: U256,
1984 block_hash: B256,
1985) -> Result<()> {
1986 let account_was_cold = ecx.journal_mut().load_account(HISTORY_STORAGE_ADDRESS)?.is_cold;
1987 let account =
1988 ecx.journal_mut().evm_state().get(&HISTORY_STORAGE_ADDRESS).expect("account is loaded");
1989 if account.info.code_hash() != keccak256(&HISTORY_STORAGE_CODE) {
1990 restore_eip2935_cold_state(ecx, account_was_cold, None);
1991 return Ok(());
1992 }
1993
1994 let slot = history_storage_slot(block_number);
1995 let slot_was_cold = ecx
1996 .journal_mut()
1997 .sstore(HISTORY_STORAGE_ADDRESS, slot, history_storage_value(block_hash))
1998 .map_err(|e| fmt_err!("failed to store EIP-2935 history slot: {:?}", e))?
1999 .is_cold;
2000 restore_eip2935_cold_state(ecx, account_was_cold, Some((slot, slot_was_cold)));
2001 Ok(())
2002}
2003
2004fn clear_eip2935_history_storage<
2010 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
2011>(
2012 ecx: &mut CTX,
2013) -> Result<()> {
2014 let checkpoint = ecx.journal_mut().checkpoint();
2015 for slot in 0..HISTORY_SERVE_WINDOW {
2016 let slot = U256::from(slot);
2017 match ecx.journal_mut().sstore(HISTORY_STORAGE_ADDRESS, slot, U256::ZERO) {
2018 Ok(result) if result.is_cold => {
2019 if let Some(storage_slot) = ecx
2020 .journal_mut()
2021 .evm_state_mut()
2022 .get_mut(&HISTORY_STORAGE_ADDRESS)
2023 .and_then(|account| account.storage.get_mut(&slot))
2024 {
2025 storage_slot.is_cold = true;
2026 }
2027 }
2028 Ok(_) => {}
2029 Err(e) => {
2030 ecx.journal_mut().checkpoint_revert(checkpoint);
2031 return Err(fmt_err!("failed to clear EIP-2935 history slot: {e:?}"));
2032 }
2033 }
2034 }
2035 ecx.journal_mut().checkpoint_commit();
2036 Ok(())
2037}
2038
2039fn restore_eip2935_cold_state<
2040 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
2041>(
2042 ecx: &mut CTX,
2043 account_was_cold: bool,
2044 slot_state: Option<(U256, bool)>,
2045) {
2046 let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&HISTORY_STORAGE_ADDRESS) else {
2047 return;
2048 };
2049 if account_was_cold {
2050 account.mark_cold();
2051 }
2052 if let Some((slot, slot_was_cold)) = slot_state
2053 && slot_was_cold
2054 && let Some(storage_slot) = account.storage.get_mut(&slot)
2055 {
2056 storage_slot.is_cold = true;
2057 }
2058}
2059
2060const TIP20_LOGO_URI_SLOT_INDEX: u64 = 5;
2065
2066fn set_tip20_logo_uri<FEN: FoundryEvmNetwork>(
2067 ccx: &mut CheatsCtxt<'_, '_, FEN>,
2068 token: &Address,
2069 new_logo_uri: &str,
2070) -> Result {
2071 validate_tip20_logo_uri(new_logo_uri).map_err(|err| match err {
2072 Tip20LogoUriValidationError::LogoURITooLong => fmt_err!("LogoURITooLong"),
2073 Tip20LogoUriValidationError::InvalidLogoURI => fmt_err!("InvalidLogoURI"),
2074 })?;
2075 ccx.ensure_not_precompile(token)?;
2076 ensure_loaded_account(ccx.ecx, *token)?;
2077 store_solidity_string(
2078 ccx.ecx,
2079 *token,
2080 U256::from(TIP20_LOGO_URI_SLOT_INDEX),
2081 new_logo_uri.as_bytes(),
2082 )
2083}
2084
2085fn store_solidity_string<CTX>(
2086 ecx: &mut CTX,
2087 target: Address,
2088 base_slot: U256,
2089 bytes: &[u8],
2090) -> Result
2091where
2092 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
2093{
2094 cleanup_long_string_tail(ecx, target, base_slot, bytes.len())?;
2095
2096 if bytes.len() <= 31 {
2097 ecx.journal_mut()
2098 .sstore(target, base_slot, encode_short_string(bytes))
2099 .map_err(|e| fmt_err!("failed to store TIP-20 logo URI: {:?}", e))?;
2100 return Ok(Default::default());
2101 }
2102
2103 ecx.journal_mut()
2104 .sstore(target, base_slot, U256::from(bytes.len() * 2 + 1))
2105 .map_err(|e| fmt_err!("failed to store TIP-20 logo URI length: {:?}", e))?;
2106
2107 let slot_start = solidity_dynamic_data_slot(base_slot);
2108 for (index, chunk) in bytes.chunks(32).enumerate() {
2109 let mut chunk_bytes = [0u8; 32];
2110 chunk_bytes[..chunk.len()].copy_from_slice(chunk);
2111 ecx.journal_mut()
2112 .sstore(target, slot_start + U256::from(index), U256::from_be_bytes(chunk_bytes))
2113 .map_err(|e| fmt_err!("failed to store TIP-20 logo URI data: {:?}", e))?;
2114 }
2115
2116 Ok(Default::default())
2117}
2118
2119fn cleanup_long_string_tail<CTX>(
2120 ecx: &mut CTX,
2121 target: Address,
2122 base_slot: U256,
2123 new_len: usize,
2124) -> Result<()>
2125where
2126 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
2127{
2128 let previous = ecx
2129 .journal_mut()
2130 .sload(target, base_slot)
2131 .map_err(|e| fmt_err!("failed to load previous TIP-20 logo URI: {:?}", e))?
2132 .data;
2133 if !is_long_string(previous) {
2134 return Ok(());
2135 }
2136
2137 let Some(previous_len) = long_string_length(previous) else {
2138 return Ok(());
2139 };
2140 let previous_chunks = string_chunks(previous_len);
2141 let new_chunks = if new_len > 31 { string_chunks(new_len) } else { 0 };
2142 if previous_chunks <= new_chunks {
2143 return Ok(());
2144 }
2145
2146 let slot_start = solidity_dynamic_data_slot(base_slot);
2147 for index in new_chunks..previous_chunks {
2148 ecx.journal_mut()
2149 .sstore(target, slot_start + U256::from(index), U256::ZERO)
2150 .map_err(|e| fmt_err!("failed to clear previous TIP-20 logo URI data: {:?}", e))?;
2151 }
2152
2153 Ok(())
2154}
2155
2156fn encode_short_string(bytes: &[u8]) -> U256 {
2157 let mut storage_bytes = [0u8; 32];
2158 storage_bytes[..bytes.len()].copy_from_slice(bytes);
2159 storage_bytes[31] =
2160 u8::try_from(bytes.len() * 2).expect("short Solidity string length tag fits in u8");
2161 U256::from_be_bytes(storage_bytes)
2162}
2163
2164fn solidity_dynamic_data_slot(base_slot: U256) -> U256 {
2165 Into::<U256>::into(keccak256(base_slot.to_be_bytes::<32>()))
2166}
2167
2168const fn is_long_string(slot_value: U256) -> bool {
2169 (slot_value.to_be_bytes::<32>()[31] & 1) != 0
2170}
2171
2172fn long_string_length(slot_value: U256) -> Option<usize> {
2173 let length: U256 = (slot_value - U256::ONE) >> 1;
2174 usize::try_from(length).ok().filter(|length| *length <= TIP20_MAX_LOGO_URI_BYTES)
2175}
2176
2177const fn string_chunks(byte_length: usize) -> usize {
2178 byte_length.div_ceil(32)
2179}
2180
2181fn get_state_diff<FEN: FoundryEvmNetwork>(state: &mut Cheatcodes<FEN>) -> Result {
2189 let mut res = state.take_recorded_account_diffs_prefix();
2190 res.extend(
2191 state
2192 .recorded_account_diffs_stack
2193 .replace(Default::default())
2194 .unwrap_or_default()
2195 .into_iter()
2196 .flatten(),
2197 );
2198 Ok(res.abi_encode())
2199}
2200
2201pub(crate) fn mark_account_accesses_reverted(accesses: &mut [AccountAccess]) {
2203 for access in accesses {
2204 access.reverted = true;
2205 for storage_access in &mut access.storageAccesses {
2206 storage_access.reverted = true;
2207 }
2208 }
2209}
2210
2211pub(crate) fn merge_recorded_frame(
2214 stack: &mut Vec<Vec<AccountAccess>>,
2215 mut accesses: Vec<AccountAccess>,
2216) {
2217 if let Some(parent) = stack.last_mut() {
2218 parent.append(&mut accesses);
2219 } else {
2220 stack.push(accesses);
2221 }
2222}
2223
2224pub(crate) fn append_storage_access(
2226 last: &mut Vec<AccountAccess>,
2227 storage_access: crate::Vm::StorageAccess,
2228 storage_depth: u64,
2229) {
2230 if !last.is_empty() && last.first().unwrap().depth < storage_depth {
2232 if last.len() == 1 {
2238 last.first_mut().unwrap().storageAccesses.push(storage_access);
2239 } else {
2240 let last_record = last.last_mut().unwrap();
2241 if last_record.kind as u8 == crate::Vm::AccountAccessKind::Resume as u8 {
2242 last_record.storageAccesses.push(storage_access);
2243 } else {
2244 let entry = last.first().unwrap();
2245 let resume_record = crate::Vm::AccountAccess {
2246 chainInfo: crate::Vm::ChainInfo {
2247 forkId: entry.chainInfo.forkId,
2248 chainId: entry.chainInfo.chainId,
2249 },
2250 accessor: entry.accessor,
2251 account: entry.account,
2252 kind: crate::Vm::AccountAccessKind::Resume,
2253 initialized: entry.initialized,
2254 storageAccesses: vec![storage_access],
2255 reverted: entry.reverted,
2256 oldBalance: U256::ZERO,
2258 newBalance: U256::ZERO,
2259 oldNonce: 0,
2260 newNonce: 0,
2261 value: U256::ZERO,
2262 data: Bytes::new(),
2263 deployedCode: Bytes::new(),
2264 depth: entry.depth,
2265 };
2266 last.push(resume_record);
2267 }
2268 }
2269 }
2270}
2271
2272fn genesis_account(account: &Account) -> GenesisAccount {
2274 GenesisAccount {
2275 nonce: Some(account.info.nonce),
2276 balance: account.info.balance,
2277 code: account.info.code.as_ref().map(|o| o.original_bytes()),
2278 storage: Some(
2279 account
2280 .storage
2281 .iter()
2282 .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
2283 .collect(),
2284 ),
2285 private_key: None,
2286 }
2287}
2288
2289fn get_recorded_state_diffs<FEN: FoundryEvmNetwork>(
2291 ccx: &mut CheatsCtxt<'_, '_, FEN>,
2292) -> BTreeMap<Address, AccountStateDiffs> {
2293 let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
2294
2295 let mut addresses_to_lookup = AddressSet::default();
2297 let mut layout_sources = AddressMap::default();
2300 for account_access in ccx.state.recorded_account_diffs() {
2301 if !account_access.storageAccesses.is_empty()
2302 || account_access.oldBalance != account_access.newBalance
2303 {
2304 addresses_to_lookup.insert(account_access.account);
2305 for storage_access in &account_access.storageAccesses {
2306 if storage_access.isWrite && !storage_access.reverted {
2307 addresses_to_lookup.insert(storage_access.account);
2308 let source = (
2309 account_access.chainInfo.chainId.to::<u64>(),
2310 account_access.chainInfo.forkId,
2311 account_access.account,
2312 );
2313 if let Some(existing) = layout_sources.get_mut(&storage_access.account) {
2314 if *existing != Some(source) {
2315 *existing = None;
2319 }
2320 } else {
2321 layout_sources.insert(storage_access.account, Some(source));
2322 }
2323 }
2324 }
2325 }
2326 }
2327
2328 let mut contract_names = AddressMap::default();
2330 let mut storage_layouts = AddressMap::default();
2331 for address in addresses_to_lookup {
2332 if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address, false) {
2333 contract_names.insert(address, artifact_id.identifier());
2334
2335 if let Some(storage_layout) = &contract_data.storage_layout {
2337 storage_layouts.insert(address, storage_layout.clone());
2338 }
2339 }
2340 }
2341
2342 if ccx.state.core.config.decode_external_storage {
2346 let current_chain_id = ccx.chain_id();
2347 let current_fork_id = ccx.active_fork_id().unwrap_or_default();
2348 let mut external_requests = BTreeMap::<u64, AddressSet>::new();
2349 let mut external_targets = Vec::new();
2350 for (storage_address, source) in layout_sources {
2351 let Some((chain_id, fork_id, code_address)) = source else {
2352 contract_names.remove(&storage_address);
2355 storage_layouts.remove(&storage_address);
2356 continue;
2357 };
2358 let recorded_context_is_current =
2359 chain_id == current_chain_id && fork_id == current_fork_id;
2360 if !recorded_context_is_current {
2361 contract_names.remove(&storage_address);
2362 }
2363 if recorded_context_is_current
2364 && code_address == storage_address
2365 && storage_layouts.contains_key(&storage_address)
2366 {
2367 continue;
2368 }
2369
2370 storage_layouts.remove(&storage_address);
2373 if recorded_context_is_current
2374 && let Some((artifact_id, contract_data)) =
2375 get_contract_data(ccx, code_address, false)
2376 && let Some(layout) = &contract_data.storage_layout
2377 {
2378 contract_names.insert(storage_address, artifact_id.identifier());
2379 storage_layouts.insert(storage_address, layout.clone());
2380 } else {
2381 external_requests.entry(chain_id).or_default().insert(code_address);
2382 external_targets.push((storage_address, chain_id, code_address));
2383 }
2384 }
2385
2386 for (chain_id, addresses) in external_requests {
2387 let external = crate::external_storage::storage_layouts(
2388 &ccx.state.core.config.external_sources,
2389 foundry_config::Chain::from(chain_id),
2390 addresses.into_iter().collect(),
2391 );
2392 for &(storage_address, target_chain_id, code_address) in &external_targets {
2393 if target_chain_id == chain_id
2394 && let Some((name, layout)) = external.get(&code_address)
2395 {
2396 contract_names.insert(storage_address, name.clone());
2397 storage_layouts.insert(storage_address, layout.clone());
2398 }
2399 }
2400 }
2401 }
2402
2403 ccx.state
2405 .recorded_account_diffs()
2406 .filter(|account_access| {
2407 !account_access.storageAccesses.is_empty()
2408 || account_access.oldBalance != account_access.newBalance
2409 || (!account_access.reverted && account_access.oldNonce != account_access.newNonce)
2410 })
2411 .for_each(|account_access| {
2412 if account_access.oldBalance != account_access.newBalance {
2414 let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| {
2415 AccountStateDiffs {
2416 label: ccx.state.core.labels.get(&account_access.account).cloned(),
2417 contract: contract_names.get(&account_access.account).cloned(),
2418 ..Default::default()
2419 }
2420 });
2421 if let Some(diff) = &mut account_diff.balance_diff {
2423 diff.new_value = account_access.newBalance;
2424 } else {
2425 account_diff.balance_diff = Some(BalanceDiff {
2426 previous_value: account_access.oldBalance,
2427 new_value: account_access.newBalance,
2428 });
2429 }
2430 }
2431
2432 if account_access.oldNonce != account_access.newNonce && !account_access.reverted {
2434 let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| {
2435 AccountStateDiffs {
2436 label: ccx.state.core.labels.get(&account_access.account).cloned(),
2437 contract: contract_names.get(&account_access.account).cloned(),
2438 ..Default::default()
2439 }
2440 });
2441 if let Some(diff) = &mut account_diff.nonce_diff {
2443 diff.new_value = account_access.newNonce;
2444 } else {
2445 account_diff.nonce_diff = Some(NonceDiff {
2446 previous_value: account_access.oldNonce,
2447 new_value: account_access.newNonce,
2448 });
2449 }
2450 }
2451
2452 let raw_changes_by_slot = account_access
2454 .storageAccesses
2455 .iter()
2456 .filter_map(|access| {
2457 (access.isWrite && !access.reverted)
2458 .then_some((access.slot, (access.previousValue, access.newValue)))
2459 })
2460 .collect::<BTreeMap<_, _>>();
2461
2462 for storage_access in &account_access.storageAccesses {
2464 if storage_access.isWrite && !storage_access.reverted {
2465 let account_diff =
2466 state_diffs.entry(storage_access.account).or_insert_with(|| {
2467 AccountStateDiffs {
2468 label: ccx.state.core.labels.get(&storage_access.account).cloned(),
2469 contract: contract_names.get(&storage_access.account).cloned(),
2470 ..Default::default()
2471 }
2472 });
2473 let layout = storage_layouts.get(&storage_access.account);
2474 let entry = match account_diff.state_diff.entry(storage_access.slot) {
2476 Entry::Vacant(slot_state_diff) => {
2477 let mapping_slots = ccx
2480 .state
2481 .core
2482 .mapping_slots
2483 .as_ref()
2484 .and_then(|slots| slots.get(&storage_access.account));
2485
2486 let slot_info = layout.and_then(|layout| {
2487 let decoder = SlotIdentifier::new(layout.clone());
2488 decoder.identify(&storage_access.slot, mapping_slots).or_else(
2489 || {
2490 let current_base_slot_values = raw_changes_by_slot
2495 .iter()
2496 .map(|(slot, (_, new_val))| (*slot, *new_val))
2497 .collect::<B256Map<_>>();
2498 decoder.identify_bytes_or_string(
2499 &storage_access.slot,
2500 ¤t_base_slot_values,
2501 )
2502 },
2503 )
2504 });
2505
2506 slot_state_diff.insert(SlotStateDiff {
2507 previous_value: storage_access.previousValue,
2508 new_value: storage_access.newValue,
2509 slot_info,
2510 })
2511 }
2512 Entry::Occupied(slot_state_diff) => {
2513 let entry = slot_state_diff.into_mut();
2514 entry.new_value = storage_access.newValue;
2515 entry
2516 }
2517 };
2518
2519 if let Some(slot_info) = &mut entry.slot_info {
2521 slot_info.decode_values(entry.previous_value, storage_access.newValue);
2522 if slot_info.is_bytes_or_string() {
2523 slot_info.decode_bytes_or_string_values(
2524 &storage_access.slot,
2525 &raw_changes_by_slot,
2526 );
2527 }
2528 }
2529 }
2530 }
2531 });
2532 state_diffs
2533}
2534
2535const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
2537
2538const EIP1822_PROXIABLE_SLOT: &str =
2540 "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
2541
2542fn get_contract_data<'a, FEN: FoundryEvmNetwork>(
2544 ccx: &'a mut CheatsCtxt<'_, '_, FEN>,
2545 address: Address,
2546 detect_proxy: bool,
2547) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
2548 let artifacts = ccx.state.core.config.available_artifacts.as_ref()?;
2550
2551 let account = ccx.ecx.journal_mut().load_account(address).ok()?;
2553 let code = account.data.info.code.as_ref()?;
2554
2555 if code.is_empty() {
2557 return None;
2558 }
2559
2560 let code_bytes = code.original_bytes();
2562 if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
2563 return Some(result);
2564 }
2565
2566 let hex_str = hex::encode(&code_bytes);
2568 let find_by_suffix =
2569 |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
2570 if detect_proxy
2571 && hex_str.contains(EIP1967_IMPL_SLOT)
2572 && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
2573 {
2574 return Some(result);
2575 } else if detect_proxy
2576 && hex_str.contains(EIP1822_PROXIABLE_SLOT)
2577 && let Some(result) = find_by_suffix(":UUPSUpgradeable")
2578 {
2579 return Some(result);
2580 }
2581
2582 artifacts.find_by_deployed_code(&code_bytes)
2584}
2585
2586fn set_cold_slot<FEN: FoundryEvmNetwork>(
2588 ccx: &mut CheatsCtxt<'_, '_, FEN>,
2589 target: Address,
2590 slot: U256,
2591 cold: bool,
2592) {
2593 if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target)
2594 && let Some(storage_slot) = account.storage.get_mut(&slot)
2595 {
2596 storage_slot.is_cold = cold;
2597 }
2598}
2599
2600#[cfg(test)]
2601mod tests {
2602 use super::*;
2603 use foundry_compilers::artifacts::StorageLayout;
2604 use std::sync::Arc;
2605
2606 #[test]
2607 fn state_diff_preserves_raw_values_with_partial_decoding() {
2608 let layout = serde_json::from_value::<StorageLayout>(serde_json::json!({
2609 "storage": [
2610 { "astId": 1, "contract": "C", "label": "data", "offset": 0, "slot": "0", "type": "t_struct" }
2611 ],
2612 "types": {
2613 "t_struct": {
2614 "encoding": "inplace", "label": "struct C.Data", "numberOfBytes": "32",
2615 "members": [
2616 { "astId": 2, "contract": "C", "label": "amount", "offset": 0, "slot": "0", "type": "t_amount" },
2617 { "astId": 3, "contract": "C", "label": "paused", "offset": 16, "slot": "0", "type": "t_bool" }
2618 ]
2619 },
2620 "t_amount": { "encoding": "inplace", "label": "Amount", "numberOfBytes": "16" },
2621 "t_bool": { "encoding": "inplace", "label": "bool", "numberOfBytes": "1" }
2622 }
2623 }))
2624 .unwrap();
2625 let identifier = SlotIdentifier::new(Arc::new(layout));
2626 let mut slot_info = identifier.identify(&B256::ZERO, None).unwrap();
2627 let new_value = B256::from(U256::from(1));
2628 slot_info.decode_values(B256::ZERO, new_value);
2629 let diffs = AccountStateDiffs {
2630 state_diff: BTreeMap::from([(
2631 B256::ZERO,
2632 SlotStateDiff { previous_value: B256::ZERO, new_value, slot_info: Some(slot_info) },
2633 )]),
2634 ..Default::default()
2635 };
2636
2637 assert_eq!(
2638 diffs.to_string(),
2639 "- state diff:\n\
2640@ 0x0000000000000000000000000000000000000000000000000000000000000000 (data, (bool,)): 0x0000000000000000000000000000000000000000000000000000000000000000 → 0x0000000000000000000000000000000000000000000000000000000000000001\n paused (bool): false → false\n"
2641 );
2642 }
2643}