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