foundry_evm/executors/invariant/
replay.rs
1use super::{
2 call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData,
3 shrink_sequence,
4};
5use crate::executors::Executor;
6use alloy_dyn_abi::JsonAbiExt;
7use alloy_primitives::{map::HashMap, Log, U256};
8use eyre::Result;
9use foundry_common::{ContractsByAddress, ContractsByArtifact};
10use foundry_evm_coverage::HitMaps;
11use foundry_evm_fuzz::{
12 invariant::{BasicTxDetails, InvariantContract},
13 BaseCounterExample,
14};
15use foundry_evm_traces::{load_contracts, TraceKind, TraceMode, Traces};
16use indicatif::ProgressBar;
17use parking_lot::RwLock;
18use proptest::test_runner::TestError;
19use std::sync::Arc;
20
21#[expect(clippy::too_many_arguments)]
24pub fn replay_run(
25 invariant_contract: &InvariantContract<'_>,
26 mut executor: Executor,
27 known_contracts: &ContractsByArtifact,
28 mut ided_contracts: ContractsByAddress,
29 logs: &mut Vec<Log>,
30 traces: &mut Traces,
31 coverage: &mut Option<HitMaps>,
32 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
33 inputs: &[BasicTxDetails],
34 show_solidity: bool,
35) -> Result<Vec<BaseCounterExample>> {
36 if executor.inspector().tracer.is_none() {
38 executor.set_tracing(TraceMode::Call);
39 }
40
41 let mut counterexample_sequence = vec![];
42
43 for tx in inputs {
45 let call_result = executor.transact_raw(
46 tx.sender,
47 tx.call_details.target,
48 tx.call_details.calldata.clone(),
49 U256::ZERO,
50 )?;
51
52 logs.extend(call_result.logs);
53 traces.push((TraceKind::Execution, call_result.traces.clone().unwrap()));
54 HitMaps::merge_opt(coverage, call_result.coverage);
55
56 ided_contracts
58 .extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts));
59
60 counterexample_sequence.push(BaseCounterExample::from_invariant_call(
62 tx.sender,
63 tx.call_details.target,
64 &tx.call_details.calldata,
65 &ided_contracts,
66 call_result.traces,
67 show_solidity,
68 ));
69 }
70
71 let (invariant_result, invariant_success) = call_invariant_function(
77 &executor,
78 invariant_contract.address,
79 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
80 )?;
81 traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap()));
82 logs.extend(invariant_result.logs);
83 deprecated_cheatcodes.extend(
84 invariant_result
85 .cheatcodes
86 .as_ref()
87 .map_or_else(Default::default, |cheats| cheats.deprecated.clone()),
88 );
89
90 if invariant_contract.call_after_invariant && invariant_success {
92 let (after_invariant_result, _) =
93 call_after_invariant_function(&executor, invariant_contract.address)?;
94 traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap()));
95 logs.extend(after_invariant_result.logs);
96 }
97
98 Ok(counterexample_sequence)
99}
100
101#[expect(clippy::too_many_arguments)]
103pub fn replay_error(
104 failed_case: &FailedInvariantCaseData,
105 invariant_contract: &InvariantContract<'_>,
106 mut executor: Executor,
107 known_contracts: &ContractsByArtifact,
108 ided_contracts: ContractsByAddress,
109 logs: &mut Vec<Log>,
110 traces: &mut Traces,
111 coverage: &mut Option<HitMaps>,
112 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
113 progress: Option<&ProgressBar>,
114 show_solidity: bool,
115) -> Result<Vec<BaseCounterExample>> {
116 match failed_case.test_error {
117 TestError::Abort(_) => Ok(vec![]),
119 TestError::Fail(_, ref calls) => {
120 let calls = shrink_sequence(
122 failed_case,
123 calls,
124 &executor,
125 invariant_contract.call_after_invariant,
126 progress,
127 )?;
128
129 set_up_inner_replay(&mut executor, &failed_case.inner_sequence);
130
131 replay_run(
133 invariant_contract,
134 executor,
135 known_contracts,
136 ided_contracts,
137 logs,
138 traces,
139 coverage,
140 deprecated_cheatcodes,
141 &calls,
142 show_solidity,
143 )
144 }
145 }
146}
147
148fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option<BasicTxDetails>]) {
150 if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer {
151 if let Some(call_generator) = &mut fuzzer.call_generator {
152 call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned()));
153 call_generator.set_replay(true);
154 }
155 }
156}