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