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