foundry_evm/executors/invariant/
error.rs
1use super::{BasicTxDetails, InvariantContract};
2use crate::executors::RawCallResult;
3use alloy_primitives::{Address, Bytes};
4use foundry_config::InvariantConfig;
5use foundry_evm_core::decode::RevertDecoder;
6use foundry_evm_fuzz::{invariant::FuzzRunIdentifiedContracts, Reason};
7use proptest::test_runner::TestError;
8
9#[derive(Clone, Default)]
11pub struct InvariantFailures {
12 pub reverts: usize,
14 pub revert_reason: Option<String>,
16 pub error: Option<InvariantFuzzError>,
18}
19
20impl InvariantFailures {
21 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn into_inner(self) -> (usize, Option<InvariantFuzzError>) {
26 (self.reverts, self.error)
27 }
28}
29
30#[derive(Clone, Debug)]
31pub enum InvariantFuzzError {
32 Revert(FailedInvariantCaseData),
33 BrokenInvariant(FailedInvariantCaseData),
34 MaxAssumeRejects(u32),
35}
36
37impl InvariantFuzzError {
38 pub fn revert_reason(&self) -> Option<String> {
39 match self {
40 Self::BrokenInvariant(case_data) | Self::Revert(case_data) => {
41 (!case_data.revert_reason.is_empty()).then(|| case_data.revert_reason.clone())
42 }
43 Self::MaxAssumeRejects(allowed) => {
44 Some(format!("`vm.assume` rejected too many inputs ({allowed} allowed)"))
45 }
46 }
47 }
48}
49
50#[derive(Clone, Debug)]
51pub struct FailedInvariantCaseData {
52 pub test_error: TestError<Vec<BasicTxDetails>>,
54 pub return_reason: Reason,
56 pub revert_reason: String,
58 pub addr: Address,
60 pub calldata: Bytes,
62 pub inner_sequence: Vec<Option<BasicTxDetails>>,
64 pub shrink_run_limit: u32,
66 pub fail_on_revert: bool,
68}
69
70impl FailedInvariantCaseData {
71 pub fn new(
72 invariant_contract: &InvariantContract<'_>,
73 invariant_config: &InvariantConfig,
74 targeted_contracts: &FuzzRunIdentifiedContracts,
75 calldata: &[BasicTxDetails],
76 call_result: RawCallResult,
77 inner_sequence: &[Option<BasicTxDetails>],
78 ) -> Self {
79 let revert_reason = RevertDecoder::new()
81 .with_abis(targeted_contracts.targets.lock().iter().map(|(_, c)| &c.abi))
82 .with_abi(invariant_contract.abi)
83 .decode(call_result.result.as_ref(), Some(call_result.exit_reason));
84
85 let func = invariant_contract.invariant_function;
86 debug_assert!(func.inputs.is_empty());
87 let origin = func.name.as_str();
88 Self {
89 test_error: TestError::Fail(
90 format!("{origin}, reason: {revert_reason}").into(),
91 calldata.to_vec(),
92 ),
93 return_reason: "".into(),
94 revert_reason,
95 addr: invariant_contract.address,
96 calldata: func.selector().to_vec().into(),
97 inner_sequence: inner_sequence.to_vec(),
98 shrink_run_limit: invariant_config.shrink_run_limit,
99 fail_on_revert: invariant_config.fail_on_revert,
100 }
101 }
102}