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