foundry_evm/executors/invariant/
result.rs
1use super::{
2 InvariantFailures, InvariantFuzzError, InvariantMetrics, InvariantTest, InvariantTestRun,
3 call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData,
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 FuzzedCases,
13 invariant::{BasicTxDetails, FuzzRunIdentifiedContracts, InvariantContract},
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 line_coverage: Option<HitMaps>,
33 pub metrics: HashMap<String, InvariantMetrics>,
35 pub failed_corpus_replays: usize,
37}
38
39pub(crate) struct RichInvariantResults {
43 pub(crate) can_continue: bool,
44 pub(crate) call_result: Option<RawCallResult>,
45}
46
47impl RichInvariantResults {
48 fn new(can_continue: bool, call_result: Option<RawCallResult>) -> Self {
49 Self { can_continue, call_result }
50 }
51}
52
53pub(crate) fn assert_invariants(
57 invariant_contract: &InvariantContract<'_>,
58 invariant_config: &InvariantConfig,
59 targeted_contracts: &FuzzRunIdentifiedContracts,
60 executor: &Executor,
61 calldata: &[BasicTxDetails],
62 invariant_failures: &mut InvariantFailures,
63) -> Result<Option<RawCallResult>> {
64 let mut inner_sequence = vec![];
65
66 if let Some(fuzzer) = &executor.inspector().fuzzer
67 && let Some(call_generator) = &fuzzer.call_generator
68 {
69 inner_sequence.extend(call_generator.last_sequence.read().iter().cloned());
70 }
71
72 let (call_result, success) = call_invariant_function(
73 executor,
74 invariant_contract.address,
75 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
76 )?;
77 if !success {
78 if invariant_failures.error.is_none() {
80 let case_data = FailedInvariantCaseData::new(
81 invariant_contract,
82 invariant_config,
83 targeted_contracts,
84 calldata,
85 call_result,
86 &inner_sequence,
87 );
88 invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data));
89 return Ok(None);
90 }
91 }
92
93 Ok(Some(call_result))
94}
95
96pub(crate) fn can_continue(
99 invariant_contract: &InvariantContract<'_>,
100 invariant_test: &InvariantTest,
101 invariant_run: &mut InvariantTestRun,
102 invariant_config: &InvariantConfig,
103 call_result: RawCallResult,
104 state_changeset: &StateChangeset,
105) -> Result<RichInvariantResults> {
106 let mut call_results = None;
107
108 let handlers_succeeded = || {
109 invariant_test.targeted_contracts.targets.lock().keys().all(|address| {
110 invariant_run.executor.is_success(
111 *address,
112 false,
113 Cow::Borrowed(state_changeset),
114 false,
115 )
116 })
117 };
118
119 if !call_result.reverted && handlers_succeeded() {
121 if let Some(traces) = call_result.traces {
122 invariant_run.run_traces.push(traces);
123 }
124
125 call_results = assert_invariants(
126 invariant_contract,
127 invariant_config,
128 &invariant_test.targeted_contracts,
129 &invariant_run.executor,
130 &invariant_run.inputs,
131 &mut invariant_test.execution_data.borrow_mut().failures,
132 )?;
133 if call_results.is_none() {
134 return Ok(RichInvariantResults::new(false, None));
135 }
136 } else {
137 let mut invariant_data = invariant_test.execution_data.borrow_mut();
139 invariant_data.failures.reverts += 1;
140 if invariant_config.fail_on_revert {
142 let case_data = FailedInvariantCaseData::new(
143 invariant_contract,
144 invariant_config,
145 &invariant_test.targeted_contracts,
146 &invariant_run.inputs,
147 call_result,
148 &[],
149 );
150 invariant_data.failures.revert_reason = Some(case_data.revert_reason.clone());
151 invariant_data.failures.error = Some(InvariantFuzzError::Revert(case_data));
152
153 return Ok(RichInvariantResults::new(false, None));
154 } else if call_result.reverted {
155 invariant_run.inputs.pop();
158 }
159 }
160 Ok(RichInvariantResults::new(true, call_results))
161}
162
163pub(crate) fn assert_after_invariant(
166 invariant_contract: &InvariantContract<'_>,
167 invariant_test: &InvariantTest,
168 invariant_run: &InvariantTestRun,
169 invariant_config: &InvariantConfig,
170) -> Result<bool> {
171 let (call_result, success) =
172 call_after_invariant_function(&invariant_run.executor, invariant_contract.address)?;
173 if !success {
175 let case_data = FailedInvariantCaseData::new(
176 invariant_contract,
177 invariant_config,
178 &invariant_test.targeted_contracts,
179 &invariant_run.inputs,
180 call_result,
181 &[],
182 );
183 invariant_test.set_error(InvariantFuzzError::BrokenInvariant(case_data));
184 }
185 Ok(success)
186}