foundry_evm/executors/invariant/
error.rs1use super::InvariantContract;
2use crate::executors::RawCallResult;
3use alloy_primitives::{Address, Bytes};
4use foundry_config::InvariantConfig;
5use foundry_evm_core::{
6 decode::{ASSERTION_FAILED_PREFIX, EMPTY_REVERT_DATA, RevertDecoder},
7 evm::FoundryEvmNetwork,
8};
9use foundry_evm_fuzz::{BasicTxDetails, Reason, invariant::FuzzRunIdentifiedContracts};
10use proptest::test_runner::TestError;
11
12#[derive(Clone, Default)]
14pub struct InvariantFailures {
15 pub reverts: usize,
17 pub revert_reason: Option<String>,
19 pub error: Option<InvariantFuzzError>,
21}
22
23impl InvariantFailures {
24 pub fn new() -> Self {
25 Self::default()
26 }
27
28 pub fn into_inner(self) -> (usize, Option<InvariantFuzzError>) {
29 (self.reverts, self.error)
30 }
31}
32
33#[derive(Clone, Debug)]
34pub enum InvariantFuzzError {
35 Revert(FailedInvariantCaseData),
36 BrokenInvariant(FailedInvariantCaseData),
37 MaxAssumeRejects(u32),
38}
39
40impl InvariantFuzzError {
41 pub fn revert_reason(&self) -> Option<String> {
42 match self {
43 Self::BrokenInvariant(case_data) | Self::Revert(case_data) => {
44 (!case_data.revert_reason.is_empty()).then(|| case_data.revert_reason.clone())
45 }
46 Self::MaxAssumeRejects(allowed) => {
47 Some(format!("`vm.assume` rejected too many inputs ({allowed} allowed)"))
48 }
49 }
50 }
51}
52
53#[derive(Clone, Debug)]
54pub struct FailedInvariantCaseData {
55 pub test_error: TestError<Vec<BasicTxDetails>>,
57 pub return_reason: Reason,
59 pub revert_reason: String,
61 pub addr: Address,
63 pub calldata: Bytes,
65 pub inner_sequence: Vec<Option<BasicTxDetails>>,
67 pub shrink_run_limit: u32,
69 pub fail_on_revert: bool,
71 pub assertion_failure: bool,
73}
74
75impl FailedInvariantCaseData {
76 pub fn new<FEN: FoundryEvmNetwork>(
77 invariant_contract: &InvariantContract<'_>,
78 invariant_config: &InvariantConfig,
79 targeted_contracts: &FuzzRunIdentifiedContracts,
80 calldata: &[BasicTxDetails],
81 call_result: RawCallResult<FEN>,
82 inner_sequence: &[Option<BasicTxDetails>],
83 ) -> Self {
84 let revert_reason = RevertDecoder::new()
86 .with_abis(targeted_contracts.targets.lock().values().map(|c| &c.abi))
87 .with_abi(invariant_contract.abi)
88 .decode(call_result.result.as_ref(), call_result.exit_reason);
89 let revert_reason =
92 if !call_result.reverted && matches!(revert_reason.as_str(), "" | EMPTY_REVERT_DATA) {
93 ASSERTION_FAILED_PREFIX.to_string()
94 } else {
95 revert_reason
96 };
97
98 let func = invariant_contract.invariant_function;
99 debug_assert!(func.inputs.is_empty());
100 let origin = func.name.as_str();
101 Self {
102 test_error: TestError::Fail(
103 format!("{origin}, reason: {revert_reason}").into(),
104 calldata.to_vec(),
105 ),
106 return_reason: "".into(),
107 revert_reason,
108 addr: invariant_contract.address,
109 calldata: func.selector().to_vec().into(),
110 inner_sequence: inner_sequence.to_vec(),
111 shrink_run_limit: invariant_config.shrink_run_limit,
112 fail_on_revert: invariant_config.fail_on_revert,
113 assertion_failure: false,
114 }
115 }
116
117 pub fn with_assertion_failure(mut self, assertion_failure: bool) -> Self {
120 self.assertion_failure = assertion_failure;
121 if assertion_failure && matches!(self.revert_reason.as_str(), "" | EMPTY_REVERT_DATA) {
122 self.revert_reason = ASSERTION_FAILED_PREFIX.to_string();
123 }
124 self
125 }
126}