@@ -5,7 +5,8 @@ use crate::{
55 } ,
66 inspectors:: Fuzzer ,
77} ;
8- use alloy_primitives:: { Address , Bytes , FixedBytes , I256 , Selector , U256 , map:: AddressMap } ;
8+ use alloy_json_abi:: Function ;
9+ use alloy_primitives:: { Address , Bytes , FixedBytes , I256 , Selector , U256 , map:: { AddressMap , HashMap } } ;
910use alloy_sol_types:: { SolCall , sol} ;
1011use eyre:: { ContextCompat , Result , eyre} ;
1112use foundry_common:: {
@@ -34,7 +35,7 @@ use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
3435use indicatif:: ProgressBar ;
3536use parking_lot:: RwLock ;
3637use proptest:: { strategy:: Strategy , test_runner:: TestRunner } ;
37- use result:: { assert_after_invariant, assert_invariants, can_continue, did_fail_on_assert} ;
38+ use result:: { assert_after_invariant, assert_invariants, can_continue, did_fail_on_assert, invariant_preflight_check } ;
3839use revm:: { context:: Block , state:: Account } ;
3940use serde:: { Deserialize , Serialize } ;
4041use serde_json:: json;
@@ -49,7 +50,7 @@ pub use error::{InvariantFailures, InvariantFuzzError};
4950use foundry_evm_coverage:: HitMaps ;
5051
5152mod replay;
52- pub use replay:: { replay_error, replay_run} ;
53+ pub use replay:: { generate_counterexample , replay_error, replay_run} ;
5354
5455mod result;
5556pub use result:: InvariantFuzzTestResult ;
@@ -266,12 +267,8 @@ impl<FEN: FoundryEvmNetwork> InvariantTest<FEN> {
266267 last_call_results : Option < RawCallResult < FEN > > ,
267268 branch_runner : TestRunner ,
268269 ) -> Self {
269- let mut fuzz_cases = vec ! [ ] ;
270- if last_call_results. is_none ( ) {
271- fuzz_cases. push ( FuzzedCases :: new ( vec ! [ ] ) ) ;
272- }
273270 let test_data = InvariantTestData {
274- fuzz_cases,
271+ fuzz_cases : vec ! [ ] ,
275272 failures,
276273 last_run_inputs : vec ! [ ] ,
277274 gas_report_traces : vec ! [ ] ,
@@ -291,13 +288,13 @@ impl<FEN: FoundryEvmNetwork> InvariantTest<FEN> {
291288 }
292289
293290 /// Whether invariant test has errors or not.
294- const fn has_errors ( & self ) -> bool {
295- self . test_data . failures . error . is_some ( )
291+ fn has_errors ( & self , invariant : & Function ) -> bool {
292+ self . test_data . failures . has_failure ( invariant )
296293 }
297294
298295 /// Set invariant test error.
299- fn set_error ( & mut self , error : InvariantFuzzError ) {
300- self . test_data . failures . error = Some ( error) ;
296+ fn set_error ( & mut self , invariant : & Function , error : InvariantFuzzError ) {
297+ self . test_data . failures . record_failure ( invariant , error) ;
301298 }
302299
303300 /// Set last invariant test call results.
@@ -455,7 +452,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
455452 early_exit : & EarlyExit ,
456453 ) -> Result < InvariantFuzzTestResult > {
457454 // Throw an error to abort test run if the invariant function accepts input params
458- if !invariant_contract. invariant_function . inputs . is_empty ( ) {
455+ if !invariant_contract. invariant_fn . inputs . is_empty ( ) {
459456 return Err ( eyre ! ( "Invariant test function should have no inputs" ) ) ;
460457 }
461458
@@ -534,9 +531,10 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
534531 current_run. inputs . pop ( ) ;
535532 current_run. rejects += 1 ;
536533 if current_run. rejects > self . config . max_assume_rejects {
537- invariant_test. set_error ( InvariantFuzzError :: MaxAssumeRejects (
538- self . config . max_assume_rejects ,
539- ) ) ;
534+ invariant_test. set_error (
535+ invariant_contract. invariant_fn ,
536+ InvariantFuzzError :: MaxAssumeRejects ( self . config . max_assume_rejects ) ,
537+ ) ;
540538 break ' stop;
541539 }
542540 } else {
@@ -602,7 +600,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
602600 || is_last_call
603601 } ;
604602
605- let result = if should_check_invariant {
603+ let can_continue_result = if should_check_invariant {
606604 can_continue (
607605 & invariant_contract,
608606 & mut invariant_test,
@@ -621,7 +619,8 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
621619 {
622620 let case_data = error:: FailedInvariantCaseData :: new (
623621 & invariant_contract,
624- & self . config ,
622+ self . config . shrink_run_limit ,
623+ self . config . fail_on_revert ,
625624 & invariant_test. targeted_contracts ,
626625 & current_run. inputs ,
627626 call_result,
@@ -630,12 +629,12 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
630629 . with_assertion_failure ( assertion_failure) ;
631630 invariant_test. test_data . failures . revert_reason =
632631 Some ( case_data. revert_reason . clone ( ) ) ;
633- invariant_test. test_data . failures . error = Some ( if assertion_failure {
632+ invariant_test. set_error ( invariant_contract . invariant_fn , if assertion_failure {
634633 InvariantFuzzError :: BrokenInvariant ( case_data)
635634 } else {
636635 InvariantFuzzError :: Revert ( case_data)
637636 } ) ;
638- result :: RichInvariantResults :: new ( false , None )
637+ false
639638 } else if call_result. reverted
640639 && !invariant_contract. is_optimization ( )
641640 && !self . config . has_delay ( )
@@ -644,33 +643,32 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
644643 // preserve their warp/roll contribution when building the final
645644 // counterexample.
646645 current_run. inputs . pop ( ) ;
647- result :: RichInvariantResults :: new ( true , None )
646+ true
648647 } else {
649- result :: RichInvariantResults :: new ( true , None )
648+ true
650649 }
651650 } ;
652651
653- if !result . can_continue || current_run. depth == self . config . depth - 1 {
652+ if !can_continue_result || current_run. depth == self . config . depth - 1 {
654653 invariant_test. set_last_run_inputs ( & current_run. inputs ) ;
655654 }
656655 // If test cannot continue then stop current run and exit test suite.
657- if !result . can_continue {
656+ if !can_continue_result {
658657 let reason = invariant_test
659658 . test_data
660659 . failures
661- . error
662- . as_ref ( )
660+ . errors
661+ . values ( )
662+ . next ( )
663663 . and_then ( |e| e. revert_reason ( ) )
664664 . unwrap_or_default ( ) ;
665665 failure_metrics. record_failure (
666- & invariant_contract. invariant_function . name ,
666+ & invariant_contract. invariant_fn . name ,
667667 invariant_contract. name ,
668668 & reason,
669669 ) ;
670670 break ' stop;
671671 }
672-
673- invariant_test. set_last_call_results ( result. call_result ) ;
674672 current_run. depth += 1 ;
675673 }
676674
@@ -696,7 +694,9 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
696694 ) ;
697695
698696 // Call `afterInvariant` only if it is declared and test didn't fail already.
699- if invariant_contract. call_after_invariant && !invariant_test. has_errors ( ) {
697+ if invariant_contract. call_after_invariant
698+ && !invariant_test. has_errors ( invariant_contract. invariant_fn )
699+ {
700700 let success = assert_after_invariant (
701701 & invariant_contract,
702702 & mut invariant_test,
@@ -708,12 +708,13 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
708708 let reason = invariant_test
709709 . test_data
710710 . failures
711- . error
712- . as_ref ( )
711+ . errors
712+ . values ( )
713+ . next ( )
713714 . and_then ( |e| e. revert_reason ( ) )
714715 . unwrap_or_default ( ) ;
715716 failure_metrics. record_failure (
716- & invariant_contract. invariant_function . name ,
717+ & invariant_contract. invariant_fn . name ,
717718 invariant_contract. name ,
718719 & reason,
719720 ) ;
@@ -746,7 +747,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
746747 // Display corpus metrics inline as JSON.
747748 let metrics = build_invariant_progress_json (
748749 SystemTime :: now ( ) . duration_since ( UNIX_EPOCH ) ?. as_secs ( ) ,
749- & invariant_contract. invariant_function . name ,
750+ & invariant_contract. invariant_fn . name ,
750751 & corpus_manager. metrics ,
751752 invariant_test. test_data . optimization_best_value ,
752753 throughput,
@@ -765,7 +766,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
765766
766767 let result = invariant_test. test_data ;
767768 Ok ( InvariantFuzzTestResult {
768- error : result. failures . error ,
769+ errors : result. failures . errors ,
769770 cases : result. fuzz_cases ,
770771 reverts : result. failures . reverts ,
771772 last_run_inputs : result. last_run_inputs ,
@@ -825,15 +826,15 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
825826 // already know if we can early exit the invariant run.
826827 // This does not count as a fuzz run. It will just register the revert.
827828 let mut failures = InvariantFailures :: new ( ) ;
828- let last_call_results = assert_invariants (
829+ invariant_preflight_check (
829830 invariant_contract,
830831 & self . config ,
831832 & targeted_contracts,
832833 & self . executor ,
833834 & [ ] ,
834835 & mut failures,
835836 ) ?;
836- if let Some ( error) = failures. error {
837+ if let Some ( error) = failures. get_failure ( invariant_contract . invariant_fn ) {
837838 return Err ( eyre ! ( error. revert_reason( ) . unwrap_or_default( ) ) ) ;
838839 }
839840
@@ -879,7 +880,7 @@ impl<'a, FEN: FoundryEvmNetwork> InvariantExecutor<'a, FEN> {
879880 fuzz_state,
880881 targeted_contracts,
881882 failures,
882- last_call_results ,
883+ None ,
883884 self . runner . clone ( ) ,
884885 ) ;
885886
0 commit comments