1use crate::{
2 executors::{Executor, RawCallResult},
3 inspectors::Fuzzer,
4};
5use alloy_primitives::{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::primitives::HashMap;
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 result = self
602 .executor
603 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactSelectorsCall {});
604
605 for IInvariantTest::FuzzArtifactSelector { artifact, selectors } in
607 result.targetedArtifactSelectors
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 selected = self
614 .executor
615 .call_sol_default(invariant_address, &IInvariantTest::targetArtifactsCall {});
616 let excluded = self
617 .executor
618 .call_sol_default(invariant_address, &IInvariantTest::excludeArtifactsCall {});
619
620 for contract in excluded.excludedArtifacts {
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 selected.targetedArtifacts {
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 = self
694 .executor
695 .call_sol_default(to, &IInvariantTest::targetSendersCall {})
696 .targetedSenders;
697 let mut excluded_senders = self
698 .executor
699 .call_sol_default(to, &IInvariantTest::excludeSendersCall {})
700 .excludedSenders;
701 excluded_senders.extend([
703 CHEATCODE_ADDRESS,
704 HARDHAT_CONSOLE_ADDRESS,
705 DEFAULT_CREATE2_DEPLOYER,
706 ]);
707 excluded_senders.extend(PRECOMPILES);
709 let sender_filters = SenderFilters::new(targeted_senders, excluded_senders);
710
711 let selected = self
712 .executor
713 .call_sol_default(to, &IInvariantTest::targetContractsCall {})
714 .targetedContracts;
715 let excluded = self
716 .executor
717 .call_sol_default(to, &IInvariantTest::excludeContractsCall {})
718 .excludedContracts;
719
720 let contracts = self
721 .setup_contracts
722 .iter()
723 .filter(|&(addr, (identifier, _))| {
724 if *addr == to && selected.contains(&to) {
726 return true;
727 }
728
729 *addr != to &&
730 *addr != CHEATCODE_ADDRESS &&
731 *addr != HARDHAT_CONSOLE_ADDRESS &&
732 (selected.is_empty() || selected.contains(addr)) &&
733 (excluded.is_empty() || !excluded.contains(addr)) &&
734 self.artifact_filters.matches(identifier)
735 })
736 .map(|(addr, (identifier, abi))| {
737 (*addr, TargetedContract::new(identifier.clone(), abi.clone()))
738 })
739 .collect();
740 let mut contracts = TargetedContracts { inner: contracts };
741
742 self.target_interfaces(to, &mut contracts)?;
743
744 self.select_selectors(to, &mut contracts)?;
745
746 if contracts.is_empty() {
748 eyre::bail!("No contracts to fuzz.");
749 }
750
751 Ok((sender_filters, FuzzRunIdentifiedContracts::new(contracts, selected.is_empty())))
752 }
753
754 pub fn target_interfaces(
759 &self,
760 invariant_address: Address,
761 targeted_contracts: &mut TargetedContracts,
762 ) -> Result<()> {
763 let interfaces = self
764 .executor
765 .call_sol_default(invariant_address, &IInvariantTest::targetInterfacesCall {})
766 .targetedInterfaces;
767
768 let mut combined = TargetedContracts::new();
774
775 for IInvariantTest::FuzzInterface { addr, artifacts } in &interfaces {
778 for identifier in artifacts {
780 if let Some(abi) = self.project_contracts.find_abi_by_name_or_identifier(identifier)
782 {
783 combined
784 .entry(*addr)
786 .and_modify(|entry| {
788 entry.abi.functions.extend(abi.functions.clone());
790 })
791 .or_insert_with(|| TargetedContract::new(identifier.to_string(), abi));
793 }
794 }
795 }
796
797 targeted_contracts.extend(combined.inner);
798
799 Ok(())
800 }
801
802 pub fn select_selectors(
805 &self,
806 address: Address,
807 targeted_contracts: &mut TargetedContracts,
808 ) -> Result<()> {
809 for (address, (identifier, _)) in self.setup_contracts {
810 if let Some(selectors) = self.artifact_filters.targeted.get(identifier) {
811 self.add_address_with_functions(*address, selectors, false, targeted_contracts)?;
812 }
813 }
814
815 let selectors =
817 self.executor.call_sol_default(address, &IInvariantTest::targetSelectorsCall {});
818 for IInvariantTest::FuzzSelector { addr, selectors } in selectors.targetedSelectors {
819 self.add_address_with_functions(addr, &selectors, false, targeted_contracts)?;
820 }
821
822 let selectors =
824 self.executor.call_sol_default(address, &IInvariantTest::excludeSelectorsCall {});
825 for IInvariantTest::FuzzSelector { addr, selectors } in selectors.excludedSelectors {
826 self.add_address_with_functions(addr, &selectors, true, targeted_contracts)?;
827 }
828
829 Ok(())
830 }
831
832 fn add_address_with_functions(
834 &self,
835 address: Address,
836 selectors: &[Selector],
837 should_exclude: bool,
838 targeted_contracts: &mut TargetedContracts,
839 ) -> eyre::Result<()> {
840 if selectors.is_empty() {
842 return Ok(())
843 }
844
845 let contract = match targeted_contracts.entry(address) {
846 Entry::Occupied(entry) => entry.into_mut(),
847 Entry::Vacant(entry) => {
848 let (identifier, abi) = self.setup_contracts.get(&address).ok_or_else(|| {
849 eyre::eyre!(
850 "[{}] address does not have an associated contract: {}",
851 if should_exclude { "excludeSelectors" } else { "targetSelectors" },
852 address
853 )
854 })?;
855 entry.insert(TargetedContract::new(identifier.clone(), abi.clone()))
856 }
857 };
858 contract.add_selectors(selectors.iter().copied(), should_exclude)?;
859 Ok(())
860 }
861}
862
863fn collect_data(
867 invariant_test: &InvariantTest,
868 state_changeset: &mut HashMap<Address, revm::primitives::Account>,
869 tx: &BasicTxDetails,
870 call_result: &RawCallResult,
871 run_depth: u32,
872) {
873 let mut has_code = false;
875 if let Some(Some(code)) =
876 state_changeset.get(&tx.sender).map(|account| account.info.code.as_ref())
877 {
878 has_code = !code.is_empty();
879 }
880
881 let mut sender_changeset = None;
883 if !has_code {
884 sender_changeset = state_changeset.remove(&tx.sender);
885 }
886
887 invariant_test.fuzz_state.collect_values_from_call(
889 &invariant_test.targeted_contracts,
890 tx,
891 &call_result.result,
892 &call_result.logs,
893 &*state_changeset,
894 run_depth,
895 );
896
897 if let Some(changed) = sender_changeset {
899 state_changeset.insert(tx.sender, changed);
900 }
901}
902
903pub(crate) fn call_after_invariant_function(
907 executor: &Executor,
908 to: Address,
909) -> std::result::Result<(RawCallResult, bool), EvmError> {
910 let calldata = Bytes::from_static(&IInvariantTest::afterInvariantCall::SELECTOR);
911 let mut call_result = executor.call_raw(CALLER, to, calldata, U256::ZERO)?;
912 let success = executor.is_raw_call_mut_success(to, &mut call_result, false);
913 Ok((call_result, success))
914}
915
916pub(crate) fn call_invariant_function(
918 executor: &Executor,
919 address: Address,
920 calldata: Bytes,
921) -> Result<(RawCallResult, bool)> {
922 let mut call_result = executor.call_raw(CALLER, address, calldata, U256::ZERO)?;
923 let success = executor.is_raw_call_mut_success(address, &mut call_result, false);
924 Ok((call_result, success))
925}