Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/cheatcodes/assets/cheatcodes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,8 @@ interface Vm {
#[cheatcode(group = Evm, safety = Safe)]
function addr(uint256 privateKey) external pure returns (address keyAddr);

/// Dump a genesis JSON file's `allocs` to disk.
/// Dumps a genesis JSON file's `allocs` to disk. Accounts created in the current transaction
/// are ordered by deployment, followed by the remaining accounts in ascending address order.
#[cheatcode(group = Evm, safety = Unsafe)]
function dumpState(string calldata pathToStateJson) external;

Expand Down
54 changes: 50 additions & 4 deletions crates/cheatcodes/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ mod record_debug_step;
use foundry_common::fmt::format_token_raw;
use foundry_config::{ExecutionSpec, evm_spec_id_from_str};
use record_debug_step::{convert_call_trace_ctx_to_debug_step, flatten_call_trace};
use serde::Serialize;
use serde::{Serialize, Serializer, ser::SerializeMap};

mod fork;
pub(crate) mod mapping;
Expand All @@ -76,6 +76,21 @@ struct LogJson {
emitter: String,
}

struct StateDump<'a>(&'a [(Address, GenesisAccount)]);

impl Serialize for StateDump<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(Some(self.0.len()))?;
for (address, account) in self.0 {
map.serialize_entry(address, account)?;
}
map.end()
}
}

