foundry_evm_symbolic/executor/
invariant.rs1use super::*;
2
3fn record_candidate_limitation(
4 limitation: &mut Option<SymbolicInvariantSearchLimitation>,
5 error: SymbolicError,
6) -> bool {
7 let search_exhausted = matches!(
8 error,
9 SymbolicError::Timeout(_) | SymbolicError::Solver(_) | SymbolicError::SolverQueryLimit(_)
10 );
11 limitation.get_or_insert_with(|| error.into());
12 search_exhausted
13}
14
15impl SymbolicExecutor {
16 #[expect(clippy::too_many_arguments)]
17 pub(super) fn execute_invariant_check<FEN: FoundryEvmNetwork>(
18 &mut self,
19 executor: &Executor<FEN>,
20 state: PathState,
21 invariant_address: Address,
22 sender: Address,
23 invariant: &Function,
24 after_invariant: Option<&Function>,
25 completed_paths: &mut usize,
26 ) -> Result<Vec<InvariantCheckOutcome>, SymbolicError> {
27 let mut call =
28 self.prepare_invariant_call(executor, state, invariant_address, sender, invariant)?;
29
30 let mut checked = Vec::new();
31 while let Some(outcome) =
32 self.execute_sequence_call_next(executor, &mut call, completed_paths)?
33 {
34 if !matches!(outcome.status, CallStatus::Success) {
35 return Ok(vec![InvariantCheckOutcome { failed: true, state: outcome.state }]);
36 }
37
38 let Some(after_invariant) = after_invariant else {
39 checked.push(InvariantCheckOutcome { failed: false, state: outcome.state });
40 continue;
41 };
42
43 let mut after_call = self.prepare_invariant_call(
44 executor,
45 outcome.state,
46 invariant_address,
47 sender,
48 after_invariant,
49 )?;
50 while let Some(after_outcome) =
51 self.execute_sequence_call_next(executor, &mut after_call, completed_paths)?
52 {
53 let failed = !matches!(after_outcome.status, CallStatus::Success);
54 let checked_outcome = InvariantCheckOutcome { failed, state: after_outcome.state };
55 if failed {
56 return Ok(vec![checked_outcome]);
57 }
58 checked.push(checked_outcome);
59 }
60 }
61 Ok(checked)
62 }
63
64 fn prepare_invariant_call<FEN: FoundryEvmNetwork>(
65 &mut self,
66 executor: &Executor<FEN>,
67 state: PathState,
68 invariant_address: Address,
69 sender: Address,
70 invariant: &Function,
71 ) -> Result<SequenceCall, SymbolicError> {
72 let calldata = SymbolicCalldata::selector_only(&mut self.cx, invariant)?;
73 let call_data = calldata.call_data(&mut self.cx);
74 let constraints = calldata.into_constraints();
75 self.prepare_sequence_call(
76 executor,
77 state,
78 invariant_address,
79 sender,
80 invariant,
81 call_data,
82 constraints,
83 )
84 }
85
86 pub(super) fn search_invariant_candidates_inner<FEN: FoundryEvmNetwork>(
87 &mut self,
88 input: &SymbolicInvariantCandidateInput<'_, FEN>,
89 candidates: &mut Vec<SymbolicInvariantCandidate>,
90 limitation: &mut Option<SymbolicInvariantSearchLimitation>,
91 ) -> Result<(), SymbolicError> {
92 if input.invariants.is_empty() {
93 return Err(SymbolicError::Unsupported("symbolic invariant has no predicates"));
94 }
95 let mut completed_paths = 0;
96
97 let mut initial_state = PathState::empty(
98 &mut self.cx,
99 input.invariant_address,
100 input.handler_sender,
101 input.ffi_enabled,
102 );
103 initial_state.apply_executor_env(&mut self.cx, input.executor);
104 initial_state.world.set_storage_layout(self.config.storage_layout);
105
106 let calldatas = SymbolicCalldata::variants_with_prefix(
107 &input.target.function,
108 &self.config,
109 &mut self.cx,
110 "frontier_handler",
111 )?;
112 'variants: for calldata in calldatas {
113 self.check_timeout()?;
114 let step = SequenceStepTemplate {
115 sender: input.handler_sender,
116 address: input.target.address,
117 contract_name: input.target.contract_name.clone(),
118 function: input.target.function.clone(),
119 calldata,
120 };
121 let call_data = step.calldata.call_data(&mut self.cx);
122 let constraints = step.calldata.constraints().to_vec();
123 let mut handler = match self.prepare_sequence_call(
124 input.executor,
125 initial_state.clone(),
126 input.target.address,
127 input.handler_sender,
128 &input.target.function,
129 call_data,
130 constraints,
131 ) {
132 Ok(call) => call,
133 Err(error) => {
134 if record_candidate_limitation(limitation, error) {
135 break;
136 }
137 continue;
138 }
139 };
140 let mut stop_after_handler = false;
141 loop {
142 let outcome = match self.execute_sequence_call_next(
143 input.executor,
144 &mut handler,
145 &mut completed_paths,
146 ) {
147 Ok(Some(outcome)) => outcome,
148 Ok(None) => break,
149 Err(error) => {
150 stop_after_handler = record_candidate_limitation(limitation, error);
151 break;
152 }
153 };
154 if !matches!(outcome.status, CallStatus::Success) {
155 continue;
156 }
157 let handler_state = outcome.state;
158 for (invariant_idx, invariant) in input.invariants.iter().enumerate() {
159 self.check_timeout()?;
160 let mut predicate = match self.prepare_invariant_call(
161 input.executor,
162 handler_state.clone(),
163 input.invariant_address,
164 CALLER,
165 invariant,
166 ) {
167 Ok(call) => call,
168 Err(error) => {
169 if record_candidate_limitation(limitation, error) {
170 break 'variants;
171 }
172 continue;
173 }
174 };
175 let mut stop_after_predicate = false;
176 let mut candidate_states = Vec::new();
177 loop {
178 let predicate_outcome = match self.execute_sequence_call_next(
179 input.executor,
180 &mut predicate,
181 &mut completed_paths,
182 ) {
183 Ok(Some(outcome)) => outcome,
184 Ok(None) => break,
185 Err(error) => {
186 stop_after_predicate =
187 record_candidate_limitation(limitation, error);
188 break;
189 }
190 };
191 if !matches!(predicate_outcome.status, CallStatus::Success) {
192 candidate_states.push(predicate_outcome.state);
193 continue;
194 }
195 let Some(after_invariant) = input.after_invariant else {
196 continue;
197 };
198
199 let mut after_state = handler_state.clone();
203 after_state.constraints = predicate_outcome.state.constraints;
204 let mut after = match self.prepare_invariant_call(
205 input.executor,
206 after_state,
207 input.invariant_address,
208 CALLER,
209 after_invariant,
210 ) {
211 Ok(call) => call,
212 Err(error) => {
213 if record_candidate_limitation(limitation, error) {
214 stop_after_predicate = true;
215 break;
216 }
217 continue;
218 }
219 };
220 loop {
221 match self.execute_sequence_call_next(
222 input.executor,
223 &mut after,
224 &mut completed_paths,
225 ) {
226 Ok(Some(outcome)) => {
227 if !matches!(outcome.status, CallStatus::Success) {
228 candidate_states.push(outcome.state);
229 }
230 }
231 Ok(None) => break,
232 Err(error) => {
233 if record_candidate_limitation(limitation, error) {
234 stop_after_predicate = true;
235 }
236 break;
237 }
238 }
239 }
240 if stop_after_predicate {
241 break;
242 }
243 }
244
245 for state in candidate_states {
246 match self.materialize_sequence(std::slice::from_ref(&step), &state) {
247 Ok((mut sequence, storage)) => {
248 let step =
249 sequence.pop().expect("one handler template produces one step");
250 candidates.push(SymbolicInvariantCandidate {
251 invariant_idx,
252 step,
253 storage,
254 });
255 }
256 Err(error) => {
257 if record_candidate_limitation(limitation, error) {
258 break 'variants;
259 }
260 }
261 }
262 }
263 if stop_after_predicate {
264 break 'variants;
265 }
266 }
267 }
268 if stop_after_handler {
269 break;
270 }
271 }
272
273 Ok(())
274 }
275
276 #[expect(clippy::too_many_arguments)]
277 pub(super) fn prepare_sequence_call<FEN: FoundryEvmNetwork>(
278 &mut self,
279 executor: &Executor<FEN>,
280 mut state: PathState,
281 target: Address,
282 sender: Address,
283 _function: &Function,
284 calldata: SymCalldata,
285 constraints: Vec<SymBoolExpr>,
286 ) -> Result<SequenceCall, SymbolicError> {
287 state.world.clear_transaction_scoped_state();
288 state.mapping_hook_keccak_preimages.clear();
289 let code = state.world.extcode(&mut self.cx, executor, target)?;
290 state.call_depth = 0;
291 state.origin = sender;
292 state.origin_word = SymExpr::constant(&mut self.cx, address_word(sender));
293 let callvalue = SymExpr::zero(&mut self.cx);
294 state.frame =
295 CallFrame::new(&mut self.cx, target, target, sender, callvalue, false, calldata);
296 state.constraints.extend(constraints);
297 Ok(SequenceCall {
298 code,
299 worklist: VecDeque::from([state]),
300 deferred_worklist: VecDeque::new(),
301 })
302 }
303
304 pub(super) fn execute_sequence_call_next<FEN: FoundryEvmNetwork>(
305 &mut self,
306 executor: &Executor<FEN>,
307 call: &mut SequenceCall,
308 completed_paths: &mut usize,
309 ) -> Result<Option<CallOutcome>, SymbolicError> {
310 if call.worklist.is_empty() && call.deferred_worklist.is_empty() {
311 return Ok(None);
312 }
313 let mut outcomes = self.execute_call_path_batch(
314 executor,
315 &call.code,
316 &mut call.worklist,
317 &mut call.deferred_worklist,
318 completed_paths,
319 CallPathKind::Sequence,
320 )?;
321 debug_assert!(outcomes.len() <= 1);
322 Ok(outcomes.pop())
323 }
324
325 pub(super) fn materialize_sequence(
326 &mut self,
327 steps: &[SequenceStepTemplate],
328 state: &PathState,
329 ) -> Result<(Vec<SymbolicInvariantStep>, Vec<SymbolicStorageAssignment>), SymbolicError> {
330 let replayable_storage = state.world.replay_storage_symbols();
331 let model = self.solver.model_with_replayable_storage(
332 &mut self.cx,
333 &state.constraints,
334 &replayable_storage,
335 )?;
336 let sequence = steps
337 .iter()
338 .map(|step| {
339 let args = step.calldata.model_to_args(&mut self.cx, &model)?;
340 let calldata = Bytes::from(step.function.abi_encode_input(&args)?);
341 Ok(SymbolicInvariantStep {
342 sender: step.sender,
343 address: step.address,
344 contract_name: step.contract_name.clone(),
345 function_name: step.function.name.clone(),
346 signature: step.function.signature(),
347 args,
348 calldata,
349 })
350 })
351 .collect::<Result<Vec<_>, SymbolicError>>()?;
352 let storage = state.world.replay_storage_assignments(&model)?;
353 Ok((sequence, storage))
354 }
355}