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