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 let modified_evm_env = ccx.ecx.evm_clone();
1139 let modified_tx_env = ccx.ecx.tx_clone();
1140
1141 executor.set_in_inner_context(true, Some(sender));
1144
1145 let cold_state = {
1147 let (_, journal) = ccx.ecx.db_journal_inner_mut();
1148 let mut state = journal.state.clone();
1149 for (addr, acc_mut) in &mut state {
1150 if journal.warm_addresses.is_cold(addr) {
1151 acc_mut.mark_cold();
1152 }
1153 for slot_mut in acc_mut.storage.values_mut() {
1154 slot_mut.is_cold = true;
1155 slot_mut.original_value = slot_mut.present_value;
1156 }
1157 }
1158 state
1159 };
1160
1161 let mut res = None;
1162 let mut cold_state = Some(cold_state);
1163 let mut nested_evm_env = {
1164 let (db, _) = ccx.ecx.db_journal_inner_mut();
1165 executor.with_fresh_nested_evm(ccx.state, db, modified_evm_env, &mut |evm| {
1166 evm.journal_inner_mut().state = cold_state.take().expect("called once");
1168 evm.journal_inner_mut().depth = 1;
1170 res = Some(evm.transact_raw(modified_tx_env.clone()));
1171 Ok(())
1172 })?
1173 };
1174 let res = res.unwrap();
1175
1176 nested_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1180 nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check;
1181 nested_evm_env.cfg_env.limit_contract_initcode_size =
1182 cached_evm_env.cfg_env.limit_contract_initcode_size;
1183 ccx.ecx.set_evm(nested_evm_env);
1184 ccx.ecx.set_tx(cached_tx_env);
1185
1186 executor.set_in_inner_context(false, None);
1188
1189 let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?;
1190
1191 for (addr, mut acc) in res.state {
1193 let Some(acc_mut) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&addr) else {
1194 ccx.ecx.journal_mut().evm_state_mut().insert(addr, acc);
1195 continue;
1196 };
1197
1198 if acc.status.contains(AccountStatus::Cold)
1200 && !acc_mut.status.contains(AccountStatus::Cold)
1201 {
1202 acc.status -= AccountStatus::Cold;
1203 }
1204 acc_mut.info = acc.info;
1205 acc_mut.status |= acc.status;
1206
1207 for (key, val) in acc.storage {
1209 let Some(slot_mut) = acc_mut.storage.get_mut(&key) else {
1210 acc_mut.storage.insert(key, val);
1211 continue;
1212 };
1213 slot_mut.present_value = val.present_value;
1214 slot_mut.is_cold &= val.is_cold;
1215 }
1216 }
1217
1218 let output = match res.result {
1220 ExecutionResult::Success { output, .. } => output.into_data(),
1221 ExecutionResult::Halt { reason, .. } => {
1222 return Err(fmt_err!("transaction halted: {reason:?}"));
1223 }
1224 ExecutionResult::Revert { output, .. } => {
1225 return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output)));
1226 }
1227 };
1228
1229 Ok(output.abi_encode())
1230 }
1231}
1232
1233impl Cheatcode for startDebugTraceRecordingCall {
1234 fn apply_full<FEN: FoundryEvmNetwork>(
1235 &self,
1236 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1237 executor: &mut dyn CheatcodesExecutor<FEN>,
1238 ) -> Result {
1239 let Some(tracer) = executor.tracing_inspector() else {
1240 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1241 };
1242
1243 let mut info = RecordDebugStepInfo {
1244 start_node_idx: 0,
1246 original_tracer_config: *tracer.config(),
1248 };
1249
1250 *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None");
1252
1253 if let Some(last_node) = tracer.traces().nodes().last() {
1255 info.start_node_idx = last_node.idx;
1256 }
1257
1258 ccx.state.record_debug_steps_info = Some(info);
1259 Ok(Default::default())
1260 }
1261}
1262
1263impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1264 fn apply_full<FEN: FoundryEvmNetwork>(
1265 &self,
1266 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1267 executor: &mut dyn CheatcodesExecutor<FEN>,
1268 ) -> Result {
1269 let Some(tracer) = executor.tracing_inspector() else {
1270 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1271 };
1272
1273 let Some(record_info) = ccx.state.record_debug_steps_info else {
1274 return Err(Error::from("nothing recorded"));
1275 };
1276
1277 let root = tracer.traces();
1279 let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1280
1281 let debug_steps: Vec<DebugStep> =
1282 steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1283 if !record_info.original_tracer_config.record_steps {
1285 tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1286 node.trace.steps = Vec::new();
1287 node.logs = Vec::new();
1288 node.ordering = Vec::new();
1289 });
1290 }
1291
1292 tracer.update_config(|_config| record_info.original_tracer_config);
1294
1295 ccx.state.record_debug_steps_info = None;
1297
1298 Ok(debug_steps.abi_encode())
1299 }
1300}
1301
1302impl Cheatcode for setEvmVersionCall {
1303 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1304 let Self { evm } = self;
1305 let spec_id = evm_spec_id(
1306 EvmVersion::from_str(evm)
1307 .map_err(|_| Error::from(format!("invalid evm version {evm}")))?,
1308 );
1309 ccx.state.execution_evm_version = Some(spec_id);
1310 Ok(Default::default())
1311 }
1312}
1313
1314impl Cheatcode for getEvmVersionCall {
1315 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1316 let spec = (*ccx.ecx.cfg().spec()).into();
1317 Ok(spec.to_string().to_lowercase().abi_encode())
1318 }
1319}
1320
1321pub(super) fn get_nonce<FEN: FoundryEvmNetwork>(
1322 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1323 address: &Address,
1324) -> Result {
1325 let account = ccx.ecx.journal_mut().load_account(*address)?;
1326 Ok(account.data.info.nonce.abi_encode())
1327}
1328
1329fn inner_snapshot_state<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1330 let evm_env = ccx.ecx.evm_clone();
1331 let (db, inner) = ccx.ecx.db_journal_inner_mut();
1332 let id = db.snapshot_state(inner, &evm_env);
1333 Ok(id.abi_encode())
1334}
1335
1336fn inner_revert_to_state<FEN: FoundryEvmNetwork>(
1337 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1338 snapshot_id: U256,
1339) -> Result {
1340 let mut evm_env = ccx.ecx.evm_clone();
1341 let caller = ccx.ecx.caller();
1342 let (db, inner) = ccx.ecx.db_journal_inner_mut();
1343 if let Some(restored) = db.revert_state(
1344 snapshot_id,
1345 inner,
1346 &mut evm_env,
1347 caller,
1348 RevertStateSnapshotAction::RevertKeep,
1349 ) {
1350 *inner = restored;
1351 ccx.ecx.set_evm(evm_env);
1352 Ok(true.abi_encode())
1353 } else {
1354 Ok(false.abi_encode())
1355 }
1356}
1357
1358fn inner_revert_to_state_and_delete<FEN: FoundryEvmNetwork>(
1359 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1360 snapshot_id: U256,
1361) -> Result {
1362 let mut evm_env = ccx.ecx.evm_clone();
1363 let caller = ccx.ecx.caller();
1364 let (db, inner) = ccx.ecx.db_journal_inner_mut();
1365 if let Some(restored) = db.revert_state(
1366 snapshot_id,
1367 inner,
1368 &mut evm_env,
1369 caller,
1370 RevertStateSnapshotAction::RevertRemove,
1371 ) {
1372 *inner = restored;
1373 ccx.ecx.set_evm(evm_env);
1374 Ok(true.abi_encode())
1375 } else {
1376 Ok(false.abi_encode())
1377 }
1378}
1379
1380fn inner_value_snapshot<FEN: FoundryEvmNetwork>(
1381 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1382 group: Option<String>,
1383 name: Option<String>,
1384 value: String,
1385) -> Result {
1386 let (group, name) = derive_snapshot_name(ccx, group, name);
1387
1388 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1389
1390 Ok(Default::default())
1391}
1392
1393fn inner_last_gas_snapshot<FEN: FoundryEvmNetwork>(
1394 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1395 group: Option<String>,
1396 name: Option<String>,
1397 value: u64,
1398) -> Result {
1399 let (group, name) = derive_snapshot_name(ccx, group, name);
1400
1401 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1402
1403 Ok(value.abi_encode())
1404}
1405
1406fn inner_start_gas_snapshot<FEN: FoundryEvmNetwork>(
1407 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1408 group: Option<String>,
1409 name: Option<String>,
1410) -> Result {
1411 if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1413 bail!("gas snapshot was already started with group: {group} and name: {name}");
1414 }
1415
1416 let (group, name) = derive_snapshot_name(ccx, group, name);
1417
1418 ccx.state.gas_metering.gas_records.push(GasRecord {
1419 group: group.clone(),
1420 name: name.clone(),
1421 gas_used: 0,
1422 depth: ccx.ecx.journal().depth(),
1423 });
1424
1425 ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1426
1427 ccx.state.gas_metering.start();
1428
1429 Ok(Default::default())
1430}
1431
1432fn inner_stop_gas_snapshot<FEN: FoundryEvmNetwork>(
1433 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1434 group: Option<String>,
1435 name: Option<String>,
1436) -> Result {
1437 let (group, name) = group
1439 .zip(name)
1440 .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone())
1441 .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?;
1442
1443 if let Some(record) = ccx
1444 .state
1445 .gas_metering
1446 .gas_records
1447 .iter_mut()
1448 .find(|record| record.group == group && record.name == name)
1449 {
1450 let value = record.gas_used.saturating_sub(171);
1453
1454 ccx.state
1455 .gas_snapshots
1456 .entry(group.clone())
1457 .or_default()
1458 .insert(name.clone(), value.to_string());
1459
1460 ccx.state.gas_metering.stop();
1462
1463 ccx.state
1465 .gas_metering
1466 .gas_records
1467 .retain(|record| record.group != group && record.name != name);
1468
1469 if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1471 && snapshot_group == &group
1472 && snapshot_name == &name
1473 {
1474 ccx.state.gas_metering.active_gas_snapshot = None;
1475 }
1476
1477 Ok(value.abi_encode())
1478 } else {
1479 bail!("no gas snapshot was started with the name: {name} in group: {group}");
1480 }
1481}
1482
1483fn derive_snapshot_name<FEN: FoundryEvmNetwork>(
1485 ccx: &CheatsCtxt<'_, '_, FEN>,
1486 group: Option<String>,
1487 name: Option<String>,
1488) -> (String, String) {
1489 let group = group.unwrap_or_else(|| {
1490 ccx.state.config.running_artifact.clone().expect("expected running contract").name
1491 });
1492 let name = name.unwrap_or_else(|| "default".to_string());
1493 (group, name)
1494}
1495
1496fn read_callers<FEN: FoundryEvmNetwork>(
1520 state: &Cheatcodes<FEN>,
1521 default_sender: &Address,
1522 call_depth: usize,
1523) -> Result {
1524 let mut mode = CallerMode::None;
1525 let mut new_caller = default_sender;
1526 let mut new_origin = default_sender;
1527 if let Some(prank) = state.get_prank(call_depth) {
1528 mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1529 new_caller = &prank.new_caller;
1530 if let Some(new) = &prank.new_origin {
1531 new_origin = new;
1532 }
1533 } else if let Some(broadcast) = &state.broadcast {
1534 mode = if broadcast.single_call {
1535 CallerMode::Broadcast
1536 } else {
1537 CallerMode::RecurrentBroadcast
1538 };
1539 new_caller = &broadcast.new_origin;
1540 new_origin = &broadcast.new_origin;
1541 }
1542
1543 Ok((mode, new_caller, new_origin).abi_encode_params())
1544}
1545
1546pub(super) fn journaled_account<
1548 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1549>(
1550 ecx: &mut CTX,
1551 addr: Address,
1552) -> Result<&mut Account> {
1553 ensure_loaded_account(ecx, addr)?;
1554 Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded"))
1555}
1556
1557pub(super) fn ensure_loaded_account<CTX: ContextTr<Db: Database<Error = DatabaseError>>>(
1558 ecx: &mut CTX,
1559 addr: Address,
1560) -> Result<()> {
1561 ecx.journal_mut().load_account(addr)?;
1562 ecx.journal_mut().touch_account(addr);
1563 Ok(())
1564}
1565
1566fn get_state_diff<FEN: FoundryEvmNetwork>(state: &mut Cheatcodes<FEN>) -> Result {
1574 let res = state
1575 .recorded_account_diffs_stack
1576 .replace(Default::default())
1577 .unwrap_or_default()
1578 .into_iter()
1579 .flatten()
1580 .collect::<Vec<_>>();
1581 Ok(res.abi_encode())
1582}
1583
1584fn genesis_account(account: &Account) -> GenesisAccount {
1586 GenesisAccount {
1587 nonce: Some(account.info.nonce),
1588 balance: account.info.balance,
1589 code: account.info.code.as_ref().map(|o| o.original_bytes()),
1590 storage: Some(
1591 account
1592 .storage
1593 .iter()
1594 .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1595 .collect(),
1596 ),
1597 private_key: None,
1598 }
1599}
1600
1601fn get_recorded_state_diffs<FEN: FoundryEvmNetwork>(
1603 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1604) -> BTreeMap<Address, AccountStateDiffs> {
1605 let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1606
1607 let mut addresses_to_lookup = HashSet::new();
1609 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1610 for account_access in records.iter().flatten() {
1611 if !account_access.storageAccesses.is_empty()
1612 || account_access.oldBalance != account_access.newBalance
1613 {
1614 addresses_to_lookup.insert(account_access.account);
1615 for storage_access in &account_access.storageAccesses {
1616 if storage_access.isWrite && !storage_access.reverted {
1617 addresses_to_lookup.insert(storage_access.account);
1618 }
1619 }
1620 }
1621 }
1622 }
1623
1624 let mut contract_names = HashMap::new();
1626 let mut storage_layouts = HashMap::new();
1627 for address in addresses_to_lookup {
1628 if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) {
1629 contract_names.insert(address, artifact_id.identifier());
1630
1631 if let Some(storage_layout) = &contract_data.storage_layout {
1633 storage_layouts.insert(address, storage_layout.clone());
1634 }
1635 }
1636 }
1637
1638 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1640 records
1641 .iter()
1642 .flatten()
1643 .filter(|account_access| {
1644 !account_access.storageAccesses.is_empty()
1645 || account_access.oldBalance != account_access.newBalance
1646 || account_access.oldNonce != account_access.newNonce
1647 })
1648 .for_each(|account_access| {
1649 if account_access.oldBalance != account_access.newBalance {
1651 let account_diff =
1652 state_diffs.entry(account_access.account).or_insert_with(|| {
1653 AccountStateDiffs {
1654 label: ccx.state.labels.get(&account_access.account).cloned(),
1655 contract: contract_names.get(&account_access.account).cloned(),
1656 ..Default::default()
1657 }
1658 });
1659 if let Some(diff) = &mut account_diff.balance_diff {
1661 diff.new_value = account_access.newBalance;
1662 } else {
1663 account_diff.balance_diff = Some(BalanceDiff {
1664 previous_value: account_access.oldBalance,
1665 new_value: account_access.newBalance,
1666 });
1667 }
1668 }
1669
1670 if account_access.oldNonce != account_access.newNonce {
1672 let account_diff =
1673 state_diffs.entry(account_access.account).or_insert_with(|| {
1674 AccountStateDiffs {
1675 label: ccx.state.labels.get(&account_access.account).cloned(),
1676 contract: contract_names.get(&account_access.account).cloned(),
1677 ..Default::default()
1678 }
1679 });
1680 if let Some(diff) = &mut account_diff.nonce_diff {
1682 diff.new_value = account_access.newNonce;
1683 } else {
1684 account_diff.nonce_diff = Some(NonceDiff {
1685 previous_value: account_access.oldNonce,
1686 new_value: account_access.newNonce,
1687 });
1688 }
1689 }
1690
1691 let raw_changes_by_slot = account_access
1693 .storageAccesses
1694 .iter()
1695 .filter_map(|access| {
1696 (access.isWrite && !access.reverted)
1697 .then_some((access.slot, (access.previousValue, access.newValue)))
1698 })
1699 .collect::<BTreeMap<_, _>>();
1700
1701 for storage_access in &account_access.storageAccesses {
1703 if storage_access.isWrite && !storage_access.reverted {
1704 let account_diff = state_diffs
1705 .entry(storage_access.account)
1706 .or_insert_with(|| AccountStateDiffs {
1707 label: ccx.state.labels.get(&storage_access.account).cloned(),
1708 contract: contract_names.get(&storage_access.account).cloned(),
1709 ..Default::default()
1710 });
1711 let layout = storage_layouts.get(&storage_access.account);
1712 let entry = match account_diff.state_diff.entry(storage_access.slot) {
1714 Entry::Vacant(slot_state_diff) => {
1715 let mapping_slots = ccx
1718 .state
1719 .mapping_slots
1720 .as_ref()
1721 .and_then(|slots| slots.get(&storage_access.account));
1722
1723 let slot_info = layout.and_then(|layout| {
1724 let decoder = SlotIdentifier::new(layout.clone());
1725 decoder.identify(&storage_access.slot, mapping_slots).or_else(
1726 || {
1727 let current_base_slot_values = raw_changes_by_slot
1732 .iter()
1733 .map(|(slot, (_, new_val))| (*slot, *new_val))
1734 .collect::<B256Map<_>>();
1735 decoder.identify_bytes_or_string(
1736 &storage_access.slot,
1737 ¤t_base_slot_values,
1738 )
1739 },
1740 )
1741 });
1742
1743 slot_state_diff.insert(SlotStateDiff {
1744 previous_value: storage_access.previousValue,
1745 new_value: storage_access.newValue,
1746 slot_info,
1747 })
1748 }
1749 Entry::Occupied(slot_state_diff) => {
1750 let entry = slot_state_diff.into_mut();
1751 entry.new_value = storage_access.newValue;
1752 entry
1753 }
1754 };
1755
1756 if let Some(slot_info) = &mut entry.slot_info {
1758 slot_info.decode_values(entry.previous_value, storage_access.newValue);
1759 if slot_info.is_bytes_or_string() {
1760 slot_info.decode_bytes_or_string_values(
1761 &storage_access.slot,
1762 &raw_changes_by_slot,
1763 );
1764 }
1765 }
1766 }
1767 }
1768 });
1769 }
1770 state_diffs
1771}
1772
1773const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
1775
1776const EIP1822_PROXIABLE_SLOT: &str =
1778 "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
1779
1780fn get_contract_data<'a, FEN: FoundryEvmNetwork>(
1782 ccx: &'a mut CheatsCtxt<'_, '_, FEN>,
1783 address: Address,
1784) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
1785 let artifacts = ccx.state.config.available_artifacts.as_ref()?;
1787
1788 let account = ccx.ecx.journal_mut().load_account(address).ok()?;
1790 let code = account.data.info.code.as_ref()?;
1791
1792 if code.is_empty() {
1794 return None;
1795 }
1796
1797 let code_bytes = code.original_bytes();
1799 let hex_str = hex::encode(&code_bytes);
1801 let find_by_suffix =
1802 |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
1803 if hex_str.contains(EIP1967_IMPL_SLOT)
1805 && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
1806 {
1807 return Some(result);
1808 } else if hex_str.contains(EIP1822_PROXIABLE_SLOT)
1809 && let Some(result) = find_by_suffix(":UUPSUpgradeable")
1810 {
1811 return Some(result);
1812 }
1813
1814 if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
1816 return Some(result);
1817 }
1818
1819 artifacts.find_by_deployed_code(&code_bytes)
1821}
1822
1823fn set_cold_slot<FEN: FoundryEvmNetwork>(
1825 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1826 target: Address,
1827 slot: U256,
1828 cold: bool,
1829) {
1830 if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target)
1831 && let Some(storage_slot) = account.storage.get_mut(&slot)
1832 {
1833 storage_slot.is_cold = cold;
1834 }
1835}