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