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