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