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 variable_name_lower = variableName.to_lowercase();
963 let storage = storage_layout
964 .storage
965 .iter()
966 .find(|s| s.label.to_lowercase() == variable_name_lower)
967 .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
968
969 let storage_type = storage_layout
970 .types
971 .get(&storage.storage_type)
972 .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
973
974 if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
975 {
976 return Err(fmt_err!(
977 "cannot get storage slots for variables with mapping or dynamic array types"
978 ));
979 }
980
981 let slot = U256::from_str(&storage.slot).map_err(|_| {
982 fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
983 })?;
984
985 let mut slots = Vec::new();
986
987 slots.push(slot);
989
990 if storage_type.encoding == ENCODING_INPLACE {
991 let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
993 fmt_err!(
994 "invalid number_of_bytes {} for variable {variableName}",
995 storage_type.number_of_bytes
996 )
997 })?;
998 let num_slots = num_bytes.div_ceil(U256::from(32));
999
1000 for i in 1..num_slots.to::<usize>() {
1002 slots.push(slot + U256::from(i));
1003 }
1004 }
1005
1006 if storage_type.encoding == ENCODING_BYTES {
1007 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1010 if let Ok(value) = journal.sload(db, *target, slot, false) {
1011 let value_bytes = value.data.to_be_bytes::<32>();
1012 let length_byte = value_bytes[31];
1013 if length_byte & 1 == 1 {
1015 let length: U256 = value.data >> 1;
1017 let num_data_slots = length.to::<usize>().div_ceil(32);
1018 let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0);
1019
1020 for i in 0..num_data_slots {
1021 slots.push(data_start + U256::from(i));
1022 }
1023 }
1024 }
1025 }
1026
1027 Ok(slots.abi_encode())
1028 }
1029}
1030
1031impl Cheatcode for getStorageAccessesCall {
1032 fn apply(&self, state: &mut Cheatcodes) -> Result {
1033 let mut storage_accesses = Vec::new();
1034
1035 if let Some(recorded_diffs) = &state.recorded_account_diffs_stack {
1036 for account_accesses in recorded_diffs.iter().flatten() {
1037 storage_accesses.extend(account_accesses.storageAccesses.clone());
1038 }
1039 }
1040
1041 Ok(storage_accesses.abi_encode())
1042 }
1043}
1044
1045impl Cheatcode for broadcastRawTransactionCall {
1046 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1047 let tx = TxEnvelope::decode(&mut self.data.as_ref())
1048 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1049
1050 let (db, journal, env) = ccx.ecx.as_db_env_and_journal();
1051 db.transact_from_tx(
1052 &tx.clone().into(),
1053 env.to_owned(),
1054 journal,
1055 &mut *executor.get_inspector(ccx.state),
1056 )?;
1057
1058 if ccx.state.broadcast.is_some() {
1059 ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1060 rpc: ccx.ecx.journaled_state.database.active_fork_url(),
1061 transaction: tx.try_into()?,
1062 });
1063 }
1064
1065 Ok(Default::default())
1066 }
1067}
1068
1069impl Cheatcode for setBlockhashCall {
1070 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1071 let Self { blockNumber, blockHash } = *self;
1072 ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1073 ensure!(
1074 blockNumber <= U256::from(ccx.ecx.block.number),
1075 "block number must be less than or equal to the current block number"
1076 );
1077
1078 ccx.ecx.journaled_state.database.set_blockhash(blockNumber, blockHash);
1079
1080 Ok(Default::default())
1081 }
1082}
1083
1084impl Cheatcode for startDebugTraceRecordingCall {
1085 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1086 let Some(tracer) = executor.tracing_inspector() else {
1087 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1088 };
1089
1090 let mut info = RecordDebugStepInfo {
1091 start_node_idx: 0,
1093 original_tracer_config: *tracer.config(),
1095 };
1096
1097 *tracer.config_mut() = TraceMode::Debug.into_config().expect("cannot be None");
1099
1100 if let Some(last_node) = tracer.traces().nodes().last() {
1102 info.start_node_idx = last_node.idx;
1103 }
1104
1105 ccx.state.record_debug_steps_info = Some(info);
1106 Ok(Default::default())
1107 }
1108}
1109
1110impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1111 fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result {
1112 let Some(tracer) = executor.tracing_inspector() else {
1113 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1114 };
1115
1116 let Some(record_info) = ccx.state.record_debug_steps_info else {
1117 return Err(Error::from("nothing recorded"));
1118 };
1119
1120 let root = tracer.traces();
1122 let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1123
1124 let debug_steps: Vec<DebugStep> =
1125 steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1126 if !record_info.original_tracer_config.record_steps {
1128 tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1129 node.trace.steps = Vec::new();
1130 node.logs = Vec::new();
1131 node.ordering = Vec::new();
1132 });
1133 }
1134
1135 tracer.update_config(|_config| record_info.original_tracer_config);
1137
1138 ccx.state.record_debug_steps_info = None;
1140
1141 Ok(debug_steps.abi_encode())
1142 }
1143}
1144
1145impl Cheatcode for setEvmVersionCall {
1146 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1147 let Self { evm } = self;
1148 let spec_id = evm_spec_id(
1149 EvmVersion::from_str(evm)
1150 .map_err(|_| Error::from(format!("invalid evm version {evm}")))?,
1151 );
1152 ccx.state.execution_evm_version = Some(spec_id);
1153 Ok(Default::default())
1154 }
1155}
1156
1157impl Cheatcode for getEvmVersionCall {
1158 fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
1159 Ok(ccx.ecx.cfg.spec.to_string().to_lowercase().abi_encode())
1160 }
1161}
1162
1163pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result {
1164 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1165 let account = journal.load_account(db, *address)?;
1166 Ok(account.info.nonce.abi_encode())
1167}
1168
1169fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result {
1170 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1171 Ok(db.snapshot_state(journal, &mut env).abi_encode())
1172}
1173
1174fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1175 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1176 let result = if let Some(journaled_state) =
1177 db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertKeep)
1178 {
1179 ccx.ecx.journaled_state.inner = journaled_state;
1181 true
1182 } else {
1183 false
1184 };
1185 Ok(result.abi_encode())
1186}
1187
1188fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1189 let (db, journal, mut env) = ccx.ecx.as_db_env_and_journal();
1190
1191 let result = if let Some(journaled_state) =
1192 db.revert_state(snapshot_id, &*journal, &mut env, RevertStateSnapshotAction::RevertRemove)
1193 {
1194 ccx.ecx.journaled_state.inner = journaled_state;
1196 true
1197 } else {
1198 false
1199 };
1200 Ok(result.abi_encode())
1201}
1202
1203fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result {
1204 let result = ccx.ecx.journaled_state.database.delete_state_snapshot(snapshot_id);
1205 Ok(result.abi_encode())
1206}
1207
1208fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result {
1209 ccx.ecx.journaled_state.database.delete_state_snapshots();
1210 Ok(Default::default())
1211}
1212
1213fn inner_value_snapshot(
1214 ccx: &mut CheatsCtxt,
1215 group: Option<String>,
1216 name: Option<String>,
1217 value: String,
1218) -> Result {
1219 let (group, name) = derive_snapshot_name(ccx, group, name);
1220
1221 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1222
1223 Ok(Default::default())
1224}
1225
1226fn inner_last_gas_snapshot(
1227 ccx: &mut CheatsCtxt,
1228 group: Option<String>,
1229 name: Option<String>,
1230 value: u64,
1231) -> Result {
1232 let (group, name) = derive_snapshot_name(ccx, group, name);
1233
1234 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1235
1236 Ok(value.abi_encode())
1237}
1238
1239fn inner_start_gas_snapshot(
1240 ccx: &mut CheatsCtxt,
1241 group: Option<String>,
1242 name: Option<String>,
1243) -> Result {
1244 if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1246 bail!("gas snapshot was already started with group: {group} and name: {name}");
1247 }
1248
1249 let (group, name) = derive_snapshot_name(ccx, group, name);
1250
1251 ccx.state.gas_metering.gas_records.push(GasRecord {
1252 group: group.clone(),
1253 name: name.clone(),
1254 gas_used: 0,
1255 depth: ccx.ecx.journaled_state.depth(),
1256 });
1257
1258 ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1259
1260 ccx.state.gas_metering.start();
1261
1262 Ok(Default::default())
1263}
1264
1265fn inner_stop_gas_snapshot(
1266 ccx: &mut CheatsCtxt,
1267 group: Option<String>,
1268 name: Option<String>,
1269) -> Result {
1270 let (group, name) = group
1272 .zip(name)
1273 .unwrap_or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone().unwrap());
1274
1275 if let Some(record) = ccx
1276 .state
1277 .gas_metering
1278 .gas_records
1279 .iter_mut()
1280 .find(|record| record.group == group && record.name == name)
1281 {
1282 let value = record.gas_used.saturating_sub(171);
1285
1286 ccx.state
1287 .gas_snapshots
1288 .entry(group.clone())
1289 .or_default()
1290 .insert(name.clone(), value.to_string());
1291
1292 ccx.state.gas_metering.stop();
1294
1295 ccx.state
1297 .gas_metering
1298 .gas_records
1299 .retain(|record| record.group != group && record.name != name);
1300
1301 if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1303 && snapshot_group == &group
1304 && snapshot_name == &name
1305 {
1306 ccx.state.gas_metering.active_gas_snapshot = None;
1307 }
1308
1309 Ok(value.abi_encode())
1310 } else {
1311 bail!("no gas snapshot was started with the name: {name} in group: {group}");
1312 }
1313}
1314
1315fn derive_snapshot_name(
1317 ccx: &CheatsCtxt,
1318 group: Option<String>,
1319 name: Option<String>,
1320) -> (String, String) {
1321 let group = group.unwrap_or_else(|| {
1322 ccx.state.config.running_artifact.clone().expect("expected running contract").name
1323 });
1324 let name = name.unwrap_or_else(|| "default".to_string());
1325 (group, name)
1326}
1327
1328fn read_callers(state: &Cheatcodes, default_sender: &Address, call_depth: usize) -> Result {
1352 let mut mode = CallerMode::None;
1353 let mut new_caller = default_sender;
1354 let mut new_origin = default_sender;
1355 if let Some(prank) = state.get_prank(call_depth) {
1356 mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1357 new_caller = &prank.new_caller;
1358 if let Some(new) = &prank.new_origin {
1359 new_origin = new;
1360 }
1361 } else if let Some(broadcast) = &state.broadcast {
1362 mode = if broadcast.single_call {
1363 CallerMode::Broadcast
1364 } else {
1365 CallerMode::RecurrentBroadcast
1366 };
1367 new_caller = &broadcast.new_origin;
1368 new_origin = &broadcast.new_origin;
1369 }
1370
1371 Ok((mode, new_caller, new_origin).abi_encode_params())
1372}
1373
1374pub(super) fn journaled_account<'a>(
1376 ecx: Ecx<'a, '_, '_>,
1377 addr: Address,
1378) -> Result<&'a mut Account> {
1379 ensure_loaded_account(ecx, addr)?;
1380 Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded"))
1381}
1382
1383pub(super) fn ensure_loaded_account(ecx: Ecx, addr: Address) -> Result<()> {
1384 let (db, journal, _) = ecx.as_db_env_and_journal();
1385 journal.load_account(db, addr)?;
1386 journal.touch(addr);
1387 Ok(())
1388}
1389
1390fn get_state_diff(state: &mut Cheatcodes) -> Result {
1398 let res = state
1399 .recorded_account_diffs_stack
1400 .replace(Default::default())
1401 .unwrap_or_default()
1402 .into_iter()
1403 .flatten()
1404 .collect::<Vec<_>>();
1405 Ok(res.abi_encode())
1406}
1407
1408fn genesis_account(account: &Account) -> GenesisAccount {
1410 GenesisAccount {
1411 nonce: Some(account.info.nonce),
1412 balance: account.info.balance,
1413 code: account.info.code.as_ref().map(|o| o.original_bytes()),
1414 storage: Some(
1415 account
1416 .storage
1417 .iter()
1418 .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
1419 .collect(),
1420 ),
1421 private_key: None,
1422 }
1423}
1424
1425fn get_recorded_state_diffs(ccx: &mut CheatsCtxt) -> BTreeMap<Address, AccountStateDiffs> {
1427 let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1428
1429 let mut addresses_to_lookup = HashSet::new();
1431 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1432 for account_access in records.iter().flatten() {
1433 if !account_access.storageAccesses.is_empty()
1434 || account_access.oldBalance != account_access.newBalance
1435 {
1436 addresses_to_lookup.insert(account_access.account);
1437 for storage_access in &account_access.storageAccesses {
1438 if storage_access.isWrite && !storage_access.reverted {
1439 addresses_to_lookup.insert(storage_access.account);
1440 }
1441 }
1442 }
1443 }
1444 }
1445
1446 let mut contract_names = HashMap::new();
1448 let mut storage_layouts = HashMap::new();
1449 for address in addresses_to_lookup {
1450 if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address) {
1451 contract_names.insert(address, artifact_id.identifier());
1452
1453 if let Some(storage_layout) = &contract_data.storage_layout {
1455 storage_layouts.insert(address, storage_layout.clone());
1456 }
1457 }
1458 }
1459
1460 if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1462 records
1463 .iter()
1464 .flatten()
1465 .filter(|account_access| {
1466 !account_access.storageAccesses.is_empty()
1467 || account_access.oldBalance != account_access.newBalance
1468 || account_access.oldNonce != account_access.newNonce
1469 })
1470 .for_each(|account_access| {
1471 if account_access.oldBalance != account_access.newBalance {
1473 let account_diff =
1474 state_diffs.entry(account_access.account).or_insert_with(|| {
1475 AccountStateDiffs {
1476 label: ccx.state.labels.get(&account_access.account).cloned(),
1477 contract: contract_names.get(&account_access.account).cloned(),
1478 ..Default::default()
1479 }
1480 });
1481 if let Some(diff) = &mut account_diff.balance_diff {
1483 diff.new_value = account_access.newBalance;
1484 } else {
1485 account_diff.balance_diff = Some(BalanceDiff {
1486 previous_value: account_access.oldBalance,
1487 new_value: account_access.newBalance,
1488 });
1489 }
1490 }
1491
1492 if account_access.oldNonce != account_access.newNonce {
1494 let account_diff =
1495 state_diffs.entry(account_access.account).or_insert_with(|| {
1496 AccountStateDiffs {
1497 label: ccx.state.labels.get(&account_access.account).cloned(),
1498 contract: contract_names.get(&account_access.account).cloned(),
1499 ..Default::default()
1500 }
1501 });
1502 if let Some(diff) = &mut account_diff.nonce_diff {
1504 diff.new_value = account_access.newNonce;
1505 } else {
1506 account_diff.nonce_diff = Some(NonceDiff {
1507 previous_value: account_access.oldNonce,
1508 new_value: account_access.newNonce,
1509 });
1510 }
1511 }
1512
1513 let raw_changes_by_slot = account_access
1515 .storageAccesses
1516 .iter()
1517 .filter_map(|access| {
1518 (access.isWrite && !access.reverted)
1519 .then_some((access.slot, (access.previousValue, access.newValue)))
1520 })
1521 .collect::<BTreeMap<_, _>>();
1522
1523 for storage_access in &account_access.storageAccesses {
1525 if storage_access.isWrite && !storage_access.reverted {
1526 let account_diff = state_diffs
1527 .entry(storage_access.account)
1528 .or_insert_with(|| AccountStateDiffs {
1529 label: ccx.state.labels.get(&storage_access.account).cloned(),
1530 contract: contract_names.get(&storage_access.account).cloned(),
1531 ..Default::default()
1532 });
1533 let layout = storage_layouts.get(&storage_access.account);
1534 let entry = match account_diff.state_diff.entry(storage_access.slot) {
1536 Entry::Vacant(slot_state_diff) => {
1537 let mapping_slots = ccx
1540 .state
1541 .mapping_slots
1542 .as_ref()
1543 .and_then(|slots| slots.get(&storage_access.account));
1544
1545 let slot_info = layout.and_then(|layout| {
1546 let decoder = SlotIdentifier::new(layout.clone());
1547 decoder.identify(&storage_access.slot, mapping_slots).or_else(
1548 || {
1549 let current_base_slot_values = raw_changes_by_slot
1554 .iter()
1555 .map(|(slot, (_, new_val))| (*slot, *new_val))
1556 .collect::<B256Map<_>>();
1557 decoder.identify_bytes_or_string(
1558 &storage_access.slot,
1559 ¤t_base_slot_values,
1560 )
1561 },
1562 )
1563 });
1564
1565 slot_state_diff.insert(SlotStateDiff {
1566 previous_value: storage_access.previousValue,
1567 new_value: storage_access.newValue,
1568 slot_info,
1569 })
1570 }
1571 Entry::Occupied(slot_state_diff) => {
1572 let entry = slot_state_diff.into_mut();
1573 entry.new_value = storage_access.newValue;
1574 entry
1575 }
1576 };
1577
1578 if let Some(slot_info) = &mut entry.slot_info {
1580 slot_info.decode_values(entry.previous_value, storage_access.newValue);
1581 if slot_info.is_bytes_or_string() {
1582 slot_info.decode_bytes_or_string_values(
1583 &storage_access.slot,
1584 &raw_changes_by_slot,
1585 );
1586 }
1587 }
1588 }
1589 }
1590 });
1591 }
1592 state_diffs
1593}
1594
1595const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
1597
1598const EIP1822_PROXIABLE_SLOT: &str =
1600 "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
1601
1602fn get_contract_data<'a>(
1604 ccx: &'a mut CheatsCtxt,
1605 address: Address,
1606) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
1607 let artifacts = ccx.state.config.available_artifacts.as_ref()?;
1609
1610 let (db, journal, _) = ccx.ecx.as_db_env_and_journal();
1612 let account = journal.load_account(db, address).ok()?;
1613 let code = account.info.code.as_ref()?;
1614
1615 if code.is_empty() {
1617 return None;
1618 }
1619
1620 let code_bytes = code.original_bytes();
1622 let hex_str = hex::encode(&code_bytes);
1624 let find_by_suffix =
1625 |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
1626 if hex_str.contains(EIP1967_IMPL_SLOT)
1628 && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
1629 {
1630 return Some(result);
1631 } else if hex_str.contains(EIP1822_PROXIABLE_SLOT)
1632 && let Some(result) = find_by_suffix(":UUPSUpgradeable")
1633 {
1634 return Some(result);
1635 }
1636
1637 if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
1639 return Some(result);
1640 }
1641
1642 artifacts.find_by_deployed_code(&code_bytes)
1644}
1645
1646fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) {
1648 if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target)
1649 && let Some(storage_slot) = account.storage.get_mut(&slot)
1650 {
1651 storage_slot.is_cold = cold;
1652 }
1653}