Skip to content

Commit 2dad626

Browse files
yash-atreyaYash AtreyazerosnacksmattsseDaniPopes
authored
feat(cheatcodes): decode structs and mappings in state diff output (#11331)
* feat(`forge`): sample typed storage values * arc it * nit * clippy * nit * strip file prefixes * fmt * don't add adjacent values to sample * feat(cheatcodes): add contract identifier to AccountStateDiffs * forge fmt * doc nits * fix tests * feat(`cheatcodes`): include `SlotInfo` in SlotStateDiff * cleanup + identify slots of static arrays * nits * nit * nits * test + nits * docs * handle 2d arrays * use DynSolType * feat: decode storage values * doc nit * skip decoded serialization if none * nit * fmt * fix * fix * fix * feat(cheatcodes): decode structs in state diff output * fix * while decode * fix: show only decoded in plaintext / display output + test * feat: format slots to only significant bits in vm.getStateDiff output * encode_prefixed * nit * chore: add @onbjerg to `CODEOWNERS` (#11343) * add @onbjerg * add @0xrusowsky * resolve conflicts * fix: disable tx gas limit cap (#11347) * chore(deps): bump all dependencies (#11349) * chore: use get_or_calculate_hash better (#11350) * resolve more conflicts * fix(lint): 'unwrapped-modifier-logic' incorrectly marked with `Severity::Gas` (#11358) fix(lint): 'unwrapped-modifier-logic' incorrectly marked with Severity::Gas * feat: identify and decode nested structs * cleanup * decode structs and members recursively * cleanup * doc fix * feat(cheatcodes): decode mappings in state diffs (#11381) * feat(cheatcodes): decode mappings in state diffs * feat: decode nested mappings * assert vm.getStateDiff output * feat: add `keys` fields to `SlotInfo` in case of mappings * remove wrapper * refactor: moves state diff decoding to common (#11413) * refactor: storage decoder * cleanup * dedup MappingSlots by moving it to common * move decoding logic into SlotInfo * rename to SlotIndentifier * docs * fix: delegate identification according to encoding types * clippy + fmt * docs fix * fix * merge match arms * merge ifs * recurse handle_struct * dedup assertContains test util * fix * Update crates/common/src/slot_identifier.rs Co-authored-by: zerosnacks <[email protected]> * Review changes: simplify get or insert, use common fmt * alloy-dyn-abi.workspace * nits --------- Co-authored-by: Yash Atreya <[email protected]> Co-authored-by: zerosnacks <[email protected]> Co-authored-by: Matthias Seitz <[email protected]> Co-authored-by: DaniPopes <[email protected]> Co-authored-by: srdtrk <[email protected]> Co-authored-by: 0xrusowsky <[email protected]> Co-authored-by: grandizzy <[email protected]> Co-authored-by: grandizzy <[email protected]>
1 parent 29bed4f commit 2dad626

File tree

10 files changed

+1352
-376
lines changed

10 files changed

+1352
-376
lines changed

crates/cheatcodes/src/evm.rs

Lines changed: 55 additions & 221 deletions
Large diffs are not rendered by default.

crates/cheatcodes/src/evm/mapping.rs

Lines changed: 3 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,16 @@
11
use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
2-
use alloy_primitives::{
3-
Address, B256, U256, keccak256,
4-
map::{AddressHashMap, B256HashMap},
5-
};
2+
use alloy_primitives::{Address, B256, U256, keccak256, map::AddressHashMap};
63
use alloy_sol_types::SolValue;
4+
use foundry_common::mapping_slots::MappingSlots;
75
use revm::{
86
bytecode::opcode,
97
interpreter::{Interpreter, interpreter_types::Jumps},
108
};
119

12-
/// Recorded mapping slots.
13-
#[derive(Clone, Debug, Default)]
14-
pub struct MappingSlots {
15-
/// Holds mapping parent (slots => slots)
16-
pub parent_slots: B256HashMap<B256>,
17-
18-
/// Holds mapping key (slots => key)
19-
pub keys: B256HashMap<B256>,
20-
21-
/// Holds mapping child (slots => slots[])
22-
pub children: B256HashMap<Vec<B256>>,
23-
24-
/// Holds the last sha3 result `sha3_result => (data_low, data_high)`, this would only record
25-
/// when sha3 is called with `size == 0x40`, and the lower 256 bits would be stored in
26-
/// `data_low`, higher 256 bits in `data_high`.
27-
/// This is needed for mapping_key detect if the slot is for some mapping and record that.
28-
pub seen_sha3: B256HashMap<(B256, B256)>,
29-
}
30-
31-
impl MappingSlots {
32-
/// Tries to insert a mapping slot. Returns true if it was inserted.
33-
pub fn insert(&mut self, slot: B256) -> bool {
34-
match self.seen_sha3.get(&slot).copied() {
35-
Some((key, parent)) => {
36-
if self.keys.contains_key(&slot) {
37-
return false;
38-
}
39-
self.keys.insert(slot, key);
40-
self.parent_slots.insert(slot, parent);
41-
self.children.entry(parent).or_default().push(slot);
42-
self.insert(parent);
43-
true
44-
}
45-
None => false,
46-
}
47-
}
48-
}
49-
5010
impl Cheatcode for startMappingRecordingCall {
5111
fn apply(&self, state: &mut Cheatcodes) -> Result {
5212
let Self {} = self;
53-
if state.mapping_slots.is_none() {
54-
state.mapping_slots = Some(Default::default());
55-
}
13+
state.mapping_slots.get_or_insert_default();
5614
Ok(Default::default())
5715
}
5816
}

crates/cheatcodes/src/inspector.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ use crate::{
44
CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result,
55
Vm::{self, AccountAccess},
66
evm::{
7-
DealRecord, GasRecord, RecordAccess,
8-
mapping::{self, MappingSlots},
7+
DealRecord, GasRecord, RecordAccess, mapping,
98
mock::{MockCallDataContext, MockCallReturnData},
109
prank::Prank,
1110
},
@@ -33,7 +32,9 @@ use alloy_rpc_types::{
3332
request::{TransactionInput, TransactionRequest},
3433
};
3534
use alloy_sol_types::{SolCall, SolInterface, SolValue};
36-
use foundry_common::{SELECTOR_LEN, TransactionMaybeSigned, evm::Breakpoints};
35+
use foundry_common::{
36+
SELECTOR_LEN, TransactionMaybeSigned, evm::Breakpoints, mapping_slots::MappingSlots,
37+
};
3738
use foundry_evm_core::{
3839
InspectorExt,
3940
abi::Vm::stopExpectSafeMemoryCall,

crates/common/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ pub mod contracts;
2626
pub mod errors;
2727
pub mod evm;
2828
pub mod fs;
29+
pub mod mapping_slots;
2930
mod preprocessor;
3031
pub mod provider;
3132
pub mod reports;
3233
pub mod retry;
3334
pub mod selectors;
3435
pub mod serde_helpers;
36+
pub mod slot_identifier;
3537
pub mod term;
3638
pub mod traits;
3739
pub mod transactions;

crates/common/src/mapping_slots.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use alloy_primitives::{B256, map::B256HashMap};
2+
3+
/// Recorded mapping slots.
4+
#[derive(Clone, Debug, Default)]
5+
pub struct MappingSlots {
6+
/// Holds mapping parent (slots => slots)
7+
pub parent_slots: B256HashMap<B256>,
8+
9+
/// Holds mapping key (slots => key)
10+
pub keys: B256HashMap<B256>,
11+
12+
/// Holds mapping child (slots => slots[])
13+
pub children: B256HashMap<Vec<B256>>,
14+
15+
/// Holds the last sha3 result `sha3_result => (data_low, data_high)`, this would only record
16+
/// when sha3 is called with `size == 0x40`, and the lower 256 bits would be stored in
17+
/// `data_low`, higher 256 bits in `data_high`.
18+
/// This is needed for mapping_key detect if the slot is for some mapping and record that.
19+
pub seen_sha3: B256HashMap<(B256, B256)>,
20+
}
21+
22+
impl MappingSlots {
23+
/// Tries to insert a mapping slot. Returns true if it was inserted.
24+
pub fn insert(&mut self, slot: B256) -> bool {
25+
match self.seen_sha3.get(&slot).copied() {
26+
Some((key, parent)) => {
27+
if self.keys.insert(slot, key).is_some() {
28+
return false;
29+
}
30+
self.parent_slots.insert(slot, parent);
31+
self.children.entry(parent).or_default().push(slot);
32+
self.insert(parent);
33+
true
34+
}
35+
None => false,
36+
}
37+
}
38+
}

0 commit comments

Comments
 (0)