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