@@ -6,6 +6,7 @@ use alloy_primitives::{
6
6
map:: { AddressIndexSet , B256IndexSet , HashMap } ,
7
7
} ;
8
8
use foundry_common:: ignore_metadata_hash;
9
+ use foundry_compilers:: artifacts:: StorageLayout ;
9
10
use foundry_config:: FuzzDictionaryConfig ;
10
11
use foundry_evm_core:: utils:: StateChangeset ;
11
12
use parking_lot:: { RawRwLock , RwLock , lock_api:: RwLockReadGuard } ;
@@ -72,8 +73,10 @@ impl EvmFuzzState {
72
73
let ( target_abi, target_function) = targets. fuzzed_artifacts ( tx) ;
73
74
dict. insert_logs_values ( target_abi, logs, run_depth) ;
74
75
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) ;
75
79
}
76
- dict. insert_new_state_values ( state_changeset) ;
77
80
}
78
81
79
82
/// Removes all newly added entries from the dictionary.
@@ -151,7 +154,7 @@ impl FuzzDictionary {
151
154
// Sort storage values before inserting to ensure deterministic dictionary.
152
155
let values = account. storage . iter ( ) . collect :: < BTreeMap < _ , _ > > ( ) ;
153
156
for ( slot, value) in values {
154
- self . insert_storage_value ( slot, value) ;
157
+ self . insert_storage_value ( slot, value, None ) ;
155
158
}
156
159
}
157
160
}
@@ -226,16 +229,21 @@ impl FuzzDictionary {
226
229
227
230
/// Insert values from call state changeset into fuzz dictionary.
228
231
/// 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
+ ) {
230
237
for ( address, account) in state_changeset {
231
238
// Insert basic account information.
232
239
self . insert_value ( address. into_word ( ) ) ;
233
240
// Insert push bytes.
234
241
self . insert_push_bytes_values ( address, & account. info ) ;
235
242
// Insert storage values.
236
243
if self . config . include_storage {
244
+ let storage_layout = storage_layouts. get ( address) . map ( |arc| arc. as_ref ( ) ) ;
237
245
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 ) ;
239
247
}
240
248
}
241
249
}
@@ -288,19 +296,132 @@ impl FuzzDictionary {
288
296
}
289
297
}
290
298
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
+
291
341
/// Insert values from single storage slot and storage value into fuzz dictionary.
292
342
/// 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
294
350
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
+ }
300
370
}
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
+ }
304
425
}
305
426
}
306
427
0 commit comments