foundry_evm/executors/invariant/
result.rs
1use super::{
2 call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData,
3 InvariantFailures, InvariantFuzzError, InvariantMetrics, InvariantTest, InvariantTestRun,
4};
5use crate::executors::{Executor, RawCallResult};
6use alloy_dyn_abi::JsonAbiExt;
7use eyre::Result;
8use foundry_config::InvariantConfig;
9use foundry_evm_core::utils::StateChangeset;
10use foundry_evm_coverage::HitMaps;
11use foundry_evm_fuzz::{
12 invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract},
13 FuzzedCases,
14};
15use revm_inspectors::tracing::CallTraceArena;
16use std::{borrow::Cow, collections::HashMap};
17
18#[derive(Debug)]
20pub struct InvariantFuzzTestResult {
21 pub error: Option<InvariantFuzzError>,
22 pub cases: Vec<FuzzedCases>,
24 pub reverts: usize,
26 pub last_run_inputs: Vec<BasicTxDetails>,
29 pub gas_report_traces: Vec<Vec<CallTraceArena>>,
31 pub coverage: Option<HitMaps>,
33 pub metrics: HashMap<String, InvariantMetrics>,
35}
36
37pub(crate) struct RichInvariantResults {
41 pub(crate) can_continue: bool,
42 pub(crate) call_result: Option<RawCallResult>,
43}
44
45impl RichInvariantResults {
46 fn new(can_continue: bool, call_result: Option<RawCallResult>) -> Self {
47 Self { can_continue, call_result }
48 }
49}
50
51pub(crate) fn assert_invariants(
55 invariant_contract: &InvariantContract<'_>,
56 invariant_config: &InvariantConfig,
57 targeted_contracts: &FuzzRunIdentifiedContracts,
58 executor: &Executor,
59 calldata: &[BasicTxDetails],
60 invariant_failures: &mut InvariantFailures,
61) -> Result<Option<RawCallResult>> {
62 let mut inner_sequence = vec![];
63
64 if let Some(fuzzer) = &executor.inspector().fuzzer {
65 if let Some(call_generator) = &fuzzer.call_generator {
66 inner_sequence.extend(call_generator.last_sequence.read().iter().cloned());
67 }
68 }
69
70 let (call_result, success) = call_invariant_function(
71 executor,
72 invariant_contract.address,
73 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
74 )?;
75 if !success {
76 if invariant_failures.error.is_none() {
78 let case_data = FailedInvariantCaseData::new(
79 invariant_contract,
80 invariant_config,
81 targeted_contracts,
82 calldata,
83 call_result,
84 &inner_sequence,
85 );
86 invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data));
87 return Ok(None);
88 }
89 }
90
91 Ok(Some(call_result))
92}
93
94pub(crate) fn can_continue(
97 invariant_contract: &InvariantContract<'_>,
98 invariant_test: &InvariantTest,
99 invariant_run: &mut InvariantTestRun,
100 invariant_config: &InvariantConfig,
101 call_result: RawCallResult,
102 state_changeset: &StateChangeset,
103) -> Result<RichInvariantResults> {
104 let mut call_results = None;
105
106 let handlers_succeeded = || {
107 invariant_test.targeted_contracts.targets.lock().keys().all(|address| {
108 invariant_run.executor.is_success(
109 *address,
110 false,
111 Cow::Borrowed(state_changeset),
112 false,
113 )
114 })
115 };
116
117 if !call_result.reverted && handlers_succeeded() {
119 if let Some(traces) = call_result.traces {
120 invariant_run.run_traces.push(traces);
121 }
122
123 call_results = assert_invariants(
124 invariant_contract,
125 invariant_config,
126 &invariant_test.targeted_contracts,
127 &invariant_run.executor,
128 &invariant_run.inputs,
129 &mut invariant_test.execution_data.borrow_mut().failures,
130 )?;
131 if call_results.is_none() {
132 return Ok(RichInvariantResults::new(false, None));
133 }
134 } else {
135 let mut invariant_data = invariant_test.execution_data.borrow_mut();
137 invariant_data.failures.reverts += 1;
138 if invariant_config.fail_on_revert {
140 let case_data = FailedInvariantCaseData::new(
141 invariant_contract,
142 invariant_config,
143 &invariant_test.targeted_contracts,
144 &invariant_run.inputs,
145 call_result,
146 &[],
147 );
148 invariant_data.failures.revert_reason = Some(case_data.revert_reason.clone());
149 invariant_data.failures.error = Some(InvariantFuzzError::Revert(case_data));
150
151 return Ok(RichInvariantResults::new(false, None));
152 } else if call_result.reverted {
153 invariant_run.inputs.pop();
156 }
157 }
158 Ok(RichInvariantResults::new(true, call_results))
159}
160
161pub(crate) fn assert_after_invariant(
164 invariant_contract: &InvariantContract<'_>,
165 invariant_test: &InvariantTest,
166 invariant_run: &InvariantTestRun,
167 invariant_config: &InvariantConfig,
168) -> Result<bool> {
169 let (call_result, success) =
170 call_after_invariant_function(&invariant_run.executor, invariant_contract.address)?;
171 if !success {
173 let case_data = FailedInvariantCaseData::new(
174 invariant_contract,
175 invariant_config,
176 &invariant_test.targeted_contracts,
177 &invariant_run.inputs,
178 call_result,
179 &[],
180 );
181 invariant_test.set_error(InvariantFuzzError::BrokenInvariant(case_data));
182 }
183 Ok(success)
184}