@@ -9,7 +9,7 @@ use eyre::{eyre, ContextCompat, Result};
9
9
use foundry_common:: contracts:: { ContractsByAddress , ContractsByArtifact } ;
10
10
use foundry_config:: { FuzzDictionaryConfig , InvariantConfig } ;
11
11
use foundry_evm_core:: {
12
- constants:: { CALLER , CHEATCODE_ADDRESS , HARDHAT_CONSOLE_ADDRESS } ,
12
+ constants:: { CALLER , CHEATCODE_ADDRESS , HARDHAT_CONSOLE_ADDRESS , MAGIC_ASSUME } ,
13
13
utils:: { get_function, StateChangeset } ,
14
14
} ;
15
15
use foundry_evm_fuzz:: {
@@ -38,11 +38,13 @@ use foundry_evm_fuzz::strategies::CalldataFuzzDictionary;
38
38
mod funcs;
39
39
pub use funcs:: { assert_invariants, replay_run} ;
40
40
41
+ use self :: error:: FailedInvariantCaseData ;
42
+
41
43
/// Alias for (Dictionary for fuzzing, initial contracts to fuzz and an InvariantStrategy).
42
44
type InvariantPreparation = (
43
45
EvmFuzzState ,
44
46
FuzzRunIdentifiedContracts ,
45
- BoxedStrategy < Vec < BasicTxDetails > > ,
47
+ BoxedStrategy < BasicTxDetails > ,
46
48
CalldataFuzzDictionary ,
47
49
) ;
48
50
@@ -143,7 +145,9 @@ impl<'a> InvariantExecutor<'a> {
143
145
// during the run. We need another proptest runner to query for random
144
146
// values.
145
147
let branch_runner = RefCell :: new ( self . runner . clone ( ) ) ;
146
- let _ = self . runner . run ( & strat, |mut inputs| {
148
+ let _ = self . runner . run ( & strat, |first_input| {
149
+ let mut inputs = vec ! [ first_input] ;
150
+
147
151
// We stop the run immediately if we have reverted, and `fail_on_revert` is set.
148
152
if self . config . fail_on_revert && failures. borrow ( ) . reverts > 0 {
149
153
return Err ( TestCaseError :: fail ( "Revert occurred." ) )
@@ -158,7 +162,10 @@ impl<'a> InvariantExecutor<'a> {
158
162
// Created contracts during a run.
159
163
let mut created_contracts = vec ! [ ] ;
160
164
161
- for current_run in 0 ..self . config . depth {
165
+ let mut current_run = 0 ;
166
+ let mut assume_rejects_counter = 0 ;
167
+
168
+ while current_run < self . config . depth {
162
169
let ( sender, ( address, calldata) ) = inputs. last ( ) . expect ( "no input generated" ) ;
163
170
164
171
// Executes the call from the randomly generated sequence.
@@ -172,65 +179,77 @@ impl<'a> InvariantExecutor<'a> {
172
179
. expect ( "could not make raw evm call" )
173
180
} ;
174
181
175
- // Collect data for fuzzing from the state changeset.
176
- let mut state_changeset =
177
- call_result. state_changeset . to_owned ( ) . expect ( "no changesets" ) ;
178
-
179
- collect_data (
180
- & mut state_changeset,
181
- sender,
182
- & call_result,
183
- fuzz_state. clone ( ) ,
184
- & self . config . dictionary ,
185
- ) ;
182
+ if call_result. result . as_ref ( ) == MAGIC_ASSUME {
183
+ inputs. pop ( ) ;
184
+ assume_rejects_counter += 1 ;
185
+ if assume_rejects_counter > self . config . max_assume_rejects {
186
+ failures. borrow_mut ( ) . error = Some ( InvariantFuzzError :: MaxAssumeRejects (
187
+ self . config . max_assume_rejects ,
188
+ ) ) ;
189
+ return Err ( TestCaseError :: fail ( "Max number of vm.assume rejects reached." ) )
190
+ }
191
+ } else {
192
+ // Collect data for fuzzing from the state changeset.
193
+ let mut state_changeset =
194
+ call_result. state_changeset . to_owned ( ) . expect ( "no changesets" ) ;
195
+
196
+ collect_data (
197
+ & mut state_changeset,
198
+ sender,
199
+ & call_result,
200
+ fuzz_state. clone ( ) ,
201
+ & self . config . dictionary ,
202
+ ) ;
186
203
187
- if let Err ( error) = collect_created_contracts (
188
- & state_changeset,
189
- self . project_contracts ,
190
- self . setup_contracts ,
191
- & self . artifact_filters ,
192
- targeted_contracts. clone ( ) ,
193
- & mut created_contracts,
194
- ) {
195
- warn ! ( target: "forge::test" , "{error}" ) ;
196
- }
204
+ if let Err ( error) = collect_created_contracts (
205
+ & state_changeset,
206
+ self . project_contracts ,
207
+ self . setup_contracts ,
208
+ & self . artifact_filters ,
209
+ targeted_contracts. clone ( ) ,
210
+ & mut created_contracts,
211
+ ) {
212
+ warn ! ( target: "forge::test" , "{error}" ) ;
213
+ }
197
214
198
- // Commit changes to the database.
199
- executor. backend . commit ( state_changeset. clone ( ) ) ;
200
-
201
- fuzz_runs. push ( FuzzCase {
202
- calldata : calldata. clone ( ) ,
203
- gas : call_result. gas_used ,
204
- stipend : call_result. stipend ,
205
- } ) ;
206
-
207
- let RichInvariantResults { success : can_continue, call_result : call_results } =
208
- can_continue (
209
- & invariant_contract,
210
- call_result,
211
- & executor,
212
- & inputs,
213
- & mut failures. borrow_mut ( ) ,
214
- & targeted_contracts,
215
- state_changeset,
216
- self . config . fail_on_revert ,
217
- self . config . shrink_sequence ,
218
- self . config . shrink_run_limit ,
219
- ) ;
215
+ // Commit changes to the database.
216
+ executor. backend . commit ( state_changeset. clone ( ) ) ;
217
+
218
+ fuzz_runs. push ( FuzzCase {
219
+ calldata : calldata. clone ( ) ,
220
+ gas : call_result. gas_used ,
221
+ stipend : call_result. stipend ,
222
+ } ) ;
223
+
224
+ let RichInvariantResults { success : can_continue, call_result : call_results } =
225
+ can_continue (
226
+ & invariant_contract,
227
+ call_result,
228
+ & executor,
229
+ & inputs,
230
+ & mut failures. borrow_mut ( ) ,
231
+ & targeted_contracts,
232
+ state_changeset,
233
+ self . config . fail_on_revert ,
234
+ self . config . shrink_sequence ,
235
+ self . config . shrink_run_limit ,
236
+ ) ;
237
+
238
+ if !can_continue || current_run == self . config . depth - 1 {
239
+ * last_run_calldata. borrow_mut ( ) = inputs. clone ( ) ;
240
+ }
220
241
221
- if !can_continue || current_run == self . config . depth - 1 {
222
- * last_run_calldata . borrow_mut ( ) = inputs . clone ( ) ;
223
- }
242
+ if !can_continue {
243
+ break
244
+ }
224
245
225
- if !can_continue {
226
- break
246
+ * last_call_results . borrow_mut ( ) = call_results ;
247
+ current_run += 1 ;
227
248
}
228
249
229
- * last_call_results. borrow_mut ( ) = call_results;
230
-
231
250
// Generates the next call from the run using the recently updated
232
251
// dictionary.
233
- inputs. extend (
252
+ inputs. push (
234
253
strat
235
254
. new_tree ( & mut branch_runner. borrow_mut ( ) )
236
255
. map_err ( |_| TestCaseError :: Fail ( "Could not generate case" . into ( ) ) ) ?
@@ -772,7 +791,7 @@ fn can_continue(
772
791
failures. reverts += 1 ;
773
792
// If fail on revert is set, we must return immediately.
774
793
if fail_on_revert {
775
- let error = InvariantFuzzError :: new (
794
+ let case_data = FailedInvariantCaseData :: new (
776
795
invariant_contract,
777
796
None ,
778
797
calldata,
@@ -781,8 +800,8 @@ fn can_continue(
781
800
shrink_sequence,
782
801
shrink_run_limit,
783
802
) ;
784
-
785
- failures . revert_reason = Some ( error . revert_reason . clone ( ) ) ;
803
+ failures . revert_reason = Some ( case_data . revert_reason . clone ( ) ) ;
804
+ let error = InvariantFuzzError :: Revert ( case_data ) ;
786
805
failures. error = Some ( error) ;
787
806
788
807
return RichInvariantResults :: new ( false , None )
0 commit comments