@@ -13,7 +13,7 @@ use alloy_primitives::{Address, Bytes, U256, address, map::HashMap};
13
13
use eyre:: Result ;
14
14
use foundry_common:: { TestFunctionExt , TestFunctionKind , contracts:: ContractsByAddress } ;
15
15
use foundry_compilers:: utils:: canonicalized;
16
- use foundry_config:: { Config , InvariantConfig } ;
16
+ use foundry_config:: Config ;
17
17
use foundry_evm:: {
18
18
constants:: CALLER ,
19
19
decode:: RevertDecoder ,
@@ -31,9 +31,7 @@ use foundry_evm::{
31
31
traces:: { TraceKind , TraceMode , load_contracts} ,
32
32
} ;
33
33
use itertools:: Itertools ;
34
- use proptest:: test_runner:: {
35
- FailurePersistence , FileFailurePersistence , RngAlgorithm , TestError , TestRng , TestRunner ,
36
- } ;
34
+ use proptest:: test_runner:: { RngAlgorithm , TestError , TestRng , TestRunner } ;
37
35
use rayon:: prelude:: * ;
38
36
use serde:: { Deserialize , Serialize } ;
39
37
use std:: {
@@ -726,8 +724,8 @@ impl<'a> FunctionRunner<'a> {
726
724
abi : & self . cr . contract . abi ,
727
725
} ;
728
726
729
- let ( failure_dir, failure_file) = invariant_failure_paths (
730
- invariant_config,
727
+ let ( failure_dir, failure_file) = test_failure_paths (
728
+ invariant_config. failure_persist_dir . clone ( ) . unwrap ( ) ,
731
729
self . cr . name ,
732
730
& invariant_contract. invariant_function . name ,
733
731
) ;
@@ -929,17 +927,40 @@ impl<'a> FunctionRunner<'a> {
929
927
fuzz_config. runs ,
930
928
) ;
931
929
930
+ let ( failure_dir, failure_file) = test_failure_paths (
931
+ fuzz_config. failure_persist_dir . clone ( ) . unwrap ( ) ,
932
+ self . cr . name ,
933
+ & func. name ,
934
+ ) ;
935
+
936
+ // Load persisted counterexample, if any.
937
+ let mut failed_input =
938
+ foundry_common:: fs:: read_json_file :: < BaseCounterExample > ( failure_file. as_path ( ) ) . ok ( ) ;
939
+
932
940
// Run fuzz test.
933
941
let mut fuzzed_executor =
934
942
FuzzedExecutor :: new ( self . executor . into_owned ( ) , runner, self . tcfg . sender , fuzz_config) ;
935
943
let result = fuzzed_executor. fuzz (
936
944
func,
945
+ & mut failed_input,
937
946
& self . setup . fuzz_fixtures ,
938
947
& self . setup . deployed_libs ,
939
948
self . address ,
940
949
& self . cr . mcr . revert_decoder ,
941
950
progress. as_ref ( ) ,
942
951
) ;
952
+
953
+ // Record counterexample.
954
+ if let Some ( CounterExample :: Single ( counterexample) ) = & result. counterexample {
955
+ if let Err ( err) = foundry_common:: fs:: create_dir_all ( failure_dir) {
956
+ error ! ( %err, "Failed to create fuzz failure dir" ) ;
957
+ } else if let Err ( err) =
958
+ foundry_common:: fs:: write_json_file ( failure_file. as_path ( ) , counterexample)
959
+ {
960
+ error ! ( %err, "Failed to record call sequence" ) ;
961
+ }
962
+ }
963
+
943
964
self . result . fuzz_result ( result) ;
944
965
self . result
945
966
}
@@ -995,40 +1016,21 @@ impl<'a> FunctionRunner<'a> {
995
1016
996
1017
fn fuzz_runner ( & self ) -> TestRunner {
997
1018
let config = & self . config . fuzz ;
998
- let failure_persist_path = config
999
- . failure_persist_dir
1000
- . as_ref ( )
1001
- . unwrap ( )
1002
- . join ( config. failure_persist_file . as_ref ( ) . unwrap ( ) )
1003
- . into_os_string ( )
1004
- . into_string ( )
1005
- . unwrap ( ) ;
1006
- fuzzer_with_cases (
1007
- config. seed ,
1008
- config. runs ,
1009
- config. max_test_rejects ,
1010
- Some ( Box :: new ( FileFailurePersistence :: Direct ( failure_persist_path. leak ( ) ) ) ) ,
1011
- )
1019
+ fuzzer_with_cases ( config. seed , config. runs , config. max_test_rejects )
1012
1020
}
1013
1021
1014
1022
fn invariant_runner ( & self ) -> TestRunner {
1015
1023
let config = & self . config . invariant ;
1016
- fuzzer_with_cases ( self . config . fuzz . seed , config. runs , config. max_assume_rejects , None )
1024
+ fuzzer_with_cases ( self . config . fuzz . seed , config. runs , config. max_assume_rejects )
1017
1025
}
1018
1026
1019
1027
fn clone_executor ( & self ) -> Executor {
1020
1028
self . executor . clone ( ) . into_owned ( )
1021
1029
}
1022
1030
}
1023
1031
1024
- fn fuzzer_with_cases (
1025
- seed : Option < U256 > ,
1026
- cases : u32 ,
1027
- max_global_rejects : u32 ,
1028
- file_failure_persistence : Option < Box < dyn FailurePersistence > > ,
1029
- ) -> TestRunner {
1032
+ fn fuzzer_with_cases ( seed : Option < U256 > , cases : u32 , max_global_rejects : u32 ) -> TestRunner {
1030
1033
let config = proptest:: test_runner:: Config {
1031
- failure_persistence : file_failure_persistence,
1032
1034
cases,
1033
1035
max_global_rejects,
1034
1036
// Disable proptest shrink: for fuzz tests we provide single counterexample,
@@ -1077,18 +1079,13 @@ fn persisted_call_sequence(path: &Path, bytecode: &Bytes) -> Option<Vec<BaseCoun
1077
1079
)
1078
1080
}
1079
1081
1080
- /// Helper functions to return canonicalized invariant failure paths.
1081
- fn invariant_failure_paths (
1082
- config : & InvariantConfig ,
1082
+ /// Helper functions to return canonicalized test failure paths.
1083
+ fn test_failure_paths (
1084
+ persist_dir : PathBuf ,
1083
1085
contract_name : & str ,
1084
1086
invariant_name : & str ,
1085
1087
) -> ( PathBuf , PathBuf ) {
1086
- let dir = config
1087
- . failure_persist_dir
1088
- . clone ( )
1089
- . unwrap ( )
1090
- . join ( "failures" )
1091
- . join ( contract_name. split ( ':' ) . next_back ( ) . unwrap ( ) ) ;
1088
+ let dir = persist_dir. join ( "failures" ) . join ( contract_name. split ( ':' ) . next_back ( ) . unwrap ( ) ) ;
1092
1089
let dir = canonicalized ( dir) ;
1093
1090
let file = canonicalized ( dir. join ( invariant_name) ) ;
1094
1091
( dir, file)
0 commit comments