foundry_evm/executors/invariant/
result.rs1use 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 alloy_primitives::I256;
8use eyre::Result;
9use foundry_config::InvariantConfig;
10use foundry_evm_core::utils::StateChangeset;
11use foundry_evm_coverage::HitMaps;
12use foundry_evm_fuzz::{
13 BasicTxDetails, FuzzedCases,
14 invariant::{FuzzRunIdentifiedContracts, InvariantContract},
15};
16use revm_inspectors::tracing::CallTraceArena;
17use std::{borrow::Cow, collections::HashMap};
18
19#[derive(Debug)]
21pub struct InvariantFuzzTestResult {
22 pub error: Option<InvariantFuzzError>,
23 pub cases: Vec<FuzzedCases>,
25 pub reverts: usize,
27 pub last_run_inputs: Vec<BasicTxDetails>,
30 pub gas_report_traces: Vec<Vec<CallTraceArena>>,
32 pub line_coverage: Option<HitMaps>,
34 pub metrics: HashMap<String, InvariantMetrics>,
36 pub failed_corpus_replays: usize,
38 pub optimization_best_value: Option<I256>,
41 pub optimization_best_sequence: Vec<BasicTxDetails>,
43}
44
45pub(crate) struct RichInvariantResults {
49 pub(crate) can_continue: bool,
50 pub(crate) call_result: Option<RawCallResult>,
51}
52
53impl RichInvariantResults {
54 pub(crate) fn new(can_continue: bool, call_result: Option<RawCallResult>) -> Self {
55 Self { can_continue, call_result }
56 }
57}
58
59pub(crate) fn assert_invariants(
63 invariant_contract: &InvariantContract<'_>,
64 invariant_config: &InvariantConfig,
65 targeted_contracts: &FuzzRunIdentifiedContracts,
66 executor: &Executor,
67 calldata: &[BasicTxDetails],
68 invariant_failures: &mut InvariantFailures,
69) -> Result<Option<RawCallResult>> {
70 let mut inner_sequence = vec![];
71
72 if let Some(fuzzer) = &executor.inspector().fuzzer
73 && let Some(call_generator) = &fuzzer.call_generator
74 {
75 inner_sequence.extend(call_generator.last_sequence.read().iter().cloned());
76 }
77
78 let (call_result, success) = call_invariant_function(
79 executor,
80 invariant_contract.address,
81 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
82 )?;
83 if !success {
84 if invariant_failures.error.is_none() {
86 let case_data = FailedInvariantCaseData::new(
87 invariant_contract,
88 invariant_config,
89 targeted_contracts,
90 calldata,
91 call_result,
92 &inner_sequence,
93 );
94 invariant_failures.error = Some(InvariantFuzzError::BrokenInvariant(case_data));
95 return Ok(None);
96 }
97 }
98
99 Ok(Some(call_result))
100}
101
102pub(crate) fn can_continue(
108 invariant_contract: &InvariantContract<'_>,
109 invariant_test: &mut InvariantTest,
110 invariant_run: &mut InvariantTestRun,
111 invariant_config: &InvariantConfig,
112 call_result: RawCallResult,
113 state_changeset: &StateChangeset,
114) -> Result<RichInvariantResults> {
115 let mut call_results = None;
116 let is_optimization = invariant_contract.is_optimization();
117
118 let handlers_succeeded = || {
119 invariant_test.targeted_contracts.targets.lock().keys().all(|address| {
120 invariant_run.executor.is_success(
121 *address,
122 false,
123 Cow::Borrowed(state_changeset),
124 false,
125 )
126 })
127 };
128
129 if !call_result.reverted && handlers_succeeded() {
130 if let Some(traces) = call_result.traces {
131 invariant_run.run_traces.push(traces);
132 }
133
134 if is_optimization {
135 let (inv_result, success) = call_invariant_function(
137 &invariant_run.executor,
138 invariant_contract.address,
139 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
140 )?;
141 if success
142 && inv_result.result.len() >= 32
143 && let Some(value) = I256::try_from_be_slice(&inv_result.result[..32])
144 {
145 invariant_test.update_optimization_value(value, &invariant_run.inputs);
146 }
147 call_results = Some(inv_result);
148 } else {
149 call_results = assert_invariants(
151 invariant_contract,
152 invariant_config,
153 &invariant_test.targeted_contracts,
154 &invariant_run.executor,
155 &invariant_run.inputs,
156 &mut invariant_test.test_data.failures,
157 )?;
158 if call_results.is_none() {
159 return Ok(RichInvariantResults::new(false, None));
160 }
161 }
162 } else {
163 let invariant_data = &mut invariant_test.test_data;
165 invariant_data.failures.reverts += 1;
166 if invariant_config.fail_on_revert {
168 let case_data = FailedInvariantCaseData::new(
169 invariant_contract,
170 invariant_config,
171 &invariant_test.targeted_contracts,
172 &invariant_run.inputs,
173 call_result,
174 &[],
175 );
176 invariant_data.failures.revert_reason = Some(case_data.revert_reason.clone());
177 invariant_data.failures.error = Some(InvariantFuzzError::Revert(case_data));
178
179 return Ok(RichInvariantResults::new(false, None));
180 } else if call_result.reverted && !is_optimization {
181 invariant_run.inputs.pop();
185 }
186 }
187 Ok(RichInvariantResults::new(true, call_results))
188}
189
190pub(crate) fn assert_after_invariant(
193 invariant_contract: &InvariantContract<'_>,
194 invariant_test: &mut InvariantTest,
195 invariant_run: &InvariantTestRun,
196 invariant_config: &InvariantConfig,
197) -> Result<bool> {
198 let (call_result, success) =
199 call_after_invariant_function(&invariant_run.executor, invariant_contract.address)?;
200 if !success {
202 let case_data = FailedInvariantCaseData::new(
203 invariant_contract,
204 invariant_config,
205 &invariant_test.targeted_contracts,
206 &invariant_run.inputs,
207 call_result,
208 &[],
209 );
210 invariant_test.set_error(InvariantFuzzError::BrokenInvariant(case_data));
211 }
212 Ok(success)
213}