@@ -208,11 +208,31 @@ pub struct WorkerCorpus {
208208}
209209
210210impl WorkerCorpus {
211+ /// Helper to check if a tx can be replayed.
212+ fn can_replay_tx (
213+ tx : & BasicTxDetails ,
214+ fuzzed_function : Option < & Function > ,
215+ fuzzed_contracts : Option < & FuzzRunIdentifiedContracts > ,
216+ ) -> bool {
217+ fuzzed_contracts. is_some_and ( |contracts| contracts. targets . lock ( ) . can_replay ( tx) )
218+ || fuzzed_function. is_some_and ( |function| {
219+ tx. call_details
220+ . calldata
221+ . get ( ..4 )
222+ . is_some_and ( |selector| function. selector ( ) == selector)
223+ } )
224+ }
225+
226+ /// Returns the file extension based on gzip config.
227+ fn file_extension ( & self ) -> & str {
228+ if self . config . corpus_gzip { ".json.gz" } else { JSON_EXTENSION }
229+ }
230+
211231 pub fn new (
212232 id : u32 ,
213233 config : FuzzCorpusConfig ,
214234 tx_generator : BoxedStrategy < BasicTxDetails > ,
215- // Only required by master worker (id = 0) to replay existing corpus
235+ // Only required by master worker (id = 0) to replay existing corpus.
216236 executor : Option < & Executor > ,
217237 fuzzed_function : Option < & Function > ,
218238 fuzzed_contracts : Option < & FuzzRunIdentifiedContracts > ,
@@ -227,24 +247,17 @@ impl WorkerCorpus {
227247 ]
228248 . boxed ( ) ;
229249
230- let worker_dir = if let Some ( corpus_dir) = & config. corpus_dir {
231- // Create the necessary directories for the worker
250+ let worker_dir = config. corpus_dir . as_ref ( ) . map ( |corpus_dir| {
232251 let worker_dir = corpus_dir. join ( format ! ( "{WORKER}{id}" ) ) ;
233- let worker_corpus = & worker_dir. join ( CORPUS_DIR ) ;
234- let sync_dir = & worker_dir. join ( SYNC_DIR ) ;
235-
236- if !worker_corpus. is_dir ( ) {
237- foundry_common:: fs:: create_dir_all ( worker_corpus) ?;
238- }
252+ let worker_corpus = worker_dir. join ( CORPUS_DIR ) ;
253+ let sync_dir = worker_dir. join ( SYNC_DIR ) ;
239254
240- if !sync_dir . is_dir ( ) {
241- foundry_common:: fs:: create_dir_all ( sync_dir ) ? ;
242- }
255+ // Create the necessary directories for the worker.
256+ let _ = foundry_common:: fs:: create_dir_all ( & worker_corpus ) ;
257+ let _ = foundry_common :: fs :: create_dir_all ( & sync_dir ) ;
243258
244- Some ( worker_dir)
245- } else {
246- None
247- } ;
259+ worker_dir
260+ } ) ;
248261
249262 let mut in_memory_corpus = vec ! [ ] ;
250263 let mut history_map = vec ! [ 0u8 ; COVERAGE_MAP_SIZE ] ;
@@ -256,16 +269,6 @@ impl WorkerCorpus {
256269 {
257270 // Master worker loads the initial corpus, if it exists.
258271 // Then, [distribute]s it to workers.
259- let can_replay_tx = |tx : & BasicTxDetails | -> bool {
260- fuzzed_contracts. is_some_and ( |contracts| contracts. targets . lock ( ) . can_replay ( tx) )
261- || fuzzed_function. is_some_and ( |function| {
262- tx. call_details
263- . calldata
264- . get ( ..4 )
265- . is_some_and ( |selector| function. selector ( ) == selector)
266- } )
267- } ;
268-
269272 let executor = executor. expect ( "Executor required for master worker" ) ;
270273 ' corpus_replay: for entry in read_corpus_dir ( corpus_dir) {
271274 if entry. is_metadata ( ) {
@@ -278,7 +281,7 @@ impl WorkerCorpus {
278281 // Warm up history map from loaded sequences.
279282 let mut executor = executor. clone ( ) ;
280283 for tx in & tx_seq {
281- if can_replay_tx ( tx) {
284+ if Self :: can_replay_tx ( tx, fuzzed_function , fuzzed_contracts ) {
282285 let mut call_result = execute_tx ( & mut executor, tx) ?;
283286 let ( new_coverage, is_edge) =
284287 call_result. merge_edge_coverage ( & mut history_map) ;
@@ -373,19 +376,14 @@ impl WorkerCorpus {
373376 let corpus = CorpusEntry :: from_tx_seq ( inputs) ;
374377 let corpus_uuid = corpus. uuid ;
375378 let timestamp = corpus. timestamp ;
379+ let ext = self . file_extension ( ) ;
380+ let file_path = worker_corpus. join ( format ! ( "{corpus_uuid}-{timestamp}{ext}" ) ) ;
381+
376382 // Persist to disk if corpus dir is configured.
377383 let write_result = if self . config . corpus_gzip {
378- foundry_common:: fs:: write_json_gzip_file (
379- worker_corpus
380- . join ( format ! ( "{corpus_uuid}-{timestamp}{JSON_EXTENSION}.gz" ) )
381- . as_path ( ) ,
382- & corpus. tx_seq ,
383- )
384+ foundry_common:: fs:: write_json_gzip_file ( & file_path, & corpus. tx_seq )
384385 } else {
385- foundry_common:: fs:: write_json_file (
386- worker_corpus. join ( format ! ( "{corpus_uuid}-{timestamp}{JSON_EXTENSION}" ) ) . as_path ( ) ,
387- & corpus. tx_seq ,
388- )
386+ foundry_common:: fs:: write_json_file ( & file_path, & corpus. tx_seq )
389387 } ;
390388
391389 if let Err ( err) = write_result {
@@ -707,18 +705,17 @@ impl WorkerCorpus {
707705 /// Exports the new corpus entries to the master worker's (id = 0) sync dir.
708706 #[ tracing:: instrument( skip_all, fields( worker_id = self . id) ) ]
709707 fn export ( & self ) -> eyre:: Result < ( ) > {
710- // Early return if no new entries or corpus dir not configured
711- if self . new_entry_indices . is_empty ( ) || self . worker_dir . is_none ( ) {
708+ // Master doesn't export (it only receives from others).
709+ if self . id == 0 {
712710 return Ok ( ( ) ) ;
713711 }
714712
715- let worker_dir = self . worker_dir . as_ref ( ) . unwrap ( ) ;
716-
717- // Master doesn't export (it only receives from others)
718- if self . id == 0 {
713+ // Early return if no new entries or corpus dir not configured.
714+ if self . new_entry_indices . is_empty ( ) || self . worker_dir . is_none ( ) {
719715 return Ok ( ( ) ) ;
720716 }
721717
718+ let worker_dir = self . worker_dir . as_ref ( ) . unwrap ( ) ;
722719 let Some ( master_sync_dir) = self
723720 . config
724721 . corpus_dir
@@ -731,14 +728,9 @@ impl WorkerCorpus {
731728 let mut exported = 0 ;
732729 let corpus_dir = worker_dir. join ( CORPUS_DIR ) ;
733730
731+ let ext = self . file_extension ( ) ;
734732 for & index in & self . new_entry_indices {
735733 if let Some ( entry) = self . in_memory_corpus . get ( index) {
736- // TODO(dani): with_added_extension
737- let ext = if self . config . corpus_gzip {
738- & format ! ( "{JSON_EXTENSION}.gz" )
739- } else {
740- JSON_EXTENSION
741- } ;
742734 let file_name = format ! ( "{}-{}{ext}" , entry. uuid, entry. timestamp) ;
743735 let file_path = corpus_dir. join ( & file_name) ;
744736 let sync_path = master_sync_dir. join ( & file_name) ;
@@ -798,17 +790,6 @@ impl WorkerCorpus {
798790 return Ok ( ( ) ) ;
799791 } ;
800792
801- // Helper to check if tx can be replayed
802- let can_replay_tx = |tx : & BasicTxDetails | -> bool {
803- fuzzed_contracts. is_some_and ( |contracts| contracts. targets . lock ( ) . can_replay ( tx) )
804- || fuzzed_function. is_some_and ( |function| {
805- tx. call_details
806- . calldata
807- . get ( ..4 )
808- . is_some_and ( |selector| function. selector ( ) == selector)
809- } )
810- } ;
811-
812793 let sync_dir = worker_dir. join ( SYNC_DIR ) ;
813794 let corpus_dir = worker_dir. join ( CORPUS_DIR ) ;
814795
@@ -817,7 +798,7 @@ impl WorkerCorpus {
817798 if !tx_seq. is_empty ( ) {
818799 let mut new_coverage_on_sync = false ;
819800 for tx in & tx_seq {
820- if can_replay_tx ( tx) {
801+ if Self :: can_replay_tx ( tx, fuzzed_function , fuzzed_contracts ) {
821802 let mut call_result = execute_tx ( & mut executor, tx) ?;
822803
823804 // Check if this provides new coverage
@@ -845,12 +826,7 @@ impl WorkerCorpus {
845826
846827 if new_coverage_on_sync {
847828 let corpus_entry = CorpusEntry :: from_tx_seq ( & tx_seq) ;
848- let ext = self
849- . config
850- . corpus_gzip
851- . then_some ( format ! ( "{JSON_EXTENSION}.gz" ) )
852- . unwrap_or ( JSON_EXTENSION . to_string ( ) ) ;
853-
829+ let ext = self . file_extension ( ) ;
854830 let file_name =
855831 format ! ( "{}-{}{ext}" , corpus_entry. uuid, corpus_entry. timestamp) ;
856832
@@ -1032,8 +1008,8 @@ fn read_corpus_dir(path: &Path) -> impl Iterator<Item = CorpusDirEntry> {
10321008 return None ;
10331009 } ;
10341010
1035- if let Ok ( ( uuid , timestamp) ) = parse_corpus_filename ( name) {
1036- Some ( CorpusDirEntry { path, uuid , timestamp } )
1011+ if let Ok ( ( _ , timestamp) ) = parse_corpus_filename ( name) {
1012+ Some ( CorpusDirEntry { path, timestamp } )
10371013 } else {
10381014 trace ! ( target: "corpus" , ?path, "failed to parse corpus filename" ) ;
10391015 None
@@ -1050,7 +1026,6 @@ fn read_corpus_dir(path: &Path) -> impl Iterator<Item = CorpusDirEntry> {
10501026
10511027struct CorpusDirEntry {
10521028 path : PathBuf ,
1053- uuid : Uuid ,
10541029 timestamp : u64 ,
10551030}
10561031
@@ -1077,9 +1052,11 @@ impl CorpusDirEntry {
10771052fn parse_corpus_filename ( name : & str ) -> eyre:: Result < ( Uuid , u64 ) > {
10781053 let name = name. trim_end_matches ( ".gz" ) . trim_end_matches ( ".json" ) . trim_end_matches ( ".metadata" ) ;
10791054
1080- let parts = name. rsplitn ( 2 , "-" ) . collect :: < Vec < _ > > ( ) ;
1081- let uuid = Uuid :: parse_str ( parts[ 0 ] ) ?;
1082- let timestamp = parts[ 1 ] . parse ( ) ?;
1055+ let ( uuid_str, timestamp_str) =
1056+ name. rsplit_once ( '-' ) . ok_or_else ( || eyre ! ( "invalid corpus filename format: {name}" ) ) ?;
1057+
1058+ let uuid = Uuid :: parse_str ( uuid_str) ?;
1059+ let timestamp = timestamp_str. parse ( ) ?;
10831060
10841061 Ok ( ( uuid, timestamp) )
10851062}
0 commit comments