/// Records storage slots reads and writes.
#[derive(Clone, Debug, Default)]
pub struct RecordAccess {
Expand Down Expand Up @@ -361,6 +376,16 @@ impl Cheatcode for dumpStateCall {
let Self { pathToStateJson } = self;
let path = Path::new(pathToStateJson);

let fork_id = ccx.ecx.db().active_fork_id();
let created_accounts = ccx
.state
.created_accounts(fork_id)
.into_iter()
.filter(|address| {
ccx.ecx.journal().evm_state().get(address).is_some_and(Account::is_created)
})
.collect::<Vec<_>>();

// Do not include system account or empty accounts in the dump.
let skip = |key: &Address, val: &Account| {
key == &CHEATCODE_ADDRESS
Expand All @@ -372,16 +397,24 @@ impl Cheatcode for dumpStateCall {
|| val.is_empty()
};

let alloc = ccx
let mut alloc = ccx
.ecx
.journal_mut()
.evm_state_mut()
.iter_mut()
.filter(|(key, val)| !skip(key, val))
.map(|(key, val)| (key, genesis_account(val)))
.map(|(key, val)| (*key, genesis_account(val)))
.collect::<BTreeMap<_, _>>();

write_json_file(path, &alloc)?;
let mut ordered_alloc = Vec::with_capacity(alloc.len());
for address in created_accounts {
if let Some(account) = alloc.remove(&address) {
ordered_alloc.push((address, account));
}
}
ordered_alloc.extend(alloc);

write_json_file(path, &StateDump(&ordered_alloc))?;
Ok(Default::default())
}
}
Expand Down Expand Up @@ -1016,6 +1049,7 @@ impl Cheatcode for deleteSnapshotCall {
let Self { snapshotId } = self;
let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
ccx.state.env_overrides_snapshots.remove(snapshotId);
ccx.state.delete_created_accounts_snapshot(*snapshotId);
Ok(result.abi_encode())
}
}
Expand All @@ -1025,6 +1059,7 @@ impl Cheatcode for deleteStateSnapshotCall {
let Self { snapshotId } = self;
let result = ccx.ecx.db_mut().delete_state_snapshot(*snapshotId);
ccx.state.env_overrides_snapshots.remove(snapshotId);
ccx.state.delete_created_accounts_snapshot(*snapshotId);
Ok(result.abi_encode())
}
}
Expand All @@ -1035,6 +1070,7 @@ impl Cheatcode for deleteSnapshotsCall {
let Self {} = self;
ccx.ecx.db_mut().delete_state_snapshots();
ccx.state.env_overrides_snapshots.clear();
ccx.state.clear_created_accounts_snapshots();
Ok(Default::default())
}
}
Expand All @@ -1044,6 +1080,7 @@ impl Cheatcode for deleteStateSnapshotsCall {
let Self {} = self;
ccx.ecx.db_mut().delete_state_snapshots();
ccx.state.env_overrides_snapshots.clear();
ccx.state.clear_created_accounts_snapshots();
Ok(Default::default())
}
}
Expand Down Expand Up @@ -1251,6 +1288,7 @@ impl Cheatcode for executeTransactionCall {
let sender =
tx.recover_signer().map_err(|err| fmt_err!("failed to recover signer: {err}"))?;
let tx_env = TxEnvFor::<FEN>::from_recovered_tx(&tx, sender);
let created_address = tx_env.kind().is_create().then(|| sender.create(tx_env.nonce()));

// Save current env for restoration after execution.
let cached_evm_env = ccx.ecx.evm_clone();
Expand Down Expand Up @@ -1281,6 +1319,10 @@ impl Cheatcode for executeTransactionCall {
// Mark as inner context so isolation mode doesn't trigger a nested transact_inner
// when the inner EVM executes calls at depth == 1.
executor.set_in_inner_context(true, Some(sender));
if let Some(address) = created_address {
let fork_id = ccx.ecx.db().active_fork_id();
ccx.state.record_created_account(fork_id, address);
Comment thread
stevencartavia marked this conversation as resolved.
}

// Clone journaled state and mark all accounts/slots cold.
let cold_state = {
Expand Down Expand Up @@ -1488,12 +1530,14 @@ fn inner_snapshot_state<FEN: FoundryEvmNetwork>(ccx: &mut CheatsCtxt<'_, '_, FEN
active.pre_override_blob_hashes = Some(ccx.ecx.tx().blob_versioned_hashes().to_vec());
}
}
let fork_id = ccx.ecx.db().active_fork_id();
let (db, inner) = ccx.ecx.db_journal_inner_mut();
let id = db.snapshot_state(inner, &evm_env);
// Capture the cheatcode-side env overrides alongside the backend
// snapshot so they can be rolled back in lockstep with `EvmEnv`. See
// `Cheatcodes::env_overrides_snapshots`.
ccx.state.env_overrides_snapshots.insert(id, all_env_overrides);
ccx.state.snapshot_created_accounts(id, fork_id);
Ok(id.abi_encode())
}

Expand Down Expand Up @@ -1558,6 +1602,7 @@ fn inner_revert_to_state<FEN: FoundryEvmNetwork>(
if let Some(snap) = ccx.state.env_overrides_snapshots.get(&snapshot_id) {
ccx.state.env_overrides = snap.clone();
}
ccx.state.revert_created_accounts(snapshot_id, false);
sync_tx_after_env_override_restore(ccx);
Ok(true.abi_encode())
} else {
Expand All @@ -1584,6 +1629,7 @@ fn inner_revert_to_state_and_delete<FEN: FoundryEvmNetwork>(
if let Some(snap) = ccx.state.env_overrides_snapshots.remove(&snapshot_id) {
ccx.state.env_overrides = snap;
}
ccx.state.revert_created_accounts(snapshot_id, true);
sync_tx_after_env_override_restore(ccx);
Ok(true.abi_encode())
} else {
Expand Down
93 changes: 77 additions & 16 deletions crates/cheatcodes/src/evm/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ use crate::{
use alloy_dyn_abi::DynSolValue;
use alloy_evm::EvmEnv;
use alloy_network::AnyNetwork;
use alloy_primitives::{Address, B256, U256};
use alloy_primitives::{Address, B256, U256, map::AddressHashMap};
use alloy_provider::Provider;
use alloy_rpc_types::Filter;
use alloy_sol_types::SolValue;
use foundry_common::provider::ProviderBuilder;
use foundry_evm_core::{
FoundryContextExt, backend::JournaledState, evm::FoundryEvmNetwork, fork::CreateFork,
FoundryContextExt,
backend::{JournaledState, LocalForkId},
evm::FoundryEvmNetwork,
fork::CreateFork,
};
use revm::context::ContextTr;

Expand Down Expand Up @@ -72,39 +75,47 @@ impl Cheatcode for rollFork_0Call {
fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
let Self { blockNumber } = self;
persist_caller(ccx);
fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
let result = fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
db.roll_fork(None, (*blockNumber).to(), evm_env, inner)
})
})?;
record_fork_roll(ccx, None);
Ok(result)
}
}

impl Cheatcode for rollFork_1Call {
fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
let Self { txHash } = self;
persist_caller(ccx);
fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
let result = fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
db.roll_fork_to_transaction(None, *txHash, evm_env, inner)
})
})?;
record_fork_roll(ccx, None);
Ok(result)
}
}

