1use crate::{
2 executors::{Executor, RawCallResult},
3 inspectors::Fuzzer,
4};
5use alloy_primitives::{map::HashMap, Address, Bytes, FixedBytes, Selector, U256};
6use alloy_sol_types::{sol, SolCall};
7use eyre::{eyre, ContextCompat, Result};
8use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact};
9use foundry_config::InvariantConfig;
10use foundry_evm_core::{
11 constants::{
12 CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME,
13 TEST_TIMEOUT,
14 },
15 precompiles::PRECOMPILES,
16};
17use foundry_evm_fuzz::{
18 invariant::{
19 ArtifactFilters, BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract,
20 RandomCallGenerator, SenderFilters, TargetedContract, TargetedContracts,
21 },
22 strategies::{invariant_strat, override_call_strat, EvmFuzzState},
23 FuzzCase, FuzzFixtures, FuzzedCases,
24};
25use foundry_evm_traces::{CallTraceArena, SparsedTraceArena};
26use indicatif::ProgressBar;
27use parking_lot::RwLock;
28use proptest::{
29 strategy::{Strategy, ValueTree},
30 test_runner::{TestCaseError, TestRunner},
31};
32use result::{assert_after_invariant, assert_invariants, can_continue};
33use revm::state::Account;
34use shrink::shrink_sequence;
35use std::{
36 cell::RefCell,
37 collections::{btree_map::Entry, HashMap as Map},
38 sync::Arc,
39};
40
41mod error;
42pub use error::{InvariantFailures, InvariantFuzzError};
43use foundry_evm_coverage::HitMaps;
44
45mod replay;
46pub use replay::{replay_error, replay_run};
47
48mod result;
49pub use result::InvariantFuzzTestResult;
50use serde::{Deserialize, Serialize};
51
52mod shrink;
53use crate::executors::{EvmError, FuzzTestTimer};
54pub use shrink::check_sequence;
55
56sol! {
57 interface IInvariantTest {
58 #[derive(Default)]
59 struct FuzzSelector {
60 address addr;
61 bytes4[] selectors;
62 }
63
64 #[derive(Default)]
65 struct FuzzArtifactSelector {
66 string artifact;
67 bytes4[] selectors;
68 }
69
70 #[derive(Default)]
71 struct FuzzInterface {
72 address addr;
73 string[] artifacts;
74 }
75
76 function afterInvariant() external;
77
78 #[derive(Default)]
79 function excludeArtifacts() public view returns (string[] memory excludedArtifacts);
80
81 #[derive(Default)]
82 function excludeContracts() public view returns (address[] memory excludedContracts);
83
84 #[derive(Default)]
85 function excludeSelectors() public view returns (FuzzSelector[] memory excludedSelectors);
86
87 #[derive(Default)]
88 function excludeSenders() public view returns (address[] memory excludedSenders);
89
90 #[derive(Default)]
91 function targetArtifacts() public view returns (string[] memory targetedArtifacts);
92
93 #[derive(Default)]
94 function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors);
95
96 #[derive(Default)]
97 function targetContracts() public view returns (address[] memory targetedContracts);
98
99 #[derive(Default)]
100 function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors);
101
102 #[derive(Default)]
103 function targetSenders() public view returns (address[] memory targetedSenders);
104
105 #[derive(Default)]
106 function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces);
107 }
108}
109
110#[derive(Default, Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
112pub struct InvariantMetrics {
113 pub calls: usize,
115 pub reverts: usize,
117 pub discards: usize,
119}
120
121pub struct InvariantTestData {
123 pub fuzz_cases: Vec<FuzzedCases>,
125 pub failures: InvariantFailures,
127 pub last_run_inputs: Vec<BasicTxDetails>,
129 pub gas_report_traces: Vec<Vec<CallTraceArena>>,
131 pub last_call_results: Option<RawCallResult>,
133 pub coverage: Option<HitMaps>,
135 pub metrics: Map<String, InvariantMetrics>,
137
138 pub branch_runner: TestRunner,
143}
144
145pub struct InvariantTest {
147 pub fuzz_state: EvmFuzzState,
149 pub targeted_contracts: FuzzRunIdentifiedContracts,
151 pub execution_data: RefCell<InvariantTestData>,
153}
154
155impl InvariantTest {
156 pub fn new(
158 fuzz_state: EvmFuzzState,
159 targeted_contracts: FuzzRunIdentifiedContracts,
160 failures: InvariantFailures,
161 last_call_results: Option<RawCallResult>,
162 branch_runner: TestRunner,
163 ) -> Self {
164 let mut fuzz_cases = vec![];
165 if last_call_results.is_none() {
166 fuzz_cases.push(FuzzedCases::new(vec![]));
167 }
168 let execution_data = RefCell::new(InvariantTestData {
169 fuzz_cases,
170 failures,
171 last_run_inputs: vec![],
172 gas_report_traces: vec![],
173 last_call_results,
174 coverage: None,
175 metrics: Map::default(),
176 branch_runner,
177 });
178 Self { fuzz_state, targeted_contracts, execution_data }
179 }
180
181 pub fn reverts(&self) -> usize {
183 self.execution_data.borrow().failures.reverts
184 }
185
186 pub fn has_errors(&self) -> bool {
188 self.execution_data.borrow().failures.error.is_some()
189 }
190
191 pub fn set_error(&self, error: InvariantFuzzError) {
193 self.execution_data.borrow_mut().failures.error = Some(error);
194 }
195
196 pub fn set_last_call_results(&self, call_result: Option<RawCallResult>) {
198 self.execution_data.borrow_mut().last_call_results = call_result;
199 }
200
201 pub fn set_last_run_inputs(&self, inputs: &Vec<BasicTxDetails>) {
203 self.execution_data.borrow_mut().last_run_inputs.clone_from(inputs);
204 }
205
206 pub fn merge_coverage(&self, new_coverage: Option<HitMaps>) {
208 HitMaps::merge_opt(&mut self.execution_data.borrow_mut().coverage, new_coverage);
209 }
210
211 pub fn record_metrics(&self, tx_details: &BasicTxDetails, reverted: bool, discarded: bool) {
215 if let Some(metric_key) =
216 self.targeted_contracts.targets.lock().fuzzed_metric_key(tx_details)
217 {
218 let test_metrics = &mut self.execution_data.borrow_mut().metrics;
219 let invariant_metrics = test_metrics.entry(metric_key).or_default();
220 invariant_metrics.calls += 1;
221 if discarded {
222 invariant_metrics.discards += 1;
223 } else if reverted {
224 invariant_metrics.reverts += 1;
225 }
226 }
227 }
228
229 pub fn end_run(&self, run: InvariantTestRun, gas_samples: usize) {
232 self.targeted_contracts.clear_created_contracts(run.created_contracts);
234
235 let mut invariant_data = self.execution_data.borrow_mut();
236 if invariant_data.gas_report_traces.len() < gas_samples {
237 invariant_data
238 .gas_report_traces
239 .push(run.run_traces.into_iter().map(|arena| arena.arena).collect());
240 }
241 invariant_data.fuzz_cases.push(FuzzedCases::new(run.fuzz_runs));
242
243 self.fuzz_state.revert();
245 }
246}
247
248pub struct InvariantTestRun {
250 pub inputs: Vec<BasicTxDetails>,
252 pub executor: Executor,
254 pub fuzz_runs: Vec<FuzzCase>,
256 pub created_contracts: Vec<Address>,
258 pub run_traces: Vec<SparsedTraceArena>,
260 pub depth: u32,
262 pub assume_rejects_counter: u32,
264}
265
266impl InvariantTestRun {
267 pub fn new(first_input: BasicTxDetails, executor: Executor, depth: usize) -> Self {
269 Self {
270 inputs: vec![first_input],
271 executor,
272 fuzz_runs: Vec::with_capacity(depth),
273 created_contracts: vec![],
274 run_traces: vec![],
275 depth: 0,
276 assume_rejects_counter: 0,
277 }
278 }
279}
280
281pub struct InvariantExecutor<'a> {
288 pub executor: Executor,
289 runner: TestRunner,
291 config: InvariantConfig,
293 setup_contracts: &'a ContractsByAddress,
295 project_contracts: &'a ContractsByArtifact,
298 artifact_filters: ArtifactFilters,
300}
301
302impl<'a> InvariantExecutor<'a> {
303 pub fn new(
305 executor: Executor,
306 runner: TestRunner,
307 config: InvariantConfig,
308 setup_contracts: &'a ContractsByAddress,
309 project_contracts: &'a ContractsByArtifact,
310 ) -> Self {
311 Self {
312 executor,
313 runner,
314 config,
315 setup_contracts,
316 project_contracts,
317 artifact_filters: ArtifactFilters::default(),
318 }
319 }
320
321 pub fn invariant_fuzz(
323 &mut self,
324 invariant_contract: InvariantContract<'_>,
325 fuzz_fixtures: &FuzzFixtures,
326 deployed_libs: &[Address],
327 progress: Option<&ProgressBar>,
328 ) -> Result<InvariantFuzzTestResult> {
329 if !invariant_contract.invariant_function.inputs.is_empty() {
331 return Err(eyre!("Invariant test function should have no inputs"))
332 }
333
334 let (invariant_test, invariant_strategy) =
335 self.prepare_test(&invariant_contract, fuzz_fixtures, deployed_libs)?;
336
337 let timer = FuzzTestTimer::new(self.config.timeout);
339
340 let _ = self.runner.run(&invariant_strategy, |first_input| {
341 let mut current_run = InvariantTestRun::new(
343 first_input,
344 self.executor.clone(),
346 self.config.depth as usize,
347 );
348
349 if self.config.fail_on_revert && invariant_test.reverts() > 0 {
351 return Err(TestCaseError::fail("call reverted"))
352 }
353
354 while current_run.depth < self.config.depth {
355 if timer.is_timed_out() {
357 return Err(TestCaseError::fail(TEST_TIMEOUT));
362 }
363
364 let tx = current_run.inputs.last().ok_or_else(|| {
365 TestCaseError::fail("no input generated to called fuzz target")
366 })?;
367
368 let mut call_result = current_run
371 .executor
372 .call_raw(
373 tx.sender,
374 tx.call_details.target,
375 tx.call_details.calldata.clone(),
376 U256::ZERO,
377 )
378 .map_err(|e| TestCaseError::fail(e.to_string()))?;
379
380 let discarded = call_result.result.as_ref() == MAGIC_ASSUME;
381 if self.config.show_metrics {
382 invariant_test.record_metrics(tx, call_result.reverted, discarded);
383 }
384
385 invariant_test.merge_coverage(call_result.coverage.clone());
387
388 if discarded {
389 current_run.inputs.pop();
390 current_run.assume_rejects_counter += 1;
391 if current_run.assume_rejects_counter > self.config.max_assume_rejects {
392 invariant_test.set_error(InvariantFuzzError::MaxAssumeRejects(
393 self.config.max_assume_rejects,
394 ));
395 return Err(TestCaseError::fail(
396 "reached maximum number of `vm.assume` rejects",
397 ));
398 }
399 } else {
400 current_run.executor.commit(&mut call_result);
402
403 let mut state_changeset = call_result.state_changeset.clone();
411 if !call_result.reverted {
412 collect_data(
413 &invariant_test,
414 &mut state_changeset,
415 tx,
416 &call_result,
417 self.config.depth,
418 );
419 }
420
421 if let Err(error) =
424 &invariant_test.targeted_contracts.collect_created_contracts(
425 &state_changeset,
426 self.project_contracts,
427 self.setup_contracts,
428 &self.artifact_filters,
429 &mut current_run.created_contracts,
430 )
431 {
432 warn!(target: "forge::test", "{error}");
433 }
434 current_run.fuzz_runs.push(FuzzCase {
435 calldata: tx.call_details.calldata.clone(),
436 gas: call_result.gas_used,
437 stipend: call_result.stipend,
438 });
439
440 let result = can_continue(
442 &invariant_contract,
443 &invariant_test,
444 &mut current_run,
445 &self.config,
446 call_result,
447 &state_changeset,
448 )
449 .map_err(|e| TestCaseError::fail(e.to_string()))?;
450 if !result.can_continue || current_run.depth == self.config.depth - 1 {
451 invariant_test.set_last_run_inputs(¤t_run.inputs);
452 }
453 if !result.can_continue {
455 return Err(TestCaseError::fail("test cannot continue"))
456 }
457
458 invariant_test.set_last_call_results(result.call_result);
459 current_run.depth += 1;
460 }
461
462 current_run.inputs.push(
465 invariant_strategy
466 .new_tree(&mut invariant_test.execution_data.borrow_mut().branch_runner)
467 .map_err(|_| TestCaseError::Fail("Could not generate case".into()))?
468 .current(),
469 );
470 }
471
472 if invariant_contract.call_after_invariant && !invariant_test.has_errors() {
474 assert_after_invariant(
475 &invariant_contract,
476 &invariant_test,
477 ¤t_run,
478 &self.config,
479 )
480 .map_err(|_| TestCaseError::Fail("Failed to call afterInvariant".into()))?;
481 }
482
483 invariant_test.end_run(current_run, self.config.gas_report_samples as usize);
485
486 if let Some(progress) = progress {
488 progress.inc(1);
489 }
490
491 Ok(())
492 });
493
494 trace!(?fuzz_fixtures);
495 invariant_test.fuzz_state.log_stats();
496
497 let result = invariant_test.execution_data.into_inner();
498 Ok(InvariantFuzzTestResult {
499 error: result.failures.error,
500 cases: result.fuzz_cases,
501 reverts: result.failures.reverts,
502 last_run_inputs: result.last_run_inputs,
503 gas_report_traces: result.gas_report_traces,
504 coverage: result.coverage,
505 metrics: result.metrics,
506 })
507 }
508
509 fn prepare_test(
513 &mut self,
514 invariant_contract: &InvariantContract<'_>,
515 fuzz_fixtures: &FuzzFixtures,
516 deployed_libs: &[Address],
517 ) -> Result<(InvariantTest, impl Strategy<Value = BasicTxDetails>)> {
518 self.select_contract_artifacts(invariant_contract.address)?;
520 let (targeted_senders, targeted_contracts) =
521 self.select_contracts_and_senders(invariant_contract.address)?;
522
523 let fuzz_state = EvmFuzzState::new(
525 self.executor.backend().mem_db(),
526 self.config.dictionary,
527 deployed_libs,
528 );
529
530 let strategy = invariant_strat(
532 fuzz_state.clone(),
533 targeted_senders,
534 targeted_contracts.clone(),
535 self.config.dictionary.dictionary_weight,
536 fuzz_fixtures.clone(),
537 )
538 .no_shrink();
539
540 let mut call_generator = None;
543 if self.config.call_override {
544 let target_contract_ref = Arc::new(RwLock::new(Address::ZERO));
545
546 call_generator = Some(RandomCallGenerator::new(
547 invariant_contract.address,
548 self.runner.clone(),
549 override_call_strat(
550 fuzz_state.clone(),
551 targeted_contracts.clone(),
552 target_contract_ref.clone(),
553 fuzz_fixtures.clone(),
554 ),
555 target_contract_ref,
556 ));
557 }
558
559 self.executor.inspector_mut().fuzzer =
560 Some(Fuzzer { call_generator, fuzz_state: fuzz_state.clone(), collect: true });
561
562 let mut failures = InvariantFailures::new();
567 let last_call_results = assert_invariants(
568 invariant_contract,
569 &self.config,
570 &targeted_contracts,
571 &self.executor,
572 &[],
573 &mut failures,
574 )?;
575 if let Some(error) = failures.error {
576 return Err(eyre!(error.revert_reason().unwrap_or_default()))
577 }
578
579 Ok((
580 InvariantTest::new(
581 fuzz_state,
582 targeted_contracts,
583 failures,
584 last_call_results,
585 self.runner.clone(),
586 ),
587 strategy,
588 ))
589 }
590
591 pub fn select_contract_artifacts(&mut self, invariant_address: Address) -> Result<()> {
601 let targeted_artifact_selectors = self
602 .executor
603 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
604
605 for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
607 targeted_artifact_selectors
608 {
609 let identifier = self.validate_selected_contract(artifact, &selectors)?;
610 self.artifact_filters.targeted.entry(identifier).or_default().extend(selectors);
611 }
612
613 let targeted_artifacts = self
614 .executor
615 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
616 let excluded_artifacts = self
617 .executor
618 .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
619
620 for contract in excluded_artifacts {
622 let identifier = self.validate_selected_contract(contract, &[])?;
623
624 if !self.artifact_filters.excluded.contains(&identifier) {
625 self.artifact_filters.excluded.push(identifier);
626 }
627 }
628
629 for (artifact, contract) in self.project_contracts.iter() {
631 if contract
632 .abi
633 .functions()
634 .filter(|func| {
635 !matches!(
636 func.state_mutability,
637 alloy_json_abi::StateMutability::Pure |
638 alloy_json_abi::StateMutability::View
639 )
640 })
641 .count() ==
642 0 &&
643 !self.artifact_filters.excluded.contains(&artifact.identifier())
644 {
645 self.artifact_filters.excluded.push(artifact.identifier());
646 }
647 }
648
649 for contract in targeted_artifacts {
652 let identifier = self.validate_selected_contract(contract, &[])?;
653
654 if !self.artifact_filters.targeted.contains_key(&identifier) &&
655 !self.artifact_filters.excluded.contains(&identifier)
656 {
657 self.artifact_filters.targeted.insert(identifier, vec![]);
658 }
659 }
660 Ok(())
661 }
662
663 fn validate_selected_contract(
666 &mut self,
667 contract: String,
668 selectors: &[FixedBytes<4>],
669 ) -> Result<String> {
670 if let Some((artifact, contract_data)) =
671 self.project_contracts.find_by_name_or_identifier(&contract)?
672 {
673 for selector in selectors {
675 contract_data
676 .abi
677 .functions()
678 .find(|func| func.selector().as_slice() == selector.as_slice())
679 .wrap_err(format!("{contract} does not have the selector {selector:?}"))?;
680 }
681
682 return Ok(artifact.identifier())
683 }
684 eyre::bail!("{contract} not found in the project. Allowed format: `contract_name` or `contract_path:contract_name`.");
685 }
686
687 pub fn select_contracts_and_senders(
690 &self,
691 to: Address,
692 ) -> Result<(SenderFilters, FuzzRunIdentifiedContracts)> {
693 let targeted_senders =
694 self.executor.call_sol_default(to, &IInvariantTest::targetSendersCall {});
695 let mut excluded_senders =
696 self.executor.call_sol_default(to, &IInvariantTest::excludeSendersCall {});
697 excluded_senders.extend([
699 CHEATCODE_ADDRESS,
700 HARDHAT_CONSOLE_ADDRESS,
701 DEFAULT_CREATE2_DEPLOYER,
702 ]);
703 excluded_senders.extend(PRECOMPILES);
705 let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
706
707 let selected = self.executor.call_sol_default(to, &IInvariantTest::targetContractsCall {});
708 let excluded = self.executor.call_sol_default(to, &IInvariantTest::excludeContractsCall {});
709
710 let contracts = self
711 .setup_contracts
712 .iter()
713 .filter(|&(addr, (identifier, _))| {
714 if *addr == to && selected.contains(&to) {
716 return true;
717 }
718
719 *addr != to &&
720 *addr != CHEATCODE_ADDRESS &&
721 *addr != HARDHAT_CONSOLE_ADDRESS &&
722 (selected.is_empty() || selected.contains(addr)) &&
723 (excluded.is_empty() || !excluded.contains(addr)) &&
724 self.artifact_filters.matches(identifier)
725 })
726 .map(|(addr, (identifier, abi))| {
727 (*addr, TargetedContract::new(identifier.clone(), abi.clone()))
728 })
729 .collect();
730 let mut contracts = TargetedContracts { inner: contracts };
731
732 self.target_interfaces(to, &mut contracts)?;
733
734 self.select_selectors(to, &mut contracts)?;
735
736 if contracts.is_empty() {
738 eyre::bail!("No contracts to fuzz.");
739 }
740
741 Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
742 }
743
744 pub fn target_interfaces(
749 &self,
750 invariant_address: Address,
751 targeted_contracts: &mut TargetedContracts,
752 ) -> Result<()> {
753 let interfaces = self
754 .executor
755 .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {});
756
757 let mut combined = TargetedContracts::new();
763
764 for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
767 for identifier in artifacts {
769 if let Some(abi) = self.project_contracts.find_abi_by_name_or_identifier(identifier)
771 {
772 combined
773 .entry(*addr)
775 .and_modify(|entry| {
777 entry.abi.functions.extend(abi.functions.clone());
779 })
780 .or_insert_with(|| TargetedContract::new(identifier.to_string(), abi));
782 }
783 }
784 }
785
786 targeted_contracts.extend(combined.inner);
787
788 Ok(())
789 }
790
791 pub fn select_selectors(
794 &self,
795 address: Address,
796 targeted_contracts: &mut TargetedContracts,
797 ) -> Result<()> {
798 for (address, (identifier, _)) in self.setup_contracts {
799 if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
800 self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
801 }
802 }
803
804 let selectors =
806 self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
807 for IInvariantTest::FuzzSelector { addr, selectors } in selectors {
808 self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
809 }
810
811 let excluded_selectors =
813 self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
814 for IInvariantTest::FuzzSelector { addr, selectors } in excluded_selectors {
815 self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
816 }
817
818 Ok(())
819 }
820
821 fn add_address_with_functions(
823 &self,
824 address: Address,
825 selectors: &[Selector],
826 should_exclude: bool,
827 targeted_contracts: &mut TargetedContracts,
828 ) -> eyre::Result<()> {
829 if selectors.is_empty() {
831 return Ok(())
832 }
833
834 let contract = match targeted_contracts.entry(address) {
835 Entry::Occupied(entry) => entry.into_mut(),
836 Entry::Vacant(entry) => {
837 let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
838 eyre::eyre!(
839 "[{}] address does not have an associated contract: {}",
840 if should_exclude { "excludeSelectors" } else { "targetSelectors" },
841 address
842 )
843 })?;
844 entry.insert(TargetedContract::new(identifier.clone(), abi.clone()))
845 }
846 };
847 contract.add_selectors(selectors.iter().copied(), should_exclude)?;
848 Ok(())
849 }
850}
851
852fn collect_data(
856 invariant_test: &InvariantTest,
857 state_changeset: &mut HashMap<Address, Account>,
858 tx: &BasicTxDetails,
859 call_result: &RawCallResult,
860 run_depth: u32,
861) {
862 let mut has_code = false;
864 if let Some(Some(code)) =
865 state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
866 {
867 has_code = !code.is_empty();
868 }
869
870 let mut sender_changeset = None;
872 if !has_code {
873 sender_changeset = state_changeset.remove(&tx.sender);
874 }
875
876 invariant_test.fuzz_state.collect_values_from_call(
878 &invariant_test.targeted_contracts,
879 tx,
880 &call_result.result,
881 &call_result.logs,
882 &*state_changeset,
883 run_depth,
884 );
885
886 if let Some(changed) = sender_changeset {
888 state_changeset.insert(tx.sender, changed);
889 }
890}
891
892pub(crate) fn call_after_invariant_function(
896 executor: &Executor,
897 to: Address,
898) -> std::result::Result<(RawCallResult, bool), EvmError> {
899 let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
900 let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
901 let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
902 Ok((call_result, success))
903}
904
905pub(crate) fn call_invariant_function(
907 executor: &Executor,
908 address: Address,
909 calldata: Bytes,
910) -> Result<(RawCallResult, bool)> {
911 let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
912 let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
913 Ok((call_result, success))
914}