1use crate::{
4 BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result,
5 Vm::*, inspector::RecordDebugStepInfo,
6};
7use alloy_consensus::transaction::SignerRecoverable;
8use alloy_evm::FromRecoveredTx;
9use alloy_genesis::{Genesis, GenesisAccount};
10use alloy_network::eip2718::EIP4844_TX_TYPE_ID;
11use alloy_primitives::{
12 Address, B256, U256, hex, keccak256,
13 map::{B256Map, HashMap},
14};
15use alloy_rlp::Decodable;
16use alloy_sol_types::SolValue;
17use foundry_common::{
18 TransactionMaybeSigned,
19 fs::{read_json_file, write_json_file},
20 slot_identifier::{
21 ENCODING_BYTES, ENCODING_DYN_ARRAY, ENCODING_INPLACE, ENCODING_MAPPING, SlotIdentifier,
22 SlotInfo,
23 },
24};
25use foundry_compilers::artifacts::EvmVersion;
26use foundry_evm_core::{
27 FoundryBlock, FoundryTransaction,
28 backend::{DatabaseError, DatabaseExt, RevertStateSnapshotAction},
29 constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
30 env::FoundryContextExt,
31 evm::{FoundryEvmNetwork, TxEnvFor, TxEnvelopeFor},
32 utils::get_blob_base_fee_update_fraction_by_spec_id,
33};
34use foundry_evm_traces::TraceMode;
35use itertools::Itertools;
36use rand::Rng;
37use revm::{
38 Database,
39 bytecode::Bytecode,
40 context::{Block, Cfg, ContextTr, Host, JournalTr, Transaction, result::ExecutionResult},
41 inspector::JournalExt,
42 primitives::{KECCAK_EMPTY, hardfork::SpecId},
43 state::{Account, AccountStatus},
44};
45use std::{
46 collections::{BTreeMap, HashSet, btree_map::Entry},
47 fmt::Display,
48 path::Path,
49 str::FromStr,
50};
51
52mod record_debug_step;
53use foundry_common::fmt::format_token_raw;
54use foundry_config::evm_spec_id;
55use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
56use serde::Serialize;
57
58mod fork;
59pub(crate) mod mapping;
60pub(crate) mod mock;
61pub(crate) mod prank;
62
63#[derive(Serialize)]
65#[serde(rename_all = "camelCase")]
66struct LogJson {
67 topics: Vec<String>,
69 data: String,
71 emitter: String,
73}
74
75#[derive(Clone, Debug, Default)]
77pub struct RecordAccess {
78 pub reads: HashMap<Address, Vec<U256>>,
80 pub writes: HashMap<Address, Vec<U256>>,
82}
83
84impl RecordAccess {
85 pub fn record_read(&mut self, target: Address, slot: U256) {
87 self.reads.entry(target).or_default().push(slot);
88 }
89
90 pub fn record_write(&mut self, target: Address, slot: U256) {
94 self.record_read(target, slot);
95 self.writes.entry(target).or_default().push(slot);
96 }
97
98 pub fn clear(&mut self) {
100 *self = Default::default();
102 }
103}
104
105#[derive(Clone, Debug)]
107pub struct GasRecord {
108 pub group: String,
110 pub name: String,
112 pub gas_used: u64,
114 pub depth: usize,
116}
117
118#[derive(Clone, Debug)]
120pub struct DealRecord {
121 pub address: Address,
123 pub old_balance: U256,
125 pub new_balance: U256,
127}
128
129#[derive(Serialize, Default)]
131#[serde(rename_all = "camelCase")]
132struct SlotStateDiff {
133 previous_value: B256,
135 new_value: B256,
137 #[serde(skip_serializing_if = "Option::is_none", flatten)]
141 slot_info: Option<SlotInfo>,
142}
143
144#[derive(Serialize, Default)]
146#[serde(rename_all = "camelCase")]
147struct BalanceDiff {
148 previous_value: U256,
150 new_value: U256,
152}
153
154#[derive(Serialize, Default)]
156#[serde(rename_all = "camelCase")]
157struct NonceDiff {
158 previous_value: u64,
160 new_value: u64,
162}
163
164#[derive(Serialize, Default)]
166#[serde(rename_all = "camelCase")]
167struct AccountStateDiffs {
168 label: Option<String>,
170 contract: Option<String>,
172 balance_diff: Option<BalanceDiff>,
174 nonce_diff: Option<NonceDiff>,
176 state_diff: BTreeMap<B256, SlotStateDiff>,
178}
179
180impl Display for AccountStateDiffs {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
182 if let Some(label) = &self.label {
184 writeln!(f, "label: {label}")?;
185 }
186 if let Some(contract) = &self.contract {
187 writeln!(f, "contract: {contract}")?;
188 }
189 if let Some(balance_diff) = &self.balance_diff
191 && balance_diff.previous_value != balance_diff.new_value
192 {
193 writeln!(
194 f,
195 "- balance diff: {} → {}",
196 balance_diff.previous_value, balance_diff.new_value
197 )?;
198 }
199 if let Some(nonce_diff) = &self.nonce_diff
201 && nonce_diff.previous_value != nonce_diff.new_value
202 {
203 writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?;
204 }
205 if !&self.state_diff.is_empty() {
207 writeln!(f, "- state diff:")?;
208 for (slot, slot_changes) in &self.state_diff {
209 match &slot_changes.slot_info {
210 Some(slot_info) => {
211 if let Some(decoded) = &slot_info.decoded {
212 writeln!(
214 f,
215 "@ {slot} ({}, {}): {} → {}",
216 slot_info.label,
217 slot_info.slot_type.dyn_sol_type,
218 format_token_raw(&decoded.previous_value),
219 format_token_raw(&decoded.new_value)
220 )?;
221 } else {
222 writeln!(
224 f,
225 "@ {slot} ({}, {}): {} → {}",
226 slot_info.label,
227 slot_info.slot_type.dyn_sol_type,
228 slot_changes.previous_value,
229 slot_changes.new_value
230 )?;
231 }
232 }
233 None => {
234 writeln!(
236 f,
237 "@ {slot}: {} → {}",
238 slot_changes.previous_value, slot_changes.new_value
239 )?;
240 }
241 }
242 }
243 }
244
245 Ok(())
246 }
247}
248
249impl Cheatcode for addrCall {
250 fn apply<FEN: FoundryEvmNetwork>(&self, _state: &mut Cheatcodes<FEN>) -> Result {
251 let Self { privateKey } = self;
252 let wallet = super::crypto::parse_wallet(privateKey)?;
253 Ok(wallet.address().abi_encode())
254 }
255}
256
257impl Cheatcode for getNonce_0Call {
258 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
259 let Self { account } = self;
260 get_nonce(ccx, account)
261 }
262}
263
264impl Cheatcode for getNonce_1Call {
265 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
266 let Self { wallet } = self;
267 get_nonce(ccx, &wallet.addr)
268 }
269}
270
271impl Cheatcode for loadCall {
272 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
273 let Self { target, slot } = *self;
274 ccx.ensure_not_precompile(&target)?;
275
276 ccx.ecx.journal_mut().load_account(target)?;
277 let mut val = ccx
278 .ecx
279 .journal_mut()
280 .sload(target, slot.into())
281 .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?;
282
283 if val.is_cold && val.data.is_zero() {
284 if ccx.state.has_arbitrary_storage(&target) {
285 let rand_value = ccx.state.rng().random();
288 ccx.state.arbitrary_storage.as_mut().unwrap().save(
289 ccx.ecx,
290 target,
291 slot.into(),
292 rand_value,
293 );
294 val.data = rand_value;
295 } else if ccx.state.is_arbitrary_storage_copy(&target) {
296 let rand_value = ccx.state.rng().random();
300 val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
301 ccx.ecx,
302 target,
303 slot.into(),
304 rand_value,
305 );
306 }
307 }
308
309 Ok(val.abi_encode())
310 }
311}
312
313impl Cheatcode for loadAllocsCall {
314 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
315 let Self { pathToAllocsJson } = self;
316
317 let path = Path::new(pathToAllocsJson);
318 ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
319
320 let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
322 Ok(allocs) => allocs,
323 Err(_) => {
324 let genesis = read_json_file::<Genesis>(path)?;
326 genesis.alloc
327 }
328 };
329
330 let (db, inner) = ccx.ecx.db_journal_inner_mut();
332 db.load_allocs(&allocs, inner)
333 .map(|()| Vec::default())
334 .map_err(|e| fmt_err!("failed to load allocs: {e}"))
335 }
336}
337
338impl Cheatcode for cloneAccountCall {
339 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
340 let Self { source, target } = self;
341
342 let account = ccx.ecx.journal_mut().load_account(*source)?;
343 let genesis = genesis_account(account.data);
344 let (db, inner) = ccx.ecx.db_journal_inner_mut();
345 db.clone_account(&genesis, target, inner)?;
346 ccx.ecx.db_mut().add_persistent_account(*target);
348 Ok(Default::default())
349 }
350}
351
352impl Cheatcode for dumpStateCall {
353 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
354 let Self { pathToStateJson } = self;
355 let path = Path::new(pathToStateJson);
356
357 let skip = |key: &Address, val: &Account| {
359 key == &CHEATCODE_ADDRESS
360 || key == &CALLER
361 || key == &HARDHAT_CONSOLE_ADDRESS
362 || key == &TEST_CONTRACT_ADDRESS
363 || key == &ccx.caller
364 || key == &ccx.state.config.evm_opts.sender
365 || val.is_empty()
366 };
367
368 let alloc = ccx
369 .ecx
370 .journal_mut()
371 .evm_state_mut()
372 .iter_mut()
373 .filter(|(key, val)| !skip(key, val))
374 .map(|(key, val)| (key, genesis_account(val)))
375 .collect::<BTreeMap<_, _>>();
376
377 write_json_file(path, &alloc)?;
378 Ok(Default::default())
379 }
380}
381
382impl Cheatcode for recordCall {
383 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
384 let Self {} = self;
385 state.recording_accesses = true;
386 state.accesses.clear();
387 Ok(Default::default())
388 }
389}
390
391impl Cheatcode for stopRecordCall {
392 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
393 state.recording_accesses = false;
394 Ok(Default::default())
395 }
396}
397
398impl Cheatcode for accessesCall {
399 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
400 let Self { target } = *self;
401 let result = (
402 state.accesses.reads.entry(target).or_default().as_slice(),
403 state.accesses.writes.entry(target).or_default().as_slice(),
404 );
405 Ok(result.abi_encode_params())
406 }
407}
408
409impl Cheatcode for recordLogsCall {
410 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
411 let Self {} = self;
412 state.recorded_logs = Some(Default::default());
413 Ok(Default::default())
414 }
415}
416
417impl Cheatcode for getRecordedLogsCall {
418 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
419 let Self {} = self;
420 Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
421 }
422}
423
424impl Cheatcode for getRecordedLogsJsonCall {
425 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
426 let Self {} = self;
427 let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default();
428 let json_logs: Vec<_> = logs
429 .into_iter()
430 .map(|log| LogJson {
431 topics: log.topics.iter().map(|t| format!("{t}")).collect(),
432 data: hex::encode_prefixed(&log.data),
433 emitter: format!("{}", log.emitter),
434 })
435 .collect();
436 Ok(serde_json::to_string(&json_logs)?.abi_encode())
437 }
438}
439
440impl Cheatcode for pauseGasMeteringCall {
441 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
442 let Self {} = self;
443 state.gas_metering.paused = true;
444 Ok(Default::default())
445 }
446}
447
448impl Cheatcode for resumeGasMeteringCall {
449 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
450 let Self {} = self;
451 state.gas_metering.resume();
452 Ok(Default::default())
453 }
454}
455
456impl Cheatcode for resetGasMeteringCall {
457 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
458 let Self {} = self;
459 state.gas_metering.reset();
460 Ok(Default::default())
461 }
462}
463
464impl Cheatcode for lastCallGasCall {
465 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
466 let Self {} = self;
467 let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
468 bail!("no external call was made yet");
469 };
470 Ok(last_call_gas.abi_encode())
471 }
472}
473
474impl Cheatcode for getChainIdCall {
475 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
476 let Self {} = self;
477 Ok(U256::from(ccx.ecx.cfg().chain_id()).abi_encode())
478 }
479}
480
481impl Cheatcode for chainIdCall {
482 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
483 let Self { newChainId } = self;
484 ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64");
485 ccx.ecx.cfg_mut().chain_id = newChainId.to();
486 Ok(Default::default())
487 }
488}
489
490impl Cheatcode for coinbaseCall {
491 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
492 let Self { newCoinbase } = self;
493 ccx.ecx.block_mut().set_beneficiary(*newCoinbase);
494 Ok(Default::default())
495 }
496}
497
498impl Cheatcode for difficultyCall {
499 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
500 let Self { newDifficulty } = self;
501 ensure!(
502 (*ccx.ecx.cfg().spec()).into() < SpecId::MERGE,
503 "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
504 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
505 );
506 ccx.ecx.block_mut().set_difficulty(*newDifficulty);
507 Ok(Default::default())
508 }
509}
510
511impl Cheatcode for feeCall {
512 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
513 let Self { newBasefee } = self;
514 ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64");
515 ccx.ecx.block_mut().set_basefee(newBasefee.saturating_to());
516 Ok(Default::default())
517 }
518}
519
520impl Cheatcode for prevrandao_0Call {
521 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
522 let Self { newPrevrandao } = self;
523 ensure!(
524 (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE,
525 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
526 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
527 );
528 ccx.ecx.block_mut().set_prevrandao(Some(*newPrevrandao));
529 Ok(Default::default())
530 }
531}
532
533impl Cheatcode for prevrandao_1Call {
534 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
535 let Self { newPrevrandao } = self;
536 ensure!(
537 (*ccx.ecx.cfg().spec()).into() >= SpecId::MERGE,
538 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
539 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
540 );
541 ccx.ecx.block_mut().set_prevrandao(Some((*newPrevrandao).into()));
542 Ok(Default::default())
543 }
544}
545
546impl Cheatcode for blobhashesCall {
547 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
548 let Self { hashes } = self;
549 ensure!(
550 (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN,
551 "`blobhashes` is not supported before the Cancun hard fork; \
552 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
553 );
554 ccx.ecx.tx_mut().set_blob_hashes(hashes.clone());
555 ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
557 Ok(Default::default())
558 }
559}
560
561impl Cheatcode for getBlobhashesCall {
562 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
563 let Self {} = self;
564 ensure!(
565 (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN,
566 "`getBlobhashes` is not supported before the Cancun hard fork; \
567 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
568 );
569 Ok(ccx.ecx.tx().blob_versioned_hashes().to_vec().abi_encode())
570 }
571}
572
573impl Cheatcode for rollCall {
574 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
575 let Self { newHeight } = self;
576 ccx.ecx.block_mut().set_number(*newHeight);
577 Ok(Default::default())
578 }
579}
580
581impl Cheatcode for getBlockNumberCall {
582 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
583 let Self {} = self;
584 Ok(ccx.ecx.block().number().abi_encode())
585 }
586}
587
588impl Cheatcode for txGasPriceCall {
589 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
590 let Self { newGasPrice } = self;
591 ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64");
592 ccx.ecx.tx_mut().set_gas_price(newGasPrice.saturating_to());
593 Ok(Default::default())
594 }
595}
596
597impl Cheatcode for warpCall {
598 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
599 let Self { newTimestamp } = self;
600 ccx.ecx.block_mut().set_timestamp(*newTimestamp);
601 Ok(Default::default())
602 }
603}
604
605impl Cheatcode for getBlockTimestampCall {
606 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
607 let Self {} = self;
608 Ok(ccx.ecx.block().timestamp().abi_encode())
609 }
610}
611
612impl Cheatcode for blobBaseFeeCall {
613 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
614 let Self { newBlobBaseFee } = self;
615 ensure!(
616 (*ccx.ecx.cfg().spec()).into() >= SpecId::CANCUN,
617 "`blobBaseFee` is not supported before the Cancun hard fork; \
618 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
619 );
620
621 let spec: SpecId = (*ccx.ecx.cfg().spec()).into();
622 ccx.ecx.block_mut().set_blob_excess_gas_and_price(
623 (*newBlobBaseFee).to(),
624 get_blob_base_fee_update_fraction_by_spec_id(spec),
625 );
626 Ok(Default::default())
627 }
628}
629
630impl Cheatcode for getBlobBaseFeeCall {
631 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
632 let Self {} = self;
633 Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode())
634 }
635}
636
637impl Cheatcode for dealCall {
638 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
639 let Self { account: address, newBalance: new_balance } = *self;
640 let account = journaled_account(ccx.ecx, address)?;
641 let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
642 let record = DealRecord { address, old_balance, new_balance };
643 ccx.state.eth_deals.push(record);
644 Ok(Default::default())
645 }
646}
647
648impl Cheatcode for etchCall {
649 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
650 let Self { target, newRuntimeBytecode } = self;
651 ccx.ensure_not_precompile(target)?;
652 ccx.ecx.journal_mut().load_account(*target)?;
653 let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone())
654 .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
655 ccx.ecx.journal_mut().set_code(*target, bytecode);
656 Ok(Default::default())
657 }
658}
659
660impl Cheatcode for resetNonceCall {
661 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
662 let Self { account } = self;
663 let account = journaled_account(ccx.ecx, *account)?;
664 let empty = account.info.code_hash == KECCAK_EMPTY;
668 let nonce = if empty { 0 } else { 1 };
669 account.info.nonce = nonce;
670 debug!(target: "cheatcodes", nonce, "reset");
671 Ok(Default::default())
672 }
673}
674
675impl Cheatcode for setNonceCall {
676 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
677 let Self { account, newNonce } = *self;
678 let account = journaled_account(ccx.ecx, account)?;
679 let current = account.info.nonce;
681 ensure!(
682 newNonce >= current,
683 "new nonce ({newNonce}) must be strictly equal to or higher than the \
684 account's current nonce ({current})"
685 );
686 account.info.nonce = newNonce;
687 Ok(Default::default())
688 }
689}
690
691impl Cheatcode for setNonceUnsafeCall {
692 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
693 let Self { account, newNonce } = *self;
694 let account = journaled_account(ccx.ecx, account)?;
695 account.info.nonce = newNonce;
696 Ok(Default::default())
697 }
698}
699
700impl Cheatcode for storeCall {
701 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
702 let Self { target, slot, value } = *self;
703 ccx.ensure_not_precompile(&target)?;
704 ensure_loaded_account(ccx.ecx, target)?;
705 ccx.ecx
706 .journal_mut()
707 .sstore(target, slot.into(), value.into())
708 .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?;
709 Ok(Default::default())
710 }
711}
712
713impl Cheatcode for coolCall {
714 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
715 let Self { target } = self;
716 if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) {
717 account.unmark_touch();
718 account.storage.values_mut().for_each(|slot| slot.mark_cold());
719 }
720 Ok(Default::default())
721 }
722}
723
724impl Cheatcode for accessListCall {
725 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
726 let Self { access } = self;
727 let access_list = access
728 .iter()
729 .map(|item| {
730 let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
731 alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
732 })
733 .collect_vec();
734 state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
735 Ok(Default::default())
736 }
737}
738
739impl Cheatcode for noAccessListCall {
740 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
741 let Self {} = self;
742 if state.access_list.is_some() {
744 state.access_list = Some(alloy_rpc_types::AccessList::default());
745 }
746 Ok(Default::default())
747 }
748}
749
750impl Cheatcode for warmSlotCall {
751 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
752 let Self { target, slot } = *self;
753 set_cold_slot(ccx, target, slot.into(), false);
754 Ok(Default::default())
755 }
756}
757
758impl Cheatcode for coolSlotCall {
759 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
760 let Self { target, slot } = *self;
761 set_cold_slot(ccx, target, slot.into(), true);
762 Ok(Default::default())
763 }
764}
765
766impl Cheatcode for readCallersCall {
767 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
768 let Self {} = self;
769 read_callers(ccx.state, &ccx.ecx.tx().caller(), ccx.ecx.journal().depth())
770 }
771}
772
773impl Cheatcode for snapshotValue_0Call {
774 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
775 let Self { name, value } = self;
776 inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
777 }
778}
779
780impl Cheatcode for snapshotValue_1Call {
781 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
782 let Self { group, name, value } = self;
783 inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
784 }
785}
786
787impl Cheatcode for snapshotGasLastCall_0Call {
788 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
789 let Self { name } = self;
790 let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
791 bail!("no external call was made yet");
792 };
793 inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed)
794 }
795}
796
797impl Cheatcode for snapshotGasLastCall_1Call {
798 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
799 let Self { name, group } = self;
800 let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
801 bail!("no external call was made yet");
802 };
803 inner_last_gas_snapshot(
804 ccx,
805 Some(group.clone()),
806 Some(name.clone()),
807 last_call_gas.gasTotalUsed,
808 )
809 }
810}
811
812impl Cheatcode for startSnapshotGas_0Call {
813 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
814 let Self { name } = self;
815 inner_start_gas_snapshot(ccx, None, Some(name.clone()))
816 }
817}
818
819impl Cheatcode for startSnapshotGas_1Call {
820 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
821 let Self { group, name } = self;
822 inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
823 }
824}
825
826impl Cheatcode for stopSnapshotGas_0Call {
827 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
828 let Self {} = self;
829 inner_stop_gas_snapshot(ccx, None, None)
830 }
831}
832
833impl Cheatcode for stopSnapshotGas_1Call {
834 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
835 let Self { name } = self;
836 inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
837 }
838}
839
840impl Cheatcode for stopSnapshotGas_2Call {
841 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
842 let Self { group, name } = self;
843 inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
844 }
845}
846
847impl Cheatcode for snapshotCall {
849 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
850 let Self {} = self;
851 inner_snapshot_state(ccx)
852 }
853}
854
855impl Cheatcode for snapshotStateCall {
856 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
857 let Self {} = self;
858 inner_snapshot_state(ccx)
859 }
860}
861
862impl Cheatcode for revertToCall {
864 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
865 let Self { snapshotId } = self;
866 inner_revert_to_state(ccx, *snapshotId)
867 }
868}
869
870impl Cheatcode for revertToStateCall {
871 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
872 let Self { snapshotId } = self;
873 inner_revert_to_state(ccx, *snapshotId)
874 }
875}
876
877impl Cheatcode for revertToAndDeleteCall {
879 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
880 let Self { snapshotId } = self;
881 inner_revert_to_state_and_delete(ccx, *snapshotId)
882 }
883}
884
885impl Cheatcode for revertToStateAndDeleteCall {
886 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
887 let Self { snapshotId } = self;
888 inner_revert_to_state_and_delete(ccx, *snapshotId)
889 }
890}
891
892impl Cheatcode for deleteSnapshotCall {
894 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
895 let Self { snapshotId } = self;
896 let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
897 Ok(result.abi_encode())
898 }
899}
900
901impl Cheatcode for deleteStateSnapshotCall {
902 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
903 let Self { snapshotId } = self;
904 let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
905 Ok(result.abi_encode())
906 }
907}
908
909impl Cheatcode for deleteSnapshotsCall {
911 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
912 let Self {} = self;
913 ccx.ecx.db_mut().delete_state_snapshots();
914 Ok(Default::default())
915 }
916}
917
918impl Cheatcode for deleteStateSnapshotsCall {
919 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
920 let Self {} = self;
921 ccx.ecx.db_mut().delete_state_snapshots();
922 Ok(Default::default())
923 }
924}
925
926impl Cheatcode for startStateDiffRecordingCall {
927 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
928 let Self {} = self;
929 state.recorded_account_diffs_stack = Some(Default::default());
930 state.mapping_slots.get_or_insert_default();
932 Ok(Default::default())
933 }
934}
935
936impl Cheatcode for stopAndReturnStateDiffCall {
937 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
938 let Self {} = self;
939 get_state_diff(state)
940 }
941}
942
943impl Cheatcode for getStateDiffCall {
944 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
945 let mut diffs = String::new();
946 let state_diffs = get_recorded_state_diffs(ccx);
947 for (address, state_diffs) in state_diffs {
948 diffs.push_str(&format!("{address}\n"));
949 diffs.push_str(&format!("{state_diffs}\n"));
950 }
951 Ok(diffs.abi_encode())
952 }
953}
954
955impl Cheatcode for getStateDiffJsonCall {
956 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
957 let state_diffs = get_recorded_state_diffs(ccx);
958 Ok(serde_json::to_string(&state_diffs)?.abi_encode())
959 }
960}
961
962impl Cheatcode for getStorageSlotsCall {
963 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
964 let Self { target, variableName } = self;
965
966 let storage_layout = get_contract_data(ccx, *target)
967 .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone()))
968 .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?;
969
970 trace!(storage = ?storage_layout.storage, "fetched storage");
971
972 let variable_name_lower = variableName.to_lowercase();
973 let storage = storage_layout
974 .storage
975 .iter()
976 .find(|s| s.label.to_lowercase() == variable_name_lower)
977 .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
978
979 let storage_type = storage_layout
980 .types
981 .get(&storage.storage_type)
982 .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
983
984 if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
985 {
986 return Err(fmt_err!(
987 "cannot get storage slots for variables with mapping or dynamic array types"
988 ));
989 }
990
991 let slot = U256::from_str(&storage.slot).map_err(|_| {
992 fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
993 })?;
994
995 let mut slots = Vec::new();
996
997 slots.push(slot);
999
1000 if storage_type.encoding == ENCODING_INPLACE {
1001 let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
1003 fmt_err!(
1004 "invalid number_of_bytes {} for variable {variableName}",
1005 storage_type.number_of_bytes
1006 )
1007 })?;
1008 let num_slots = num_bytes.div_ceil(U256::from(32));
1009
1010 for i in 1..num_slots.to::<usize>() {
1012 slots.push(slot + U256::from(i));
1013 }
1014 }
1015
1016 if storage_type.encoding == ENCODING_BYTES {
1017 if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) {
1020 let value_bytes = value.data.to_be_bytes::<32>();
1021 let length_byte = value_bytes[31];
1022 if length_byte & 1 == 1 {
1024 let length: U256 = value.data >> 1;
1026 let num_data_slots = length.to::<usize>().div_ceil(32);
1027 let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0);
1028
1029 for i in 0..num_data_slots {
1030 slots.push(data_start + U256::from(i));
1031 }
1032 }
1033 }
1034 }
1035
1036 Ok(slots.abi_encode())
1037 }
1038}
1039
1040impl Cheatcode for getStorageAccessesCall {
1041 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1042 let mut storage_accesses = Vec::new();
1043
1044 if let Some(recorded_diffs) = &state.recorded_account_diffs_stack {
1045 for account_accesses in recorded_diffs.iter().flatten() {
1046 storage_accesses.extend(account_accesses.storageAccesses.clone());
1047 }
1048 }
1049
1050 Ok(storage_accesses.abi_encode())
1051 }
1052}
1053
1054impl Cheatcode for broadcastRawTransactionCall {
1055 fn apply_full<FEN: FoundryEvmNetwork>(
1056 &self,
1057 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1058 executor: &mut dyn CheatcodesExecutor<FEN>,
1059 ) -> Result {
1060 let tx = TxEnvelopeFor::<FEN>::decode(&mut self.data.as_ref())
1061 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1062
1063 let sender =
1064 tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1065 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1066 let from = sender;
1067
1068 executor.transact_from_tx_on_db(ccx.state, ccx.ecx, tx_env)?;
1069
1070 if ccx.state.broadcast.is_some() {
1071 ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1072 rpc: ccx.ecx.db().active_fork_url(),
1073 transaction: TransactionMaybeSigned::Signed { tx, from },
1074 });
1075 }
1076
1077 Ok(Default::default())
1078 }
1079}
1080
1081impl Cheatcode for setBlockhashCall {
1082 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1083 let Self { blockNumber, blockHash } = *self;
1084 ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1085 ensure!(
1086 blockNumber <= U256::from(ccx.ecx.block().number()),
1087 "block number must be less than or equal to the current block number"
1088 );
1089
1090 ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash);
1091
1092 Ok(Default::default())
1093 }
1094}
1095
1096impl Cheatcode for executeTransactionCall {
1097 fn apply_full<FEN: FoundryEvmNetwork>(
1098 &self,
1099 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1100 executor: &mut dyn CheatcodesExecutor<FEN>,
1101 ) -> Result {
1102 use crate::env::FORGE_CONTEXT;
1103
1104 if let Some(ctx) = FORGE_CONTEXT.get()
1106 && *ctx == ForgeContext::ScriptGroup
1107 {
1108 return Err(fmt_err!("executeTransaction is not allowed in forge script"));
1109 }
1110
1111 let tx = TxEnvelopeFor::<FEN>::decode(&mut self.rawTx.as_ref())
1113 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1114
1115 let sender =
1117 tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1118 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1119
1120 let cached_evm_env = ccx.ecx.evm_clone();
1122 let cached_tx_env = ccx.ecx.tx_clone();
1123
1124 ccx.ecx.block_mut().set_basefee(0);
1126 ccx.ecx.set_tx(tx_env);
1127 ccx.ecx.tx_mut().set_gas_price(0);
1128 ccx.ecx.tx_mut().set_gas_priority_fee(None);
1129
1130 ccx.ecx.cfg_mut().disable_nonce_check = false;
1132
1133 ccx.ecx.cfg_mut().limit_contract_initcode_size =
1135 Some(revm::primitives::eip3860::MAX_INITCODE_SIZE);
1136
1137 ccx.ecx.cfg_mut().tx_gas_limit_cap = None;
1141
1142 let modified_evm_env = ccx.ecx.evm_clone();
1144 let modified_tx_env = ccx.ecx.tx_clone();
1145
1146 executor.set_in_inner_context(true, Some(sender));
1149
1150 let cold_state = {
1152 let (_, journal) = ccx.ecx.db_journal_inner_mut();
1153 let mut state = journal.state.clone();
1154 for (addr, acc_mut) in &mut state {
1155 if journal.warm_addresses.is_cold(addr) {
1156 acc_mut.mark_cold();
1157 }
1158 for slot_mut in acc_mut.storage.values_mut() {
1159 slot_mut.is_cold = true;
1160 slot_mut.original_value = slot_mut.present_value;
1161 }
1162 }
1163 state
1164 };
1165
1166 let mut res = None;
1167 let mut cold_state = Some(cold_state);
1168 let mut nested_evm_env = {
1169 let (db, _) = ccx.ecx.db_journal_inner_mut();
1170 executor.with_fresh_nested_evm(ccx.state, db, modified_evm_env, &mut |evm| {
1171 evm.journal_inner_mut().state = cold_state.take().expect("called once");
1173 evm.journal_inner_mut().depth = 1;
1175 res = Some(evm.transact_raw(modified_tx_env.clone()));
1176 Ok(())
1177 })?
1178 };
1179 let res = res.unwrap();
1180
1181 nested_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1185 nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check;
1186 nested_evm_env.cfg_env.limit_contract_initcode_size =
1187 cached_evm_env.cfg_env.limit_contract_initcode_size;
1188 nested_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1189 ccx.ecx.set_evm(nested_evm_env);
1190 ccx.ecx.set_tx(cached_tx_env);
1191
1192 executor.set_in_inner_context(false, None);
1194
1195 let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?;
1196
1197 for (addr, mut acc) in res.state {
1199 let Some(acc_mut) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&addr) else {
1200 ccx.ecx.journal_mut().evm_state_mut().insert(addr, acc);
1201 continue;
1202 };
1203
1204 if acc.status.contains(AccountStatus::Cold)
1206 && !acc_mut.status.contains(AccountStatus::Cold)
1207 {
1208 acc.status -= AccountStatus::Cold;
1209 }
1210 acc_mut.info = acc.info;
1211 acc_mut.status |= acc.status;
1212
1213 for (key, val) in acc.storage {
1215 let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
1216 acc_mut.storage.insert(key, val);
1217 continue;
1218 };
1219 slot_mut.present_value = val.present_value;
1220 slot_mut.is_cold &= val.is_cold;
1221 }
1222 }
1223
1224 let output = match res.result {
1226 ExecutionResult::Success { output, .. } => output.into_data(),
1227 ExecutionResult::Halt { reason, .. } => {
1228 return Err(fmt_err!("transaction halted: {reason:?}"));
1229 }
1230 ExecutionResult::Revert { output, .. } => {
1231 return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output)));
1232 }
1233 };
1234
1235 Ok(output.abi_encode())
1236 }
1237}
1238
1239impl Cheatcode for startDebugTraceRecordingCall {
1240 fn apply_full<FEN: FoundryEvmNetwork>(
1241 &self,
1242 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1243 executor: &mut dyn CheatcodesExecutor<FEN>,
1244 ) -> Result {
1245 let Some(tracer) = executor.tracing_inspector() else {
1246 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1247 };
1248
1249 if ccx.state.record_debug_steps_info.is_some() {
1250 bail!("debug trace recording was already started");
1251 }
1252
1253 let mut info = RecordDebugStepInfo {
1254 start_node_idx: 0,
1256 original_tracer_config: *tracer.config(),
1258 };
1259
1260 *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None");
1262
1263 if let Some(last_node) = tracer.traces().nodes().last() {
1265 info.start_node_idx = last_node.idx;
1266 }
1267
1268 ccx.state.record_debug_steps_info = Some(info);
1269 Ok(Default::default())
1270 }
1271}
1272
1273impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1274 fn apply_full<FEN: FoundryEvmNetwork>(
1275 &self,
1276 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1277 executor: &mut dyn CheatcodesExecutor<FEN>,
1278 ) -> Result {
1279 let Some(tracer) = executor.tracing_inspector() else {
1280 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1281 };
1282
1283 let Some(record_info) = ccx.state.record_debug_steps_info else {
1284 return Err(Error::from("nothing recorded"));
1285 };
1286
1287 let root = tracer.traces();
1289 let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1290
1291 let debug_steps: Vec<DebugStep> =
1292 steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1293 if !record_info.original_tracer_config.record_steps {
1295 tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1296 node.trace.steps = Vec::new();
1297 node.logs = Vec::new();
1298 node.ordering = Vec::new();
1299 });
1300 }
1301
1302 tracer.update_config(|_config| record_info.original_tracer_config);
1304
1305 ccx.state.record_debug_steps_info = None;
1307
1308 Ok(debug_steps.abi_encode())
1309 }
1310}
1311
1312impl Cheatcode for setEvmVersionCall {
1313 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1314 let Self { evm } = self;
1315 let spec_id = evm_spec_id(
1316 EvmVersion::from_str(evm)
1317 .map_err(|_| Error::from(format!("invalid evm version {evm}")))?,
1318 );
1319 ccx.state.execution_evm_version = Some(spec_id);
1320 Ok(Default::default())
1321 }
1322}
1323
1324impl Cheatcode for getEvmVersionCall {
1325 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1326 let spec = (*ccx.ecx.cfg().spec()).into();
1327 Ok(spec.to_string().to_lowercase().abi_encode())
1328 }
1329}
1330
1331pub(super) fn get_nonce<FEN: FoundryEvmNetwork>(
1332 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1333 address: &Address,
1334) -> Result {
1335 let account = ccx.ecx.journal_mut().load_account(*address)?;
1336 Ok(account.data.info.nonce.abi_encode())
1337}
1338
1339fn inner_snapshot_state<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1340 let evm_env = ccx.ecx.evm_clone();
1341 let (db, inner) = ccx.ecx.db_journal_inner_mut();
1342 let id = db.snapshot_state(inner, &evm_env);
1343 Ok(id.abi_encode())
1344}
1345
1346fn inner_revert_to_state<FEN: FoundryEvmNetwork>(
1347 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1348 snapshot_id: U256,
1349) -> Result {
1350 let mut evm_env = ccx.ecx.evm_clone();
1351 let caller = ccx.ecx.caller();
1352 let (db, inner) = ccx.ecx.db_journal_inner_mut();
1353 if let Some(restored) = db.revert_state(
1354 snapshot_id,
1355 inner,
1356 &mut evm_env,
1357 caller,
1358 RevertStateSnapshotAction::RevertKeep,
1359 ) {
1360 *inner = restored;
1361 ccx.ecx.set_evm(evm_env);
1362 Ok(true.abi_encode())
1363 } else {
1364 Ok(false.abi_encode())
1365 }
1366}
1367
1368fn inner_revert_to_state_and_delete<FEN: FoundryEvmNetwork>(
1369 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1370 snapshot_id: U256,
1371) -> Result {
1372 let mut evm_env = ccx.ecx.evm_clone();
1373 let caller = ccx.ecx.caller();
1374 let (db, inner) = ccx.ecx.db_journal_inner_mut();
1375 if let Some(restored) = db.revert_state(
1376 snapshot_id,
1377 inner,
1378 &mut evm_env,
1379 caller,
1380 RevertStateSnapshotAction::RevertRemove,
1381 ) {
1382 *inner = restored;
1383 ccx.ecx.set_evm(evm_env);
1384 Ok(true.abi_encode())
1385 } else {
1386 Ok(false.abi_encode())
1387 }
1388}
1389
1390fn inner_value_snapshot<FEN: FoundryEvmNetwork>(
1391 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1392 group: Option<String>,
1393 name: Option<String>,
1394 value: String,
1395) -> Result {
1396 let (group, name) = derive_snapshot_name(ccx, group, name);
1397
1398 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1399
1400 Ok(Default::default())
1401}
1402
1403fn inner_last_gas_snapshot<FEN: FoundryEvmNetwork>(
1404 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1405 group: Option<String>,
1406 name: Option<String>,
1407 value: u64,
1408) -> Result {
1409 let (group, name) = derive_snapshot_name(ccx, group, name);
1410
1411 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1412
1413 Ok(value.abi_encode())
1414}
1415
1416fn inner_start_gas_snapshot<FEN: FoundryEvmNetwork>(
1417 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1418 group: Option<String>,
1419 name: Option<String>,
1420) -> Result {
1421 if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1423 bail!("gas snapshot was already started with group: {group} and name: {name}");
1424 }
1425
1426 let (group, name) = derive_snapshot_name(ccx, group, name);
1427
1428 ccx.state.gas_metering.gas_records.push(GasRecord {
1429 group: group.clone(),
1430 name: name.clone(),
1431 gas_used: 0,
1432 depth: ccx.ecx.journal().depth(),
1433 });
1434
1435 ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1436
1437 ccx.state.gas_metering.start();
1438
1439 Ok(Default::default())
1440}
1441
1442fn inner_stop_gas_snapshot<FEN: FoundryEvmNetwork>(
1443 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1444 group: Option<String>,
1445 name: Option<String>,
1446) -> Result {
1447 let (group, name) = group
1449 .zip(name)
1450 .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone())
1451 .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?;
1452
1453 if let Some(record) = ccx
1454 .state
1455 .gas_metering
1456 .gas_records
1457 .iter_mut()
1458 .find(|record| record.group == group && record.name == name)
1459 {
1460 let value = record.gas_used.saturating_sub(171);
1463
1464 ccx.state
1465 .gas_snapshots
1466 .entry(group.clone())
1467 .or_default()
1468 .insert(name.clone(), value.to_string());
1469
1470 ccx.state.gas_metering.stop();
1472
1473 ccx.state
1475 .gas_metering
1476 .gas_records
1477 .retain(|record| record.group != group && record.name != name);
1478
1479 if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1481 && snapshot_group == &group
1482 && snapshot_name == &name
1483 {
1484 ccx.state.gas_metering.active_gas_snapshot = None;
1485 }
1486
1487 Ok(value.abi_encode())
1488 } else {
1489 bail!("no gas snapshot was started with the name: {name} in group: {group}");
1490 }
1491}
1492
1493fn derive_snapshot_name<FEN: FoundryEvmNetwork>(
1495 ccx: &CheatsCtxt<'_, '_, FEN>,
1496 group: Option<String>,
1497 name: Option<String>,
1498) -> (String, String) {
1499 let group = group.unwrap_or_else(|| {
1500 ccx.state.config.running_artifact.clone().expect("expected running contract").name
1501 });
1502 let name = name.unwrap_or_else(|| "default".to_string());
1503 (group, name)
1504}
1505
1506fn read_callers<FEN: FoundryEvmNetwork>(
1530 state: &Cheatcodes<FEN>,
1531 default_sender: &Address,
1532 call_depth: usize,
1533) -> Result {
1534 let mut mode = CallerMode::None;
1535 let mut new_caller = default_sender;
1536 let mut new_origin = default_sender;
1537 if let Some(prank) = state.get_prank(call_depth) {
1538 mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1539 new_caller = &prank.new_caller;
1540 if let Some(new) = &prank.new_origin {
1541 new_origin = new;
1542 }
1543 } else if let Some(broadcast) = &state.broadcast {
1544 mode = if broadcast.single_call {
1545 CallerMode::Broadcast
1546 } else {
1547 CallerMode::RecurrentBroadcast
1548 };
1549 new_caller = &broadcast.new_origin;
1550 new_origin = &broadcast.new_origin;
1551 }
1552
1553 Ok((mode, new_caller, new_origin).abi_encode_params())
1554}
1555
1556pub(super) fn journaled_account<
1558 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1559>(
1560 ecx: &mut CTX,
1561 addr: Address,
1562) -> Result<&mut Account> {
1563 ensure_loaded_account(ecx, addr)?;
1564 Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded"))
1565}
1566
1567pub(super) fn ensure_loaded_account<CTX: ContextTr<Db: Database<Error = DatabaseError>>>(
1568 ecx: &mut CTX,
1569 addr: Address,
1570) -> Result<()> {
1571 ecx.journal_mut().load_account(addr)?;
1572 ecx.journal_mut().touch_account(addr);
1573 Ok(())
1574}
1575
1576fn get_state_diff<FEN: FoundryEvmNetwork>(state: &mut Cheatcodes<FEN>) -> Result {
1584 let res = state
1585 .recorded_account_diffs_stack
1586 .replace(Default::default())
1587 .unwrap_or_default()
1588 .into_iter()
1589 .flatten()
1590 .collect::<Vec<_>>();
1591 Ok(res.abi_encode())
1592}
1593
1594fn genesis_account(account: &Account) -> GenesisAccount {
1596 GenesisAccount {
1597 nonce: Some(account.info.nonce),
1598 balance: account.info.balance,
1599 code: account.info.code.as_ref().map(|o| o.original_bytes()),
1600 storage: Some(
1601 account
1602 .storage
1603 .iter()
1604 .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1605 .collect(),
1606 ),
1607 private_key: None,
1608 }
1609}
1610
1611fn get_recorded_state_diffs<FEN: FoundryEvmNetwork>(
1613 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1614) -> BTreeMap<Address, AccountStateDiffs> {
1615 let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1616
1617 let mut addresses_to_lookup = HashSet::new();
1619 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1620 for account_access in records.iter().flatten() {
1621 if !account_access.storageAccesses.is_empty()
1622 || account_access.oldBalance != account_access.newBalance
1623 {
1624 addresses_to_lookup.insert(account_access.account);
1625 for storage_access in &account_access.storageAccesses {
1626 if storage_access.isWrite && !storage_access.reverted {
1627 addresses_to_lookup.insert(storage_access.account);
1628 }
1629 }
1630 }
1631 }
1632 }
1633
1634 let mut contract_names = HashMap::new();
1636 let mut storage_layouts = HashMap::new();
1637 for address in addresses_to_lookup {
1638 if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) {
1639 contract_names.insert(address, artifact_id.identifier());
1640
1641 if let Some(storage_layout) = &contract_data.storage_layout {
1643 storage_layouts.insert(address, storage_layout.clone());
1644 }
1645 }
1646 }
1647
1648 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1650 records
1651 .iter()
1652 .flatten()
1653 .filter(|account_access| {
1654 !account_access.storageAccesses.is_empty()
1655 || account_access.oldBalance != account_access.newBalance
1656 || account_access.oldNonce != account_access.newNonce
1657 })
1658 .for_each(|account_access| {
1659 if account_access.oldBalance != account_access.newBalance {
1661 let account_diff =
1662 state_diffs.entry(account_access.account).or_insert_with(|| {
1663 AccountStateDiffs {
1664 label: ccx.state.labels.get(&account_access.account).cloned(),
1665 contract: contract_names.get(&account_access.account).cloned(),
1666 ..Default::default()
1667 }
1668 });
1669 if let Some(diff) = &mut account_diff.balance_diff {
1671 diff.new_value = account_access.newBalance;
1672 } else {
1673 account_diff.balance_diff = Some(BalanceDiff {
1674 previous_value: account_access.oldBalance,
1675 new_value: account_access.newBalance,
1676 });
1677 }
1678 }
1679
1680 if account_access.oldNonce != account_access.newNonce {
1682 let account_diff =
1683 state_diffs.entry(account_access.account).or_insert_with(|| {
1684 AccountStateDiffs {
1685 label: ccx.state.labels.get(&account_access.account).cloned(),
1686 contract: contract_names.get(&account_access.account).cloned(),
1687 ..Default::default()
1688 }
1689 });
1690 if let Some(diff) = &mut account_diff.nonce_diff {
1692 diff.new_value = account_access.newNonce;
1693 } else {
1694 account_diff.nonce_diff = Some(NonceDiff {
1695 previous_value: account_access.oldNonce,
1696 new_value: account_access.newNonce,
1697 });
1698 }
1699 }
1700
1701 let raw_changes_by_slot = account_access
1703 .storageAccesses
1704 .iter()
1705 .filter_map(|access| {
1706 (access.isWrite && !access.reverted)
1707 .then_some((access.slot, (access.previousValue, access.newValue)))
1708 })
1709 .collect::<BTreeMap<_, _>>();
1710
1711 for storage_access in &account_access.storageAccesses {
1713 if storage_access.isWrite && !storage_access.reverted {
1714 let account_diff = state_diffs
1715 .entry(storage_access.account)
1716 .or_insert_with(|| AccountStateDiffs {
1717 label: ccx.state.labels.get(&storage_access.account).cloned(),
1718 contract: contract_names.get(&storage_access.account).cloned(),
1719 ..Default::default()
1720 });
1721 let layout = storage_layouts.get(&storage_access.account);
1722 let entry = match account_diff.state_diff.entry(storage_access.slot) {
1724 Entry::Vacant(slot_state_diff) => {
1725 let mapping_slots = ccx
1728 .state
1729 .mapping_slots
1730 .as_ref()
1731 .and_then(|slots| slots.get(&storage_access.account));
1732
1733 let slot_info = layout.and_then(|layout| {
1734 let decoder = SlotIdentifier::new(layout.clone());
1735 decoder.identify(&storage_access.slot, mapping_slots).or_else(
1736 || {
1737 let current_base_slot_values = raw_changes_by_slot
1742 .iter()
1743 .map(|(slot, (_, new_val))| (*slot, *new_val))
1744 .collect::<B256Map<_>>();
1745 decoder.identify_bytes_or_string(
1746 &storage_access.slot,
1747 ¤t_base_slot_values,
1748 )
1749 },
1750 )
1751 });
1752
1753 slot_state_diff.insert(SlotStateDiff {
1754 previous_value: storage_access.previousValue,
1755 new_value: storage_access.newValue,
1756 slot_info,
1757 })
1758 }
1759 Entry::Occupied(slot_state_diff) => {
1760 let entry = slot_state_diff.into_mut();
1761 entry.new_value = storage_access.newValue;
1762 entry
1763 }
1764 };
1765
1766 if let Some(slot_info) = &mut entry.slot_info {
1768 slot_info.decode_values(entry.previous_value, storage_access.newValue);
1769 if slot_info.is_bytes_or_string() {
1770 slot_info.decode_bytes_or_string_values(
1771 &storage_access.slot,
1772 &raw_changes_by_slot,
1773 );
1774 }
1775 }
1776 }
1777 }
1778 });
1779 }
1780 state_diffs
1781}
1782
1783const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
1785
1786const EIP1822_PROXIABLE_SLOT: &str =
1788 "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
1789
1790fn get_contract_data<'a, FEN: FoundryEvmNetwork>(
1792 ccx: &'a mut CheatsCtxt<'_, '_, FEN>,
1793 address: Address,
1794) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
1795 let artifacts = ccx.state.config.available_artifacts.as_ref()?;
1797
1798 let account = ccx.ecx.journal_mut().load_account(address).ok()?;
1800 let code = account.data.info.code.as_ref()?;
1801
1802 if code.is_empty() {
1804 return None;
1805 }
1806
1807 let code_bytes = code.original_bytes();
1809 let hex_str = hex::encode(&code_bytes);
1811 let find_by_suffix =
1812 |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
1813 if hex_str.contains(EIP1967_IMPL_SLOT)
1815 && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
1816 {
1817 return Some(result);
1818 } else if hex_str.contains(EIP1822_PROXIABLE_SLOT)
1819 && let Some(result) = find_by_suffix(":UUPSUpgradeable")
1820 {
1821 return Some(result);
1822 }
1823
1824 if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
1826 return Some(result);
1827 }
1828
1829 artifacts.find_by_deployed_code(&code_bytes)
1831}
1832
1833fn set_cold_slot<FEN: FoundryEvmNetwork>(
1835 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1836 target: Address,
1837 slot: U256,
1838 cold: bool,
1839) {
1840 if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target)
1841 && let Some(storage_slot) = account.storage.get_mut(&slot)
1842 {
1843 storage_slot.is_cold = cold;
1844 }
1845}