impl Cheatcode for rollFork_2Call {
fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
let Self { forkId, blockNumber } = self;
persist_caller(ccx);
fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
let result = fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
db.roll_fork(Some(*forkId), (*blockNumber).to(), evm_env, inner)
})
})?;
record_fork_roll(ccx, Some(*forkId));
Ok(result)
}
}

impl Cheatcode for rollFork_3Call {
fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
let Self { forkId, txHash } = self;
persist_caller(ccx);
fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
let result = fork_env_op(ccx.ecx, |db, evm_env, _, inner| {
db.roll_fork_to_transaction(Some(*forkId), *txHash, evm_env, inner)
})
})?;
record_fork_roll(ccx, Some(*forkId));
Ok(result)
}
}

Expand All @@ -113,9 +124,14 @@ impl Cheatcode for selectForkCall {
let Self { forkId } = self;
persist_caller(ccx);
check_broadcast(ccx.state)?;
fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
let source_fork_id = ccx.ecx.db().active_fork_id();
let initial = ccx.state.created_account_bindings(None);
let propagated = persistent_created_accounts(ccx);
let result = fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
db.select_fork(*forkId, evm_env, tx_env, inner)
})
})?;
record_fork_switch(ccx, source_fork_id, initial, propagated);
Ok(result)
}
}

Expand Down Expand Up @@ -322,9 +338,14 @@ fn create_select_fork<FEN: FoundryEvmNetwork>(
check_broadcast(ccx.state)?;

let fork = create_fork_request(ccx, url_or_alias, block)?;
fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
let source_fork_id = ccx.ecx.db().active_fork_id();
let initial = ccx.state.created_account_bindings(None);
let propagated = persistent_created_accounts(ccx);
let result = fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
db.create_select_fork(fork, evm_env, tx_env, inner)
})
})?;
record_fork_switch(ccx, source_fork_id, initial, propagated);
Ok(result)
}

/// Creates a new fork
Expand All @@ -347,9 +368,14 @@ fn create_select_fork_at_transaction<FEN: FoundryEvmNetwork>(
check_broadcast(ccx.state)?;

let fork = create_fork_request(ccx, url_or_alias, None)?;
fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
let source_fork_id = ccx.ecx.db().active_fork_id();
let initial = ccx.state.created_account_bindings(None);
let propagated = persistent_created_accounts(ccx);
let result = fork_env_op(ccx.ecx, |db, evm_env, tx_env, inner| {
db.create_select_fork_at_transaction(fork, evm_env, tx_env, inner, *transaction)
})
})?;
record_fork_switch(ccx, source_fork_id, initial, propagated);
Ok(result)
}

/// Creates a new fork at the given transaction
Expand Down Expand Up @@ -410,6 +436,41 @@ fn fork_env_op<CTX: FoundryContextExt, T: SolValue>(
Ok(result.abi_encode())
}

fn persistent_created_accounts<FEN: FoundryEvmNetwork>(
ccx: &CheatsCtxt<'_, '_, FEN>,
) -> Vec<(Address, usize)> {
let fork_id = ccx.ecx.db().active_fork_id();
ccx.state
.created_account_bindings(fork_id)
.into_iter()
.filter(|(address, _)| ccx.ecx.db().is_persistent(address))
.collect()
}

fn record_fork_switch<FEN: FoundryEvmNetwork>(
ccx: &mut CheatsCtxt<'_, '_, FEN>,
source_fork_id: Option<LocalForkId>,
initial: AddressHashMap<usize>,
propagated: Vec<(Address, usize)>,
) {
let target_fork_id = ccx.ecx.db().active_fork_id();
if source_fork_id != target_fork_id {
ccx.state.commit_created_account_changes(source_fork_id);
Comment thread
stevencartavia marked this conversation as resolved.
}
ccx.state.record_initial_created_accounts(target_fork_id, initial);
ccx.state.record_propagated_accounts(target_fork_id, propagated);
}

fn record_fork_roll<FEN: FoundryEvmNetwork>(
ccx: &mut CheatsCtxt<'_, '_, FEN>,
target_fork_id: Option<LocalForkId>,
) {
let active_fork_id = ccx.ecx.db().active_fork_id();
if target_fork_id.is_none() || target_fork_id == active_fork_id {
ccx.state.commit_created_account_changes(active_fork_id);
}
}

fn check_broadcast<FEN: FoundryEvmNetwork>(state: &Cheatcodes<FEN>) -> Result<()> {
if state.broadcast.is_none() {
Ok(())
Expand Down
Loading
Loading