@@ -6,6 +6,7 @@ use alloy_primitives::{
66 map:: { AddressIndexSet , B256IndexSet , HashMap } ,
77} ;
88use foundry_common:: ignore_metadata_hash;
9+ use foundry_compilers:: artifacts:: StorageLayout ;
910use foundry_config:: FuzzDictionaryConfig ;
1011use foundry_evm_core:: utils:: StateChangeset ;
1112use parking_lot:: { RawRwLock , RwLock , lock_api:: RwLockReadGuard } ;
@@ -72,8 +73,10 @@ impl EvmFuzzState {
7273 let ( target_abi, target_function) = targets. fuzzed_artifacts ( tx) ;
7374 dict. insert_logs_values ( target_abi, logs, run_depth) ;
7475 dict. insert_result_values ( target_function, result, run_depth) ;
76+ // Get storage layouts for contracts in the state changeset
77+ let storage_layouts = targets. get_storage_layouts ( ) ;
78+ dict. insert_new_state_values ( state_changeset, & storage_layouts) ;
7579 }
76- dict. insert_new_state_values ( state_changeset) ;
7780 }
7881
7982 /// Removes all newly added entries from the dictionary.
@@ -151,7 +154,7 @@ impl FuzzDictionary {
151154 // Sort storage values before inserting to ensure deterministic dictionary.
152155 let values = account. storage . iter ( ) . collect :: < BTreeMap < _ , _ > > ( ) ;
153156 for ( slot, value) in values {
154- self . insert_storage_value ( slot, value) ;
157+ self . insert_storage_value ( slot, value, None ) ;
155158 }
156159 }
157160 }
@@ -226,16 +229,21 @@ impl FuzzDictionary {
226229
227230 /// Insert values from call state changeset into fuzz dictionary.
228231 /// These values are removed at the end of current run.
229- fn insert_new_state_values ( & mut self , state_changeset : & StateChangeset ) {
232+ fn insert_new_state_values (
233+ & mut self ,
234+ state_changeset : & StateChangeset ,
235+ storage_layouts : & HashMap < Address , Arc < StorageLayout > > ,
236+ ) {
230237 for ( address, account) in state_changeset {
231238 // Insert basic account information.
232239 self . insert_value ( address. into_word ( ) ) ;
233240 // Insert push bytes.
234241 self . insert_push_bytes_values ( address, & account. info ) ;
235242 // Insert storage values.
236243 if self . config . include_storage {
244+ let storage_layout = storage_layouts. get ( address) . map ( |arc| arc. as_ref ( ) ) ;
237245 for ( slot, value) in & account. storage {
238- self . insert_storage_value ( slot, & value. present_value ) ;
246+ self . insert_storage_value ( slot, & value. present_value , storage_layout ) ;
239247 }
240248 }
241249 }
@@ -288,19 +296,132 @@ impl FuzzDictionary {
288296 }
289297 }
290298
299+ /// Resolves storage types from a storage layout for a given slot and all mapping types.
300+ /// Returns a tuple of (slot_type, mapping_types) where slot_type is the specific type
301+ /// for the storage slot and mapping_types are all mapping value types found in the layout.
302+ fn resolve_storage_types (
303+ & self ,
304+ storage_layout : Option < & StorageLayout > ,
305+ storage_slot : & U256 ,
306+ ) -> ( Option < DynSolType > , Vec < DynSolType > ) {
307+ let Some ( layout) = storage_layout else {
308+ return ( None , Vec :: new ( ) ) ;
309+ } ;
310+
311+ // Try to determine the type of this specific storage slot
312+ let slot_type =
313+ layout. storage . iter ( ) . find ( |s| s. slot == storage_slot. to_string ( ) ) . and_then (
314+ |storage| {
315+ layout
316+ . types
317+ . get ( & storage. storage_type )
318+ . and_then ( |t| DynSolType :: parse ( & t. label ) . ok ( ) )
319+ } ,
320+ ) ;
321+
322+ // Collect all mapping value types from the layout
323+ let mapping_types = layout
324+ . types
325+ . values ( )
326+ . filter_map ( |type_info| {
327+ if type_info. encoding == "mapping"
328+ && let Some ( t_value) = type_info. value . as_ref ( )
329+ && let Some ( mapping_value) = t_value. strip_prefix ( "t_" )
330+ {
331+ DynSolType :: parse ( mapping_value) . ok ( )
332+ } else {
333+ None
334+ }
335+ } )
336+ . collect ( ) ;
337+
338+ ( slot_type, mapping_types)
339+ }
340+
291341 /// Insert values from single storage slot and storage value into fuzz dictionary.
292342 /// If storage values are newly collected then they are removed at the end of current run.
293- fn insert_storage_value ( & mut self , storage_slot : & U256 , storage_value : & U256 ) {
343+ fn insert_storage_value (
344+ & mut self ,
345+ storage_slot : & U256 ,
346+ storage_value : & U256 ,
347+ storage_layout : Option < & StorageLayout > ,
348+ ) {
349+ // Always insert the slot itself
294350 self . insert_value ( B256 :: from ( * storage_slot) ) ;
295- self . insert_value ( B256 :: from ( * storage_value) ) ;
296- // also add the value below and above the storage value to the dictionary.
297- if * storage_value != U256 :: ZERO {
298- let below_value = storage_value - U256 :: from ( 1 ) ;
299- self . insert_value ( B256 :: from ( below_value) ) ;
351+
352+ let ( slot_type, mapping_types) = self . resolve_storage_types ( storage_layout, storage_slot) ;
353+
354+ if let Some ( sol_type) = slot_type {
355+ self . insert_decoded_storage_value ( sol_type, storage_value) ;
356+ } else if !mapping_types. is_empty ( ) {
357+ self . insert_mapping_storage_values ( mapping_types, storage_value) ;
358+ } else {
359+ // No type information available, insert as raw values (old behavior)
360+ self . insert_value ( B256 :: from ( * storage_value) ) ;
361+ // also add the value below and above the storage value to the dictionary.
362+ if * storage_value != U256 :: ZERO {
363+ let below_value = storage_value - U256 :: from ( 1 ) ;
364+ self . insert_value ( B256 :: from ( below_value) ) ;
365+ }
366+ if * storage_value != U256 :: MAX {
367+ let above_value = storage_value + U256 :: from ( 1 ) ;
368+ self . insert_value ( B256 :: from ( above_value) ) ;
369+ }
300370 }
301- if * storage_value != U256 :: MAX {
302- let above_value = storage_value + U256 :: from ( 1 ) ;
303- self . insert_value ( B256 :: from ( above_value) ) ;
371+ }
372+
373+ /// Insert decoded storage values into the fuzz dictionary.
374+ /// Only simple static type values are inserted as sample values.
375+ /// Complex types (dynamic arrays, structs) are inserted as raw values
376+ fn insert_decoded_storage_value ( & mut self , sol_type : DynSolType , storage_value : & U256 ) {
377+ // Only insert values for types that can be represented as a single word
378+ match & sol_type {
379+ DynSolType :: Address
380+ | DynSolType :: Uint ( _)
381+ | DynSolType :: Int ( _)
382+ | DynSolType :: Bool
383+ | DynSolType :: FixedBytes ( _)
384+ | DynSolType :: Bytes => {
385+ // Insert as a typed sample value
386+ self . sample_values . entry ( sol_type) . or_default ( ) . insert ( B256 :: from ( * storage_value) ) ;
387+ }
388+ _ => {
389+ // For complex types (arrays, mappings, structs), insert as raw value
390+ self . insert_value ( B256 :: from ( * storage_value) ) ;
391+ }
392+ }
393+ }
394+
395+ /// Insert storage values of mapping value types as sample values in the fuzz dictionary.
396+ ///
397+ /// ```solidity
398+ /// mapping(uint256 => address) public myMapping;
399+ /// // `address` is the mapping value type here.
400+ /// // `uint256` is the mapping key type.
401+ /// ```
402+ ///
403+ /// A storage value is inserted if and only if it can be decoded into one of the mapping
404+ /// [`DynSolType`] value types found in the [`StorageLayout`].
405+ ///
406+ /// If decoding fails, the value is inserted as a raw value.
407+ fn insert_mapping_storage_values (
408+ & mut self ,
409+ mapping_types : Vec < DynSolType > ,
410+ storage_value : & U256 ,
411+ ) {
412+ for sol_type in mapping_types {
413+ match sol_type. abi_decode ( storage_value. as_le_slice ( ) ) {
414+ Ok ( _) => {
415+ self . sample_values
416+ . entry ( sol_type)
417+ . or_default ( )
418+ . insert ( B256 :: from ( * storage_value) ) ;
419+ }
420+ Err ( _) => {
421+ // If decoding fails, insert as raw value
422+ self . insert_value ( B256 :: from ( * storage_value) ) ;
423+ }
424+ }
304425 }
305426 }
306427
0 commit comments