1use super::{Executor, RawCallResult};
4use alloy_primitives::U256;
5use eyre::{Result, eyre};
6use foundry_evm_core::{
7 FoundryBlock,
8 constants::MAGIC_ASSUME,
9 evm::{BlockEnvFor, FoundryEvmNetwork},
10};
11use foundry_evm_fuzz::BasicTxDetails;
12use revm::context::Block;
13
14#[derive(Clone, Copy, Debug)]
16pub(crate) enum FuzzCampaignMode {
17 Stateless,
19 Invariant { check_interval: u32, optimization: bool },
21}
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub(crate) enum CampaignCallKind {
26 Accepted,
27 AssumptionRejected,
28}
29
30pub(crate) enum CampaignEvent<'a, FEN: FoundryEvmNetwork> {
31 Feedback(&'a mut RawCallResult<FEN>),
32 Check { result: &'a mut Option<RawCallResult<FEN>>, kind: CampaignCallKind, should_check: bool },
33 Advance,
34 Next { discarded: bool, depth: u32 },
35 PostCheck,
36}
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
39pub(crate) enum CampaignControl {
40 Continue,
41 Stop,
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub(crate) enum CampaignSequenceOutcome {
46 Complete,
47 Cancelled,
48 Stopped,
49}
50
51pub(crate) struct FuzzCampaign {
53 mode: FuzzCampaignMode,
54}
55
56impl FuzzCampaign {
57 pub(crate) const fn new(mode: FuzzCampaignMode) -> Self {
58 Self { mode }
59 }
60
61 pub(crate) fn run_sequence<S, FEN, Parts, Depth, Stop, Event>(
63 &self,
64 state: &mut S,
65 run_depth: u32,
66 mut parts: Parts,
67 mut depth: Depth,
68 mut should_stop: Stop,
69 mut on_event: Event,
70 ) -> Result<CampaignSequenceOutcome>
71 where
72 FEN: FoundryEvmNetwork,
73 Parts: for<'a> FnMut(&'a mut S) -> (&'a mut Executor<FEN>, &'a mut BasicTxDetails),
74 Depth: FnMut(&S) -> u32,
75 Stop: FnMut(&S) -> bool,
76 Event: for<'a> FnMut(&mut S, CampaignEvent<'a, FEN>) -> Result<CampaignControl>,
77 {
78 let outcome = loop {
79 if depth(state) >= run_depth {
80 break CampaignSequenceOutcome::Complete;
81 }
82 if should_stop(state) {
83 return Ok(CampaignSequenceOutcome::Cancelled);
84 }
85 let current_depth = depth(state);
86 let last_call = current_depth == run_depth - 1;
87 let (mut result, block_snapshot) = {
88 let (executor, tx) = parts(state);
89 let snapshot = matches!(self.mode, FuzzCampaignMode::Invariant { .. })
90 .then(|| BlockSnapshot::for_delayed_call(executor, tx))
91 .flatten();
92 let result = match self.mode {
93 FuzzCampaignMode::Stateless => executor.call_raw(
94 tx.sender,
95 tx.call_details.target,
96 tx.call_details.calldata.clone(),
97 tx.call_details.value.unwrap_or_default(),
98 )?,
99 FuzzCampaignMode::Invariant { .. } => execute_invariant_tx(executor, tx)?,
100 };
101 (result, snapshot)
102 };
103 if result.execution_cancelled {
104 if let Some(snapshot) = block_snapshot {
105 let (executor, _) = parts(state);
106 snapshot.restore(executor);
107 }
108 return Ok(CampaignSequenceOutcome::Cancelled);
109 }
110 on_event(state, CampaignEvent::Feedback(&mut result))?;
111 let kind = match self.mode {
112 FuzzCampaignMode::Invariant { .. } => {
113 let (executor, _) = parts(state);
114 transition_invariant_call(executor, &mut result, block_snapshot)
115 }
116 FuzzCampaignMode::Stateless => {
117 if result.result.as_ref() == MAGIC_ASSUME {
118 CampaignCallKind::AssumptionRejected
119 } else {
120 CampaignCallKind::Accepted
121 }
122 }
123 };
124 let discarded = kind == CampaignCallKind::AssumptionRejected;
125 let should_check = match self.mode {
126 FuzzCampaignMode::Stateless => true,
127 FuzzCampaignMode::Invariant { optimization: true, .. } => true,
128 FuzzCampaignMode::Invariant { check_interval: 0, .. } => last_call,
129 FuzzCampaignMode::Invariant { check_interval, .. } => {
130 check_interval == 1
131 || (current_depth + 1).is_multiple_of(check_interval)
132 || last_call
133 }
134 };
135 let mut result = Some(result);
136 if on_event(state, CampaignEvent::Check { result: &mut result, kind, should_check })?
137 == CampaignControl::Stop
138 {
139 break CampaignSequenceOutcome::Stopped;
140 }
141 if !discarded {
142 on_event(state, CampaignEvent::Advance)?;
143 }
144 let next_depth = depth(state);
145 on_event(state, CampaignEvent::Next { discarded, depth: next_depth })?;
146 };
147 on_event(state, CampaignEvent::PostCheck)?;
148 Ok(outcome)
149 }
150}
151
152struct BlockSnapshot<FEN: FoundryEvmNetwork> {
153 env: BlockEnvFor<FEN>,
154 cheatcode: Option<BlockEnvFor<FEN>>,
155}
156
157impl<FEN: FoundryEvmNetwork> BlockSnapshot<FEN> {
158 fn for_delayed_call(executor: &Executor<FEN>, tx: &BasicTxDetails) -> Option<Self> {
159 let has_delay = tx.warp.is_some_and(|delay| !delay.is_zero())
160 || tx.roll.is_some_and(|delay| !delay.is_zero());
161 has_delay.then(|| Self::new(executor))
162 }
163
164 fn new(executor: &Executor<FEN>) -> Self {
165 Self {
166 env: executor.evm_env().block_env.clone(),
167 cheatcode: executor.inspector().cheatcodes.as_ref().and_then(|c| c.block.clone()),
168 }
169 }
170
171 fn restore(self, executor: &mut Executor<FEN>) {
172 executor.evm_env_mut().block_env = self.env;
173 if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() {
174 cheatcodes.block = self.cheatcode;
175 }
176 }
177}
178
179fn transition_invariant_call<FEN: FoundryEvmNetwork>(
180 executor: &mut Executor<FEN>,
181 result: &mut RawCallResult<FEN>,
182 block_snapshot: Option<BlockSnapshot<FEN>>,
183) -> CampaignCallKind {
184 if result.result.as_ref() == MAGIC_ASSUME {
185 if let Some(snapshot) = block_snapshot {
186 snapshot.restore(executor);
187 }
188 CampaignCallKind::AssumptionRejected
189 } else {
190 executor.commit(result);
191 CampaignCallKind::Accepted
192 }
193}
194
195pub(super) fn execute_invariant_replay_tx<FEN: FoundryEvmNetwork>(
197 executor: &mut Executor<FEN>,
198 tx: &BasicTxDetails,
199) -> Result<(CampaignCallKind, RawCallResult<FEN>)> {
200 let block_snapshot = BlockSnapshot::for_delayed_call(executor, tx);
201 let mut result = execute_invariant_tx(executor, &mut tx.clone())?;
202 if result.execution_cancelled {
203 if let Some(snapshot) = block_snapshot {
204 snapshot.restore(executor);
205 }
206 return Err(eyre!("invariant replay call was cancelled"));
207 }
208 let kind = transition_invariant_call(executor, &mut result, block_snapshot);
209 Ok((kind, result))
210}
211
212pub(super) fn execute_invariant_tx<FEN: FoundryEvmNetwork>(
213 executor: &mut Executor<FEN>,
214 tx: &mut BasicTxDetails,
215) -> Result<RawCallResult<FEN>> {
216 let warp = tx.warp.unwrap_or_default();
217 let roll = tx.roll.unwrap_or_default();
218 if warp > 0 || roll > 0 {
219 let needs_cheatcode_block = executor
220 .inspector()
221 .cheatcodes
222 .as_ref()
223 .is_some_and(|cheatcodes| cheatcodes.block.is_none());
224 let block_env = {
225 let block_env = &mut executor.evm_env_mut().block_env;
226 block_env.set_timestamp(block_env.timestamp() + warp);
227 block_env.set_number(block_env.number() + roll);
228 needs_cheatcode_block.then(|| block_env.clone())
229 };
230 if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() {
231 if let Some(block) = cheatcodes.block.as_mut() {
232 block.set_timestamp(block.timestamp() + warp);
233 block.set_number(block.number() + roll);
234 } else {
235 cheatcodes.block = Some(block_env.unwrap());
236 }
237 }
238 }
239 let value = match tx.call_details.value {
240 Some(requested) if !requested.is_zero() => requested.min(executor.get_balance(tx.sender)?),
241 _ => U256::ZERO,
242 };
243 tx.call_details.value = (!value.is_zero()).then_some(value);
246 executor
247 .call_raw(tx.sender, tx.call_details.target, tx.call_details.calldata.clone(), value)
248 .map_err(|error| eyre!("Could not make raw evm call: {error}"))
249}