|
5 | 5 |
|
6 | 6 | use crate::mapping_slots::MappingSlots; |
7 | 7 | 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}; |
9 | 9 | use foundry_common_fmt::format_token_raw; |
10 | 10 | use foundry_compilers::artifacts::{Storage, StorageLayout, StorageType}; |
11 | 11 | use serde::Serialize; |
@@ -229,7 +229,7 @@ impl SlotInfo { |
229 | 229 | // Long bytes/string - populate members |
230 | 230 | let length: U256 = U256::from_be_bytes(base_value.0) >> 1; |
231 | 231 | 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); |
233 | 233 |
|
234 | 234 | let mut members = Vec::new(); |
235 | 235 | let mut full_data = Vec::with_capacity(length.to::<usize>()); |
@@ -466,8 +466,40 @@ impl SlotIdentifier { |
466 | 466 | && let Some(mapping_slots) = mapping_slots |
467 | 467 | { |
468 | 468 | 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 | + } |
471 | 503 | } |
472 | 504 | } |
473 | 505 |
|
@@ -822,15 +854,28 @@ impl SlotIdentifier { |
822 | 854 | /// # Arguments |
823 | 855 | /// * `slot` - The accessed slot being identified |
824 | 856 | /// * `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> { |
826 | 865 | for storage in &self.storage_layout.storage { |
827 | 866 | // Get the type information and base slot |
828 | | - let base_slot = U256::from_str(&storage.slot).ok()?; |
| 867 | + |
829 | 868 | let Some(storage_type) = self.storage_layout.types.get(&storage.storage_type) else { |
830 | 869 | continue; |
831 | 870 | }; |
832 | 871 |
|
| 872 | + // Skip if not bytes or string encoding |
| 873 | + if storage_type.encoding != ENCODING_BYTES { |
| 874 | + continue; |
| 875 | + } |
| 876 | + |
833 | 877 | // Check if this is the main slot |
| 878 | + let base_slot = U256::from_str(&storage.slot).ok()?; |
834 | 879 | if slot == base_slot { |
835 | 880 | // Parse the type to get the correct DynSolType |
836 | 881 | let dyn_type = if storage_type.label == "string" { |
@@ -860,24 +905,33 @@ impl SlotIdentifier { |
860 | 905 | let data_start = |
861 | 906 | U256::from_be_bytes(alloy_primitives::keccak256(base_slot.to_be_bytes::<32>()).0); |
862 | 907 |
|
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); |
867 | 916 |
|
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 | + } |
881 | 935 | } |
882 | 936 | } |
883 | 937 |
|
|
0 commit comments