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