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 has_delay = tx.warp.is_some_and(|delay| !delay.is_zero())
90 || tx.roll.is_some_and(|delay| !delay.is_zero());
91 let snapshot = (matches!(self.mode, FuzzCampaignMode::Invariant { .. })
92 && has_delay)
93 .then(|| BlockSnapshot::new(executor));
94 let result = match self.mode {
95 FuzzCampaignMode::Stateless => executor.call_raw(
96 tx.sender,
97 tx.call_details.target,
98 tx.call_details.calldata.clone(),
99 tx.call_details.value.unwrap_or_default(),
100 )?,
101 FuzzCampaignMode::Invariant { .. } => execute_invariant_tx(executor, tx)?,
102 };
103 (result, snapshot)
104 };
105 if result.execution_cancelled {
106 if let Some(snapshot) = block_snapshot {
107 let (executor, _) = parts(state);
108 snapshot.restore(executor);
109 }
110 return Ok(CampaignSequenceOutcome::Cancelled);
111 }
112 on_event(state, CampaignEvent::Feedback(&mut result))?;
113 let discarded = result.result.as_ref() == MAGIC_ASSUME;
114 let kind = if discarded {
115 if let Some(snapshot) = block_snapshot {
116 let (executor, _) = parts(state);
117 snapshot.restore(executor);
118 }
119 CampaignCallKind::AssumptionRejected
120 } else {
121 if matches!(self.mode, FuzzCampaignMode::Invariant { .. }) {
122 let (executor, _) = parts(state);
123 executor.commit(&mut result);
124 }
125 CampaignCallKind::Accepted
126 };
127 let should_check = match self.mode {
128 FuzzCampaignMode::Stateless => true,
129 FuzzCampaignMode::Invariant { optimization: true, .. } => true,
130 FuzzCampaignMode::Invariant { check_interval: 0, .. } => last_call,
131 FuzzCampaignMode::Invariant { check_interval, .. } => {
132 check_interval == 1
133 || (current_depth + 1).is_multiple_of(check_interval)
134 || last_call
135 }
136 };
137 let mut result = Some(result);
138 if on_event(state, CampaignEvent::Check { result: &mut result, kind, should_check })?
139 == CampaignControl::Stop
140 {
141 break CampaignSequenceOutcome::Stopped;
142 }
143 if !discarded {
144 on_event(state, CampaignEvent::Advance)?;
145 }
146 let next_depth = depth(state);
147 on_event(state, CampaignEvent::Next { discarded, depth: next_depth })?;
148 };
149 on_event(state, CampaignEvent::PostCheck)?;
150 Ok(outcome)
151 }
152}
153
154struct BlockSnapshot<FEN: FoundryEvmNetwork> {
155 env: BlockEnvFor<FEN>,
156 cheatcode: Option<BlockEnvFor<FEN>>,
157}
158
159impl<FEN: FoundryEvmNetwork> BlockSnapshot<FEN> {
160 fn new(executor: &Executor<FEN>) -> Self {
161 Self {
162 env: executor.evm_env().block_env.clone(),
163 cheatcode: executor.inspector().cheatcodes.as_ref().and_then(|c| c.block.clone()),
164 }
165 }
166
167 fn restore(self, executor: &mut Executor<FEN>) {
168 executor.evm_env_mut().block_env = self.env;
169 if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() {
170 cheatcodes.block = self.cheatcode;
171 }
172 }
173}
174
175pub(super) fn execute_invariant_tx<FEN: FoundryEvmNetwork>(
176 executor: &mut Executor<FEN>,
177 tx: &mut BasicTxDetails,
178) -> Result<RawCallResult<FEN>> {
179 let warp = tx.warp.unwrap_or_default();
180 let roll = tx.roll.unwrap_or_default();
181 if warp > 0 || roll > 0 {
182 let needs_cheatcode_block = executor
183 .inspector()
184 .cheatcodes
185 .as_ref()
186 .is_some_and(|cheatcodes| cheatcodes.block.is_none());
187 let block_env = {
188 let block_env = &mut executor.evm_env_mut().block_env;
189 block_env.set_timestamp(block_env.timestamp() + warp);
190 block_env.set_number(block_env.number() + roll);
191 needs_cheatcode_block.then(|| block_env.clone())
192 };
193 if let Some(cheatcodes) = executor.inspector_mut().cheatcodes.as_mut() {
194 if let Some(block) = cheatcodes.block.as_mut() {
195 block.set_timestamp(block.timestamp() + warp);
196 block.set_number(block.number() + roll);
197 } else {
198 cheatcodes.block = Some(block_env.unwrap());
199 }
200 }
201 }
202 let value = match tx.call_details.value {
203 Some(requested) if !requested.is_zero() => requested.min(executor.get_balance(tx.sender)?),
204 _ => U256::ZERO,
205 };
206 tx.call_details.value = (!value.is_zero()).then_some(value);
209 executor
210 .call_raw(tx.sender, tx.call_details.target, tx.call_details.calldata.clone(), value)
211 .map_err(|error| eyre!("Could not make raw evm call: {error}"))
212}