11use crate :: executors:: {
22 DURATION_BETWEEN_METRICS_REPORT , EarlyExit , Executor , FuzzTestTimer , RawCallResult ,
33 corpus:: { GlobalCorpusMetrics , WorkerCorpus } ,
4+ sequence:: replay_tx,
45} ;
56use alloy_dyn_abi:: JsonAbiExt ;
67use alloy_json_abi:: Function ;
@@ -18,17 +19,15 @@ use foundry_evm_coverage::HitMaps;
1819use foundry_evm_fuzz:: {
1920 BaseCounterExample , BasicTxDetails , CallDetails , CounterExample , FuzzCase , FuzzError ,
2021 FuzzFixtures , FuzzRunMetadata , FuzzTestResult ,
21- strategies:: { EvmFuzzState , fuzz_calldata , fuzz_calldata_from_state } ,
22+ strategies:: { EvmFuzzState , TxGenerator } ,
2223} ;
2324use foundry_evm_traces:: SparsedTraceArena ;
2425use indicatif:: ProgressBar ;
25- use proptest:: {
26- strategy:: Strategy ,
27- test_runner:: { RngAlgorithm , TestCaseError , TestRng , TestRunner } ,
28- } ;
26+ use proptest:: test_runner:: { RngAlgorithm , TestCaseError , TestRng , TestRunner } ;
2927use rayon:: iter:: { IntoParallelIterator , ParallelIterator } ;
3028use serde_json:: json;
3129use std:: {
30+ ops:: ControlFlow ,
3231 sync:: {
3332 Arc , OnceLock ,
3433 atomic:: { AtomicU32 , Ordering } ,
@@ -260,69 +259,70 @@ impl<FEN: FoundryEvmNetwork> FuzzedExecutor<FEN> {
260259 fn single_fuzz (
261260 & self ,
262261 executor : & Executor < FEN > ,
263- address : Address ,
264- calldata : Bytes ,
262+ tx : BasicTxDetails ,
265263 coverage_metrics : & mut WorkerCorpus ,
266264 ) -> Result < FuzzOutcome < FEN > , TestCaseError > {
267- let mut call = executor
268- . call_raw ( self . sender , address, calldata. clone ( ) , U256 :: ZERO )
269- . map_err ( |e| TestCaseError :: fail ( e. to_string ( ) ) ) ?;
270- let cmp_values = call. evm_cmp_values . take ( ) . unwrap_or_default ( ) ;
271- let new_coverage = coverage_metrics. merge_edge_coverage ( & mut call) ;
272- coverage_metrics. process_inputs (
273- & [ BasicTxDetails {
274- warp : None ,
275- roll : None ,
276- sender : self . sender ,
277- call_details : CallDetails {
278- target : address,
279- calldata : calldata. clone ( ) ,
280- value : None ,
281- } ,
282- } ] ,
283- & [ cmp_values] ,
284- new_coverage,
285- None ,
286- ) ;
287-
288- // Handle `vm.assume`.
289- if call. result . as_ref ( ) == MAGIC_ASSUME {
290- return Err ( TestCaseError :: reject ( FuzzError :: AssumeReject ) ) ;
291- }
265+ let target = tx. call_details . target ;
266+ let calldata = tx. call_details . calldata . clone ( ) ;
267+ let tx_for_corpus = tx. clone ( ) ;
268+ let mut executor = executor. clone ( ) ;
269+ let outcome =
270+ replay_tx ( & mut executor, & tx, /* commit_state */ false , |executor, mut call| {
271+ let cmp_values = call. evm_cmp_values . take ( ) . unwrap_or_default ( ) ;
272+ let new_coverage = coverage_metrics. merge_edge_coverage ( & mut call) ;
273+ coverage_metrics. process_inputs (
274+ std:: slice:: from_ref ( & tx_for_corpus) ,
275+ & [ cmp_values] ,
276+ new_coverage,
277+ None ,
278+ ) ;
292279
293- let ( breakpoints, deprecated_cheatcodes) =
294- call. cheatcodes . as_ref ( ) . map_or_else ( Default :: default, |cheats| {
295- ( cheats. breakpoints . clone ( ) , cheats. deprecated . clone ( ) )
296- } ) ;
297-
298- // Consider call success if test should not fail on reverts and reverter is not the
299- // cheatcode or test address.
300- let success = if !self . config . fail_on_revert
301- && call
302- . reverter
303- . is_some_and ( |reverter| reverter != address && reverter != CHEATCODE_ADDRESS )
304- {
305- true
306- } else {
307- executor. is_raw_call_mut_success ( address, & mut call, false )
308- } ;
280+ // Handle `vm.assume`.
281+ if call. result . as_ref ( ) == MAGIC_ASSUME {
282+ return Ok ( ControlFlow :: Break ( Err ( TestCaseError :: reject (
283+ FuzzError :: AssumeReject ,
284+ ) ) ) ) ;
285+ }
309286
310- if success {
311- Ok ( FuzzOutcome :: Case ( CaseOutcome {
312- case : FuzzCase { gas : call. gas_used , stipend : call. stipend } ,
313- traces : call. traces ,
314- coverage : call. line_coverage ,
315- breakpoints,
316- logs : call. logs ,
317- deprecated_cheatcodes,
318- } ) )
319- } else {
320- Ok ( FuzzOutcome :: CounterExample ( CounterExampleOutcome {
321- exit_reason : call. exit_reason ,
322- counterexample : ( calldata, call) ,
323- breakpoints,
324- } ) )
325- }
287+ let ( breakpoints, deprecated_cheatcodes) =
288+ call. cheatcodes . as_ref ( ) . map_or_else ( Default :: default, |cheats| {
289+ ( cheats. breakpoints . clone ( ) , cheats. deprecated . clone ( ) )
290+ } ) ;
291+
292+ // Consider call success if test should not fail on reverts and reverter is not the
293+ // cheatcode or test address.
294+ let success = if !self . config . fail_on_revert
295+ && call
296+ . reverter
297+ . is_some_and ( |reverter| reverter != target && reverter != CHEATCODE_ADDRESS )
298+ {
299+ true
300+ } else {
301+ executor
302+ . is_raw_call_mut_success ( target, & mut call, /* should_fail */ false )
303+ } ;
304+
305+ let outcome = if success {
306+ FuzzOutcome :: Case ( CaseOutcome {
307+ case : FuzzCase { gas : call. gas_used , stipend : call. stipend } ,
308+ traces : call. traces ,
309+ coverage : call. line_coverage ,
310+ breakpoints,
311+ logs : call. logs ,
312+ deprecated_cheatcodes,
313+ } )
314+ } else {
315+ FuzzOutcome :: CounterExample ( CounterExampleOutcome {
316+ exit_reason : call. exit_reason ,
317+ counterexample : ( calldata. clone ( ) , call) ,
318+ breakpoints,
319+ } )
320+ } ;
321+ Ok ( ControlFlow :: Break ( Ok ( outcome) ) )
322+ } )
323+ . map_err ( |e| TestCaseError :: fail ( e. to_string ( ) ) ) ?;
324+
325+ outcome. expect ( "depth-1 stateless replay always stops after the first tx" )
326326 }
327327
328328 /// Aggregates the results from all workers
@@ -441,22 +441,20 @@ impl<FEN: FoundryEvmNetwork> FuzzedExecutor<FEN> {
441441 ) -> Result < WorkerState < FEN > > {
442442 // Prepare
443443 let fuzz_state = shared_state. state . fork ( ) ;
444- let dictionary_weight = self . config . dictionary . dictionary_weight . min ( 100 ) ;
445- let strategy = proptest:: prop_oneof![
446- 100 - dictionary_weight => fuzz_calldata( func. clone( ) , fuzz_fixtures) ,
447- dictionary_weight => fuzz_calldata_from_state( func. clone( ) , & fuzz_state) ,
448- ]
449- . prop_map ( move |calldata| BasicTxDetails {
450- warp : None ,
451- roll : None ,
452- sender : Default :: default ( ) ,
453- call_details : CallDetails { target : Default :: default ( ) , calldata, value : None } ,
454- } ) ;
444+ let strategy = TxGenerator :: stateless (
445+ fuzz_state. clone ( ) ,
446+ self . sender ,
447+ address,
448+ func. clone ( ) ,
449+ fuzz_fixtures. clone ( ) ,
450+ self . config . dictionary . dictionary_weight ,
451+ )
452+ . strategy ( ) ;
455453
456454 let mut corpus = WorkerCorpus :: new (
457455 worker_id,
458456 self . config . corpus . clone ( ) ,
459- strategy. boxed ( ) ,
457+ strategy,
460458 // Master worker replays the persisted corpus using the executor
461459 ( worker_id == 0 ) . then_some ( & self . executor_f ) ,
462460 Some ( func) ,
@@ -487,7 +485,9 @@ impl<FEN: FoundryEvmNetwork> FuzzedExecutor<FEN> {
487485
488486 if let Some ( target_run) = self . config . run {
489487 for _ in 1 ..target_run {
490- if let Err ( err) = corpus. new_input ( & mut runner, & fuzz_state, func) {
488+ if let Err ( err) =
489+ corpus. new_input ( & mut runner, & fuzz_state, func, self . sender , address)
490+ {
491491 worker. failure = Some ( TestCaseError :: fail ( format ! (
492492 "failed to generate fuzzed input in worker {}: {err}" ,
493493 worker. id
@@ -526,7 +526,16 @@ impl<FEN: FoundryEvmNetwork> FuzzedExecutor<FEN> {
526526 }
527527
528528 (
529- failure. calldata . clone ( ) ,
529+ BasicTxDetails {
530+ warp : None ,
531+ roll : None ,
532+ sender : self . sender ,
533+ call_details : CallDetails {
534+ target : address,
535+ calldata : failure. calldata . clone ( ) ,
536+ value : None ,
537+ } ,
538+ } ,
530539 Some ( FuzzRunMetadata :: new (
531540 seed,
532541 failure. fuzz . run ,
@@ -556,17 +565,18 @@ impl<FEN: FoundryEvmNetwork> FuzzedExecutor<FEN> {
556565 cheats. set_seed ( Self :: fuzz_run_seed ( seed, worker_id, fuzz_run) ) ;
557566 }
558567
559- let input = match corpus. new_input ( & mut runner, & fuzz_state, func) {
560- Ok ( input) => input,
561- Err ( err) => {
562- worker. failure = Some ( TestCaseError :: fail ( format ! (
563- "failed to generate fuzzed input in worker {}: {err}" ,
564- worker. id
565- ) ) ) ;
566- shared_state. try_claim_failure ( worker_id) ;
567- break ' stop;
568- }
569- } ;
568+ let input =
569+ match corpus. new_input ( & mut runner, & fuzz_state, func, self . sender , address) {
570+ Ok ( input) => input,
571+ Err ( err) => {
572+ worker. failure = Some ( TestCaseError :: fail ( format ! (
573+ "failed to generate fuzzed input in worker {}: {err}" ,
574+ worker. id
575+ ) ) ) ;
576+ shared_state. try_claim_failure ( worker_id) ;
577+ break ' stop;
578+ }
579+ } ;
570580
571581 (
572582 input,
@@ -594,7 +604,7 @@ impl<FEN: FoundryEvmNetwork> FuzzedExecutor<FEN> {
594604 } ;
595605
596606 worker. last_run_timestamp = SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) ?. as_millis ( ) ;
597- match self . single_fuzz ( & executor, address , input, & mut corpus) {
607+ match self . single_fuzz ( & executor, input, & mut corpus) {
598608 Ok ( fuzz_outcome) => match fuzz_outcome {
599609 FuzzOutcome :: Case ( case) => {
600610 let total_runs = inc_runs ( ) ;
0 commit comments