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::{AddressMap, AddressSet, 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 eip2935::{
31 HISTORY_SERVE_WINDOW, HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE, forward_fill_start,
32 history_storage_slot, history_storage_value,
33 },
34 env::FoundryContextExt,
35 evm::{FoundryEvmNetwork, TxEnvFor, TxEnvelopeFor, merge_child_state, prepare_child_state},
36 refresh_chain_journal,
37 utils::get_blob_base_fee_update_fraction_by_spec_id,
38};
39use foundry_evm_traces::TraceRequirements;
40use itertools::Itertools;
41use rand::Rng;
42use revm::{
43 Database,
44 bytecode::Bytecode,
45 context::{Block, Cfg, ContextTr, Host, JournalTr, Transaction, result::ExecutionResult},
46 inspector::JournalExt,
47 primitives::{KECCAK_EMPTY, hardfork::SpecId},
48 state::Account,
49};
50use std::{
51 collections::{BTreeMap, btree_map::Entry},
52 fmt::Display,
53 path::Path,
54 str::FromStr,
55};
56
57mod record_debug_step;
58use foundry_common::fmt::format_token_raw;
59use foundry_config::{ExecutionSpec, evm_spec_id_from_str, fs_permissions::FsAccessKind};
60use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
61use serde::{Serialize, Serializer, ser::SerializeMap};
62
63mod fork;
64pub(crate) mod mapping;
65pub(crate) mod mock;
66pub(crate) mod prank;
67
68#[derive(Serialize)]
70#[serde(rename_all = "camelCase")]
71struct LogJson {
72 topics: Vec<String>,
74 data: String,
76 emitter: String,
78}
79
80struct StateDump<'a>(&'a [(Address, GenesisAccount)]);
81
82impl Serialize for StateDump<'_> {
83 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
84 where
85 S: Serializer,
86 {
87 let mut map = serializer.serialize_map(Some(self.0.len()))?;
88 for (address, account) in self.0 {
89 map.serialize_entry(address, account)?;
90 }
91 map.end()
92 }
93}
94
95#[derive(Clone, Debug, Default)]
97pub struct RecordAccess {
98 pub reads: HashMap<Address, Vec<U256>>,
100 pub writes: HashMap<Address, Vec<U256>>,
102}
103
104impl RecordAccess {
105 pub fn record_read(&mut self, target: Address, slot: U256) {
107 self.reads.entry(target).or_default().push(slot);
108 }
109
110 pub fn record_write(&mut self, target: Address, slot: U256) {
114 self.record_read(target, slot);
115 self.writes.entry(target).or_default().push(slot);
116 }
117
118 pub fn clear(&mut self) {
120 *self = Default::default();
122 }
123}
124
125#[derive(Clone, Debug)]
127pub struct GasRecord {
128 pub group: String,
130 pub name: String,
132 pub gas_used: u64,
134 pub depth: usize,
136}
137
138#[derive(Clone, Debug)]
140pub struct DealRecord {
141 pub address: Address,
143 pub old_balance: U256,
145 pub new_balance: U256,
147}
148
149#[derive(Serialize, Default)]
151#[serde(rename_all = "camelCase")]
152struct SlotStateDiff {
153 previous_value: B256,
155 new_value: B256,
157 #[serde(skip_serializing_if = "Option::is_none", flatten)]
161 slot_info: Option<SlotInfo>,
162}
163
164#[derive(Serialize, Default)]
166#[serde(rename_all = "camelCase")]
167struct BalanceDiff {
168 previous_value: U256,
170 new_value: U256,
172}
173
174#[derive(Serialize, Default)]
176#[serde(rename_all = "camelCase")]
177struct NonceDiff {
178 previous_value: u64,
180 new_value: u64,
182}
183
184#[derive(Serialize, Default)]
186#[serde(rename_all = "camelCase")]
187struct AccountStateDiffs {
188 label: Option<String>,
190 contract: Option<String>,
192 balance_diff: Option<BalanceDiff>,
194 nonce_diff: Option<NonceDiff>,
196 state_diff: BTreeMap<B256, SlotStateDiff>,
198}
199
200impl Display for AccountStateDiffs {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> eyre::Result<(), std::fmt::Error> {
202 if let Some(label) = &self.label {
204 writeln!(f, "label: {label}")?;
205 }
206 if let Some(contract) = &self.contract {
207 writeln!(f, "contract: {contract}")?;
208 }
209 if let Some(balance_diff) = &self.balance_diff
211 && balance_diff.previous_value != balance_diff.new_value
212 {
213 writeln!(
214 f,
215 "- balance diff: {} → {}",
216 balance_diff.previous_value, balance_diff.new_value
217 )?;
218 }
219 if let Some(nonce_diff) = &self.nonce_diff
221 && nonce_diff.previous_value != nonce_diff.new_value
222 {
223 writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?;
224 }
225 if !&self.state_diff.is_empty() {
227 writeln!(f, "- state diff:")?;
228 for (slot, slot_changes) in &self.state_diff {
229 match &slot_changes.slot_info {
230 Some(slot_info) => {
231 if let Some(decoded) = &slot_info.decoded {
232 writeln!(
234 f,
235 "@ {slot} ({}, {}): {} → {}",
236 slot_info.label,
237 slot_info.slot_type.dyn_sol_type,
238 format_token_raw(&decoded.previous_value),
239 format_token_raw(&decoded.new_value)
240 )?;
241 } else {
242 writeln!(
244 f,
245 "@ {slot} ({}, {}): {} → {}",
246 slot_info.label,
247 slot_info.slot_type.dyn_sol_type,
248 slot_changes.previous_value,
249 slot_changes.new_value
250 )?;
251 }
252 }
253 None => {
254 writeln!(
256 f,
257 "@ {slot}: {} → {}",
258 slot_changes.previous_value, slot_changes.new_value
259 )?;
260 }
261 }
262 }
263 }
264
265 Ok(())
266 }
267}
268
269impl Cheatcode for addrCall {
270 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
271 let Self { privateKey } = self;
272 super::crypto::with_private_key_signer(state, privateKey, |wallet| {
273 Ok(wallet.address().abi_encode())
274 })
275 }
276}
277
278impl Cheatcode for getNonce_0Call {
279 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
280 let Self { account } = self;
281 get_nonce(ccx, account)
282 }
283}
284
285impl Cheatcode for getNonce_1Call {
286 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
287 let Self { wallet } = self;
288 get_nonce(ccx, &wallet.addr)
289 }
290}
291
292impl Cheatcode for loadCall {
293 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
294 let Self { target, slot } = *self;
295
296 ccx.ecx.journal_mut().load_account(target)?;
297 let mut val = ccx
298 .ecx
299 .journal_mut()
300 .sload(target, slot.into())
301 .map_err(|e| fmt_err!("failed to load storage slot: {:?}", e))?;
302
303 if val.is_cold
304 && val.data.is_zero()
305 && !ccx.state.is_arbitrary_storage_slot_explicit(target, slot.into())
306 {
307 if ccx.state.has_arbitrary_storage(&target) {
308 let rand_value = ccx
311 .state
312 .cached_arbitrary_storage_value(target, slot.into())
313 .unwrap_or_else(|| ccx.state.rng().random());
314 ccx.state.arbitrary_storage.as_mut().unwrap().save(
315 ccx.ecx,
316 target,
317 slot.into(),
318 rand_value,
319 );
320 val.data = rand_value;
321 } else if ccx.state.is_arbitrary_storage_copy(&target) {
322 let rand_value = ccx.state.rng().random();
326 val.data = ccx.state.arbitrary_storage.as_mut().unwrap().copy(
327 ccx.ecx,
328 target,
329 slot.into(),
330 rand_value,
331 );
332 }
333 }
334
335 Ok(val.abi_encode())
336 }
337}
338
339impl Cheatcode for loadAllocsCall {
340 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
341 let Self { pathToAllocsJson } = self;
342
343 let path = Path::new(pathToAllocsJson);
344 ensure!(path.exists(), "allocs file does not exist: {pathToAllocsJson}");
345
346 let allocs: BTreeMap<Address, GenesisAccount> = match read_json_file(path) {
348 Ok(allocs) => allocs,
349 Err(_) => {
350 let genesis = read_json_file::<Genesis>(path)?;
352 genesis.alloc
353 }
354 };
355
356 let (db, inner) = ccx.ecx.db_journal_inner_mut();
358 db.load_allocs(&allocs, inner).map_err(|e| fmt_err!("failed to load allocs: {e}"))?;
359 refresh_chain_journal(ccx.ecx);
360 Ok(Default::default())
361 }
362}
363
364impl Cheatcode for cloneAccountCall {
365 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
366 let Self { source, target } = self;
367
368 let account = ccx.ecx.journal_mut().load_account(*source)?;
369 let genesis = genesis_account(account.data);
370 let (db, inner) = ccx.ecx.db_journal_inner_mut();
371 db.clone_account(&genesis, target, inner)?;
372 ccx.ecx.db_mut().add_persistent_account(*target);
374 refresh_chain_journal(ccx.ecx);
375 Ok(Default::default())
376 }
377}
378
379impl Cheatcode for dumpStateCall {
380 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
381 let Self { pathToStateJson } = self;
382 let path = ccx.state.config.ensure_path_allowed(pathToStateJson, FsAccessKind::Write)?;
383 ccx.state.config.ensure_not_foundry_toml(&path)?;
384
385 let fork_id = ccx.ecx.db().active_fork_id();
386 let created_accounts = ccx
387 .state
388 .created_accounts(fork_id)
389 .into_iter()
390 .filter(|address| {
391 ccx.ecx.journal().evm_state().get(address).is_some_and(Account::is_created)
392 })
393 .collect::<Vec<_>>();
394
395 let skip = |key: &Address, val: &Account| {
397 key == &CHEATCODE_ADDRESS
398 || key == &CALLER
399 || key == &HARDHAT_CONSOLE_ADDRESS
400 || key == &TEST_CONTRACT_ADDRESS
401 || key == &ccx.caller
402 || key == &ccx.state.config.evm_opts.sender
403 || val.is_empty()
404 };
405
406 let mut alloc = ccx
407 .ecx
408 .journal_mut()
409 .evm_state_mut()
410 .iter_mut()
411 .filter(|(key, val)| !skip(key, val))
412 .map(|(key, val)| (*key, genesis_account(val)))
413 .collect::<BTreeMap<_, _>>();
414
415 let mut ordered_alloc = Vec::with_capacity(alloc.len());
416 for address in created_accounts {
417 if let Some(account) = alloc.remove(&address) {
418 ordered_alloc.push((address, account));
419 }
420 }
421 ordered_alloc.extend(alloc);
422
423 write_json_file(&path, &StateDump(&ordered_alloc))?;
424 Ok(Default::default())
425 }
426}
427
428impl Cheatcode for recordCall {
429 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
430 let Self {} = self;
431 state.recording_accesses = true;
432 state.accesses.clear();
433 Ok(Default::default())
434 }
435}
436
437impl Cheatcode for stopRecordCall {
438 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
439 state.recording_accesses = false;
440 Ok(Default::default())
441 }
442}
443
444impl Cheatcode for accessesCall {
445 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
446 let Self { target } = *self;
447 let result = (
448 state.accesses.reads.entry(target).or_default().as_slice(),
449 state.accesses.writes.entry(target).or_default().as_slice(),
450 );
451 Ok(result.abi_encode_params())
452 }
453}
454
455impl Cheatcode for registerSloadHookCall {
456 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
457 let Self { target, callback } = *self;
458 ccx.state.register_storage_load_hook(target, ccx.caller, callback.0);
459 Ok(Default::default())
460 }
461}
462
463impl Cheatcode for registerSstoreHookCall {
464 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
465 let Self { target, callback } = *self;
466 if ccx.state.has_mapping_storage_store_hooks(target) {
467 bail!("cannot register raw SSTORE hook: mapping SSTORE hooks already exist for target");
468 }
469 ccx.state.register_storage_store_hook(target, ccx.caller, callback.0);
470 Ok(Default::default())
471 }
472}
473
474impl Cheatcode for registerMappingSstoreHookCall {
475 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
476 let Self { target, rootSlot, callback } = *self;
477 if !ccx.state.register_mapping_storage_store_hook(target, rootSlot, ccx.caller, callback.0)
478 {
479 bail!("cannot register mapping SSTORE hook: raw SSTORE hook already exists for target");
480 }
481 Ok(Default::default())
482 }
483}
484
485impl Cheatcode for recordLogsCall {
486 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
487 let Self {} = self;
488 state.recorded_logs = Some(Default::default());
489 Ok(Default::default())
490 }
491}
492
493impl Cheatcode for getRecordedLogsCall {
494 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
495 let Self {} = self;
496 Ok(state.recorded_logs.replace(Default::default()).unwrap_or_default().abi_encode())
497 }
498}
499
500impl Cheatcode for getRecordedLogsJsonCall {
501 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
502 let Self {} = self;
503 let logs = state.recorded_logs.replace(Default::default()).unwrap_or_default();
504 let json_logs: Vec<_> = logs
505 .into_iter()
506 .map(|log| LogJson {
507 topics: log.topics.iter().map(|t| format!("{t}")).collect(),
508 data: hex::encode_prefixed(&log.data),
509 emitter: format!("{}", log.emitter),
510 })
511 .collect();
512 Ok(serde_json::to_string(&json_logs)?.abi_encode())
513 }
514}
515
516impl Cheatcode for pauseGasMeteringCall {
517 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
518 let Self {} = self;
519 state.gas_metering.paused = true;
520 Ok(Default::default())
521 }
522}
523
524impl Cheatcode for resumeGasMeteringCall {
525 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
526 let Self {} = self;
527 state.gas_metering.resume();
528 Ok(Default::default())
529 }
530}
531
532impl Cheatcode for resetGasMeteringCall {
533 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
534 let Self {} = self;
535 state.gas_metering.reset();
536 Ok(Default::default())
537 }
538}
539
540impl Cheatcode for lastCallGasCall {
541 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
542 let Self {} = self;
543 let Some(last_call_gas) = &state.gas_metering.last_call_gas else {
544 bail!("no external call was made yet");
545 };
546 Ok(last_call_gas.abi_encode())
547 }
548}
549
550impl Cheatcode for lastFrameGasCall {
551 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
552 let Self {} = self;
553 let Some(last_frame_gas) = &state.gas_metering.last_frame_gas else {
554 bail!("no external call or create was made yet");
555 };
556 Ok(last_frame_gas.abi_encode())
557 }
558}
559
560impl Cheatcode for getChainIdCall {
561 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
562 let Self {} = self;
563 Ok(U256::from(ccx.ecx.cfg().chain_id()).abi_encode())
564 }
565}
566
567impl Cheatcode for chainIdCall {
568 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
569 let Self { newChainId } = self;
570 ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64");
571 ccx.ecx.cfg_env_mut().chain_id = newChainId.to();
572 Ok(Default::default())
573 }
574}
575
576impl Cheatcode for coinbaseCall {
577 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
578 let Self { newCoinbase } = self;
579 ccx.ecx.block_mut().set_beneficiary(*newCoinbase);
580 Ok(Default::default())
581 }
582}
583
584impl Cheatcode for difficultyCall {
585 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
586 let Self { newDifficulty } = self;
587 ensure!(
588 ccx.ecx.cfg().spec().into() < SpecId::MERGE,
589 "`difficulty` is not supported after the Paris hard fork, use `prevrandao` instead; \
590 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
591 );
592 ccx.ecx.block_mut().set_difficulty(*newDifficulty);
593 Ok(Default::default())
594 }
595}
596
597impl Cheatcode for feeCall {
598 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
599 let Self { newBasefee } = self;
600 ensure!(*newBasefee <= U256::from(u64::MAX), "base fee must be less than 2^64");
601 let basefee: u64 = newBasefee.saturating_to();
602 let fork_id = ccx.ecx.db().active_fork_id();
606 ccx.state.env_overrides_for_mut(fork_id).basefee = Some(basefee);
607 if !ccx.state.in_isolation_context {
610 ccx.ecx.block_mut().set_basefee(basefee);
611 }
612 Ok(Default::default())
613 }
614}
615
616impl Cheatcode for prevrandao_0Call {
617 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
618 let Self { newPrevrandao } = self;
619 ensure!(
620 ccx.ecx.cfg().spec().into() >= SpecId::MERGE,
621 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
622 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
623 );
624 ccx.ecx.block_mut().set_prevrandao(Some(*newPrevrandao));
625 Ok(Default::default())
626 }
627}
628
629impl Cheatcode for prevrandao_1Call {
630 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
631 let Self { newPrevrandao } = self;
632 ensure!(
633 ccx.ecx.cfg().spec().into() >= SpecId::MERGE,
634 "`prevrandao` is not supported before the Paris hard fork, use `difficulty` instead; \
635 see EIP-4399: https://eips.ethereum.org/EIPS/eip-4399"
636 );
637 ccx.ecx.block_mut().set_prevrandao(Some((*newPrevrandao).into()));
638 Ok(Default::default())
639 }
640}
641
642impl Cheatcode for blobhashesCall {
643 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
644 let Self { hashes } = self;
645 ensure!(
646 ccx.ecx.cfg().spec().into() >= SpecId::CANCUN,
647 "`blobhashes` is not supported before the Cancun hard fork; \
648 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
649 );
650 let fork_id = ccx.ecx.db().active_fork_id();
654 ccx.state.env_overrides_for_mut(fork_id).blob_hashes = Some(hashes.clone());
655 if !ccx.state.in_isolation_context {
658 ccx.ecx.tx_mut().set_blob_hashes(hashes.clone());
659 ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
661 }
662 Ok(Default::default())
663 }
664}
665
666impl Cheatcode for getBlobhashesCall {
667 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
668 let Self {} = self;
669 ensure!(
670 ccx.ecx.cfg().spec().into() >= SpecId::CANCUN,
671 "`getBlobhashes` is not supported before the Cancun hard fork; \
672 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
673 );
674 let fork_id = ccx.ecx.db().active_fork_id();
675 let hashes = ccx
676 .state
677 .env_overrides
678 .get(&fork_id)
679 .and_then(|o| o.blob_hashes.as_deref())
680 .unwrap_or_else(|| ccx.ecx.tx().blob_versioned_hashes());
681 Ok(hashes.to_vec().abi_encode())
682 }
683}
684
685impl Cheatcode for rollCall {
686 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
687 let Self { newHeight } = self;
688 let current_height = ccx.ecx.block().number();
689 if ccx.ecx.cfg().spec().into() >= SpecId::PRAGUE && *newHeight > current_height {
690 let mut block_number = forward_fill_start(current_height, *newHeight);
691 while block_number < *newHeight {
692 let block_hash =
693 ccx.ecx.db_mut().block_hash(block_number.saturating_to()).unwrap_or_default();
694 set_eip2935_blockhash(ccx.ecx, block_number, block_hash)?;
695 block_number += U256::from(1);
696 }
697 }
698 ccx.ecx.block_mut().set_number(*newHeight);
699 Ok(Default::default())
700 }
701}
702
703impl Cheatcode for getBlockNumberCall {
704 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
705 let Self {} = self;
706 Ok(ccx.ecx.block().number().abi_encode())
707 }
708}
709
710impl Cheatcode for rollSlotCall {
711 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
712 ensure!(
713 ccx.ecx.cfg().spec().into() >= SpecId::AMSTERDAM,
714 "`rollSlot` is not supported before the Amsterdam hard fork; \
715 see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
716 );
717 ccx.ecx.block_mut().set_slot_num(self.newSlotNumber);
718 Ok(Default::default())
719 }
720}
721
722impl Cheatcode for getSlotNumberCall {
723 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
724 ensure!(
725 ccx.ecx.cfg().spec().into() >= SpecId::AMSTERDAM,
726 "`getSlotNumber` is not supported before the Amsterdam hard fork; \
727 see EIP-7843: https://eips.ethereum.org/EIPS/eip-7843"
728 );
729 Ok(ccx.ecx.block().slot_num().abi_encode())
730 }
731}
732
733impl Cheatcode for txGasPriceCall {
734 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
735 let Self { newGasPrice } = self;
736 ensure!(*newGasPrice <= U256::from(u64::MAX), "gas price must be less than 2^64");
737 let gas_price: u128 = newGasPrice.saturating_to();
738 let fork_id = ccx.ecx.db().active_fork_id();
742 ccx.state.env_overrides_for_mut(fork_id).gas_price = Some(gas_price);
743 if !ccx.state.in_isolation_context {
748 ccx.ecx.tx_mut().set_gas_price(gas_price);
749 }
750 Ok(Default::default())
751 }
752}
753
754impl Cheatcode for warpCall {
755 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
756 let Self { newTimestamp } = self;
757 ccx.ecx.block_mut().set_timestamp(*newTimestamp);
758 Ok(Default::default())
759 }
760}
761
762impl Cheatcode for getBlockTimestampCall {
763 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
764 let Self {} = self;
765 Ok(ccx.ecx.block().timestamp().abi_encode())
766 }
767}
768
769impl Cheatcode for blobBaseFeeCall {
770 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
771 let Self { newBlobBaseFee } = self;
772 ensure!(
773 ccx.ecx.cfg().spec().into() >= SpecId::CANCUN,
774 "`blobBaseFee` is not supported before the Cancun hard fork; \
775 see EIP-4844: https://eips.ethereum.org/EIPS/eip-4844"
776 );
777
778 let spec: SpecId = ccx.ecx.cfg().spec().into();
779 ccx.ecx.block_mut().set_blob_excess_gas_and_price(
780 (*newBlobBaseFee).to(),
781 get_blob_base_fee_update_fraction_by_spec_id(spec),
782 );
783 Ok(Default::default())
784 }
785}
786
787impl Cheatcode for getBlobBaseFeeCall {
788 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
789 let Self {} = self;
790 Ok(ccx.ecx.block().blob_excess_gas().unwrap_or(0).abi_encode())
791 }
792}
793
794impl Cheatcode for dealCall {
795 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
796 let Self { account: address, newBalance: new_balance } = *self;
797 let account = journaled_account(ccx.ecx, address)?;
798 let old_balance = std::mem::replace(&mut account.info.balance, new_balance);
799 let record = DealRecord { address, old_balance, new_balance };
800 ccx.state.eth_deals.push(record);
801 refresh_chain_journal(ccx.ecx);
802 Ok(Default::default())
803 }
804}
805
806impl Cheatcode for etchCall {
807 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
808 let Self { target, newRuntimeBytecode } = self;
809 ccx.ensure_not_precompile(target)?;
810 ccx.ecx.journal_mut().load_account(*target)?;
811 let bytecode = Bytecode::new_raw_checked(newRuntimeBytecode.clone())
812 .map_err(|e| fmt_err!("failed to create bytecode: {e}"))?;
813 if *target == HISTORY_STORAGE_ADDRESS
814 && bytecode.hash_slow() != keccak256(&HISTORY_STORAGE_CODE)
815 {
816 let account =
817 ccx.ecx.journal_mut().evm_state_mut().get_mut(target).expect("account is loaded");
818 if account.info.code_hash == keccak256(&HISTORY_STORAGE_CODE) {
819 account.storage.clear();
820 account.mark_created();
821 }
822 }
823 ccx.ecx.journal_mut().set_code(*target, bytecode);
824 Ok(Default::default())
825 }
826}
827
828impl Cheatcode for resetNonceCall {
829 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
830 let Self { account } = self;
831 let account = journaled_account(ccx.ecx, *account)?;
832 let empty = account.info.code_hash == KECCAK_EMPTY;
836 let nonce = if empty { 0 } else { 1 };
837 account.info.nonce = nonce;
838 debug!(target: "cheatcodes", nonce, "reset");
839 Ok(Default::default())
840 }
841}
842
843impl Cheatcode for setNonceCall {
844 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
845 let Self { account, newNonce } = *self;
846 let account = journaled_account(ccx.ecx, account)?;
847 let current = account.info.nonce;
849 ensure!(
850 newNonce >= current,
851 "new nonce ({newNonce}) must be strictly equal to or higher than the \
852 account's current nonce ({current})"
853 );
854 account.info.nonce = newNonce;
855 Ok(Default::default())
856 }
857}
858
859impl Cheatcode for setNonceUnsafeCall {
860 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
861 let Self { account, newNonce } = *self;
862 let account = journaled_account(ccx.ecx, account)?;
863 account.info.nonce = newNonce;
864 Ok(Default::default())
865 }
866}
867
868impl Cheatcode for storeCall {
869 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
870 let Self { target, slot, value } = *self;
871 ccx.ensure_not_precompile(&target)?;
872 ensure_loaded_account(ccx.ecx, target)?;
873 ccx.ecx
874 .journal_mut()
875 .sstore(target, slot.into(), value.into())
876 .map_err(|e| fmt_err!("failed to store storage slot: {:?}", e))?;
877 ccx.state.mark_arbitrary_storage_slot_explicit(target, slot.into());
878 Ok(Default::default())
879 }
880}
881
882impl Cheatcode for setTip20LogoURICall {
883 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
884 let Self { token, newLogoURI } = self;
885 set_tip20_logo_uri(ccx, token, newLogoURI)
886 }
887}
888
889impl Cheatcode for setLogoURICall {
890 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
891 let Self { token, newLogoURI } = self;
892 set_tip20_logo_uri(ccx, token, newLogoURI)
893 }
894}
895
896impl Cheatcode for coolCall {
897 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
898 let Self { target } = self;
899 if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(target) {
900 account.unmark_touch();
901 account.storage.values_mut().for_each(|slot| slot.mark_cold());
902 }
903 Ok(Default::default())
904 }
905}
906
907impl Cheatcode for accessListCall {
908 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
909 let Self { access } = self;
910 let access_list = access
911 .iter()
912 .map(|item| {
913 let keys = item.storageKeys.iter().map(|key| B256::from(*key)).collect_vec();
914 alloy_rpc_types::AccessListItem { address: item.target, storage_keys: keys }
915 })
916 .collect_vec();
917 state.access_list = Some(alloy_rpc_types::AccessList::from(access_list));
918 Ok(Default::default())
919 }
920}
921
922impl Cheatcode for noAccessListCall {
923 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
924 let Self {} = self;
925 if state.access_list.is_some() {
927 state.access_list = Some(alloy_rpc_types::AccessList::default());
928 }
929 Ok(Default::default())
930 }
931}
932
933impl Cheatcode for warmSlotCall {
934 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
935 let Self { target, slot } = *self;
936 set_cold_slot(ccx, target, slot.into(), false);
937 Ok(Default::default())
938 }
939}
940
941impl Cheatcode for coolSlotCall {
942 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
943 let Self { target, slot } = *self;
944 set_cold_slot(ccx, target, slot.into(), true);
945 Ok(Default::default())
946 }
947}
948
949impl Cheatcode for isIsolateModeCall {
950 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
951 let Self {} = self;
952 Ok(state.config.isolate.abi_encode())
953 }
954}
955
956impl Cheatcode for readCallersCall {
957 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
958 let Self {} = self;
959 read_callers(ccx.state, &ccx.ecx.tx().caller(), ccx.ecx.journal().depth())
960 }
961}
962
963impl Cheatcode for snapshotValue_0Call {
964 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
965 let Self { name, value } = self;
966 inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string())
967 }
968}
969
970impl Cheatcode for snapshotValue_1Call {
971 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
972 let Self { group, name, value } = self;
973 inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string())
974 }
975}
976
977impl Cheatcode for snapshotGasLastCall_0Call {
978 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
979 let Self { name } = self;
980 if ccx.state.gas_metering.last_call_gas.is_none() {
981 bail!("no external call was made yet");
982 }
983 let gas_used = ccx.state.gas_metering.last_call_snapshot_gas_used;
984 inner_last_gas_snapshot(ccx, None, Some(name.clone()), gas_used)
985 }
986}
987
988impl Cheatcode for snapshotGasLastCall_1Call {
989 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
990 let Self { name, group } = self;
991 if ccx.state.gas_metering.last_call_gas.is_none() {
992 bail!("no external call was made yet");
993 }
994 let gas_used = ccx.state.gas_metering.last_call_snapshot_gas_used;
995 inner_last_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()), gas_used)
996 }
997}
998
999impl Cheatcode for snapshotGasLastFrame_0Call {
1000 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1001 let Self { name } = self;
1002 if ccx.state.gas_metering.last_frame_gas.is_none() {
1003 bail!("no external call or create was made yet");
1004 }
1005 let gas_used = ccx.state.gas_metering.last_frame_snapshot_gas_used;
1006 inner_last_gas_snapshot(ccx, None, Some(name.clone()), gas_used)
1007 }
1008}
1009
1010impl Cheatcode for snapshotGasLastFrame_1Call {
1011 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1012 let Self { name, group } = self;
1013 if ccx.state.gas_metering.last_frame_gas.is_none() {
1014 bail!("no external call or create was made yet");
1015 }
1016 let gas_used = ccx.state.gas_metering.last_frame_snapshot_gas_used;
1017 inner_last_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()), gas_used)
1018 }
1019}
1020
1021impl Cheatcode for startSnapshotGas_0Call {
1022 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1023 let Self { name } = self;
1024 inner_start_gas_snapshot(ccx, None, Some(name.clone()))
1025 }
1026}
1027
1028impl Cheatcode for startSnapshotGas_1Call {
1029 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1030 let Self { group, name } = self;
1031 inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
1032 }
1033}
1034
1035impl Cheatcode for stopSnapshotGas_0Call {
1036 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1037 let Self {} = self;
1038 inner_stop_gas_snapshot(ccx, None, None)
1039 }
1040}
1041
1042impl Cheatcode for stopSnapshotGas_1Call {
1043 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1044 let Self { name } = self;
1045 inner_stop_gas_snapshot(ccx, None, Some(name.clone()))
1046 }
1047}
1048
1049impl Cheatcode for stopSnapshotGas_2Call {
1050 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1051 let Self { group, name } = self;
1052 inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()))
1053 }
1054}
1055
1056impl Cheatcode for snapshotCall {
1058 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1059 let Self {} = self;
1060 inner_snapshot_state(ccx)
1061 }
1062}
1063
1064impl Cheatcode for snapshotStateCall {
1065 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1066 let Self {} = self;
1067 inner_snapshot_state(ccx)
1068 }
1069}
1070
1071impl Cheatcode for revertToCall {
1073 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1074 let Self { snapshotId } = self;
1075 inner_revert_to_state(ccx, *snapshotId)
1076 }
1077}
1078
1079impl Cheatcode for revertToStateCall {
1080 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1081 let Self { snapshotId } = self;
1082 inner_revert_to_state(ccx, *snapshotId)
1083 }
1084}
1085
1086impl Cheatcode for revertToAndDeleteCall {
1088 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1089 let Self { snapshotId } = self;
1090 inner_revert_to_state_and_delete(ccx, *snapshotId)
1091 }
1092}
1093
1094impl Cheatcode for revertToStateAndDeleteCall {
1095 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1096 let Self { snapshotId } = self;
1097 inner_revert_to_state_and_delete(ccx, *snapshotId)
1098 }
1099}
1100
1101impl Cheatcode for deleteSnapshotCall {
1103 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1104 let Self { snapshotId } = self;
1105 let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
1106 ccx.state.env_overrides_snapshots.remove(snapshotId);
1107 ccx.state.fork_block_number_override_snapshots.remove(snapshotId);
1108 #[cfg(feature = "monad")]
1109 ccx.state.context_snapshots.remove(snapshotId);
1110 ccx.state.delete_created_accounts_snapshot(*snapshotId);
1111 Ok(result.abi_encode())
1112 }
1113}
1114
1115impl Cheatcode for deleteStateSnapshotCall {
1116 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1117 let Self { snapshotId } = self;
1118 let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
1119 ccx.state.env_overrides_snapshots.remove(snapshotId);
1120 ccx.state.fork_block_number_override_snapshots.remove(snapshotId);
1121 #[cfg(feature = "monad")]
1122 ccx.state.context_snapshots.remove(snapshotId);
1123 ccx.state.delete_created_accounts_snapshot(*snapshotId);
1124 Ok(result.abi_encode())
1125 }
1126}
1127
1128impl Cheatcode for deleteSnapshotsCall {
1130 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1131 let Self {} = self;
1132 ccx.ecx.db_mut().delete_state_snapshots();
1133 ccx.state.env_overrides_snapshots.clear();
1134 ccx.state.fork_block_number_override_snapshots.clear();
1135 #[cfg(feature = "monad")]
1136 ccx.state.context_snapshots.clear();
1137 ccx.state.clear_created_accounts_snapshots();
1138 Ok(Default::default())
1139 }
1140}
1141
1142impl Cheatcode for deleteStateSnapshotsCall {
1143 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1144 let Self {} = self;
1145 ccx.ecx.db_mut().delete_state_snapshots();
1146 ccx.state.env_overrides_snapshots.clear();
1147 ccx.state.fork_block_number_override_snapshots.clear();
1148 #[cfg(feature = "monad")]
1149 ccx.state.context_snapshots.clear();
1150 ccx.state.clear_created_accounts_snapshots();
1151 Ok(Default::default())
1152 }
1153}
1154
1155impl Cheatcode for startStateDiffRecordingCall {
1156 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1157 let Self {} = self;
1158 state.start_state_diff_recording();
1159 state.mapping_slots.get_or_insert_default();
1161 Ok(Default::default())
1162 }
1163}
1164
1165impl Cheatcode for stopAndReturnStateDiffCall {
1166 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1167 let Self {} = self;
1168 get_state_diff(state)
1169 }
1170}
1171
1172impl Cheatcode for getStateDiffCall {
1173 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1174 let mut diffs = String::new();
1175 let state_diffs = get_recorded_state_diffs(ccx);
1176 for (address, state_diffs) in state_diffs {
1177 diffs.push_str(&format!("{address}\n"));
1178 diffs.push_str(&format!("{state_diffs}\n"));
1179 }
1180 Ok(diffs.abi_encode())
1181 }
1182}
1183
1184impl Cheatcode for getStateDiffJsonCall {
1185 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1186 let state_diffs = get_recorded_state_diffs(ccx);
1187 Ok(serde_json::to_string(&state_diffs)?.abi_encode())
1188 }
1189}
1190
1191impl Cheatcode for getStorageSlotsCall {
1192 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1193 let Self { target, variableName } = self;
1194
1195 let storage_layout = get_contract_data(ccx, *target, true)
1196 .and_then(|(_, data)| data.storage_layout.as_ref().map(|layout| layout.clone()))
1197 .ok_or_else(|| fmt_err!("Storage layout not available for contract at {target}. Try compiling contracts with `--extra-output storageLayout`"))?;
1198
1199 trace!(storage = ?storage_layout.storage, "fetched storage");
1200
1201 let variable_name_lower = variableName.to_lowercase();
1202 let storage = storage_layout
1203 .storage
1204 .iter()
1205 .find(|s| s.label.to_lowercase() == variable_name_lower)
1206 .ok_or_else(|| fmt_err!("variable '{variableName}' not found in storage layout"))?;
1207
1208 let storage_type = storage_layout
1209 .types
1210 .get(&storage.storage_type)
1211 .ok_or_else(|| fmt_err!("storage type not found for variable {variableName}"))?;
1212
1213 if storage_type.encoding == ENCODING_MAPPING || storage_type.encoding == ENCODING_DYN_ARRAY
1214 {
1215 return Err(fmt_err!(
1216 "cannot get storage slots for variables with mapping or dynamic array types"
1217 ));
1218 }
1219
1220 let slot = U256::from_str(&storage.slot).map_err(|_| {
1221 fmt_err!("invalid slot {} format for variable {variableName}", storage.slot)
1222 })?;
1223
1224 let mut slots = Vec::new();
1225
1226 slots.push(slot);
1228
1229 if storage_type.encoding == ENCODING_INPLACE {
1230 let num_bytes = U256::from_str(&storage_type.number_of_bytes).map_err(|_| {
1232 fmt_err!(
1233 "invalid number_of_bytes {} for variable {variableName}",
1234 storage_type.number_of_bytes
1235 )
1236 })?;
1237 let num_slots = num_bytes.div_ceil(U256::from(32));
1238 let num_slots = usize::try_from(num_slots).map_err(|_| {
1239 fmt_err!("number_of_bytes {} exceeds host usize", storage_type.number_of_bytes)
1240 })?;
1241
1242 for i in 1..num_slots {
1244 slots.push(slot + U256::from(i));
1245 }
1246 }
1247
1248 if storage_type.encoding == ENCODING_BYTES {
1249 if let Ok(value) = ccx.ecx.journal_mut().sload(*target, slot) {
1252 let value_bytes = value.data.to_be_bytes::<32>();
1253 let length_byte = value_bytes[31];
1254 if length_byte & 1 == 1 {
1256 let length: U256 = value.data >> 1;
1258 let length = usize::try_from(length)
1259 .map_err(|_| fmt_err!("long bytes/string length exceeds host usize"))?;
1260 let num_data_slots = length.div_ceil(32);
1261 let data_start = U256::from_be_bytes(keccak256(B256::from(slot).0).0);
1262
1263 for i in 0..num_data_slots {
1264 slots.push(data_start + U256::from(i));
1265 }
1266 }
1267 }
1268 }
1269
1270 Ok(slots.abi_encode())
1271 }
1272}
1273
1274impl Cheatcode for getStorageAccessesCall {
1275 fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
1276 let mut storage_accesses = Vec::new();
1277
1278 for account_access in state.recorded_account_diffs() {
1279 storage_accesses.extend(account_access.storageAccesses.clone());
1280 }
1281
1282 Ok(storage_accesses.abi_encode())
1283 }
1284}
1285
1286impl Cheatcode for broadcastRawTransactionCall {
1287 fn apply_full<FEN: FoundryEvmNetwork>(
1288 &self,
1289 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1290 executor: &mut dyn CheatcodesExecutor<FEN>,
1291 ) -> Result {
1292 let tx = TxEnvelopeFor::<FEN>::decode(&mut self.data.as_ref())
1293 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1294
1295 let sender =
1296 tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1297 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1298 let from = sender;
1299
1300 executor.transact_from_tx_on_db(ccx.state, ccx.ecx, tx_env)?;
1301 refresh_chain_journal(ccx.ecx);
1302
1303 if ccx.state.broadcast.is_some() {
1304 ccx.state.broadcastable_transactions.push_back(BroadcastableTransaction {
1305 rpc: ccx.ecx.db().active_fork_url(),
1306 transaction: TransactionMaybeSigned::Signed { tx, from },
1307 });
1308 }
1309
1310 Ok(Default::default())
1311 }
1312}
1313
1314impl Cheatcode for setBlockhashCall {
1315 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1316 let Self { blockNumber, blockHash } = *self;
1317 ensure!(blockNumber <= U256::from(u64::MAX), "blockNumber must be less than 2^64");
1318 ensure!(
1319 blockNumber <= U256::from(ccx.ecx.block().number()),
1320 "block number must be less than or equal to the current block number"
1321 );
1322
1323 ccx.ecx.db_mut().set_blockhash(blockNumber, blockHash);
1324 let current_block = U256::from(ccx.ecx.block().number());
1325 if ccx.ecx.cfg().spec().into() >= SpecId::PRAGUE
1326 && blockNumber < current_block
1327 && current_block - blockNumber <= U256::from(HISTORY_SERVE_WINDOW)
1328 {
1329 set_eip2935_blockhash(ccx.ecx, blockNumber, blockHash)?;
1330 }
1331
1332 Ok(Default::default())
1333 }
1334}
1335
1336impl Cheatcode for executeTransactionCall {
1337 fn apply_full<FEN: FoundryEvmNetwork>(
1338 &self,
1339 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1340 executor: &mut dyn CheatcodesExecutor<FEN>,
1341 ) -> Result {
1342 use crate::env::FORGE_CONTEXT;
1343
1344 if let Some(ctx) = FORGE_CONTEXT.get()
1346 && *ctx == ForgeContext::ScriptGroup
1347 {
1348 return Err(fmt_err!("executeTransaction is not allowed in forge script"));
1349 }
1350
1351 let tx = TxEnvelopeFor::<FEN>::decode(&mut self.rawTx.as_ref())
1353 .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?;
1354
1355 let sender =
1357 tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
1358 let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
1359 let chain_context = ccx.ecx.db().chain_context_for_synthetic_transaction(&tx_env)?;
1360 let created_address = tx_env.kind().is_create().then(|| sender.create(tx_env.nonce()));
1361
1362 let cached_evm_env = ccx.ecx.evm_clone();
1364 let cached_tx_env = ccx.ecx.tx_clone();
1365
1366 ccx.ecx.block_mut().set_basefee(0);
1368 ccx.ecx.set_tx(tx_env);
1369 ccx.ecx.tx_mut().set_gas_price(0);
1370 ccx.ecx.tx_mut().set_gas_priority_fee(None);
1371
1372 ccx.ecx.cfg_env_mut().disable_nonce_check = false;
1374
1375 let code_size_limit = ccx.ecx.cfg_env().limit_contract_code_size;
1379 let configured_code_size_limit = ccx.state.config.evm_opts.env.code_size_limit;
1380 ccx.ecx.cfg_env_mut().limit_contract_code_size = configured_code_size_limit;
1381 let initcode_size_limit = ccx.ecx.cfg().max_initcode_size();
1382 ccx.ecx.cfg_env_mut().limit_contract_code_size = code_size_limit;
1383 ccx.ecx.cfg_env_mut().limit_contract_initcode_size = Some(initcode_size_limit);
1384
1385 ccx.ecx.cfg_env_mut().tx_gas_limit_cap = None;
1389
1390 let modified_evm_env = ccx.ecx.evm_clone();
1392 let modified_tx_env = ccx.ecx.tx_clone();
1393
1394 executor.set_in_inner_context(true, Some(sender));
1397 if let Some(address) = created_address {
1398 let fork_id = ccx.ecx.db().active_fork_id();
1399 ccx.state.record_created_account(fork_id, address);
1400 }
1401
1402 let cold_state = prepare_child_state(ccx.ecx.journal_inner());
1404
1405 let mut res = None;
1406 let mut cold_state = Some(cold_state);
1407 let mut nested_evm_env = {
1408 let (db, _) = ccx.ecx.db_journal_inner_mut();
1409 executor.with_fresh_nested_evm(
1410 ccx.state,
1411 db,
1412 modified_evm_env,
1413 chain_context,
1414 &mut |evm| {
1415 evm.journal_inner_mut().state = cold_state.take().expect("called once");
1417 evm.journal_inner_mut().depth = 1;
1419 res = Some(evm.transact_raw(modified_tx_env.clone()));
1420 Ok(())
1421 },
1422 )?
1423 };
1424 let res = res.unwrap();
1425
1426 nested_evm_env.block_env.set_basefee(cached_evm_env.block_env.basefee());
1430 nested_evm_env.cfg_env.disable_nonce_check = cached_evm_env.cfg_env.disable_nonce_check;
1431 nested_evm_env.cfg_env.limit_contract_initcode_size =
1432 cached_evm_env.cfg_env.limit_contract_initcode_size;
1433 nested_evm_env.cfg_env.tx_gas_limit_cap = cached_evm_env.cfg_env.tx_gas_limit_cap;
1434 ccx.ecx.set_evm(nested_evm_env);
1435 ccx.ecx.set_tx(cached_tx_env);
1436
1437 executor.set_in_inner_context(false, None);
1439
1440 let res = res.map_err(|e| fmt_err!("transaction execution failed: {e}"))?;
1441
1442 merge_child_state(ccx.ecx.journal_mut().evm_state_mut(), res.state);
1444
1445 refresh_chain_journal(ccx.ecx);
1448
1449 let output = match res.result {
1451 ExecutionResult::Success { output, .. } => output.into_data(),
1452 ExecutionResult::Halt { reason, .. } => {
1453 return Err(fmt_err!("transaction halted: {reason:?}"));
1454 }
1455 ExecutionResult::Revert { output, .. } => {
1456 return Err(fmt_err!("transaction reverted: {}", hex::encode_prefixed(&output)));
1457 }
1458 };
1459
1460 Ok(output.abi_encode())
1461 }
1462}
1463
1464impl Cheatcode for startDebugTraceRecordingCall {
1465 fn apply_full<FEN: FoundryEvmNetwork>(
1466 &self,
1467 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1468 executor: &mut dyn CheatcodesExecutor<FEN>,
1469 ) -> Result {
1470 let Some(tracer) = executor.tracing_inspector() else {
1471 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1472 };
1473
1474 if ccx.state.record_debug_steps_info.is_some() {
1475 bail!("debug trace recording was already started");
1476 }
1477
1478 let mut info = RecordDebugStepInfo {
1479 start_node_idx: 0,
1481 original_tracer_config: *tracer.config(),
1483 };
1484
1485 *tracer.config_mut() =
1487 TraceRequirements::none().with_debug(true).into_config().expect("cannot be None");
1488
1489 if let Some(last_node) = tracer.traces().nodes().last() {
1491 info.start_node_idx = last_node.idx;
1492 }
1493
1494 ccx.state.record_debug_steps_info = Some(info);
1495 Ok(Default::default())
1496 }
1497}
1498
1499impl Cheatcode for stopAndReturnDebugTraceRecordingCall {
1500 fn apply_full<FEN: FoundryEvmNetwork>(
1501 &self,
1502 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1503 executor: &mut dyn CheatcodesExecutor<FEN>,
1504 ) -> Result {
1505 let Some(tracer) = executor.tracing_inspector() else {
1506 return Err(Error::from("no tracer initiated, consider adding -vvv flag"));
1507 };
1508
1509 let Some(record_info) = ccx.state.record_debug_steps_info else {
1510 return Err(Error::from("nothing recorded"));
1511 };
1512
1513 let root = tracer.traces();
1515 let steps = flatten_call_trace(0, root, record_info.start_node_idx);
1516
1517 let debug_steps: Vec<DebugStep> =
1518 steps.iter().map(|step| convert_call_trace_ctx_to_debug_step(step)).collect();
1519 if !record_info.original_tracer_config.record_steps {
1521 tracer.traces_mut().nodes_mut().iter_mut().for_each(|node| {
1522 node.trace.steps = Vec::new();
1523 node.logs = Vec::new();
1524 node.ordering = Vec::new();
1525 });
1526 }
1527
1528 tracer.update_config(|_config| record_info.original_tracer_config);
1530
1531 ccx.state.record_debug_steps_info = None;
1533
1534 Ok(debug_steps.abi_encode())
1535 }
1536}
1537
1538impl Cheatcode for setEvmVersionCall {
1539 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1540 let Self { evm } = self;
1541 let spec_id = evm_spec_id_from_str(evm)
1542 .ok_or_else(|| Error::from(format!("invalid evm version {evm}")))?;
1543 ccx.state.execution_evm_version = Some(spec_id);
1544 Ok(Default::default())
1545 }
1546}
1547
1548impl Cheatcode for getEvmVersionCall {
1549 fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1550 let spec = ccx.ecx.cfg().spec();
1551 Ok(spec.evm_version_name().to_lowercase().abi_encode())
1552 }
1553}
1554
1555pub(super) fn get_nonce<FEN: FoundryEvmNetwork>(
1556 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1557 address: &Address,
1558) -> Result {
1559 let account = ccx.ecx.journal_mut().load_account(*address)?;
1560 Ok(account.data.info.nonce.abi_encode())
1561}
1562
1563fn inner_snapshot_state<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
1564 let evm_env = ccx.ecx.evm_clone();
1565 let fork_id = ccx.ecx.db().active_fork_id();
1570 let mut all_env_overrides = ccx.state.env_overrides.clone();
1571 {
1572 let active = all_env_overrides.entry(fork_id).or_default();
1573 if active.gas_price.is_none() {
1574 active.pre_override_gas_price = Some(ccx.ecx.tx().gas_price());
1575 }
1576 if active.blob_hashes.is_none() {
1577 active.pre_override_tx_type = Some(ccx.ecx.tx().tx_type());
1578 active.pre_override_blob_hashes = Some(ccx.ecx.tx().blob_versioned_hashes().to_vec());
1579 }
1580 }
1581 let journaled_state = ccx.ecx.journal_inner().clone();
1582 let id = ccx.ecx.db_mut().snapshot_state(&journaled_state, &evm_env);
1583 ccx.state.env_overrides_snapshots.insert(id, all_env_overrides);
1587 ccx.state.fork_block_number_override_snapshots.insert(id, ccx.state.fork_block_number_override);
1588 #[cfg(feature = "monad")]
1589 {
1590 use foundry_evm_core::FoundryJournal as _;
1591
1592 ccx.state
1593 .context_snapshots
1594 .insert(id, (ccx.ecx.chain().clone(), ccx.ecx.journal().capture_reserve_balance()));
1595 }
1596 ccx.state.snapshot_created_accounts(id, fork_id);
1597 Ok(id.abi_encode())
1598}
1599
1600fn sync_tx_after_env_override_restore<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN>) {
1608 let fork_id = ccx.ecx.db().active_fork_id();
1609 let env_overrides = ccx.state.env_overrides.get(&fork_id).cloned().unwrap_or_default();
1611 let remove_inactive_entry = !env_overrides.is_any_set();
1612 match env_overrides.gas_price {
1613 Some(p) if !ccx.state.in_isolation_context => ccx.ecx.tx_mut().set_gas_price(p),
1614 None => {
1615 let pre = env_overrides.pre_override_gas_price.unwrap_or(0);
1619 ccx.ecx.tx_mut().set_gas_price(pre);
1620 }
1621 _ => {}
1622 }
1623 match env_overrides.blob_hashes {
1624 Some(hashes) if !ccx.state.in_isolation_context => {
1625 ccx.ecx.tx_mut().set_blob_hashes(hashes);
1626 ccx.ecx.tx_mut().set_tx_type(EIP4844_TX_TYPE_ID);
1627 }
1628 None => {
1629 let pre_hashes = env_overrides.pre_override_blob_hashes.unwrap_or_default();
1631 ccx.ecx.tx_mut().set_blob_hashes(pre_hashes);
1632 let pre_type = env_overrides.pre_override_tx_type.unwrap_or(0);
1635 ccx.ecx.tx_mut().set_tx_type(pre_type);
1636 }
1637 _ => {}
1638 }
1639 if remove_inactive_entry {
1640 ccx.state.env_overrides.remove(&fork_id);
1641 }
1642}
1643
1644fn inner_revert_to_state<FEN: FoundryEvmNetwork>(
1645 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1646 snapshot_id: U256,
1647) -> Result {
1648 let mut evm_env = ccx.ecx.evm_clone();
1649 let caller = ccx.ecx.caller();
1650 let journaled_state = ccx.ecx.journal_inner().clone();
1651 if let Some(restored) = ccx.ecx.db_mut().revert_state(
1652 snapshot_id,
1653 &journaled_state,
1654 &mut evm_env,
1655 caller,
1656 RevertStateSnapshotAction::RevertKeep,
1657 ) {
1658 ccx.ecx.set_journal_inner(restored);
1659 #[cfg(feature = "monad")]
1660 {
1661 use foundry_evm_core::FoundryJournal as _;
1662
1663 if let Some((context, state)) = ccx.state.context_snapshots.get(&snapshot_id) {
1664 *ccx.ecx.chain_mut() = context.clone();
1665 ccx.ecx.journal_mut().restore_reserve_balance(state.clone());
1666 }
1667 }
1668 refresh_chain_journal(ccx.ecx);
1669 ccx.ecx.set_evm(evm_env);
1670 if let Some(snap) = ccx.state.env_overrides_snapshots.get(&snapshot_id) {
1673 ccx.state.env_overrides = snap.clone();
1674 }
1675 if let Some(&fork_block_number) =
1676 ccx.state.fork_block_number_override_snapshots.get(&snapshot_id)
1677 {
1678 ccx.state.fork_block_number_override = fork_block_number;
1679 }
1680 ccx.state.revert_created_accounts(snapshot_id, false);
1681 sync_tx_after_env_override_restore(ccx);
1682 Ok(true.abi_encode())
1683 } else {
1684 Ok(false.abi_encode())
1685 }
1686}
1687
1688fn inner_revert_to_state_and_delete<FEN: FoundryEvmNetwork>(
1689 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1690 snapshot_id: U256,
1691) -> Result {
1692 let mut evm_env = ccx.ecx.evm_clone();
1693 let caller = ccx.ecx.caller();
1694 let journaled_state = ccx.ecx.journal_inner().clone();
1695 if let Some(restored) = ccx.ecx.db_mut().revert_state(
1696 snapshot_id,
1697 &journaled_state,
1698 &mut evm_env,
1699 caller,
1700 RevertStateSnapshotAction::RevertRemove,
1701 ) {
1702 ccx.ecx.set_journal_inner(restored);
1703 #[cfg(feature = "monad")]
1704 {
1705 use foundry_evm_core::FoundryJournal as _;
1706
1707 if let Some((context, state)) = ccx.state.context_snapshots.remove(&snapshot_id) {
1708 *ccx.ecx.chain_mut() = context;
1709 ccx.ecx.journal_mut().restore_reserve_balance(state);
1710 }
1711 }
1712 refresh_chain_journal(ccx.ecx);
1713 ccx.ecx.set_evm(evm_env);
1714 if let Some(snap) = ccx.state.env_overrides_snapshots.remove(&snapshot_id) {
1715 ccx.state.env_overrides = snap;
1716 }
1717 if let Some(fork_block_number) =
1718 ccx.state.fork_block_number_override_snapshots.remove(&snapshot_id)
1719 {
1720 ccx.state.fork_block_number_override = fork_block_number;
1721 }
1722 ccx.state.revert_created_accounts(snapshot_id, true);
1723 sync_tx_after_env_override_restore(ccx);
1724 Ok(true.abi_encode())
1725 } else {
1726 Ok(false.abi_encode())
1727 }
1728}
1729
1730fn inner_value_snapshot<FEN: FoundryEvmNetwork>(
1731 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1732 group: Option<String>,
1733 name: Option<String>,
1734 value: String,
1735) -> Result {
1736 let (group, name) = derive_snapshot_name(ccx, group, name);
1737
1738 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value);
1739
1740 Ok(Default::default())
1741}
1742
1743fn inner_last_gas_snapshot<FEN: FoundryEvmNetwork>(
1744 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1745 group: Option<String>,
1746 name: Option<String>,
1747 value: u64,
1748) -> Result {
1749 let (group, name) = derive_snapshot_name(ccx, group, name);
1750
1751 ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string());
1752
1753 Ok(value.abi_encode())
1754}
1755
1756fn inner_start_gas_snapshot<FEN: FoundryEvmNetwork>(
1757 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1758 group: Option<String>,
1759 name: Option<String>,
1760) -> Result {
1761 if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
1763 bail!("gas snapshot was already started with group: {group} and name: {name}");
1764 }
1765
1766 let (group, name) = derive_snapshot_name(ccx, group, name);
1767
1768 ccx.state.gas_metering.gas_records.push(GasRecord {
1769 group: group.clone(),
1770 name: name.clone(),
1771 gas_used: 0,
1772 depth: ccx.ecx.journal().depth(),
1773 });
1774
1775 ccx.state.gas_metering.active_gas_snapshot = Some((group, name));
1776
1777 ccx.state.gas_metering.start();
1778
1779 Ok(Default::default())
1780}
1781
1782fn inner_stop_gas_snapshot<FEN: FoundryEvmNetwork>(
1783 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1784 group: Option<String>,
1785 name: Option<String>,
1786) -> Result {
1787 let (group, name) = group
1789 .zip(name)
1790 .or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone())
1791 .ok_or_else(|| fmt_err!("no active gas snapshot; call `startGasSnapshot` first"))?;
1792
1793 if let Some(record) = ccx
1794 .state
1795 .gas_metering
1796 .gas_records
1797 .iter_mut()
1798 .find(|record| record.group == group && record.name == name)
1799 {
1800 let value = record.gas_used.saturating_sub(171);
1803
1804 ccx.state
1805 .gas_snapshots
1806 .entry(group.clone())
1807 .or_default()
1808 .insert(name.clone(), value.to_string());
1809
1810 ccx.state.gas_metering.stop();
1812
1813 ccx.state
1815 .gas_metering
1816 .gas_records
1817 .retain(|record| record.group != group && record.name != name);
1818
1819 if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot
1821 && snapshot_group == &group
1822 && snapshot_name == &name
1823 {
1824 ccx.state.gas_metering.active_gas_snapshot = None;
1825 }
1826
1827 Ok(value.abi_encode())
1828 } else {
1829 bail!("no gas snapshot was started with the name: {name} in group: {group}");
1830 }
1831}
1832
1833fn derive_snapshot_name<FEN: FoundryEvmNetwork>(
1835 ccx: &CheatsCtxt<'_, '_, FEN>,
1836 group: Option<String>,
1837 name: Option<String>,
1838) -> (String, String) {
1839 let group = group.unwrap_or_else(|| {
1840 ccx.state.config.running_artifact.clone().expect("expected running contract").name
1841 });
1842 let name = name.unwrap_or_else(|| "default".to_string());
1843 (group, name)
1844}
1845
1846fn read_callers<FEN: FoundryEvmNetwork>(
1870 state: &Cheatcodes<FEN>,
1871 default_sender: &Address,
1872 call_depth: usize,
1873) -> Result {
1874 let mut mode = CallerMode::None;
1875 let mut new_caller = default_sender;
1876 let mut new_origin = default_sender;
1877 if let Some(prank) = state.get_prank(call_depth) {
1878 mode = if prank.single_call { CallerMode::Prank } else { CallerMode::RecurrentPrank };
1879 new_caller = &prank.new_caller;
1880 if let Some(new) = &prank.new_origin {
1881 new_origin = new;
1882 }
1883 } else if let Some(broadcast) = &state.broadcast {
1884 mode = if broadcast.single_call {
1885 CallerMode::Broadcast
1886 } else {
1887 CallerMode::RecurrentBroadcast
1888 };
1889 new_caller = &broadcast.new_origin;
1890 new_origin = &broadcast.new_origin;
1891 }
1892
1893 Ok((mode, new_caller, new_origin).abi_encode_params())
1894}
1895
1896pub(super) fn journaled_account<
1898 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1899>(
1900 ecx: &mut CTX,
1901 addr: Address,
1902) -> Result<&mut Account> {
1903 ensure_loaded_account(ecx, addr)?;
1904 Ok(ecx.journal_mut().evm_state_mut().get_mut(&addr).expect("account is loaded"))
1905}
1906
1907pub(super) fn ensure_loaded_account<CTX: ContextTr<Db: Database<Error = DatabaseError>>>(
1908 ecx: &mut CTX,
1909 addr: Address,
1910) -> Result<()> {
1911 ecx.journal_mut().load_account(addr)?;
1912 ecx.journal_mut().touch_account(addr);
1913 Ok(())
1914}
1915
1916fn set_eip2935_blockhash<
1917 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1918>(
1919 ecx: &mut CTX,
1920 block_number: U256,
1921 block_hash: B256,
1922) -> Result<()> {
1923 let account_was_cold = ecx.journal_mut().load_account(HISTORY_STORAGE_ADDRESS)?.is_cold;
1924 let account =
1925 ecx.journal_mut().evm_state().get(&HISTORY_STORAGE_ADDRESS).expect("account is loaded");
1926 if account.info.code_hash != keccak256(&HISTORY_STORAGE_CODE) {
1927 restore_eip2935_cold_state(ecx, account_was_cold, None);
1928 return Ok(());
1929 }
1930
1931 let slot = history_storage_slot(block_number);
1932 let slot_was_cold = ecx
1933 .journal_mut()
1934 .sstore(HISTORY_STORAGE_ADDRESS, slot, history_storage_value(block_hash))
1935 .map_err(|e| fmt_err!("failed to store EIP-2935 history slot: {:?}", e))?
1936 .is_cold;
1937 restore_eip2935_cold_state(ecx, account_was_cold, Some((slot, slot_was_cold)));
1938 Ok(())
1939}
1940
1941fn restore_eip2935_cold_state<
1942 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1943>(
1944 ecx: &mut CTX,
1945 account_was_cold: bool,
1946 slot_state: Option<(U256, bool)>,
1947) {
1948 let Some(account) = ecx.journal_mut().evm_state_mut().get_mut(&HISTORY_STORAGE_ADDRESS) else {
1949 return;
1950 };
1951 if account_was_cold {
1952 account.mark_cold();
1953 }
1954 if let Some((slot, slot_was_cold)) = slot_state
1955 && slot_was_cold
1956 && let Some(storage_slot) = account.storage.get_mut(&slot)
1957 {
1958 storage_slot.is_cold = true;
1959 }
1960}
1961
1962const TIP20_LOGO_URI_SLOT_INDEX: u64 = 5;
1967
1968fn set_tip20_logo_uri<FEN: FoundryEvmNetwork>(
1969 ccx: &mut CheatsCtxt<'_, '_, FEN>,
1970 token: &Address,
1971 new_logo_uri: &str,
1972) -> Result {
1973 validate_tip20_logo_uri(new_logo_uri).map_err(|err| match err {
1974 Tip20LogoUriValidationError::LogoURITooLong => fmt_err!("LogoURITooLong"),
1975 Tip20LogoUriValidationError::InvalidLogoURI => fmt_err!("InvalidLogoURI"),
1976 })?;
1977 ccx.ensure_not_precompile(token)?;
1978 ensure_loaded_account(ccx.ecx, *token)?;
1979 store_solidity_string(
1980 ccx.ecx,
1981 *token,
1982 U256::from(TIP20_LOGO_URI_SLOT_INDEX),
1983 new_logo_uri.as_bytes(),
1984 )
1985}
1986
1987fn store_solidity_string<CTX>(
1988 ecx: &mut CTX,
1989 target: Address,
1990 base_slot: U256,
1991 bytes: &[u8],
1992) -> Result
1993where
1994 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
1995{
1996 cleanup_long_string_tail(ecx, target, base_slot, bytes.len())?;
1997
1998 if bytes.len() <= 31 {
1999 ecx.journal_mut()
2000 .sstore(target, base_slot, encode_short_string(bytes))
2001 .map_err(|e| fmt_err!("failed to store TIP-20 logo URI: {:?}", e))?;
2002 return Ok(Default::default());
2003 }
2004
2005 ecx.journal_mut()
2006 .sstore(target, base_slot, U256::from(bytes.len() * 2 + 1))
2007 .map_err(|e| fmt_err!("failed to store TIP-20 logo URI length: {:?}", e))?;
2008
2009 let slot_start = solidity_dynamic_data_slot(base_slot);
2010 for (index, chunk) in bytes.chunks(32).enumerate() {
2011 let mut chunk_bytes = [0u8; 32];
2012 chunk_bytes[..chunk.len()].copy_from_slice(chunk);
2013 ecx.journal_mut()
2014 .sstore(target, slot_start + U256::from(index), U256::from_be_bytes(chunk_bytes))
2015 .map_err(|e| fmt_err!("failed to store TIP-20 logo URI data: {:?}", e))?;
2016 }
2017
2018 Ok(Default::default())
2019}
2020
2021fn cleanup_long_string_tail<CTX>(
2022 ecx: &mut CTX,
2023 target: Address,
2024 base_slot: U256,
2025 new_len: usize,
2026) -> Result<()>
2027where
2028 CTX: ContextTr<Db: Database<Error = DatabaseError>, Journal: JournalExt>,
2029{
2030 let previous = ecx
2031 .journal_mut()
2032 .sload(target, base_slot)
2033 .map_err(|e| fmt_err!("failed to load previous TIP-20 logo URI: {:?}", e))?
2034 .data;
2035 if !is_long_string(previous) {
2036 return Ok(());
2037 }
2038
2039 let Some(previous_len) = long_string_length(previous) else {
2040 return Ok(());
2041 };
2042 let previous_chunks = string_chunks(previous_len);
2043 let new_chunks = if new_len > 31 { string_chunks(new_len) } else { 0 };
2044 if previous_chunks <= new_chunks {
2045 return Ok(());
2046 }
2047
2048 let slot_start = solidity_dynamic_data_slot(base_slot);
2049 for index in new_chunks..previous_chunks {
2050 ecx.journal_mut()
2051 .sstore(target, slot_start + U256::from(index), U256::ZERO)
2052 .map_err(|e| fmt_err!("failed to clear previous TIP-20 logo URI data: {:?}", e))?;
2053 }
2054
2055 Ok(())
2056}
2057
2058fn encode_short_string(bytes: &[u8]) -> U256 {
2059 let mut storage_bytes = [0u8; 32];
2060 storage_bytes[..bytes.len()].copy_from_slice(bytes);
2061 storage_bytes[31] =
2062 u8::try_from(bytes.len() * 2).expect("short Solidity string length tag fits in u8");
2063 U256::from_be_bytes(storage_bytes)
2064}
2065
2066fn solidity_dynamic_data_slot(base_slot: U256) -> U256 {
2067 U256::from_be_bytes(keccak256(base_slot.to_be_bytes::<32>()).0)
2068}
2069
2070const fn is_long_string(slot_value: U256) -> bool {
2071 (slot_value.to_be_bytes::<32>()[31] & 1) != 0
2072}
2073
2074fn long_string_length(slot_value: U256) -> Option<usize> {
2075 let length: U256 = (slot_value - U256::ONE) >> 1;
2076 usize::try_from(length).ok().filter(|length| *length <= TIP20_MAX_LOGO_URI_BYTES)
2077}
2078
2079const fn string_chunks(byte_length: usize) -> usize {
2080 byte_length.div_ceil(32)
2081}
2082
2083fn get_state_diff<FEN: FoundryEvmNetwork>(state: &mut Cheatcodes<FEN>) -> Result {
2091 let mut res = state.take_recorded_account_diffs_prefix();
2092 res.extend(
2093 state
2094 .recorded_account_diffs_stack
2095 .replace(Default::default())
2096 .unwrap_or_default()
2097 .into_iter()
2098 .flatten(),
2099 );
2100 Ok(res.abi_encode())
2101}
2102
2103fn genesis_account(account: &Account) -> GenesisAccount {
2105 GenesisAccount {
2106 nonce: Some(account.info.nonce),
2107 balance: account.info.balance,
2108 code: account.info.code.as_ref().map(|o| o.original_bytes()),
2109 storage: Some(
2110 account
2111 .storage
2112 .iter()
2113 .map(|(k, v)| (B256::from(*k), B256::from(v.present_value())))
2114 .collect(),
2115 ),
2116 private_key: None,
2117 }
2118}
2119
2120fn get_recorded_state_diffs<FEN: FoundryEvmNetwork>(
2122 ccx: &mut CheatsCtxt<'_, '_, FEN>,
2123) -> BTreeMap<Address, AccountStateDiffs> {
2124 let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
2125
2126 let mut addresses_to_lookup = AddressSet::default();
2128 let mut layout_sources = AddressMap::default();
2131 for account_access in ccx.state.recorded_account_diffs() {
2132 if !account_access.storageAccesses.is_empty()
2133 || account_access.oldBalance != account_access.newBalance
2134 {
2135 addresses_to_lookup.insert(account_access.account);
2136 for storage_access in &account_access.storageAccesses {
2137 if storage_access.isWrite && !storage_access.reverted {
2138 addresses_to_lookup.insert(storage_access.account);
2139 let source = (
2140 account_access.chainInfo.chainId.to::<u64>(),
2141 account_access.chainInfo.forkId,
2142 account_access.account,
2143 );
2144 if let Some(existing) = layout_sources.get_mut(&storage_access.account) {
2145 if *existing != Some(source) {
2146 *existing = None;
2150 }
2151 } else {
2152 layout_sources.insert(storage_access.account, Some(source));
2153 }
2154 }
2155 }
2156 }
2157 }
2158
2159 let mut contract_names = AddressMap::default();
2161 let mut storage_layouts = AddressMap::default();
2162 for address in addresses_to_lookup {
2163 if let Some((artifact_id, contract_data)) = get_contract_data(ccx, address, false) {
2164 contract_names.insert(address, artifact_id.identifier());
2165
2166 if let Some(storage_layout) = &contract_data.storage_layout {
2168 storage_layouts.insert(address, storage_layout.clone());
2169 }
2170 }
2171 }
2172
2173 if ccx.state.config.decode_external_storage {
2177 let current_chain_id = ccx.ecx.cfg().chain_id();
2178 let current_fork_id = ccx.ecx.db().active_fork_id().unwrap_or_default();
2179 let mut external_requests = BTreeMap::<u64, AddressSet>::new();
2180 let mut external_targets = Vec::new();
2181 for (storage_address, source) in layout_sources {
2182 let Some((chain_id, fork_id, code_address)) = source else {
2183 contract_names.remove(&storage_address);
2186 storage_layouts.remove(&storage_address);
2187 continue;
2188 };
2189 let recorded_context_is_current =
2190 chain_id == current_chain_id && fork_id == current_fork_id;
2191 if !recorded_context_is_current {
2192 contract_names.remove(&storage_address);
2193 }
2194 if recorded_context_is_current
2195 && code_address == storage_address
2196 && storage_layouts.contains_key(&storage_address)
2197 {
2198 continue;
2199 }
2200
2201 storage_layouts.remove(&storage_address);
2204 if recorded_context_is_current
2205 && let Some((artifact_id, contract_data)) =
2206 get_contract_data(ccx, code_address, false)
2207 && let Some(layout) = &contract_data.storage_layout
2208 {
2209 contract_names.insert(storage_address, artifact_id.identifier());
2210 storage_layouts.insert(storage_address, layout.clone());
2211 } else {
2212 external_requests.entry(chain_id).or_default().insert(code_address);
2213 external_targets.push((storage_address, chain_id, code_address));
2214 }
2215 }
2216
2217 for (chain_id, addresses) in external_requests {
2218 let external = crate::external_storage::storage_layouts(
2219 &ccx.state.config.external_sources,
2220 foundry_config::Chain::from(chain_id),
2221 addresses.into_iter().collect(),
2222 );
2223 for &(storage_address, target_chain_id, code_address) in &external_targets {
2224 if target_chain_id == chain_id
2225 && let Some((name, layout)) = external.get(&code_address)
2226 {
2227 contract_names.insert(storage_address, name.clone());
2228 storage_layouts.insert(storage_address, layout.clone());
2229 }
2230 }
2231 }
2232 }
2233
2234 ccx.state
2236 .recorded_account_diffs()
2237 .filter(|account_access| {
2238 !account_access.storageAccesses.is_empty()
2239 || account_access.oldBalance != account_access.newBalance
2240 || (!account_access.reverted && account_access.oldNonce != account_access.newNonce)
2241 })
2242 .for_each(|account_access| {
2243 if account_access.oldBalance != account_access.newBalance {
2245 let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| {
2246 AccountStateDiffs {
2247 label: ccx.state.labels.get(&account_access.account).cloned(),
2248 contract: contract_names.get(&account_access.account).cloned(),
2249 ..Default::default()
2250 }
2251 });
2252 if let Some(diff) = &mut account_diff.balance_diff {
2254 diff.new_value = account_access.newBalance;
2255 } else {
2256 account_diff.balance_diff = Some(BalanceDiff {
2257 previous_value: account_access.oldBalance,
2258 new_value: account_access.newBalance,
2259 });
2260 }
2261 }
2262
2263 if account_access.oldNonce != account_access.newNonce && !account_access.reverted {
2265 let account_diff = state_diffs.entry(account_access.account).or_insert_with(|| {
2266 AccountStateDiffs {
2267 label: ccx.state.labels.get(&account_access.account).cloned(),
2268 contract: contract_names.get(&account_access.account).cloned(),
2269 ..Default::default()
2270 }
2271 });
2272 if let Some(diff) = &mut account_diff.nonce_diff {
2274 diff.new_value = account_access.newNonce;
2275 } else {
2276 account_diff.nonce_diff = Some(NonceDiff {
2277 previous_value: account_access.oldNonce,
2278 new_value: account_access.newNonce,
2279 });
2280 }
2281 }
2282
2283 let raw_changes_by_slot = account_access
2285 .storageAccesses
2286 .iter()
2287 .filter_map(|access| {
2288 (access.isWrite && !access.reverted)
2289 .then_some((access.slot, (access.previousValue, access.newValue)))
2290 })
2291 .collect::<BTreeMap<_, _>>();
2292
2293 for storage_access in &account_access.storageAccesses {
2295 if storage_access.isWrite && !storage_access.reverted {
2296 let account_diff =
2297 state_diffs.entry(storage_access.account).or_insert_with(|| {
2298 AccountStateDiffs {
2299 label: ccx.state.labels.get(&storage_access.account).cloned(),
2300 contract: contract_names.get(&storage_access.account).cloned(),
2301 ..Default::default()
2302 }
2303 });
2304 let layout = storage_layouts.get(&storage_access.account);
2305 let entry = match account_diff.state_diff.entry(storage_access.slot) {
2307 Entry::Vacant(slot_state_diff) => {
2308 let mapping_slots = ccx
2311 .state
2312 .mapping_slots
2313 .as_ref()
2314 .and_then(|slots| slots.get(&storage_access.account));
2315
2316 let slot_info = layout.and_then(|layout| {
2317 let decoder = SlotIdentifier::new(layout.clone());
2318 decoder.identify(&storage_access.slot, mapping_slots).or_else(
2319 || {
2320 let current_base_slot_values = raw_changes_by_slot
2325 .iter()
2326 .map(|(slot, (_, new_val))| (*slot, *new_val))
2327 .collect::<B256Map<_>>();
2328 decoder.identify_bytes_or_string(
2329 &storage_access.slot,
2330 ¤t_base_slot_values,
2331 )
2332 },
2333 )
2334 });
2335
2336 slot_state_diff.insert(SlotStateDiff {
2337 previous_value: storage_access.previousValue,
2338 new_value: storage_access.newValue,
2339 slot_info,
2340 })
2341 }
2342 Entry::Occupied(slot_state_diff) => {
2343 let entry = slot_state_diff.into_mut();
2344 entry.new_value = storage_access.newValue;
2345 entry
2346 }
2347 };
2348
2349 if let Some(slot_info) = &mut entry.slot_info {
2351 slot_info.decode_values(entry.previous_value, storage_access.newValue);
2352 if slot_info.is_bytes_or_string() {
2353 slot_info.decode_bytes_or_string_values(
2354 &storage_access.slot,
2355 &raw_changes_by_slot,
2356 );
2357 }
2358 }
2359 }
2360 }
2361 });
2362 state_diffs
2363}
2364
2365const EIP1967_IMPL_SLOT: &str = "360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc";
2367
2368const EIP1822_PROXIABLE_SLOT: &str =
2370 "c5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7";
2371
2372fn get_contract_data<'a, FEN: FoundryEvmNetwork>(
2374 ccx: &'a mut CheatsCtxt<'_, '_, FEN>,
2375 address: Address,
2376 detect_proxy: bool,
2377) -> Option<(&'a foundry_compilers::ArtifactId, &'a foundry_common::contracts::ContractData)> {
2378 let artifacts = ccx.state.config.available_artifacts.as_ref()?;
2380
2381 let account = ccx.ecx.journal_mut().load_account(address).ok()?;
2383 let code = account.data.info.code.as_ref()?;
2384
2385 if code.is_empty() {
2387 return None;
2388 }
2389
2390 let code_bytes = code.original_bytes();
2392 if let Some(result) = artifacts.find_by_deployed_code_exact(&code_bytes) {
2393 return Some(result);
2394 }
2395
2396 let hex_str = hex::encode(&code_bytes);
2398 let find_by_suffix =
2399 |suffix: &str| artifacts.iter().find(|(a, _)| a.identifier().ends_with(suffix));
2400 if detect_proxy
2401 && hex_str.contains(EIP1967_IMPL_SLOT)
2402 && let Some(result) = find_by_suffix(":TransparentUpgradeableProxy")
2403 {
2404 return Some(result);
2405 } else if detect_proxy
2406 && hex_str.contains(EIP1822_PROXIABLE_SLOT)
2407 && let Some(result) = find_by_suffix(":UUPSUpgradeable")
2408 {
2409 return Some(result);
2410 }
2411
2412 artifacts.find_by_deployed_code(&code_bytes)
2414}
2415
2416fn set_cold_slot<FEN: FoundryEvmNetwork>(
2418 ccx: &mut CheatsCtxt<'_, '_, FEN>,
2419 target: Address,
2420 slot: U256,
2421 cold: bool,
2422) {
2423 if let Some(account) = ccx.ecx.journal_mut().evm_state_mut().get_mut(&target)
2424 && let Some(storage_slot) = account.storage.get_mut(&slot)
2425 {
2426 storage_slot.is_cold = cold;
2427 }
2428}