Skip to content

Commit f258f66

Browse files
authored
feat(cheatcodes): add contract identifier to state diffs (#11214)
1 parent 8725e17 commit f258f66

File tree

3 files changed

+81
-16
lines changed

3 files changed

+81
-16
lines changed

crates/cheatcodes/src/evm.rs

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use revm::{
2727
state::Account,
2828
};
2929
use std::{
30-
collections::{BTreeMap, btree_map::Entry},
30+
collections::{BTreeMap, HashSet, btree_map::Entry},
3131
fmt::Display,
3232
path::Path,
3333
};
@@ -131,6 +131,8 @@ struct NonceDiff {
131131
struct AccountStateDiffs {
132132
/// Address label, if any set.
133133
label: Option<String>,
134+
/// Contract identifier from artifact. e.g "src/Counter.sol:Counter"
135+
contract: Option<String>,
134136
/// Account balance changes.
135137
balance_diff: Option<BalanceDiff>,
136138
/// Account nonce changes.
@@ -145,6 +147,9 @@ impl Display for AccountStateDiffs {
145147
if let Some(label) = &self.label {
146148
writeln!(f, "label: {label}")?;
147149
}
150+
if let Some(contract) = &self.contract {
151+
writeln!(f, "contract: {contract}")?;
152+
}
148153
// Print balance diff if changed.
149154
if let Some(balance_diff) = &self.balance_diff
150155
&& balance_diff.previous_value != balance_diff.new_value
@@ -832,9 +837,9 @@ impl Cheatcode for stopAndReturnStateDiffCall {
832837
}
833838

834839
impl Cheatcode for getStateDiffCall {
835-
fn apply(&self, state: &mut Cheatcodes) -> Result {
840+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
836841
let mut diffs = String::new();
837-
let state_diffs = get_recorded_state_diffs(state);
842+
let state_diffs = get_recorded_state_diffs(ccx);
838843
for (address, state_diffs) in state_diffs {
839844
diffs.push_str(&format!("{address}\n"));
840845
diffs.push_str(&format!("{state_diffs}\n"));
@@ -844,8 +849,8 @@ impl Cheatcode for getStateDiffCall {
844849
}
845850

846851
impl Cheatcode for getStateDiffJsonCall {
847-
fn apply(&self, state: &mut Cheatcodes) -> Result {
848-
let state_diffs = get_recorded_state_diffs(state);
852+
fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result {
853+
let state_diffs = get_recorded_state_diffs(ccx);
849854
Ok(serde_json::to_string(&state_diffs)?.abi_encode())
850855
}
851856
}
@@ -1218,9 +1223,36 @@ fn genesis_account(account: &Account) -> GenesisAccount {
12181223
}
12191224

12201225
/// Helper function to returns state diffs recorded for each changed account.
1221-
fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap<Address, AccountStateDiffs> {
1226+
fn get_recorded_state_diffs(ccx: &mut CheatsCtxt) -> BTreeMap<Address, AccountStateDiffs> {
12221227
let mut state_diffs: BTreeMap<Address, AccountStateDiffs> = BTreeMap::default();
1223-
if let Some(records) = &state.recorded_account_diffs_stack {
1228+
1229+
// First, collect all unique addresses we need to look up
1230+
let mut addresses_to_lookup = HashSet::new();
1231+
if let Some(records) = &ccx.state.recorded_account_diffs_stack {
1232+
for account_access in records.iter().flatten() {
1233+
if !account_access.storageAccesses.is_empty()
1234+
|| account_access.oldBalance != account_access.newBalance
1235+
{
1236+
addresses_to_lookup.insert(account_access.account);
1237+
for storage_access in &account_access.storageAccesses {
1238+
if storage_access.isWrite && !storage_access.reverted {
1239+
addresses_to_lookup.insert(storage_access.account);
1240+
}
1241+
}
1242+
}
1243+
}
1244+
}
1245+
1246+
// Look up contract names for all addresses
1247+
let mut contract_names = HashMap::new();
1248+
for address in addresses_to_lookup {
1249+
if let Some(name) = get_contract_name(ccx, address) {
1250+
contract_names.insert(address, name);
1251+
}
1252+
}
1253+
1254+
// Now process the records
1255+
if let Some(records) = &ccx.state.recorded_account_diffs_stack {
12241256
records
12251257
.iter()
12261258
.flatten()
@@ -1235,7 +1267,8 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap<Address, Account
12351267
let account_diff =
12361268
state_diffs.entry(account_access.account).or_insert_with(|| {
12371269
AccountStateDiffs {
1238-
label: state.labels.get(&account_access.account).cloned(),
1270+
label: ccx.state.labels.get(&account_access.account).cloned(),
1271+
contract: contract_names.get(&account_access.account).cloned(),
12391272
..Default::default()
12401273
}
12411274
});
@@ -1255,7 +1288,8 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap<Address, Account
12551288
let account_diff =
12561289
state_diffs.entry(account_access.account).or_insert_with(|| {
12571290
AccountStateDiffs {
1258-
label: state.labels.get(&account_access.account).cloned(),
1291+
label: ccx.state.labels.get(&account_access.account).cloned(),
1292+
contract: contract_names.get(&account_access.account).cloned(),
12591293
..Default::default()
12601294
}
12611295
});
@@ -1276,7 +1310,8 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap<Address, Account
12761310
let account_diff = state_diffs
12771311
.entry(storage_access.account)
12781312
.or_insert_with(|| AccountStateDiffs {
1279-
label: state.labels.get(&storage_access.account).cloned(),
1313+
label: ccx.state.labels.get(&storage_access.account).cloned(),
1314+
contract: contract_names.get(&storage_access.account).cloned(),
12801315
..Default::default()
12811316
});
12821317
// Update state diff. Do not overwrite the initial value if already set.
@@ -1298,6 +1333,34 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap<Address, Account
12981333
state_diffs
12991334
}
13001335

1336+
/// Helper function to get the contract name from the deployed code.
1337+
fn get_contract_name(ccx: &mut CheatsCtxt, address: Address) -> Option<String> {
1338+
// Check if we have available artifacts to match against
1339+
let artifacts = ccx.state.config.available_artifacts.as_ref()?;
1340+
1341+
// Try to load the account and get its code
1342+
let account = ccx.ecx.journaled_state.load_account(address).ok()?;
1343+
let code = account.info.code.as_ref()?;
1344+
1345+
// Skip if code is empty
1346+
if code.is_empty() {
1347+
return None;
1348+
}
1349+
1350+
// Try to find the artifact by deployed code
1351+
let code_bytes = code.original_bytes();
1352+
if let Some((artifact_id, _)) = artifacts.find_by_deployed_code_exact(&code_bytes) {
1353+
return Some(artifact_id.identifier());
1354+
}
1355+
1356+
// Fallback to fuzzy matching if exact match fails
1357+
if let Some((artifact_id, _)) = artifacts.find_by_deployed_code(&code_bytes) {
1358+
return Some(artifact_id.identifier());
1359+
}
1360+
1361+
None
1362+
}
1363+
13011364
/// Helper function to set / unset cold storage slot of the target address.
13021365
fn set_cold_slot(ccx: &mut CheatsCtxt, target: Address, slot: U256, cold: bool) {
13031366
if let Some(account) = ccx.ecx.journaled_state.state.get_mut(&target)

testdata/default/cheats/RecordAccountAccesses.t.sol

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,12 +264,12 @@ contract RecordAccountAccessesTest is DSTest {
264264

265265
string memory diffs = cheats.getStateDiff();
266266
assertEq(
267-
"0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9\n- state diff:\n@ 0x00000000000000000000000000000000000000000000000000000000000004d3: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x000000000000000000000000000000000000000000000000000000000000162e\n\n0xc7183455a4C133Ae270771860664b6B7ec320bB1\n- state diff:\n@ 0x000000000000000000000000000000000000000000000000000000000000162e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x00000000000000000000000000000000000000000000000000000000000004d2\n\n",
267+
"0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9\ncontract: default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\n- state diff:\n@ 0x00000000000000000000000000000000000000000000000000000000000004d3: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x000000000000000000000000000000000000000000000000000000000000162e\n\n0xc7183455a4C133Ae270771860664b6B7ec320bB1\ncontract: default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\n- state diff:\n@ 0x000000000000000000000000000000000000000000000000000000000000162e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x00000000000000000000000000000000000000000000000000000000000004d2\n\n",
268268
diffs
269269
);
270270
string memory diffsJson = cheats.getStateDiffJson();
271271
assertEq(
272-
"{\"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x00000000000000000000000000000000000000000000000000000000000004d3\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000162e\"}}},\"0xc7183455a4c133ae270771860664b6b7ec320bb1\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x000000000000000000000000000000000000000000000000000000000000162e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x00000000000000000000000000000000000000000000000000000000000004d2\"}}}}",
272+
"{\"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\":{\"label\":null,\"contract\":\"default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\",\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x00000000000000000000000000000000000000000000000000000000000004d3\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000162e\"}}},\"0xc7183455a4c133ae270771860664b6b7ec320bb1\":{\"label\":null,\"contract\":\"default/cheats/RecordAccountAccesses.t.sol:StorageAccessor\",\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x000000000000000000000000000000000000000000000000000000000000162e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x00000000000000000000000000000000000000000000000000000000000004d2\"}}}}",
273273
diffsJson
274274
);
275275
Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff());
@@ -347,6 +347,8 @@ contract RecordAccountAccessesTest is DSTest {
347347
string memory expectedStateDiff =
348348
"0x000000000000000000000000000000000000162e\n- balance diff: 0 \xE2\x86\x92 1000000000000000000\n\n";
349349
expectedStateDiff = string.concat(expectedStateDiff, callerAddress);
350+
expectedStateDiff =
351+
string.concat(expectedStateDiff, "\ncontract: default/cheats/RecordAccountAccesses.t.sol:SelfCaller");
350352
expectedStateDiff = string.concat(
351353
expectedStateDiff,
352354
"\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n- nonce diff: 0 \xE2\x86\x92 1\n\n"
@@ -489,7 +491,7 @@ contract RecordAccountAccessesTest is DSTest {
489491
cheats.getStateDiff()
490492
);
491493
assertEq(
492-
"{\"0x00000000000000000000000000000000000004d2\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x16345785d8a0000\"},\"nonceDiff\":null,\"stateDiff\":{}}}",
494+
"{\"0x00000000000000000000000000000000000004d2\":{\"label\":null,\"contract\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x16345785d8a0000\"},\"nonceDiff\":null,\"stateDiff\":{}}}",
493495
cheats.getStateDiffJson()
494496
);
495497
Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff());
@@ -831,11 +833,11 @@ contract RecordAccountAccessesTest is DSTest {
831833
nestedStorer.run();
832834
cheats.label(address(nestedStorer), "NestedStorer");
833835
assertEq(
834-
"0x2e234DAe75C793f67A35089C9d99245E1C58470b\nlabel: NestedStorer\n- state diff:\n@ 0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n\n",
836+
"0x2e234DAe75C793f67A35089C9d99245E1C58470b\nlabel: NestedStorer\ncontract: default/cheats/RecordAccountAccesses.t.sol:NestedStorer\n- state diff:\n@ 0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n@ 0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e: 0x0000000000000000000000000000000000000000000000000000000000000000 \xE2\x86\x92 0x0000000000000000000000000000000000000000000000000000000000000001\n\n",
835837
cheats.getStateDiff()
836838
);
837839
assertEq(
838-
"{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":\"NestedStorer\",\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}}}}",
840+
"{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":\"NestedStorer\",\"contract\":\"default/cheats/RecordAccountAccesses.t.sol:NestedStorer\",\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}}}}",
839841
cheats.getStateDiffJson()
840842
);
841843
Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff());

testdata/default/repros/Issue9643.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ contract Issue9643Test is DSTest {
4242
proxied.setCounter(42);
4343
string memory rawDiff = vm.getStateDiffJson();
4444
assertEq(
45-
"{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{\"0x0000000000000000000000000000000000000000000000000000000000000000\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000002a\"}}},\"0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{}}}",
45+
"{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":null,\"contract\":\"default/repros/Issue9643.t.sol:DelegateProxy\",\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{\"0x0000000000000000000000000000000000000000000000000000000000000000\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000002a\"}}},\"0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\":{\"label\":null,\"contract\":\"default/repros/Issue9643.t.sol:Mock\",\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{}}}",
4646
rawDiff
4747
);
4848
}

0 commit comments

Comments
 (0)