Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
926e8d5
feat(`forge`): sample typed storage values
yash-atreya Aug 4, 2025
8ec91b3
arc it
yash-atreya Aug 4, 2025
cfd4a15
nit
yash-atreya Aug 4, 2025
c56c920
clippy
yash-atreya Aug 4, 2025
90a1dea
nit
yash-atreya Aug 4, 2025
e7caa86
strip file prefixes
yash-atreya Aug 4, 2025
bccf3ad
fmt
yash-atreya Aug 4, 2025
fd6ecd6
don't add adjacent values to sample
yash-atreya Aug 5, 2025
a4c4a79
Merge branch 'master' into yash/fuzz-storage-values-by-type
yash-atreya Aug 5, 2025
f62bcb8
feat(cheatcodes): add contract identifier to AccountStateDiffs
yash-atreya Aug 5, 2025
1b4520f
forge fmt
yash-atreya Aug 5, 2025
cc29481
Merge branch 'master' into yash/state-diff-storage-labels
yash-atreya Aug 11, 2025
729ea8e
doc nits
yash-atreya Aug 11, 2025
2a8da88
fix tests
yash-atreya Aug 11, 2025
3c74ed9
feat(`cheatcodes`): include `SlotInfo` in SlotStateDiff
yash-atreya Aug 12, 2025
85a225b
cleanup + identify slots of static arrays
yash-atreya Aug 12, 2025
a4228ef
nits
yash-atreya Aug 12, 2025
1853d8e
nit
yash-atreya Aug 12, 2025
5b8b95b
nits
yash-atreya Aug 12, 2025
e619e0d
test + nits
yash-atreya Aug 12, 2025
caf21b9
docs
yash-atreya Aug 12, 2025
e9630a0
handle 2d arrays
yash-atreya Aug 12, 2025
b44b410
use DynSolType
yash-atreya Aug 12, 2025
e78b087
feat: decode storage values
yash-atreya Aug 13, 2025
ebcef04
doc nit
yash-atreya Aug 13, 2025
65db931
skip decoded serialization if none
yash-atreya Aug 13, 2025
7d3c87b
nit
yash-atreya Aug 13, 2025
fe8b59d
Merge branch 'master' into yash/state-diff-storage-labels
yash-atreya Aug 13, 2025
e63e817
fmt
yash-atreya Aug 13, 2025
f9ffcea
fix
yash-atreya Aug 13, 2025
05d4a38
Merge branch 'yash/state-diff-storage-labels' into yash/decode-label-…
yash-atreya Aug 13, 2025
f25c85d
fix
yash-atreya Aug 13, 2025
055797d
fix
yash-atreya Aug 13, 2025
0882694
Merge branch 'yash/state-diff-storage-labels' into yash/decode-label-…
yash-atreya Aug 13, 2025
0026752
resolve conflicts
Aug 18, 2025
2d62559
feat(cheatcodes): decode structs in state diff output
Aug 18, 2025
0db68f7
fix
yash-atreya Aug 18, 2025
927e756
while decode
yash-atreya Aug 19, 2025
e487e82
fix: show only decoded in plaintext / display output + test
yash-atreya Aug 19, 2025
a28c41c
Merge branch 'master' into yash/decode-label-state-diffs
yash-atreya Aug 19, 2025
1482e30
Merge branch 'yash/decode-label-state-diffs' into yash/decode-structs…
yash-atreya Aug 19, 2025
aa36022
feat: format slots to only significant bits in vm.getStateDiff output
yash-atreya Aug 19, 2025
3cfefdc
encode_prefixed
yash-atreya Aug 19, 2025
850dc6c
nit
yash-atreya Aug 19, 2025
8f9f82f
Merge branch 'yash/decode-label-state-diffs' into yash/decode-structs…
yash-atreya Aug 19, 2025
6e3120b
chore: add @onbjerg to `CODEOWNERS` (#11343)
zerosnacks Aug 19, 2025
0ea49a2
resolve conflicts
yash-atreya Aug 20, 2025
a11c347
fix: disable tx gas limit cap (#11347)
mattsse Aug 19, 2025
f9964de
chore(deps): bump all dependencies (#11349)
DaniPopes Aug 19, 2025
6ebfaf3
chore: use get_or_calculate_hash better (#11350)
DaniPopes Aug 19, 2025
cc09011
resolve more conflicts
yash-atreya Aug 20, 2025
9c78c61
fix(lint): 'unwrapped-modifier-logic' incorrectly marked with `Severi…
srdtrk Aug 19, 2025
cbbbdb6
feat: identify and decode nested structs
yash-atreya Aug 20, 2025
156bc80
resolve conflicts
yash-atreya Aug 20, 2025
55249e8
cleanup
yash-atreya Aug 20, 2025
1b1f626
decode structs and members recursively
yash-atreya Aug 20, 2025
499abf6
cleanup
yash-atreya Aug 20, 2025
a1de1dc
doc fix
yash-atreya Aug 20, 2025
bb255b3
Merge branch 'master' into yash/decode-structs-in-state-diffs
0xrusowsky Aug 20, 2025
c9d1cef
feat(cheatcodes): decode mappings in state diffs (#11381)
yash-atreya Aug 25, 2025
7460161
dedup assertContains test util
yash-atreya Aug 25, 2025
befdf4b
Merge branch 'master' into yash/decode-structs-in-state-diffs
yash-atreya Aug 25, 2025
5cd7b12
fix
yash-atreya Aug 25, 2025
398c5ab
Update crates/common/src/slot_identifier.rs
grandizzy Aug 26, 2025
8be596d
Review changes: simplify get or insert, use common fmt
grandizzy Aug 26, 2025
b850bc7
alloy-dyn-abi.workspace
grandizzy Aug 26, 2025
3a88f5d
identify slot types using `SlotIdentifier`
yash-atreya Aug 28, 2025
70450fe
Merge branch 'master' into yash/slot-identifier-invariant
yash-atreya Aug 28, 2025
5526293
clippy
yash-atreya Aug 28, 2025
19dd55e
feat(`invariants`): record mapping keys and slots to identify their t…
yash-atreya Aug 28, 2025
62415bc
fix
yash-atreya Aug 29, 2025
b88e836
only insert if value decodes
yash-atreya Aug 29, 2025
a567c13
tracing::info logs
yash-atreya Sep 1, 2025
fb20c5d
remove logs
yash-atreya Sep 1, 2025
b6886a3
nit
yash-atreya Sep 1, 2025
96c0fc8
rm
yash-atreya Sep 1, 2025
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
1 change: 1 addition & 0 deletions Cargo.lock

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

32 changes: 1 addition & 31 deletions crates/cheatcodes/src/evm/mapping.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
use crate::{Cheatcode, Cheatcodes, Result, Vm::*};
use alloy_primitives::{Address, B256, U256, keccak256, map::AddressHashMap};
use alloy_primitives::{Address, B256};
use alloy_sol_types::SolValue;
use foundry_common::mapping_slots::MappingSlots;
use revm::{
bytecode::opcode,
interpreter::{Interpreter, interpreter_types::Jumps},
};

impl Cheatcode for startMappingRecordingCall {
fn apply(&self, state: &mut Cheatcodes) -> Result {
Expand Down Expand Up @@ -74,29 +70,3 @@ fn slot_child<'a>(
) -> Option<&'a Vec<B256>> {
mapping_slot(state, target)?.children.get(slot)
}

#[cold]
pub(crate) fn step(mapping_slots: &mut AddressHashMap<MappingSlots>, interpreter: &Interpreter) {
match interpreter.bytecode.opcode() {
opcode::KECCAK256 => {
if interpreter.stack.peek(1) == Ok(U256::from(0x40)) {
let address = interpreter.input.target_address;
let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to();
let data = interpreter.memory.slice_len(offset, 0x40);
let low = B256::from_slice(&data[..0x20]);
let high = B256::from_slice(&data[0x20..]);
let result = keccak256(&*data);

mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high));
}
}
opcode::SSTORE => {
if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.input.target_address)
&& let Ok(slot) = interpreter.stack.peek(0)
{
mapping_slots.insert(slot.into());
}
}
_ => {}
}
}
8 changes: 5 additions & 3 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result,
Vm::{self, AccountAccess},
evm::{
DealRecord, GasRecord, RecordAccess, mapping,
DealRecord, GasRecord, RecordAccess,
mock::{MockCallDataContext, MockCallReturnData},
prank::Prank,
},
Expand Down Expand Up @@ -33,7 +33,9 @@ use alloy_rpc_types::{
};
use alloy_sol_types::{SolCall, SolInterface, SolValue};
use foundry_common::{
SELECTOR_LEN, TransactionMaybeSigned, evm::Breakpoints, mapping_slots::MappingSlots,
SELECTOR_LEN, TransactionMaybeSigned,
evm::Breakpoints,
mapping_slots::{MappingSlots, step as mapping_step},
};
use foundry_evm_core::{
InspectorExt,
Expand Down Expand Up @@ -1103,7 +1105,7 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {

// `startMappingRecording`: record SSTORE and KECCAK256.
if let Some(mapping_slots) = &mut self.mapping_slots {
mapping::step(mapping_slots, interpreter);
mapping_step(mapping_slots, interpreter);
}

// `snapshotGas*`: take a snapshot of the current gas.
Expand Down
2 changes: 2 additions & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ alloy-transport.workspace = true
alloy-consensus = { workspace = true, features = ["k256"] }
alloy-network.workspace = true

revm.workspace = true

solar.workspace = true

tower.workspace = true
Expand Down
36 changes: 35 additions & 1 deletion crates/common/src/mapping_slots.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use alloy_primitives::{B256, map::B256HashMap};
use alloy_primitives::{
B256, U256, keccak256,
map::{AddressHashMap, B256HashMap},
};
use revm::{
bytecode::opcode,
interpreter::{Interpreter, interpreter_types::Jumps},
};

/// Recorded mapping slots.
#[derive(Clone, Debug, Default)]
Expand Down Expand Up @@ -36,3 +43,30 @@ impl MappingSlots {
}
}
}

/// Function to be used in Inspector::step to record mapping slots and keys
#[cold]
pub fn step(mapping_slots: &mut AddressHashMap<MappingSlots>, interpreter: &Interpreter) {
match interpreter.bytecode.opcode() {
opcode::KECCAK256 => {
if interpreter.stack.peek(1) == Ok(U256::from(0x40)) {
let address = interpreter.input.target_address;
let offset = interpreter.stack.peek(0).expect("stack size > 1").saturating_to();
let data = interpreter.memory.slice_len(offset, 0x40);
let low = B256::from_slice(&data[..0x20]);
let high = B256::from_slice(&data[0x20..]);
let result = keccak256(&*data);

mapping_slots.entry(address).or_default().seen_sha3.insert(result, (low, high));
}
}
opcode::SSTORE => {
if let Some(mapping_slots) = mapping_slots.get_mut(&interpreter.input.target_address)
&& let Ok(slot) = interpreter.stack.peek(0)
{
mapping_slots.insert(slot.into());
}
}
_ => {}
}
}
3 changes: 2 additions & 1 deletion crates/common/src/slot_identifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ impl SlotIdentifier {
///
/// It can also identify whether a slot belongs to a mapping if provided with [`MappingSlots`].
pub fn identify(&self, slot: &B256, mapping_slots: Option<&MappingSlots>) -> Option<SlotInfo> {
trace!(?slot, "identifying slot");
let slot_u256 = U256::from_be_bytes(slot.0);
let slot_str = slot_u256.to_string();

Expand Down Expand Up @@ -629,7 +630,7 @@ impl SlotIdentifier {

// Check if the value is another mapping (nested case)
if let Some(value_storage_type) = self.storage_layout.types.get(value_type_ref) {
if value_storage_type.encoding == "mapping" {
if value_storage_type.encoding == ENCODING_MAPPING {
// Recursively resolve the nested mapping
let (nested_keys, final_value, _) = self.resolve_mapping_type(value_type_ref)?;
key_types.extend(nested_keys);
Expand Down
14 changes: 13 additions & 1 deletion crates/evm/evm/src/executors/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ use crate::{
executors::{Executor, RawCallResult},
inspectors::Fuzzer,
};
use alloy_primitives::{Address, Bytes, FixedBytes, Selector, U256, map::HashMap};
use alloy_primitives::{
Address, Bytes, FixedBytes, Selector, U256,
map::{AddressMap, HashMap},
};
use alloy_sol_types::{SolCall, sol};
use eyre::{ContextCompat, Result, eyre};
use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact};
Expand Down Expand Up @@ -598,6 +601,15 @@ impl<'a> InvariantExecutor<'a> {
));
}

// If any of the targeted contracts have the storage layout enabled then we can sample
// mapping values. To accomplish, we need to record the mapping storage slots and keys.
let fuzz_state =
if targeted_contracts.targets.lock().iter().any(|(_, t)| t.storage_layout.is_some()) {
fuzz_state.with_mapping_slots(AddressMap::default())
} else {
fuzz_state
};

self.executor.inspector_mut().set_fuzzer(Fuzzer {
call_generator,
fuzz_state: fuzz_state.clone(),
Expand Down
4 changes: 4 additions & 0 deletions crates/evm/fuzz/src/inspector.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState};
use foundry_common::mapping_slots::step as mapping_step;
use revm::{
Inspector,
context::{ContextTr, Transaction},
Expand Down Expand Up @@ -26,6 +27,9 @@ where
// We only collect `stack` and `memory` data before and after calls.
if self.collect {
self.collect_data(interp);
if let Some(mapping_slots) = &mut self.fuzz_state.mapping_slots {
mapping_step(mapping_slots, interp);
}
}
}

Expand Down
Loading
Loading