1use crate::{
4 BroadcastableTransaction, Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Error, Result,
5 Vm::*,
6 inspector::{Ecx, RecordDebugStepInfo},
7};
8use alloy_consensus::TxEnvelope;
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 fs::{read_json_file, write_json_file},
19 slot_identifier::{
20 ENCODING_BYTES, ENCODING_DYN_ARRAY, ENCODING_INPLACE, ENCODING_MAPPING, SlotIdentifier,
21 SlotInfo,
22 },
23};
24use foundry_compilers::artifacts::EvmVersion;
25use foundry_evm_core::{
26 ContextExt,
27 backend::{DatabaseExt, RevertStateSnapshotAction},
28 constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS},
29 utils::get_blob_base_fee_update_fraction_by_spec_id,
30};
31use foundry_evm_traces::TraceMode;
32use itertools::Itertools;
33use rand::Rng;
34use revm::{
35 bytecode::Bytecode,
36 context::{Block, JournalTr},
37 primitives::{KECCAK_EMPTY, hardfork::SpecId},
38 state::Account,
39};
40use std::{
41 collections::{BTreeMap, HashSet, btree_map::Entry},
42 fmt::Display,
43 path::Path,
44 str::FromStr,
45};
46
47mod record_debug_step;
48use foundry_common::fmt::format_token_raw;
49use foundry_config::evm_spec_id;
50use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
51use serde::Serialize;
52
53mod fork;
54pub(crate) mod mapping;
55pub(crate) mod mock;
56pub(crate) mod prank;
57
58#[derive(Clone, Debug, Default)]
60pub struct RecordAccess {
61 pub reads: HashMap<Address, Vec<U256>>,
63 pub writes: HashMap<Address, Vec<U256>>,
65}
66
67impl RecordAccess {
68 pub fn record_read(&mut self, target: Address, slot: U256) {
70 self.reads.entry(target).or_default().push(slot);
71 }
72
73 pub fn record_write(&mut self, target: Address, slot: U256) {
77 self.record_read(target, slot);
78 self.writes.entry(target).or_default().push(slot);
79 }
80
81 pub fn clear(&mut self) {
83 *self = Default::default();
85 }
86}
87
88#[derive(Clone, Debug)]
90pub struct GasRecord {
91 pub group: String,
93 pub name: String,
95 pub gas_used: u64,
97 pub depth: usize,
99}
100
101#[derive(Clone, Debug)]
103pub struct DealRecord {
104 pub address: Address,
106 pub old_balance: U256,
108 pub new_balance: U256,
110}
111
112#[derive(Serialize, Default)]
114#[serde(rename_all = "camelCase")]
115struct SlotStateDiff {
116 previous_value: B256,
118 new_value: B256,
120 #[serde(skip_serializing_if = "Option::is_none", flatten)]
124 slot_info: Option<SlotInfo>,
125}
126
127#[derive(Serialize, Default)]
129#[serde(rename_all = "camelCase")]
130struct BalanceDiff {
131 previous_value: U256,
133 new_value: U256,
135}
136
137#[derive(Serialize, Default)]
139#[serde(rename_all = "camelCase")]
140struct NonceDiff {
141 previous_value: u64,
143 new_value: u64,
145}
146
147#[derive(Serialize, Default)]
149#[serde(rename_all = "camelCase")]
150struct AccountStateDiffs {
151 label: Option<String>,
153 contract: Option<String>,
155 balance_diff: Option<BalanceDiff>,
157 nonce_diff: Option<NonceDiff>,
159 state_diff: BTreeMap<B256, SlotStateDiff>,
161}
162
163impl Display for AccountStateDiffs {
164 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
165 if let Some(label) = &self.label {
167 writeln!(f, "label: {label}")?;
168 }
169 if let Some(contract) = &self.contract {
170 writeln!(f, "contract: {contract}")?;
171 }
172 if let Some(balance_diff) = &self.balance_diff
174 && balance_diff.previous_value != balance_diff.new_value
175 {
176 writeln!(
177 f,
178 "- balance diff: {} → {}",
179 balance_diff.previous_value, balance_diff.new_value
180 )?;
181 }
182 if let Some(nonce_diff) = &self.nonce_diff
184 && nonce_diff.previous_value != nonce_diff.new_value
185 {
186 writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?;
187 }
188 if !&self.state_diff.is_empty() {
190 writeln!(f, "- state diff:")?;
191 for (slot, slot_changes) in &self.state_diff {
192 match &slot_changes.slot_info {
193 Some(slot_info) => {
194 if let Some(decoded) = &slot_info.decoded {
195 writeln!(
197 f,
198 "@ {slot} ({}, {}): {} → {}",
199 slot_info.label,
200 slot_info.slot_type.dyn_sol_type,
201 format_token_raw(&decoded.previous_value),
202 format_token_raw(&decoded.new_value)
203 )?;
204 } else {
205 writeln!(
207 f,
208 "@ {slot} ({}, {}): {} → {}",
209 slot_info.label,
210 slot_info.slot_type.dyn_sol_type,
211 slot_changes.previous_value,
212 slot_changes.new_value
213 )?;
214 }
215 }
216 None => {
217 writeln!(
219 f,
220 "@ {slot}: {} → {}",
221 slot_changes.previous_value, slot_changes.new_value
222 )?;
223 }
224 }
225 }
226 }
227
228 Ok(())
229 }
230}
231
232impl Cheatcode for addrCall {
233 fn apply(&self, _state: &mut Cheatcodes) -> Result {
234 let Self { privateKey } = self;
235 let wallet = super::crypto::parse_wallet(privateKey)?;
236 Ok(wallet.address().abi_encode())
237 }
238}
239
240impl Cheatcode for getNonce_0Call {
241 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
242 let Self { account } = self;
243 get_nonce(ccx, account)
244 }
245}
246
247impl Cheatcode for getNonce_1Call {
248 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
249 let Self { wallet } = self;
250 get_nonce(ccx, &wallet.addr)
251 }
252}
253
254impl Cheatcode for loadCall {
255 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
256 let Self { target, slot } = *self;
257 ccx.ensure_not_precompile(&target)?;
258
259 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
260 journal.load_account(db, target)?;
261 let mut val = journal
262 .sload(db, target, slot.into(), false)
263 .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?;
264
265 if val.is_cold && val.data.is_zero() {
266 if ccx.state.has_arbitrary_storage(&target) {
267 let rand_value = ccx.state.rng().random();
270 ccx.state.arbitrary_storage.as_mut().unwrap().save(
271 ccx.ecx,
272 target,
273 slot.into(),
274 rand_value,
275 );
276 val.data = rand_value;
277 } else if ccx.state.is_arbitrary_storage_copy(&target) {
278 let rand_value = ccx.state.rng().random();
282 val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
283 ccx.ecx,
284 target,
285 slot.into(),
286 rand_value,
287 );
288 }
289 }
290
291 Ok(val.abi_encode())
292 }
293}
294
295impl Cheatcode for loadAllocsCall {
296 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
297 let Self { pathToAllocsJson } = self;
298
299 let path = Path::new(pathToAllocsJson);
300 ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
301
302 let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
304 Ok(allocs) => allocs,
305 Err(_) => {
306 let genesis = read_json_file::<Genesis>(path)?;
308 genesis.alloc
309 }
310 };
311
312 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
314 db.load_allocs(&allocs, journal)
315 .map(|()| Vec::default())
316 .map_err(|e| fmt_err!("failed to load allocs: {e}"))
317 }
318}
319
320impl Cheatcode for cloneAccountCall {
321 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
322 let Self { source, target } = self;
323
324 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
325 let account = journal.load_account(db, *source)?;
326 let genesis = &genesis_account(account.data);
327 db.clone_account(genesis, target, journal)?;
328 ccx.ecx.journaled_state.database.add_persistent_account(*target);
330 Ok(Default::default())
331 }
332}
333
334impl Cheatcode for dumpStateCall {
335 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
336 let Self { pathToStateJson } = self;
337 let path = Path::new(pathToStateJson);
338
339 let skip = |key: &Address, val: &Account| {
341 key == &CHEATCODE_ADDRESS
342 || key == &CALLER
343 || key == &HARDHAT_CONSOLE_ADDRESS
344 || key == &TEST_CONTRACT_ADDRESS
345 || key == &ccx.caller
346 || key == &ccx.state.config.evm_opts.sender
347 || val.is_empty()
348 };
349
350 let alloc = ccx
351 .ecx
352 .journaled_state
353 .state()
354 .iter_mut()
355 .filter(|(key, val)| !skip(key, val))
356 .map(|(key, val)| (key, genesis_account(val)))
357 .collect::<BTreeMap<_, _>>();
358
359 write_json_file(path, &alloc)?;
360 Ok(Default::default())
361 }
362}
363
364impl Cheatcode for recordCall {
365 fn apply(&self, state: &mut Cheatcodes) -> Result {
366 let Self {} = self;
367 state.recording_accesses = true;
368 state.accesses.clear();
369 Ok(Default::default())
370 }
371}
372
373impl Cheatcode for stopRecordCall {
374 fn apply(&self, state: &mut Cheatcodes) -> Result {
375 state.recording_accesses = false;
376 Ok(Default::default())
377 }
378}
379
380impl Cheatcode for accessesCall {
381 fn apply(&self, state: &mut Cheatcodes) -> Result {
382 let Self { target } = *self;
383 let result = (
384 state.accesses.reads.entry(target).or_default().as_slice(),
385 state.accesses.writes.entry(target).or_default().as_slice(),
386 );
387 Ok(result.abi_encode_params())
388 }
389}
390
391impl Cheatcode for recordLogsCall {
392 fn apply(&self, state: &mut Cheatcodes) -> Result {
393 let Self {} = self;
394 state.recorded_logs = Some(Default::default());
395 Ok(Default::default())
396 }
397}
398
399impl Cheatcode for getRecordedLogsCall {
400 fn apply(&self, state: &mut Cheatcodes) -> Result {
401 let Self {} = self;
402 Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
403 }
404}
405
406impl Cheatcode for pauseGasMeteringCall {
407 fn apply(&self, state: &mut Cheatcodes) -> Result {
408 let Self {} = self;
409 state.gas_metering.paused = true;
410 Ok(Default::default())
411 }
412}
413
414impl Cheatcode for resumeGasMeteringCall {
415 fn apply(&self, state: &mut Cheatcodes) -> Result {
416 let Self {} = self;
417 state.gas_metering.resume();
418 Ok(Default::default())
419 }
420}
421
422impl Cheatcode for resetGasMeteringCall {
423 fn apply(&self, state: &mut Cheatcodes) -> Result {
424 let Self {} = self;
425 state.gas_metering.reset();
426 Ok(Default::default())
427 }
428}
429
430impl Cheatcode for lastCallGasCall {
431 fn apply(&self, state: &mut Cheatcodes) -> Result {
432 let Self {} = self;
433 let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
434 bail!("no external call was made yet");
435 };
436 Ok(last_call_gas.abi_encode())
437 }
438}
439
440impl Cheatcode for getChainIdCall {
441 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
442 let Self {} = self;
443 Ok(U256::from(ccx.ecx.cfg.chain_id).abi_encode())
444 }
445}
446
447impl Cheatcode for chainIdCall {
448 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
449 let Self { newChainId } = self;
450 ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64");
451 ccx.ecx.cfg.chain_id = newChainId.to();
452 Ok(Default::default())
453 }
454}
455
456impl Cheatcode for coinbaseCall {
457 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
458 let Self { newCoinbase } = self;
459 ccx.ecx.block.beneficiary = *newCoinbase;
460 Ok(Default::default())
461 }
462}
463
464impl Cheatcode for difficultyCall {
465 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
466 let Self { newDifficulty } = self;
467 ensure!(
468 ccx.ecx.cfg.spec < SpecId::MERGE,
469 "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
470 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
471 );
472 ccx.ecx.block.difficulty = *newDifficulty;
473 Ok(Default::default())
474 }
475}
476
477impl Cheatcode for feeCall {
478 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
479 let Self { newBasefee } = self;
480 ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64");
481 ccx.ecx.block.basefee = newBasefee.saturating_to();
482 Ok(Default::default())
483 }
484}
485
486impl Cheatcode for prevrandao_0Call {
487 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
488 let Self { newPrevrandao } = self;
489 ensure!(
490 ccx.ecx.cfg.spec >= SpecId::MERGE,
491 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
492 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
493 );
494 ccx.ecx.block.prevrandao = Some(*newPrevrandao);
495 Ok(Default::default())
496 }
497}
498
499impl Cheatcode for prevrandao_1Call {
500 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
501 let Self { newPrevrandao } = self;
502 ensure!(
503 ccx.ecx.cfg.spec >= SpecId::MERGE,
504 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
505 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
506 );
507 ccx.ecx.block.prevrandao = Some((*newPrevrandao).into());
508 Ok(Default::default())
509 }
510}
511
512impl Cheatcode for blobhashesCall {
513 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
514 let Self { hashes } = self;
515 ensure!(
516 ccx.ecx.cfg.spec >= SpecId::CANCUN,
517 "`blobhashes` is not supported before the Cancun hard fork; \
518 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
519 );
520 ccx.ecx.tx.blob_hashes.clone_from(hashes);
521 ccx.ecx.tx.tx_type = EIP4844_TX_TYPE_ID;
523 Ok(Default::default())
524 }
525}
526
527impl Cheatcode for getBlobhashesCall {
528 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
529 let Self {} = self;
530 ensure!(
531 ccx.ecx.cfg.spec >= SpecId::CANCUN,
532 "`getBlobhashes` is not supported before the Cancun hard fork; \
533 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
534 );
535 Ok(ccx.ecx.tx.blob_hashes.clone().abi_encode())
536 }
537}
538
539impl Cheatcode for rollCall {
540 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
541 let Self { newHeight } = self;
542 ccx.ecx.block.number = *newHeight;
543 Ok(Default::default())
544 }
545}
546
547impl Cheatcode for getBlockNumberCall {
548 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
549 let Self {} = self;
550 Ok(ccx.ecx.block.number.abi_encode())
551 }
552}
553
554impl Cheatcode for txGasPriceCall {
555 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
556 let Self { newGasPrice } = self;
557 ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64");
558 ccx.ecx.tx.gas_price = newGasPrice.saturating_to();
559 Ok(Default::default())
560 }
561}
562
563impl Cheatcode for warpCall {
564 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
565 let Self { newTimestamp } = self;
566 ccx.ecx.block.timestamp = *newTimestamp;
567 Ok(Default::default())
568 }
569}
570
571impl Cheatcode for getBlockTimestampCall {
572 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
573 let Self {} = self;
574 Ok(ccx.ecx.block.timestamp.abi_encode())
575 }
576}
577
578impl Cheatcode for blobBaseFeeCall {
579 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
580 let Self { newBlobBaseFee } = self;
581 ensure!(
582 ccx.ecx.cfg.spec >= SpecId::CANCUN,
583 "`blobBaseFee` is not supported before the Cancun hard fork; \
584 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
585 );
586
587 ccx.ecx.block.set_blob_excess_gas_and_price(
588 (*newBlobBaseFee).to(),
589 get_blob_base_fee_update_fraction_by_spec_id(ccx.ecx.cfg.spec),
590 );
591 Ok(Default::default())
592 }
593}
594
595impl Cheatcode for getBlobBaseFeeCall {
596 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
597 let Self {} = self;
598 Ok(ccx.ecx.block.blob_excess_gas().unwrap_or(0).abi_encode())
599 }
600}
601
602impl Cheatcode for dealCall {
603 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
604 let Self { account: address, newBalance: new_balance } = *self;
605 let account = journaled_account(ccx.ecx, address)?;
606 let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
607 let record = DealRecord { address, old_balance, new_balance };
608 ccx.state.eth_deals.push(record);
609 Ok(Default::default())
610 }
611}
612
613impl Cheatcode for etchCall {
614 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
615 let Self { target, newRuntimeBytecode } = self;
616 ccx.ensure_not_precompile(target)?;
617 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
618 journal.load_account(db, *target)?;
619 let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone())
620 .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
621 journal.set_code(*target, bytecode);
622 Ok(Default::default())
623 }
624}
625
626impl Cheatcode for resetNonceCall {
627 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
628 let Self { account } = self;
629 let account = journaled_account(ccx.ecx, *account)?;
630 let empty = account.info.code_hash == KECCAK_EMPTY;
634 let nonce = if empty { 0 } else { 1 };
635 account.info.nonce = nonce;
636 debug!(target: "cheatcodes", nonce, "reset");
637 Ok(Default::default())
638 }
639}
640
641impl Cheatcode for setNonceCall {
642 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
643 let Self { account, newNonce } = *self;
644 let account = journaled_account(ccx.ecx, account)?;
645 let current = account.info.nonce;
647 ensure!(
648 newNonce >= current,
649 "new nonce ({newNonce}) must be strictly equal to or higher than the \
650 account's current nonce ({current})"
651 );
652 account.info.nonce = newNonce;
653 Ok(Default::default())
654 }
655}
656
657impl Cheatcode for setNonceUnsafeCall {
658 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
659 let Self { account, newNonce } = *self;
660 let account = journaled_account(ccx.ecx, account)?;
661 account.info.nonce = newNonce;
662 Ok(Default::default())
663 }
664}
665
666impl Cheatcode for storeCall {
667 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
668 let Self { target, slot, value } = *self;
669 ccx.ensure_not_precompile(&target)?;
670 ensure_loaded_account(ccx.ecx, target)?;
671 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
672 journal
673 .sstore(db, target, slot.into(), value.into(), false)
674 .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?;
675 Ok(Default::default())
676 }
677}
678
679impl Cheatcode for coolCall {
680 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
681 let Self { target } = self;
682 if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) {
683 account.unmark_touch();
684 account.storage.values_mut().for_each(|slot| slot.mark_cold());
685 }
686 Ok(Default::default())
687 }
688}
689
690impl Cheatcode for accessListCall {
691 fn apply(&self, state: &mut Cheatcodes) -> Result {
692 let Self { access } = self;
693 let access_list = access
694 .iter()
695 .map(|item| {
696 let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
697 alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
698 })
699 .collect_vec();
700 state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
701 Ok(Default::default())
702 }
703}
704
705impl Cheatcode for noAccessListCall {
706 fn apply(&self, state: &mut Cheatcodes) -> Result {
707 let Self {} = self;
708 if state.access_list.is_some() {
710 state.access_list = Some(alloy_rpc_types::AccessList::default());
711 }
712 Ok(Default::default())
713 }
714}
715
716impl Cheatcode for warmSlotCall {
717 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
718 let Self { target, slot } = *self;
719 set_cold_slot(ccx, target, slot.into(), false);
720 Ok(Default::default())
721 }
722}
723
724impl Cheatcode for coolSlotCall {
725 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
726 let Self { target, slot } = *self;
727 set_cold_slot(ccx, target, slot.into(), true);
728 Ok(Default::default())
729 }
730}
731
732impl Cheatcode for readCallersCall {
733 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
734 let Self {} = self;
735 read_callers(ccx.state, &ccx.ecx.tx.caller, ccx.ecx.journaled_state.depth())
736 }
737}
738
739impl Cheatcode for snapshotValue_0Call {
740 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
741 let Self { name, value } = self;
742 inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
743 }
744}
745
746impl Cheatcode for snapshotValue_1Call {
747 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
748 let Self { group, name, value } = self;
749 inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
750 }
751}
752
753impl Cheatcode for snapshotGasLastCall_0Call {
754 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
755 let Self { name } = self;
756 let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
757 bail!("no external call was made yet");
758 };
759 inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed)
760 }
761}
762
763impl Cheatcode for snapshotGasLastCall_1Call {
764 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
765 let Self { name, group } = self;
766 let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else {
767 bail!("no external call was made yet");
768 };
769 inner_last_gas_snapshot(
770 ccx,
771 Some(group.clone()),
772 Some(name.clone()),
773 last_call_gas.gasTotalUsed,
774 )
775 }
776}
777
778impl Cheatcode for startSnapshotGas_0Call {
779 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
780 let Self { name } = self;
781 inner_start_gas_snapshot(ccx, None, Some(name.clone()))
782 }
783}
784
785impl Cheatcode for startSnapshotGas_1Call {
786 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
787 let Self { group, name } = self;
788 inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
789 }
790}
791
792impl Cheatcode for stopSnapshotGas_0Call {
793 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
794 let Self {} = self;
795 inner_stop_gas_snapshot(ccx, None, None)
796 }
797}
798
799impl Cheatcode for stopSnapshotGas_1Call {
800 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
801 let Self { name } = self;
802 inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
803 }
804}
805
806impl Cheatcode for stopSnapshotGas_2Call {
807 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
808 let Self { group, name } = self;
809 inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
810 }
811}
812
813impl Cheatcode for snapshotCall {
815 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
816 let Self {} = self;
817 inner_snapshot_state(ccx)
818 }
819}
820
821impl Cheatcode for snapshotStateCall {
822 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
823 let Self {} = self;
824 inner_snapshot_state(ccx)
825 }
826}
827
828impl Cheatcode for revertToCall {
830 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
831 let Self { snapshotId } = self;
832 inner_revert_to_state(ccx, *snapshotId)
833 }
834}
835
836impl Cheatcode for revertToStateCall {
837 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
838 let Self { snapshotId } = self;
839 inner_revert_to_state(ccx, *snapshotId)
840 }
841}
842
843impl Cheatcode for revertToAndDeleteCall {
845 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
846 let Self { snapshotId } = self;
847 inner_revert_to_state_and_delete(ccx, *snapshotId)
848 }
849}
850
851impl Cheatcode for revertToStateAndDeleteCall {
852 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
853 let Self { snapshotId } = self;
854 inner_revert_to_state_and_delete(ccx, *snapshotId)
855 }
856}
857
858impl Cheatcode for deleteSnapshotCall {
860 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
861 let Self { snapshotId } = self;
862 inner_delete_state_snapshot(ccx, *snapshotId)
863 }
864}
865
866impl Cheatcode for deleteStateSnapshotCall {
867 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
868 let Self { snapshotId } = self;
869 inner_delete_state_snapshot(ccx, *snapshotId)
870 }
871}
872
873impl Cheatcode for deleteSnapshotsCall {
875 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
876 let Self {} = self;
877 inner_delete_state_snapshots(ccx)
878 }
879}
880
881impl Cheatcode for deleteStateSnapshotsCall {
882 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
883 let Self {} = self;
884 inner_delete_state_snapshots(ccx)
885 }
886}
887
888impl Cheatcode for startStateDiffRecordingCall {
889 fn apply(&self, state: &mut Cheatcodes) -> Result {
890 let Self {} = self;
891 state.recorded_account_diffs_stack = Some(Default::default());
892 state.mapping_slots.get_or_insert_default();
894 Ok(Default::default())
895 }
896}
897
898impl Cheatcode for stopAndReturnStateDiffCall {
899 fn apply(&self, state: &mut Cheatcodes) -> Result {
900 let Self {} = self;
901 get_state_diff(state)
902 }
903}
904
905impl Cheatcode for getStateDiffCall {
906 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
907 let mut diffs = String::new();
908 let state_diffs = get_recorded_state_diffs(ccx);
909 for (address, state_diffs) in state_diffs {
910 diffs.push_str(&format!("{address}\n"));
911 diffs.push_str(&format!("{state_diffs}\n"));
912 }
913 Ok(diffs.abi_encode())
914 }
915}
916
917impl Cheatcode for getStateDiffJsonCall {
918 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
919 let state_diffs = get_recorded_state_diffs(ccx);
920 Ok(serde_json::to_string(&state_diffs)?.abi_encode())
921 }
922}
923
924impl Cheatcode for getStorageSlotsCall {
925 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
926 let Self { target, variableName } = self;
927
928 let storage_layout = get_contract_data(ccx, *target)
929 .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone()))
930 .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?;
931
932 trace!(storage = ?storage_layout.storage, "fetched storage");
933
934 let storage = storage_layout
935 .storage
936 .iter()
937 .find(|s| s.label.to_lowercase() == *variableName.to_lowercase())
938 .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
939
940 let storage_type = storage_layout
941 .types
942 .get(&storage.storage_type)
943 .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
944
945 if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
946 {
947 return Err(fmt_err!(
948 "cannot get storage slots for variables with mapping or dynamic array types"
949 ));
950 }
951
952 let slot = U256::from_str(&storage.slot).map_err(|_| {
953 fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
954 })?;
955
956 let mut slots = Vec::new();
957
958 slots.push(slot);
960
961 if storage_type.encoding == ENCODING_INPLACE {
962 let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
964 fmt_err!(
965 "invalid number_of_bytes {} for variable {variableName}",
966 storage_type.number_of_bytes
967 )
968 })?;
969 let num_slots = num_bytes.div_ceil(U256::from(32));
970
971 for i in 1..num_slots.to::<usize>() {
973 slots.push(slot + U256::from(i));
974 }
975 }
976
977 if storage_type.encoding == ENCODING_BYTES {
978 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
981 if let Ok(value) = journal.sload(db, *target, slot, false) {
982 let value_bytes = value.data.to_be_bytes::<32>();
983 let length_byte = value_bytes[31];
984 if length_byte & 1 == 1 {
986 let length: U256 = value.data >> 1;
988 let num_data_slots = length.to::<usize>().div_ceil(32);
989 let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0);
990
991 for i in 0..num_data_slots {
992 slots.push(data_start + U256::from(i));
993 }
994 }
995 }
996 }
997
998 Ok(slots.abi_encode())
999 }
1000}
1001
1002impl Cheatcode for getStorageAccessesCall {
1003 fn apply(&self, state: &mut Cheatcodes) -> Result {
1004 let mut storage_accesses = Vec::new();
1005
1006 if let Some(recorded_diffs) = &state.recorded_account_diffs_stack {
1007 for account_accesses in recorded_diffs.iter().flatten() {
1008 storage_accesses.extend(account_accesses.storageAccesses.clone());
1009 }
1010 }
1011
1012 Ok(storage_accesses.abi_encode())
1013 }
1014}
1015
1016impl Cheatcode for broadcastRawTransactionCall {
1017 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1018 let tx = TxEnvelope::decode(&mut self.data.as_ref())
1019 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1020
1021 let (db, journal, env) = ccx.ecx.as_db_env_and_journal();
1022 db.transact_from_tx(
1023 &tx.clone().into(),
1024 env.to_owned(),
1025 journal,
1026 &mut *executor.get_inspector(ccx.state),
1027 )?;
1028
1029 if ccx.state.broadcast.is_some() {
1030 ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1031 rpc: ccx.ecx.journaled_state.database.active_fork_url(),
1032 transaction: tx.try_into()?,
1033 });
1034 }
1035
1036 Ok(Default::default())
1037 }
1038}
1039
1040impl Cheatcode for setBlockhashCall {
1041 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1042 let Self { blockNumber, blockHash } = *self;
1043 ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1044 ensure!(
1045 blockNumber <= U256::from(ccx.ecx.block.number),
1046 "block number must be less than or equal to the current block number"
1047 );
1048
1049 ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash);
1050
1051 Ok(Default::default())
1052 }
1053}
1054
1055impl Cheatcode for startDebugTraceRecordingCall {
1056 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1057 let Some(tracer) = executor.tracing_inspector() else {
1058 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1059 };
1060
1061 let mut info = RecordDebugStepInfo {
1062 start_node_idx: 0,
1064 original_tracer_config: *tracer.config(),
1066 };
1067
1068 *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None");
1070
1071 if let Some(last_node) = tracer.traces().nodes().last() {
1073 info.start_node_idx = last_node.idx;
1074 }
1075
1076 ccx.state.record_debug_steps_info = Some(info);
1077 Ok(Default::default())
1078 }
1079}
1080
1081impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1082 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1083 let Some(tracer) = executor.tracing_inspector() else {
1084 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1085 };
1086
1087 let Some(record_info) = ccx.state.record_debug_steps_info else {
1088 return Err(Error::from("nothing recorded"));
1089 };
1090
1091 let root = tracer.traces();
1093 let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1094
1095 let debug_steps: Vec<DebugStep> =
1096 steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1097 if !record_info.original_tracer_config.record_steps {
1099 tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1100 node.trace.steps = Vec::new();
1101 node.logs = Vec::new();
1102 node.ordering = Vec::new();
1103 });
1104 }
1105
1106 tracer.update_config(|_config| record_info.original_tracer_config);
1108
1109 ccx.state.record_debug_steps_info = None;
1111
1112 Ok(debug_steps.abi_encode())
1113 }
1114}
1115
1116impl Cheatcode for setEvmVersionCall {
1117 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1118 let Self { evm } = self;
1119 let spec_id = evm_spec_id(
1120 EvmVersion::from_str(evm)
1121 .map_err(|_| Error::from(format!("invalid evm version {evm}")))?,
1122 );
1123 ccx.state.execution_evm_version = Some(spec_id);
1124 Ok(Default::default())
1125 }
1126}
1127
1128impl Cheatcode for getEvmVersionCall {
1129 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1130 Ok(ccx.ecx.cfg.spec.to_string().to_lowercase().abi_encode())
1131 }
1132}
1133
1134pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result {
1135 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1136 let account = journal.load_account(db, *address)?;
1137 Ok(account.info.nonce.abi_encode())
1138}
1139
1140fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result {
1141 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1142 Ok(db.snapshot_state(journal, &mut env).abi_encode())
1143}
1144
1145fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1146 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1147 let result = if let Some(journaled_state) =
1148 db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep)
1149 {
1150 ccx.ecx.journaled_state.inner = journaled_state;
1152 true
1153 } else {
1154 false
1155 };
1156 Ok(result.abi_encode())
1157}
1158
1159fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1160 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1161
1162 let result = if let Some(journaled_state) =
1163 db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove)
1164 {
1165 ccx.ecx.journaled_state.inner = journaled_state;
1167 true
1168 } else {
1169 false
1170 };
1171 Ok(result.abi_encode())
1172}
1173
1174fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1175 let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id);
1176 Ok(result.abi_encode())
1177}
1178
1179fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result {
1180 ccx.ecx.journaled_state.database.delete_state_snapshots();
1181 Ok(Default::default())
1182}
1183
1184fn inner_value_snapshot(
1185 ccx: &mut CheatsCtxt,
1186 group: Option<String>,
1187 name: Option<String>,
1188 value: String,
1189) -> Result {
1190 let (group, name) = derive_snapshot_name(ccx, group, name);
1191
1192 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1193
1194 Ok(Default::default())
1195}
1196
1197fn inner_last_gas_snapshot(
1198 ccx: &mut CheatsCtxt,
1199 group: Option<String>,
1200 name: Option<String>,
1201 value: u64,
1202) -> Result {
1203 let (group, name) = derive_snapshot_name(ccx, group, name);
1204
1205 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1206
1207 Ok(value.abi_encode())
1208}
1209
1210fn inner_start_gas_snapshot(
1211 ccx: &mut CheatsCtxt,
1212 group: Option<String>,
1213 name: Option<String>,
1214) -> Result {
1215 if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1217 bail!("gas snapshot was already started with group: {group} and name: {name}");
1218 }
1219
1220 let (group, name) = derive_snapshot_name(ccx, group, name);
1221
1222 ccx.state.gas_metering.gas_records.push(GasRecord {
1223 group: group.clone(),
1224 name: name.clone(),
1225 gas_used: 0,
1226 depth: ccx.ecx.journaled_state.depth(),
1227 });
1228
1229 ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1230
1231 ccx.state.gas_metering.start();
1232
1233 Ok(Default::default())
1234}
1235
1236fn inner_stop_gas_snapshot(
1237 ccx: &mut CheatsCtxt,
1238 group: Option<String>,
1239 name: Option<String>,
1240) -> Result {
1241 let (group, name) = group
1243 .zip(name)
1244 .unwrap_or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone().unwrap());
1245
1246 if let Some(record) = ccx
1247 .state
1248 .gas_metering
1249 .gas_records
1250 .iter_mut()
1251 .find(|record| record.group == group && record.name == name)
1252 {
1253 let value = record.gas_used.saturating_sub(171);
1256
1257 ccx.state
1258 .gas_snapshots
1259 .entry(group.clone())
1260 .or_default()
1261 .insert(name.clone(), value.to_string());
1262
1263 ccx.state.gas_metering.stop();
1265
1266 ccx.state
1268 .gas_metering
1269 .gas_records
1270 .retain(|record| record.group != group && record.name != name);
1271
1272 if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1274 && snapshot_group == &group
1275 && snapshot_name == &name
1276 {
1277 ccx.state.gas_metering.active_gas_snapshot = None;
1278 }
1279
1280 Ok(value.abi_encode())
1281 } else {
1282 bail!("no gas snapshot was started with the name: {name} in group: {group}");
1283 }
1284}
1285
1286fn derive_snapshot_name(
1288 ccx: &CheatsCtxt,
1289 group: Option<String>,
1290 name: Option<String>,
1291) -> (String, String) {
1292 let group = group.unwrap_or_else(|| {
1293 ccx.state.config.running_artifact.clone().expect("expected running contract").name
1294 });
1295 let name = name.unwrap_or_else(|| "default".to_string());
1296 (group, name)
1297}
1298
1299fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) -> Result {
1323 let mut mode = CallerMode::None;
1324 let mut new_caller = default_sender;
1325 let mut new_origin = default_sender;
1326 if let Some(prank) = state.get_prank(call_depth) {
1327 mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1328 new_caller = &prank.new_caller;
1329 if let Some(new) = &prank.new_origin {
1330 new_origin = new;
1331 }
1332 } else if let Some(broadcast) = &state.broadcast {
1333 mode = if broadcast.single_call {
1334 CallerMode::Broadcast
1335 } else {
1336 CallerMode::RecurrentBroadcast
1337 };
1338 new_caller = &broadcast.new_origin;
1339 new_origin = &broadcast.new_origin;
1340 }
1341
1342 Ok((mode, new_caller, new_origin).abi_encode_params())
1343}
1344
1345pub(super) fn journaled_account<'a>(
1347 ecx: Ecx<'a, '_, '_>,
1348 addr: Address,
1349) -> Result<&'a mut Account> {
1350 ensure_loaded_account(ecx, addr)?;
1351 Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded"))
1352}
1353
1354pub(super) fn ensure_loaded_account(ecx: Ecx, addr: Address) -> Result<()> {
1355 let (db, journal, _) = ecx.as_db_env_and_journal();
1356 journal.load_account(db, addr)?;
1357 journal.touch(addr);
1358 Ok(())
1359}
1360
1361fn get_state_diff(state: &mut Cheatcodes) -> Result {
1369 let res = state
1370 .recorded_account_diffs_stack
1371 .replace(Default::default())
1372 .unwrap_or_default()
1373 .into_iter()
1374 .flatten()
1375 .collect::<Vec<_>>();
1376 Ok(res.abi_encode())
1377}
1378
1379fn genesis_account(account: &Account) -> GenesisAccount {
1381 GenesisAccount {
1382 nonce: Some(account.info.nonce),
1383 balance: account.info.balance,
1384 code: account.info.code.as_ref().map(|o| o.original_bytes()),
1385 storage: Some(
1386 account
1387 .storage
1388 .iter()
1389 .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1390 .collect(),
1391 ),
1392 private_key: None,
1393 }
1394}
1395
1396fn get_recorded_state_diffs(ccx: &mut CheatsCtxt) -> BTreeMap<Address, AccountStateDiffs> {
1398 let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1399
1400 let mut addresses_to_lookup = HashSet::new();
1402 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1403 for account_access in records.iter().flatten() {
1404 if !account_access.storageAccesses.is_empty()
1405 || account_access.oldBalance != account_access.newBalance
1406 {
1407 addresses_to_lookup.insert(account_access.account);
1408 for storage_access in &account_access.storageAccesses {
1409 if storage_access.isWrite && !storage_access.reverted {
1410 addresses_to_lookup.insert(storage_access.account);
1411 }
1412 }
1413 }
1414 }
1415 }
1416
1417 let mut contract_names = HashMap::new();
1419 let mut storage_layouts = HashMap::new();
1420 for address in addresses_to_lookup {
1421 if let Some((artifact_id, _)) = get_contract_data(ccx, address) {
1422 contract_names.insert(address, artifact_id.identifier());
1423 }
1424
1425 if let Some((_artifact_id, contract_data)) = get_contract_data(ccx, address)
1427 && let Some(storage_layout) = &contract_data.storage_layout
1428 {
1429 storage_layouts.insert(address, storage_layout.clone());
1430 }
1431 }
1432
1433 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1435 records
1436 .iter()
1437 .flatten()
1438 .filter(|account_access| {
1439 !account_access.storageAccesses.is_empty()
1440 || account_access.oldBalance != account_access.newBalance
1441 || account_access.oldNonce != account_access.newNonce
1442 })
1443 .for_each(|account_access| {
1444 if account_access.oldBalance != account_access.newBalance {
1446 let account_diff =
1447 state_diffs.entry(account_access.account).or_insert_with(|| {
1448 AccountStateDiffs {
1449 label: ccx.state.labels.get(&account_access.account).cloned(),
1450 contract: contract_names.get(&account_access.account).cloned(),
1451 ..Default::default()
1452 }
1453 });
1454 if let Some(diff) = &mut account_diff.balance_diff {
1456 diff.new_value = account_access.newBalance;
1457 } else {
1458 account_diff.balance_diff = Some(BalanceDiff {
1459 previous_value: account_access.oldBalance,
1460 new_value: account_access.newBalance,
1461 });
1462 }
1463 }
1464
1465 if account_access.oldNonce != account_access.newNonce {
1467 let account_diff =
1468 state_diffs.entry(account_access.account).or_insert_with(|| {
1469 AccountStateDiffs {
1470 label: ccx.state.labels.get(&account_access.account).cloned(),
1471 contract: contract_names.get(&account_access.account).cloned(),
1472 ..Default::default()
1473 }
1474 });
1475 if let Some(diff) = &mut account_diff.nonce_diff {
1477 diff.new_value = account_access.newNonce;
1478 } else {
1479 account_diff.nonce_diff = Some(NonceDiff {
1480 previous_value: account_access.oldNonce,
1481 new_value: account_access.newNonce,
1482 });
1483 }
1484 }
1485
1486 let raw_changes_by_slot = account_access
1488 .storageAccesses
1489 .iter()
1490 .filter_map(|access| {
1491 (access.isWrite && !access.reverted)
1492 .then_some((access.slot, (access.previousValue, access.newValue)))
1493 })
1494 .collect::<BTreeMap<_, _>>();
1495
1496 for storage_access in &account_access.storageAccesses {
1498 if storage_access.isWrite && !storage_access.reverted {
1499 let account_diff = state_diffs
1500 .entry(storage_access.account)
1501 .or_insert_with(|| AccountStateDiffs {
1502 label: ccx.state.labels.get(&storage_access.account).cloned(),
1503 contract: contract_names.get(&storage_access.account).cloned(),
1504 ..Default::default()
1505 });
1506 let layout = storage_layouts.get(&storage_access.account);
1507 let entry = match account_diff.state_diff.entry(storage_access.slot) {
1509 Entry::Vacant(slot_state_diff) => {
1510 let mapping_slots = ccx
1513 .state
1514 .mapping_slots
1515 .as_ref()
1516 .and_then(|slots| slots.get(&storage_access.account));
1517
1518 let slot_info = layout.and_then(|layout| {
1519 let decoder = SlotIdentifier::new(layout.clone());
1520 decoder.identify(&storage_access.slot, mapping_slots).or_else(
1521 || {
1522 let current_base_slot_values = raw_changes_by_slot
1527 .iter()
1528 .map(|(slot, (_, new_val))| (*slot, *new_val))
1529 .collect::<B256Map<_>>();
1530 decoder.identify_bytes_or_string(
1531 &storage_access.slot,
1532 ¤t_base_slot_values,
1533 )
1534 },
1535 )
1536 });
1537
1538 slot_state_diff.insert(SlotStateDiff {
1539 previous_value: storage_access.previousValue,
1540 new_value: storage_access.newValue,
1541 slot_info,
1542 })
1543 }
1544 Entry::Occupied(slot_state_diff) => {
1545 let entry = slot_state_diff.into_mut();
1546 entry.new_value = storage_access.newValue;
1547 entry
1548 }
1549 };
1550
1551 if let Some(slot_info) = &mut entry.slot_info {
1553 slot_info.decode_values(entry.previous_value, storage_access.newValue);
1554 if slot_info.is_bytes_or_string() {
1555 slot_info.decode_bytes_or_string_values(
1556 &storage_access.slot,
1557 &raw_changes_by_slot,
1558 );
1559 }
1560 }
1561 }
1562 }
1563 });
1564 }
1565 state_diffs
1566}
1567
1568const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
1570
1571const EIP1822_PROXIABLE_SLOT: &str =
1573 "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
1574
1575fn get_contract_data<'a>(
1577 ccx: &'a mut CheatsCtxt,
1578 address: Address,
1579) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
1580 let artifacts = ccx.state.config.available_artifacts.as_ref()?;
1582
1583 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1585 let account = journal.load_account(db, address).ok()?;
1586 let code = account.info.code.as_ref()?;
1587
1588 if code.is_empty() {
1590 return None;
1591 }
1592
1593 let code_bytes = code.original_bytes();
1595 let hex_str = hex::encode(&code_bytes);
1597 let find_by_suffix =
1598 |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
1599 if hex_str.contains(EIP1967_IMPL_SLOT)
1601 && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
1602 {
1603 return Some(result);
1604 } else if hex_str.contains(EIP1822_PROXIABLE_SLOT)
1605 && let Some(result) = find_by_suffix(":UUPSUpgradeable")
1606 {
1607 return Some(result);
1608 }
1609
1610 if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
1612 return Some(result);
1613 }
1614
1615 artifacts.find_by_deployed_code(&code_bytes)
1617}
1618
1619fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) {
1621 if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target)
1622 && let Some(storage_slot) = account.storage.get_mut(&slot)
1623 {
1624 storage_slot.is_cold = cold;
1625 }
1626}