Skip to content

Commit 81d100a

Browse files
committed
identify the slots using the length of data in base slot value
1 parent cb9530e commit 81d100a

File tree

2 files changed

+97
-25
lines changed

2 files changed

+97
-25
lines changed

crates/cheatcodes/src/evm.rs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use crate::{
88
use alloy_consensus::TxEnvelope;
99
use alloy_genesis::{Genesis, GenesisAccount};
1010
use alloy_network::eip2718::EIP4844_TX_TYPE_ID;
11-
use alloy_primitives::{Address, B256, U256, hex, map::HashMap};
11+
use alloy_primitives::{
12+
Address, B256, U256, hex,
13+
map::{B256Map, HashMap},
14+
};
1215
use alloy_rlp::Decodable;
1316
use alloy_sol_types::SolValue;
1417
use foundry_common::{
@@ -1410,7 +1413,22 @@ fn get_recorded_state_diffs(ccx: &mut CheatsCtxt) -> BTreeMap<Address, AccountSt
14101413

14111414
let mut slot_info = layout.and_then(|layout| {
14121415
let decoder = SlotIdentifier::new(layout.clone());
1413-
decoder.identify(&storage_access.slot, mapping_slots)
1416+
decoder.identify(&storage_access.slot, mapping_slots).or_else(
1417+
|| {
1418+
// Create a map of new values for bytes/string
1419+
// identification. These values are used to determine
1420+
// the length of the data which helps determine how many
1421+
// slots to search
1422+
let current_base_slot_values = raw_changes_by_slot
1423+
.iter()
1424+
.map(|(slot, (_, new_val))| (*slot, *new_val))
1425+
.collect::<B256Map<_>>();
1426+
decoder.identify_bytes_or_string(
1427+
&storage_access.slot,
1428+
&current_base_slot_values,
1429+
)
1430+
},
1431+
)
14141432
});
14151433

14161434
// Decode values if we have slot info

crates/common/src/slot_identifier.rs

Lines changed: 77 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
use crate::mapping_slots::MappingSlots;
77
use alloy_dyn_abi::{DynSolType, DynSolValue};
8-
use alloy_primitives::{B256, U256, hex, map::B256Map};
8+
use alloy_primitives::{B256, U256, hex, keccak256, map::B256Map};
99
use foundry_common_fmt::format_token_raw;
1010
use foundry_compilers::artifacts::{Storage, StorageLayout, StorageType};
1111
use serde::Serialize;
@@ -229,7 +229,7 @@ impl SlotInfo {
229229
// Long bytes/string - populate members
230230
let length: U256 = U256::from_be_bytes(base_value.0) >> 1;
231231
let num_slots = length.to::<usize>().div_ceil(32).min(256);
232-
let data_start = U256::from_be_bytes(alloy_primitives::keccak256(base_slot.0).0);
232+
let data_start = U256::from_be_bytes(keccak256(base_slot.0).0);
233233

234234
let mut members = Vec::new();
235235
let mut full_data = Vec::with_capacity(length.to::<usize>());
@@ -466,8 +466,40 @@ impl SlotIdentifier {
466466
&& let Some(mapping_slots) = mapping_slots
467467
{
468468
return self.handle_mapping(storage, storage_type, slot, &slot_str, mapping_slots);
469-
} else if storage_type.encoding == ENCODING_BYTES {
470-
return self.handle_bytes_string(slot_u256, &slot_str);
469+
}
470+
}
471+
472+
None
473+
}
474+
475+
/// Identifies a bytes or string storage slot by checking all bytes/string variables
476+
/// in the storage layout and using their base slot values from the provided storage changes.
477+
///
478+
/// # Arguments
479+
/// * `slot` - The slot being identified
480+
/// * `storage_values` - Map of storage slots to their current values
481+
pub fn identify_bytes_or_string(
482+
&self,
483+
slot: &B256,
484+
storage_values: &B256Map<B256>,
485+
) -> Option<SlotInfo> {
486+
let slot_u256 = U256::from_be_bytes(slot.0);
487+
let slot_str = slot_u256.to_string();
488+
489+
// Search through all bytes/string variables in the storage layout
490+
for storage in &self.storage_layout.storage {
491+
if let Some(storage_type) = self.storage_layout.types.get(&storage.storage_type)
492+
&& storage_type.encoding == ENCODING_BYTES
493+
{
494+
let Some(base_slot) = U256::from_str(&storage.slot).map(B256::from).ok() else {
495+
continue;
496+
};
497+
// Get the base slot value from storage_values
498+
if let Some(base_value) = storage_values.get(&base_slot)
499+
&& let Some(info) = self.hanlde_bytes_string(slot_u256, &slot_str, base_value)
500+
{
501+
return Some(info);
502+
}
471503
}
472504
}
473505

@@ -822,15 +854,28 @@ impl SlotIdentifier {
822854
/// # Arguments
823855
/// * `slot` - The accessed slot being identified
824856
/// * `slot_str` - String representation of the slot for output
825-
fn handle_bytes_string(&self, slot: U256, slot_str: &str) -> Option<SlotInfo> {
857+
/// * `base_slot_value` - The value at the base slot (used to determine length for long
858+
/// bytes/strings)
859+
fn hanlde_bytes_string(
860+
&self,
861+
slot: U256,
862+
slot_str: &str,
863+
base_slot_value: &B256,
864+
) -> Option<SlotInfo> {
826865
for storage in &self.storage_layout.storage {
827866
// Get the type information and base slot
828-
let base_slot = U256::from_str(&storage.slot).ok()?;
867+
829868
let Some(storage_type) = self.storage_layout.types.get(&storage.storage_type) else {
830869
continue;
831870
};
832871

872+
// Skip if not bytes or string encoding
873+
if storage_type.encoding != ENCODING_BYTES {
874+
continue;
875+
}
876+
833877
// Check if this is the main slot
878+
let base_slot = U256::from_str(&storage.slot).ok()?;
834879
if slot == base_slot {
835880
// Parse the type to get the correct DynSolType
836881
let dyn_type = if storage_type.label == "string" {
@@ -860,24 +905,33 @@ impl SlotIdentifier {
860905
let data_start =
861906
U256::from_be_bytes(alloy_primitives::keccak256(base_slot.to_be_bytes::<32>()).0);
862907

863-
// Check if our slot could be in this data region
864-
// We check up to 1000 slots (arbitrary limit for safety)
865-
if slot >= data_start && slot < data_start + U256::from(1000) {
866-
let slot_index = (slot - data_start).to::<usize>();
908+
// Get the length from the base slot value to calculate exact number of slots
909+
// For long bytes/strings, the length is stored as (length * 2 + 1) in the base slot
910+
let length_byte = base_slot_value.0[31];
911+
if length_byte & 1 == 1 {
912+
// It's a long bytes/string
913+
let length = U256::from_be_bytes(base_slot_value.0) >> 1;
914+
// Calculate number of slots needed (round up)
915+
let num_slots = (length + U256::from(31)) / U256::from(32);
867916

868-
return Some(SlotInfo {
869-
label: format!("{}[{}]", storage.label, slot_index),
870-
slot_type: StorageTypeInfo {
871-
label: storage_type.label.clone(), /* Keep original type
872-
* (string/bytes) */
873-
dyn_sol_type: DynSolType::FixedBytes(32),
874-
},
875-
offset: 0,
876-
slot: slot_str.to_string(),
877-
members: None,
878-
decoded: None,
879-
keys: None,
880-
});
917+
// Check if our slot is within the data region
918+
if slot >= data_start && slot < data_start + num_slots {
919+
let slot_index = (slot - data_start).to::<usize>();
920+
921+
return Some(SlotInfo {
922+
label: format!("{}[{}]", storage.label, slot_index),
923+
slot_type: StorageTypeInfo {
924+
label: storage_type.label.clone(),
925+
// Type is assigned as FixedBytes(32) for data slots
926+
dyn_sol_type: DynSolType::FixedBytes(32),
927+
},
928+
offset: 0,
929+
slot: slot_str.to_string(),
930+
members: None,
931+
decoded: None,
932+
keys: None,
933+
});
934+
}
881935
}
882936
}
883937

0 commit comments

Comments
 (0)