foundry_evm_symbolic/executor/
run.rs1use super::*;
2
3impl SymbolicExecutor {
4 pub fn new(config: SymbolicConfig) -> Self {
12 let solver = SmtLibSubprocessSolver::from_config(&config);
13 Self { config, solver: Box::new(solver) }
14 }
15
16 pub fn portfolio_diagnostics(&self) -> Option<PortfolioDiagnostics> {
18 self.solver.portfolio_diagnostics().cloned()
19 }
20
21 pub fn capture_diagnostics(&mut self) {
23 self.solver.capture_diagnostics();
24 }
25
26 pub fn take_diagnostics(&mut self) -> Option<String> {
28 self.solver.take_diagnostics()
29 }
30
31 pub fn set_query_observer(&mut self, observer: impl Fn(usize) + Send + Sync + 'static) {
33 self.solver.set_query_observer(Some(Box::new(observer)));
34 }
35
36 pub fn run<FEN: FoundryEvmNetwork>(
51 &mut self,
52 input: SymbolicRunInput<'_, FEN>,
53 ) -> SymbolicRunResult {
54 if let Err(err) = self.solver.check_available() {
55 return SymbolicRunResult::Incomplete {
56 kind: err.stop_reason(),
57 reason: err.to_string(),
58 stats: SymbolicStats::default(),
59 };
60 }
61
62 match self.run_inner(input) {
63 Ok(result) => result,
64 Err(err) => SymbolicRunResult::Incomplete {
65 kind: err.stop_reason(),
66 reason: err.to_string(),
67 stats: self.solver.stats(),
68 },
69 }
70 }
71
72 pub fn run_invariant<FEN: FoundryEvmNetwork>(
82 &mut self,
83 input: SymbolicInvariantRunInput<'_, FEN>,
84 ) -> SymbolicInvariantRunResult {
85 if let Err(err) = self.solver.check_available() {
86 return SymbolicInvariantRunResult::Incomplete {
87 kind: err.stop_reason(),
88 reason: err.to_string(),
89 stats: SymbolicStats::default(),
90 };
91 }
92
93 match self.run_invariant_inner(input) {
94 Ok(result) => result,
95 Err(err) => SymbolicInvariantRunResult::Incomplete {
96 kind: err.stop_reason(),
97 reason: err.to_string(),
98 stats: self.solver.stats(),
99 },
100 }
101 }
102
103 pub(super) fn run_inner<FEN: FoundryEvmNetwork>(
105 &mut self,
106 input: SymbolicRunInput<'_, FEN>,
107 ) -> Result<SymbolicRunResult, SymbolicError> {
108 let heuristic_witness_baseline = self.solver.heuristic_witnesses();
109 let account = input
110 .executor
111 .backend()
112 .basic_ref(input.target)
113 .map_err(|err| SymbolicError::Backend(err.to_string()))?
114 .ok_or(SymbolicError::MissingAccount(input.target))?;
115 let code =
116 account.code.ok_or(SymbolicError::MissingCode(input.target))?.original_bytes().to_vec();
117 let code = SymCode::concrete(code);
118 let jumpdests = analyze_jumpdests(&code);
119 let mut worklist = VecDeque::new();
120 for calldata in SymbolicCalldata::variants(input.function, &self.config)? {
121 let mut root = PathState::new(
122 input.target,
123 input.sender,
124 input.value,
125 calldata,
126 input.ffi_enabled,
127 );
128 root.apply_executor_env(input.executor);
129 root.world.set_storage_layout(self.config.storage_layout);
130 root.world.clear_transient_storage();
131 worklist.push_back(root);
132 }
133 let mut completed_paths = 0usize;
134 let mut reverted_paths = 0usize;
135 let mut normal_paths = 0usize;
136 let path_limit = self.config.path_width() as usize;
137 let depth_limit = self.config.execution_depth() as usize;
138
139 while let Some(mut state) = pop_worklist(&mut worklist, self.config.exploration_order) {
140 if completed_paths >= path_limit {
141 debug!(completed_paths, path_limit, "symbolic path limit reached");
142 return Ok(SymbolicRunResult::Incomplete {
143 kind: SymbolicStopReason::Stuck,
144 reason: format!("symbolic path limit exceeded ({path_limit})"),
145 stats: self.stats_with_paths(completed_paths),
146 });
147 }
148 let _path_span =
149 trace_span!("symbolic_path", completed_paths, worklist_size = worklist.len())
150 .entered();
151 trace!(completed_paths, worklist_size = worklist.len(), "exploring symbolic path");
152
153 loop {
154 if state.depth >= depth_limit {
155 debug!(depth = state.depth, depth_limit, "symbolic depth limit reached");
156 return Ok(SymbolicRunResult::Incomplete {
157 kind: SymbolicStopReason::Stuck,
158 reason: format!("symbolic depth limit exceeded ({depth_limit})"),
159 stats: self.stats_with_paths(completed_paths),
160 });
161 }
162 state.depth += 1;
163
164 let Some(op) = code.opcode(state.pc)? else {
165 if !state.expectations_satisfied() {
166 let (args, calldata_bytes) = self.materialize_stateless_counterexample(
167 state.root_calldata.as_ref().ok_or_else(|| {
168 SymbolicError::Unsupported("missing root symbolic calldata")
169 })?,
170 input.function,
171 &state,
172 )?;
173 return Ok(SymbolicRunResult::Counterexample {
174 args,
175 calldata: calldata_bytes,
176 stats: self.stats_with_paths(completed_paths + 1),
177 });
178 }
179 completed_paths += 1;
180 break;
181 };
182
183 let _step_span = trace_span!("symbolic_step", pc = state.pc - 1, op).entered();
184 match self.step(
185 input.executor,
186 &code,
187 &jumpdests,
188 &mut state,
189 &mut worklist,
190 &mut completed_paths,
191 op,
192 )? {
193 StepOutcome::Continue => {}
194 StepOutcome::Halt => {
195 if !state.expectations_satisfied() {
196 let (args, calldata_bytes) = self
197 .materialize_stateless_counterexample(
198 state.root_calldata.as_ref().ok_or_else(|| {
199 SymbolicError::Unsupported("missing root symbolic calldata")
200 })?,
201 input.function,
202 &state,
203 )?;
204 return Ok(SymbolicRunResult::Counterexample {
205 args,
206 calldata: calldata_bytes,
207 stats: self.stats_with_paths(completed_paths + 1),
208 });
209 }
210 completed_paths += 1;
211 normal_paths += 1;
212 break;
213 }
214 StepOutcome::Revert => {
215 completed_paths += 1;
216 reverted_paths += 1;
217 break;
218 }
219 StepOutcome::AssumeRejected => break,
220 StepOutcome::Forked => break,
221 StepOutcome::Failure => {
222 let (args, calldata_bytes) = self.materialize_stateless_counterexample(
223 state.root_calldata.as_ref().ok_or_else(|| {
224 SymbolicError::Unsupported("missing root symbolic calldata")
225 })?,
226 input.function,
227 &state,
228 )?;
229 return Ok(SymbolicRunResult::Counterexample {
230 args,
231 calldata: calldata_bytes,
232 stats: self.stats_with_paths(completed_paths + 1),
233 });
234 }
235 }
236 }
237 }
238
239 if normal_paths == 0 && reverted_paths > 0 {
240 debug!(completed_paths, "all symbolic paths reverted");
241 return Ok(SymbolicRunResult::Incomplete {
242 kind: SymbolicStopReason::RevertAll,
243 reason: "all symbolic paths reverted".to_string(),
244 stats: self.stats_with_paths(completed_paths),
245 });
246 }
247
248 if self.heuristic_witnesses_used_since(heuristic_witness_baseline) {
249 return Ok(SymbolicRunResult::Incomplete {
250 kind: SymbolicStopReason::Timeout,
251 reason: Self::hard_arith_heuristic_incomplete_reason(),
252 stats: self.stats_with_paths(completed_paths),
253 });
254 }
255
256 debug!(completed_paths, "symbolic execution safe");
257 Ok(SymbolicRunResult::Safe(self.stats_with_paths(completed_paths)))
258 }
259
260 pub(super) fn materialize_stateless_counterexample(
262 &mut self,
263 calldata: &SymbolicCalldata,
264 function: &Function,
265 state: &PathState,
266 ) -> Result<(Vec<DynSolValue>, Bytes), SymbolicError> {
267 debug!(
268 constraint_count = state.constraints.len(),
269 "materializing counterexample from solver model"
270 );
271 let model = self.solver.model(&state.constraints)?;
272 let args = calldata.model_to_args(&model)?;
273 let calldata_bytes = Bytes::from(function.abi_encode_input(&args)?);
274 Ok((args, calldata_bytes))
275 }
276
277 pub(super) fn run_invariant_inner<FEN: FoundryEvmNetwork>(
279 &mut self,
280 input: SymbolicInvariantRunInput<'_, FEN>,
281 ) -> Result<SymbolicInvariantRunResult, SymbolicError> {
282 let heuristic_witness_baseline = self.solver.heuristic_witnesses();
283 if input.targets.is_empty() {
284 return Err(SymbolicError::Unsupported("symbolic invariant has no targets"));
285 }
286
287 let senders =
288 if input.senders.is_empty() { vec![input.sender] } else { input.senders.clone() };
289 let mut completed_paths = 0usize;
290 let mut initial_state =
291 PathState::empty(input.invariant_address, input.sender, input.ffi_enabled);
292 initial_state.apply_executor_env(input.executor);
293 initial_state.world.set_storage_layout(self.config.storage_layout);
294 let initial = SequencePath { state: initial_state, steps: Vec::new() };
295
296 for outcome in self.execute_invariant_check(
297 input.executor,
298 initial.state.clone(),
299 input.invariant_address,
300 input.sender,
301 input.invariant,
302 input.after_invariant,
303 &mut completed_paths,
304 )? {
305 if outcome.failed {
306 let sequence = self.materialize_sequence(&initial.steps, &outcome.state)?;
307 return Ok(SymbolicInvariantRunResult::Counterexample {
308 sequence,
309 stats: self.stats_with_paths(completed_paths),
310 });
311 }
312 }
313
314 let path_limit = self.config.path_width() as usize;
315 let mut frontier = vec![initial];
316 for depth in 0..input.depth {
317 let mut next_frontier = Vec::new();
318 for sequence in frontier {
319 for (target_idx, target) in input.targets.iter().enumerate() {
320 for (sender_idx, sender) in senders.iter().copied().enumerate() {
321 let prefix = format!("sequence_{depth}_{target_idx}_{sender_idx}");
322 let calldatas = SymbolicCalldata::variants_with_prefix(
323 &target.function,
324 &self.config,
325 prefix,
326 )?;
327 for calldata in calldatas {
328 let step = SequenceStepTemplate {
329 sender,
330 address: target.address,
331 contract_name: target.contract_name.clone(),
332 function: target.function.clone(),
333 calldata,
334 };
335 let outcomes = self.execute_sequence_call(
336 input.executor,
337 sequence.state.clone(),
338 target.address,
339 sender,
340 &target.function,
341 step.calldata.call_data(),
342 step.calldata.constraints.clone(),
343 &mut completed_paths,
344 )?;
345
346 for outcome in outcomes {
347 let mut steps = sequence.steps.clone();
348 steps.push(step.clone());
349
350 match outcome.status {
351 TopLevelCallStatus::Failure => {
352 let sequence =
353 self.materialize_sequence(&steps, &outcome.state)?;
354 return Ok(SymbolicInvariantRunResult::Counterexample {
355 sequence,
356 stats: self.stats_with_paths(completed_paths),
357 });
358 }
359 TopLevelCallStatus::Revert => {
360 if input.fail_on_revert {
361 let sequence =
362 self.materialize_sequence(&steps, &outcome.state)?;
363 return Ok(
364 SymbolicInvariantRunResult::Counterexample {
365 sequence,
366 stats: self.stats_with_paths(completed_paths),
367 },
368 );
369 }
370 let mut reverted_state = sequence.state.clone();
371 reverted_state.world.clear_transient_storage();
372 for invariant_outcome in self.execute_invariant_check(
373 input.executor,
374 reverted_state,
375 input.invariant_address,
376 input.sender,
377 input.invariant,
378 input.after_invariant,
379 &mut completed_paths,
380 )? {
381 if invariant_outcome.failed {
382 let sequence = self.materialize_sequence(
383 &steps,
384 &invariant_outcome.state,
385 )?;
386 return Ok(
387 SymbolicInvariantRunResult::Counterexample {
388 sequence,
389 stats: self
390 .stats_with_paths(completed_paths),
391 },
392 );
393 }
394 next_frontier.push(SequencePath {
395 state: invariant_outcome.state,
396 steps: steps.clone(),
397 });
398 }
399 }
400 TopLevelCallStatus::Success => {
401 for invariant_outcome in self.execute_invariant_check(
402 input.executor,
403 outcome.state.clone(),
404 input.invariant_address,
405 input.sender,
406 input.invariant,
407 input.after_invariant,
408 &mut completed_paths,
409 )? {
410 if invariant_outcome.failed {
411 let sequence = self.materialize_sequence(
412 &steps,
413 &invariant_outcome.state,
414 )?;
415 return Ok(
416 SymbolicInvariantRunResult::Counterexample {
417 sequence,
418 stats: self
419 .stats_with_paths(completed_paths),
420 },
421 );
422 }
423 next_frontier.push(SequencePath {
424 state: invariant_outcome.state,
425 steps: steps.clone(),
426 });
427 }
428 }
429 }
430
431 if completed_paths >= path_limit {
432 return Ok(SymbolicInvariantRunResult::Incomplete {
433 kind: SymbolicStopReason::Stuck,
434 reason: format!(
435 "symbolic path limit exceeded ({path_limit})"
436 ),
437 stats: self.stats_with_paths(completed_paths),
438 });
439 }
440 }
441 }
442 }
443 }
444 }
445
446 if next_frontier.is_empty() {
447 break;
448 }
449 frontier = next_frontier;
450 }
451
452 if self.heuristic_witnesses_used_since(heuristic_witness_baseline) {
453 return Ok(SymbolicInvariantRunResult::Incomplete {
454 kind: SymbolicStopReason::Timeout,
455 reason: Self::hard_arith_heuristic_incomplete_reason(),
456 stats: self.stats_with_paths(completed_paths),
457 });
458 }
459
460 Ok(SymbolicInvariantRunResult::Safe(self.stats_with_paths(completed_paths)))
461 }
462
463 pub(super) fn stats_with_paths(&self, paths: usize) -> SymbolicStats {
465 let mut stats = self.solver.stats();
466 stats.paths = paths;
467 stats
468 }
469
470 fn heuristic_witnesses_used_since(&self, baseline: usize) -> bool {
472 self.solver.heuristic_witnesses() > baseline
473 }
474
475 fn hard_arith_heuristic_incomplete_reason() -> String {
477 "hard arithmetic heuristic witness used; no replayed counterexample found".to_string()
478 }
479}