1use super::*;
2use foundry_evm::revm::context_interface::cfg::gas::CALL_STIPEND;
3
4const MAX_BOUND_ANALYSIS_VISITS: usize = 256;
5
6#[derive(Clone, Debug)]
7pub(crate) struct PathState {
8 pub(crate) depth: usize,
9 pub(crate) call_depth: usize,
10 pub(crate) origin: Address,
11 pub(crate) origin_word: SymExpr,
12 pub(crate) gas_price: SymExpr,
13 pub(crate) ffi_enabled: bool,
14 pub(crate) block: SymbolicBlock,
15 pub(crate) frame: CallFrame,
16 pub(crate) world: SymbolicWorld,
17 pub(crate) prank: SymbolicPrank,
18 pub(crate) constraints: Vec<SymBoolExpr>,
19 pub(crate) next_symbol: usize,
20 pub(crate) recorded_logs: Option<Vec<SymbolicLog>>,
21 pub(crate) access_record: Option<AccessRecord>,
22 pub(crate) root_calldata: Option<SymbolicCalldata>,
23 pub(crate) invariant_predicate: bool,
25 corpus_seed_models: Vec<Arc<SymbolicModel>>,
26 branch_target: Option<SymbolicBranchTarget>,
27 branch_target_reached: bool,
28 needs_feasibility_check: bool,
29 pub(crate) loop_jumps: HashMap<usize, u32>,
30 pub(crate) expected_revert: Option<ExpectedRevert>,
31 pub(crate) assume_no_revert_next_call: Option<AssumeNoRevert>,
32 pub(crate) expected_emit: Option<ExpectedEmit>,
33 pub(crate) expected_calls: Vec<ExpectedCall>,
34 pub(crate) expected_creates: Vec<ExpectedCreate>,
35 pub(crate) call_mocks: Vec<CallMock>,
36 pub(crate) function_mocks: Vec<FunctionMock>,
37 pub(crate) persistent_accounts: HashSet<Address>,
38 pub(crate) wallets: IndexSet<Address>,
39 pub(crate) labels: HashMap<Address, String>,
40 pub(crate) storage_load_hooks: HashMap<Address, SymbolicStorageHook>,
41 pub(crate) storage_store_hooks: HashMap<Address, SymbolicStorageHook>,
42 pub(crate) mapping_storage_store_hooks: HashMap<(Address, U256), SymbolicStorageHook>,
43 pub(crate) mapping_hook_keccak_preimages: HashMap<(Address, SymExpr), Arc<[SymExpr]>>,
44 pub(crate) storage_hook_active: bool,
45 pub(crate) pending_storage_hook_revert: bool,
46}
47
48impl PathState {
49 pub(crate) fn new(
50 cx: &mut SymCx,
51 address: Address,
52 caller: Address,
53 callvalue: U256,
54 calldata: SymbolicCalldata,
55 ffi_enabled: bool,
56 ) -> Self {
57 let mut state = Self::empty(cx, address, caller, ffi_enabled);
58 let callvalue = SymExpr::constant(cx, callvalue);
59 let call_data = calldata.call_data(cx);
60 state.frame = CallFrame::new(cx, address, address, caller, callvalue, false, call_data);
61 state.constraints = calldata.constraints().to_vec();
62 state.root_calldata = Some(calldata);
63 state
64 }
65
66 pub(crate) fn empty(
67 cx: &mut SymCx,
68 address: Address,
69 caller: Address,
70 ffi_enabled: bool,
71 ) -> Self {
72 let origin_word = SymExpr::constant(cx, address_word(caller));
73 let gas_price = SymExpr::zero(cx);
74 let block = SymbolicBlock::new(cx);
75 let callvalue = SymExpr::zero(cx);
76 let calldata = SymBytes::empty(cx);
77 let calldata = SymCalldata::from_bytes(cx, calldata);
78 let frame = CallFrame::new(cx, address, address, caller, callvalue, false, calldata);
79 Self {
80 depth: 0,
81 call_depth: 0,
82 origin: caller,
83 origin_word,
84 gas_price,
85 ffi_enabled,
86 block,
87 frame,
88 world: SymbolicWorld::default(),
89 prank: SymbolicPrank::default(),
90 constraints: Vec::new(),
91 next_symbol: 0,
92 recorded_logs: None,
93 access_record: None,
94 root_calldata: None,
95 invariant_predicate: false,
96 corpus_seed_models: Vec::new(),
97 branch_target: None,
98 branch_target_reached: false,
99 needs_feasibility_check: false,
100 loop_jumps: HashMap::default(),
101 expected_revert: None,
102 assume_no_revert_next_call: None,
103 expected_emit: None,
104 expected_calls: Vec::new(),
105 expected_creates: Vec::new(),
106 call_mocks: Vec::new(),
107 function_mocks: Vec::new(),
108 persistent_accounts: HashSet::default(),
109 wallets: IndexSet::default(),
110 labels: HashMap::default(),
111 storage_load_hooks: HashMap::default(),
112 storage_store_hooks: HashMap::default(),
113 mapping_storage_store_hooks: HashMap::default(),
114 mapping_hook_keccak_preimages: HashMap::default(),
115 storage_hook_active: false,
116 pending_storage_hook_revert: false,
117 }
118 }
119
120 pub(crate) fn apply_executor_env<FEN: FoundryEvmNetwork>(
121 &mut self,
122 cx: &mut SymCx,
123 executor: &Executor<FEN>,
124 ) {
125 self.block = SymbolicBlock::from_executor(cx, executor);
126 let gas_price = executor
127 .inspector()
128 .cheatcodes
129 .as_ref()
130 .and_then(|cheats| cheats.gas_price)
131 .unwrap_or_else(|| executor.tx_env().gas_price());
132 self.gas_price = SymExpr::constant(cx, U256::from(gas_price));
133 if let Some(cheats) = executor.inspector().cheatcodes.as_ref() {
134 for (target, overwrite) in cheats.arbitrary_storage_target_overwrite_modes() {
135 self.world.enable_arbitrary_storage(target, overwrite);
136 }
137 for (target, source) in cheats.arbitrary_storage_copied_target_sources() {
138 Arc::make_mut(&mut self.world.state)
139 .arbitrary_storage_copies
140 .insert(target, source);
141 }
142 self.storage_load_hooks.extend(cheats.storage_load_hooks().map(|(target, hook)| {
143 (
144 target,
145 SymbolicStorageHook {
146 callback_target: hook.callback_target,
147 callback_selector: hook.callback_selector,
148 },
149 )
150 }));
151 self.storage_store_hooks.extend(cheats.storage_store_hooks().map(|(target, hook)| {
152 (
153 target,
154 SymbolicStorageHook {
155 callback_target: hook.callback_target,
156 callback_selector: hook.callback_selector,
157 },
158 )
159 }));
160 self.mapping_storage_store_hooks.extend(cheats.mapping_storage_store_hooks().map(
161 |(target, root, hook)| {
162 (
163 (target, root.into()),
164 SymbolicStorageHook {
165 callback_target: hook.callback_target,
166 callback_selector: hook.callback_selector,
167 },
168 )
169 },
170 ));
171 }
172 }
173
174 pub(crate) fn child(&self, frame: CallFrame) -> Self {
175 let mut child = self.clone();
176 child.call_depth += 1;
177 child.frame = frame;
178 child.prank = SymbolicPrank::default();
181 child.loop_jumps.clear();
182 child.expected_revert = None;
183 child.assume_no_revert_next_call = None;
184 child
185 }
186
187 pub(crate) fn storage_hook_child(&self, frame: CallFrame) -> Self {
188 let mut child = self.child(frame);
189 child.storage_hook_active = true;
190 child.recorded_logs = None;
191 child.access_record = None;
192 child.expected_emit = None;
193 child.expected_calls.clear();
194 child.expected_creates.clear();
195 child.call_mocks.clear();
196 child.function_mocks.clear();
197 child.set_branch_target(None);
198 child
199 }
200
201 pub(crate) fn copy_call_output_offset(
202 &mut self,
203 cx: &mut SymCx,
204 dest: SymExpr,
205 size: &BoundedCopySize,
206 ) -> Result<(), SymbolicError> {
207 let CallFrame { memory, return_data, .. } = &mut self.frame;
208 memory.copy_call_output_offset(cx, dest, size, return_data)
209 }
210
211 pub(crate) fn constrained_usize(&self, cx: &mut SymCx, expr: &SymExpr) -> Option<usize> {
212 self.constrained_usize_checked(cx, expr).and_then(Result::ok)
213 }
214
215 pub(crate) fn constrained_usize_checked(
216 &self,
217 cx: &mut SymCx,
218 expr: &SymExpr,
219 ) -> Option<Result<usize, U256>> {
220 self.constrained_word(cx, expr).map(|value| usize::try_from(value).map_err(|_| value))
221 }
222
223 pub(crate) fn upper_bound_usize(&self, _cx: &mut SymCx, expr: &SymExpr) -> Option<usize> {
224 let mut bounds = HashMap::default();
225 let mut ordering = HashMap::default();
226 let mut remaining = MAX_BOUND_ANALYSIS_VISITS;
227 self.expr_upper_bound_usize_cached(expr, &mut bounds, &mut ordering, &mut remaining)
228 }
229
230 pub(crate) fn lower_bound_usize(&self, expr: &SymExpr) -> usize {
232 let mut lower_bounds = HashMap::default();
233 let mut upper_bounds = HashMap::default();
234 let mut ordering = HashMap::default();
235 let mut remaining = MAX_BOUND_ANALYSIS_VISITS;
236 self.expr_lower_bound_usize(
237 expr,
238 &mut lower_bounds,
239 &mut upper_bounds,
240 &mut ordering,
241 &mut remaining,
242 )
243 }
244
245 pub(crate) fn constrained_word(&self, cx: &mut SymCx, expr: &SymExpr) -> Option<U256> {
246 expr.as_const().or_else(|| {
247 self.constraints
248 .iter()
249 .find_map(|constraint| {
250 constraint.forces_expr_const_with_context(expr, &self.constraints)
251 })
252 .or_else(|| self.constrained_expr_value(cx, expr))
253 })
254 }
255
256 pub(crate) fn constrained_expr_value(&self, cx: &mut SymCx, expr: &SymExpr) -> Option<U256> {
257 if let Some(value) = expr.eval() {
258 return Some(value);
259 }
260 if let Some(value) = expr.known_word() {
261 return Some(value);
262 }
263
264 let mut vars = SymbolicVars::default();
265 expr.collect_eval_vars(&mut vars);
266 let mut model = SymbolicModel::default();
267 for var in vars {
268 let var_expr = SymExpr::get_var(cx, var);
269 let value = self.constraints.iter().find_map(|constraint| {
270 constraint.forces_expr_const_with_context(&var_expr, &self.constraints)
271 })?;
272 model.insert(var, value);
273 }
274
275 expr.eval_model(&model).ok()
276 }
277
278 pub(crate) fn split_corpus_seed_models(
279 &self,
280 condition: &SymBoolExpr,
281 ) -> (Vec<Arc<SymbolicModel>>, Vec<Arc<SymbolicModel>>) {
282 let mut true_models = Vec::new();
283 let mut false_models = Vec::new();
284 for model in &self.corpus_seed_models {
285 match condition.eval_model_if_complete(model.as_ref()) {
286 Ok(Some(true)) => true_models.push(Arc::clone(model)),
287 Ok(Some(false)) => false_models.push(Arc::clone(model)),
288 Ok(None) | Err(_) => {
289 true_models.push(Arc::clone(model));
290 false_models.push(Arc::clone(model));
291 }
292 }
293 }
294 (true_models, false_models)
295 }
296
297 pub(crate) fn set_corpus_seed_models(&mut self, models: Vec<Arc<SymbolicModel>>) {
298 self.corpus_seed_models = models;
299 }
300
301 pub(crate) const fn corpus_seed_model_count(&self) -> usize {
302 self.corpus_seed_models.len()
303 }
304
305 pub(crate) const fn set_branch_target(&mut self, target: Option<SymbolicBranchTarget>) {
306 self.branch_target = target;
307 self.branch_target_reached = false;
308 }
309
310 pub(crate) const fn branch_target(&self) -> Option<SymbolicBranchTarget> {
311 self.branch_target
312 }
313
314 pub(crate) const fn mark_branch_target_reached(&mut self) {
315 self.branch_target_reached = true;
316 }
317
318 pub(crate) fn inherit_branch_target_progress(&mut self, child: &Self) {
319 if self.branch_target == child.branch_target && child.branch_target_reached {
320 self.mark_branch_target_reached();
321 }
322 }
323
324 pub(crate) fn take_noncommitting_check_state(&mut self, check: &mut Self) {
325 self.constraints = std::mem::take(&mut check.constraints);
326 self.next_symbol = self.next_symbol.max(check.next_symbol);
327 self.world.merge_replay_metadata_from(&check.world);
328 self.storage_load_hooks = std::mem::take(&mut check.storage_load_hooks);
329 self.storage_store_hooks = std::mem::take(&mut check.storage_store_hooks);
330 self.mapping_storage_store_hooks = std::mem::take(&mut check.mapping_storage_store_hooks);
331 }
332
333 pub(crate) fn take_call_outcome_state(&mut self, child: &mut Self) {
334 self.constraints = std::mem::take(&mut child.constraints);
335 self.next_symbol = child.next_symbol;
336 self.inherit_branch_target_progress(child);
337 self.storage_load_hooks = std::mem::take(&mut child.storage_load_hooks);
338 self.storage_store_hooks = std::mem::take(&mut child.storage_store_hooks);
339 self.mapping_storage_store_hooks = std::mem::take(&mut child.mapping_storage_store_hooks);
340 self.mapping_hook_keccak_preimages =
341 std::mem::take(&mut child.mapping_hook_keccak_preimages);
342 self.recorded_logs = child.recorded_logs.take();
343 self.access_record = child.access_record.take();
344 self.block = child.block.clone();
346 self.expected_calls = std::mem::take(&mut child.expected_calls);
347 self.call_mocks = std::mem::take(&mut child.call_mocks);
348 self.function_mocks = std::mem::take(&mut child.function_mocks);
349 }
350
351 pub(crate) fn take_reverted_top_level_effects(&mut self, mut reverted: Self) {
352 self.take_noncommitting_check_state(&mut reverted);
353 self.block = reverted.block;
354 self.recorded_logs = reverted.recorded_logs;
355 self.access_record = reverted.access_record;
356 self.expected_revert = reverted.expected_revert;
357 self.assume_no_revert_next_call = reverted.assume_no_revert_next_call;
358 self.expected_emit = reverted.expected_emit;
359 self.expected_calls = reverted.expected_calls;
360 self.expected_creates = reverted.expected_creates;
361 self.call_mocks = reverted.call_mocks;
362 self.function_mocks = reverted.function_mocks;
363 }
364
365 pub(crate) fn can_materialize_seed(&self) -> bool {
370 !self.constraints.iter().any(SymBoolExpr::contains_gasleft)
371 }
372
373 pub(crate) const fn satisfies_branch_target(&self) -> bool {
374 self.branch_target.is_none() || self.branch_target_reached
375 }
376
377 pub(crate) const fn defer_feasibility_check(&mut self) {
378 self.needs_feasibility_check = true;
379 }
380
381 pub(crate) const fn take_deferred_feasibility_check(&mut self) -> bool {
382 let needs_check = self.needs_feasibility_check;
383 self.needs_feasibility_check = false;
384 needs_check
385 }
386
387 fn expr_upper_bound_usize_cached(
388 &self,
389 expr: &SymExpr,
390 bounds: &mut HashMap<SymExpr, Option<usize>>,
391 ordering: &mut HashMap<(SymExpr, SymExpr), bool>,
392 remaining: &mut usize,
393 ) -> Option<usize> {
394 if let Some(bound) = bounds.get(expr) {
395 return *bound;
396 }
397 if let Some(value) = expr.as_const() {
398 return usize::try_from(value).ok();
399 }
400
401 let constraint_bound = self.constraint_upper_bound_usize(expr);
402 let structural_bound = remaining.checked_sub(1).and_then(|next| {
403 *remaining = next;
404 match expr.kind() {
405 SymExprKind::Const(value) => usize::try_from(*value).ok(),
406 SymExprKind::Var(_)
407 | SymExprKind::GasLeft(_)
408 | SymExprKind::Keccak { .. }
409 | SymExprKind::Hash { .. } => None,
410 SymExprKind::Not(_) => None,
411 SymExprKind::TernOp(_, _, _, modulus) => match modulus.as_const() {
412 Some(modulus) if modulus.is_zero() => Some(0),
413 Some(modulus) => usize::try_from(modulus - U256::ONE).ok(),
414 None => self
415 .expr_upper_bound_usize_cached(modulus, bounds, ordering, remaining)
416 .and_then(|bound| bound.checked_sub(1)),
417 },
418 SymExprKind::Ite(condition, left, right) => {
419 let left_bound =
420 self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining);
421 let right_bound =
422 self.expr_upper_bound_usize_cached(right, bounds, ordering, remaining);
423 match (left_bound, right_bound) {
424 (Some(left_bound), Some(right_bound)) => Some(left_bound.max(right_bound)),
425 (None, Some(right_bound)) => condition
426 .implies_unsigned_less_or_equal(true, left, right, remaining)
427 .then_some(right_bound),
428 (Some(left_bound), None) => condition
429 .implies_unsigned_less_or_equal(false, right, left, remaining)
430 .then_some(left_bound),
431 (None, None) => None,
432 }
433 }
434 SymExprKind::BinOp(op, left, right) => match op {
435 SymBinOp::Add => self
436 .expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?
437 .checked_add(
438 self.expr_upper_bound_usize_cached(right, bounds, ordering, remaining)?,
439 ),
440 SymBinOp::Mul => self
441 .expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?
442 .checked_mul(
443 self.expr_upper_bound_usize_cached(right, bounds, ordering, remaining)?,
444 ),
445 SymBinOp::UDiv => {
446 let left =
447 self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
448 match right.as_const()? {
449 divisor if divisor.is_zero() => Some(0),
450 divisor => Some(left / usize::try_from(divisor).ok()?),
451 }
452 }
453 SymBinOp::URem => match right.as_const() {
454 Some(divisor) if divisor.is_zero() => Some(0),
455 Some(divisor) => usize::try_from(divisor - U256::ONE).ok(),
456 None => {
457 self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)
458 }
459 },
460 SymBinOp::And => right
461 .as_const()
462 .and_then(|value| usize::try_from(value).ok())
463 .or_else(|| left.as_const().and_then(|value| usize::try_from(value).ok()))
464 .map(|mask| {
465 self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)
466 .or_else(|| {
467 self.expr_upper_bound_usize_cached(
468 right, bounds, ordering, remaining,
469 )
470 })
471 .map_or(mask, |bound| bound.min(mask))
472 }),
473 SymBinOp::Shr => {
474 let left =
475 self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
476 let shift = usize::try_from(right.as_const()?).ok()?;
477 Some(if shift >= usize::BITS as usize { 0 } else { left >> shift })
478 }
479 SymBinOp::Sub => {
480 if let Some(difference) = left
481 .constant_difference(right)
482 .and_then(|difference| usize::try_from(difference).ok())
483 {
484 return Some(difference);
485 }
486 let left_bound =
487 self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
488 self.expressions_are_unsigned_ordered(right, left, ordering, remaining)
489 .then_some(left_bound)
490 }
491 SymBinOp::Shl => match right.as_const() {
492 Some(shift) if shift >= U256::from(256) => Some(0),
493 Some(shift) => usize::try_from(shift).ok().and_then(|shift| {
494 let bound = self
495 .expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
496 if bound == 0 {
497 return Some(0);
498 }
499 let factor = 1usize.checked_shl(u32::try_from(shift).ok()?)?;
500 bound.checked_mul(factor)
501 }),
502 None => None,
503 },
504 SymBinOp::SDiv
505 | SymBinOp::SRem
506 | SymBinOp::Or
507 | SymBinOp::Xor
508 | SymBinOp::Sar => None,
509 },
510 }
511 });
512
513 let bound = match (constraint_bound, structural_bound) {
514 (Some(left), Some(right)) => Some(left.min(right)),
515 (Some(bound), None) | (None, Some(bound)) => Some(bound),
516 (None, None) => None,
517 };
518 bounds.insert(expr.clone(), bound);
519 bound
520 }
521
522 fn expressions_are_unsigned_ordered(
523 &self,
524 left: &SymExpr,
525 right: &SymExpr,
526 ordering: &mut HashMap<(SymExpr, SymExpr), bool>,
527 remaining: &mut usize,
528 ) -> bool {
529 if left == right {
530 return true;
531 }
532 let key = (left.clone(), right.clone());
533 if let Some(ordered) = ordering.get(&key) {
534 return *ordered;
535 }
536 let Some(next) = remaining.checked_sub(1) else { return false };
537 *remaining = next;
538
539 let ordered = if let (Some(left), Some(right)) = (left.as_const(), right.as_const()) {
540 left <= right
541 } else if let SymExprKind::Ite(condition, then_value, else_value) = left.kind() {
542 let then_ordered = condition
543 .implies_unsigned_less_or_equal(true, then_value, right, remaining)
544 || self.expressions_are_unsigned_ordered(then_value, right, ordering, remaining);
545 let else_ordered = condition
546 .implies_unsigned_less_or_equal(false, else_value, right, remaining)
547 || self.expressions_are_unsigned_ordered(else_value, right, ordering, remaining);
548 then_ordered && else_ordered
549 } else if let SymExprKind::Ite(condition, then_value, else_value) = right.kind() {
550 let then_ordered = condition
551 .implies_unsigned_less_or_equal(true, left, then_value, remaining)
552 || self.expressions_are_unsigned_ordered(left, then_value, ordering, remaining);
553 let else_ordered = condition
554 .implies_unsigned_less_or_equal(false, left, else_value, remaining)
555 || self.expressions_are_unsigned_ordered(left, else_value, ordering, remaining);
556 then_ordered && else_ordered
557 } else {
558 false
559 };
560 ordering.insert(key, ordered);
561 ordered
562 }
563
564 fn expr_lower_bound_usize(
565 &self,
566 expr: &SymExpr,
567 lower_bounds: &mut HashMap<SymExpr, usize>,
568 upper_bounds: &mut HashMap<SymExpr, Option<usize>>,
569 ordering: &mut HashMap<(SymExpr, SymExpr), bool>,
570 remaining: &mut usize,
571 ) -> usize {
572 if let Some(bound) = lower_bounds.get(expr) {
573 return *bound;
574 }
575 if let Some(value) = expr.as_const().and_then(|value| usize::try_from(value).ok()) {
576 return value;
577 }
578 let Some(next) = remaining.checked_sub(1) else {
579 return 0;
580 };
581 *remaining = next;
582
583 let bound = match expr.kind() {
584 SymExprKind::Const(value) => usize::try_from(*value).unwrap_or_default(),
585 SymExprKind::Ite(_, left, right) => {
586 let left = self.expr_lower_bound_usize(
587 left,
588 lower_bounds,
589 upper_bounds,
590 ordering,
591 remaining,
592 );
593 let right = self.expr_lower_bound_usize(
594 right,
595 lower_bounds,
596 upper_bounds,
597 ordering,
598 remaining,
599 );
600 left.min(right)
601 }
602 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
603 let no_wrap = self
604 .expr_upper_bound_usize_cached(left, upper_bounds, ordering, remaining)
605 .and_then(|left| {
606 self.expr_upper_bound_usize_cached(right, upper_bounds, ordering, remaining)
607 .and_then(|right| left.checked_add(right))
608 })
609 .is_some();
610 if no_wrap {
611 let left = self.expr_lower_bound_usize(
612 left,
613 lower_bounds,
614 upper_bounds,
615 ordering,
616 remaining,
617 );
618 let right = self.expr_lower_bound_usize(
619 right,
620 lower_bounds,
621 upper_bounds,
622 ordering,
623 remaining,
624 );
625 left.checked_add(right).unwrap_or_default()
626 } else {
627 0
628 }
629 }
630 SymExprKind::BinOp(SymBinOp::Sub, left, right) => left
631 .constant_difference(right)
632 .and_then(|difference| usize::try_from(difference).ok())
633 .unwrap_or_default(),
634 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
635 let left = self.expr_lower_bound_usize(
636 left,
637 lower_bounds,
638 upper_bounds,
639 ordering,
640 remaining,
641 );
642 let right = self.expr_lower_bound_usize(
643 right,
644 lower_bounds,
645 upper_bounds,
646 ordering,
647 remaining,
648 );
649 left.max(right)
650 }
651 SymExprKind::Var(_)
652 | SymExprKind::GasLeft(_)
653 | SymExprKind::Keccak { .. }
654 | SymExprKind::Hash { .. }
655 | SymExprKind::Not(_)
656 | SymExprKind::BinOp(_, _, _)
657 | SymExprKind::TernOp(_, _, _, _) => 0,
658 };
659 lower_bounds.insert(expr.clone(), bound);
660 bound
661 }
662
663 pub(crate) fn constraint_upper_bound_usize(&self, expr: &SymExpr) -> Option<usize> {
664 let mut bound: Option<usize> = None;
665 for constraint in &self.constraints {
666 if let Some(candidate) = constraint.upper_bound_usize(expr) {
667 bound = Some(bound.map_or(candidate, |bound| bound.min(candidate)));
668 }
669 }
670 bound
671 }
672
673 pub(crate) fn expect_constrained_usize(
674 &self,
675 cx: &mut SymCx,
676 expr: SymExpr,
677 reason: &'static str,
678 ) -> Result<usize, SymbolicError> {
679 self.constrained_usize(cx, &expr).ok_or(SymbolicError::Unsupported(reason))
680 }
681
682 pub(crate) fn expect_constrained_word(
683 &self,
684 cx: &mut SymCx,
685 expr: SymExpr,
686 reason: &'static str,
687 ) -> Result<U256, SymbolicError> {
688 self.constrained_word(cx, &expr).ok_or(SymbolicError::Unsupported(reason))
689 }
690
691 pub(crate) fn bin_word(&mut self, cx: &mut SymCx, op: SymBinOp) -> Result<(), SymbolicError> {
692 let a = self.stack.pop()?;
693 let b = self.stack.pop()?;
694 self.stack.push(SymExpr::binop(cx, op, a, b))
695 }
696
697 pub(crate) fn bin_word_div_zero_guard(
698 &mut self,
699 cx: &mut SymCx,
700 op: SymBinOp,
701 ) -> Result<(), SymbolicError> {
702 let a = self.stack.pop()?;
703 let b = self.stack.pop()?;
704 let zero = SymExpr::zero(cx);
705 let condition = SymBoolExpr::eq(cx, b.clone(), zero.clone());
706 let expr = SymExpr::binop(cx, op, a, b);
707 self.stack.push(SymExpr::ite(cx, condition, zero, expr))
708 }
709
710 pub(crate) fn cmp_word_condition(
711 &mut self,
712 cx: &mut SymCx,
713 op: SymCmpOp,
714 ) -> Result<SymBoolExpr, SymbolicError> {
715 let a = self.stack.pop()?;
716 let b = self.stack.pop()?;
717 Ok(SymBoolExpr::cmp(cx, op, a, b))
718 }
719
720 pub(crate) fn shift_word(&mut self, cx: &mut SymCx, op: SymBinOp) -> Result<(), SymbolicError> {
721 let shift = self.stack.pop()?;
722 let value = self.stack.pop()?;
723 let expr = SymExpr::binop(cx, op, value, shift);
724 let result = expr.known_word().map(|word| SymExpr::constant(cx, word)).unwrap_or(expr);
725 self.stack.push(result)
726 }
727
728 pub(crate) fn exp_word(&mut self, cx: &mut SymCx) -> Result<(), SymbolicError> {
729 let base = self.stack.pop()?;
730 let exponent = self.stack.pop()?;
731 let result = if let Some(exponent) = self.constrained_word(cx, &exponent) {
732 if let Some(base_value) = base.as_const() {
733 SymExpr::constant(cx, base_value.wrapping_pow(exponent))
734 } else if exponent <= U256::from(SYMBOLIC_EXP_CONCRETE_EXPONENT_LIMIT) {
735 exp_expr_for_concrete_exponent(
736 cx,
737 base,
738 usize::try_from(exponent).expect("checked symbolic exponent"),
739 )
740 } else {
741 return Err(SymbolicError::Unsupported("symbolic EXP base"));
742 }
743 } else {
744 let exponent_limit = if base.as_const().is_some() {
745 CONCRETE_BASE_SYMBOLIC_EXPONENT_LIMIT
746 } else {
747 SYMBOLIC_EXP_CONCRETE_EXPONENT_LIMIT
748 };
749 let max_exponent = self
750 .upper_bound_usize(cx, &exponent)
751 .filter(|exponent| *exponent <= exponent_limit as usize)
752 .ok_or(SymbolicError::Unsupported("symbolic EXP exponent"))?;
753 let mut expr = SymExpr::zero(cx);
754 for candidate in (0..=max_exponent).rev() {
755 let candidate_expr = SymExpr::constant(cx, U256::from(candidate));
756 let condition = SymBoolExpr::eq(cx, exponent.clone(), candidate_expr);
757 let value = exp_expr_for_concrete_exponent(cx, base.clone(), candidate);
758 expr = SymExpr::ite(cx, condition, value, expr);
759 }
760 expr
761 };
762 self.stack.push(result)
763 }
764
765 pub(crate) fn balance<FEN: FoundryEvmNetwork>(
766 &self,
767 cx: &mut SymCx,
768 executor: &Executor<FEN>,
769 address: Address,
770 ) -> SymExpr {
771 self.world.balance_word_for_address(cx, executor, address)
772 }
773
774 pub(crate) fn balance_word<FEN: FoundryEvmNetwork>(
775 &mut self,
776 cx: &mut SymCx,
777 executor: &Executor<FEN>,
778 address_expr: SymExpr,
779 ) -> Result<SymExpr, SymbolicError> {
780 self.world.balance_word(cx, executor, address_expr)
781 }
782
783 pub(crate) fn extcode_size_word<FEN: FoundryEvmNetwork>(
784 &mut self,
785 cx: &mut SymCx,
786 executor: &Executor<FEN>,
787 address_expr: SymExpr,
788 ) -> Result<SymExpr, SymbolicError> {
789 self.world.extcode_size_word(cx, executor, address_expr)
790 }
791
792 pub(crate) fn extcode_hash_word<FEN: FoundryEvmNetwork>(
793 &mut self,
794 cx: &mut SymCx,
795 executor: &Executor<FEN>,
796 address_expr: SymExpr,
797 ) -> Result<SymExpr, SymbolicError> {
798 self.world.extcode_hash_word(cx, executor, address_expr)
799 }
800
801 pub(crate) fn extcode_bytes_word<FEN: FoundryEvmNetwork>(
802 &mut self,
803 cx: &mut SymCx,
804 executor: &Executor<FEN>,
805 address_expr: SymExpr,
806 offset: SymExpr,
807 size: usize,
808 ) -> Result<SymBytes, SymbolicError> {
809 self.world.extcode_bytes_word(cx, executor, address_expr, offset, size)
810 }
811
812 pub(crate) fn pop_address_word_or_symbolic_slot(
813 &mut self,
814 cx: &mut SymCx,
815 ) -> Result<(SymExpr, Address), SymbolicError> {
816 let expr = self.stack.pop()?;
817 let address = self.address_or_symbolic_slot(cx, expr.clone());
818 Ok((expr, address))
819 }
820
821 pub(crate) fn address_or_symbolic_slot(&mut self, cx: &mut SymCx, expr: SymExpr) -> Address {
822 if let Some(value) = self.constrained_word(cx, &expr) {
823 return word_to_address(value);
824 }
825 self.world.resolve_address(&expr).unwrap_or_else(|| self.world.symbolic_address_slot(expr))
826 }
827
828 pub(crate) fn fresh_word(&mut self, cx: &mut SymCx, prefix: &'static str) -> SymExpr {
829 let id = self.next_symbol;
830 self.next_symbol += 1;
831 SymExpr::var(cx, &format!("{prefix}_{id}"))
832 }
833
834 pub(crate) fn fresh_gasleft(&mut self, cx: &mut SymCx) -> SymExpr {
835 let id = self.next_symbol;
836 self.next_symbol += 1;
837 SymExpr::gas_left(cx, id)
838 }
839
840 pub(crate) fn fresh_bounded_uint(&mut self, cx: &mut SymCx, bits: U256) -> SymExpr {
841 let value = self.fresh_word(cx, "symbolic");
842 if bits < U256::from(256) {
843 let upper = if bits.is_zero() {
844 U256::ZERO
845 } else {
846 U256::ONE << usize::try_from(bits).expect("checked bit width")
847 };
848 self.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &value, upper));
849 }
850 value
851 }
852
853 pub(crate) fn fresh_bytes(&mut self, cx: &mut SymCx, len: usize) -> Vec<SymExpr> {
854 (0..len).map(|_| self.fresh_bounded_uint(cx, U256::from(8))).collect()
855 }
856
857 pub(crate) fn fresh_printable_ascii_bytes(
858 &mut self,
859 cx: &mut SymCx,
860 len: usize,
861 ) -> Vec<SymExpr> {
862 (0..len)
863 .map(|_| {
864 let byte = self.fresh_bounded_uint(cx, U256::from(8));
865 self.constraints.push(SymBoolExpr::cmp_word_const(
866 cx,
867 SymCmpOp::Uge,
868 &byte,
869 U256::from(0x20),
870 ));
871 self.constraints.push(SymBoolExpr::cmp_word_const(
872 cx,
873 SymCmpOp::Ule,
874 &byte,
875 U256::from(0x7e),
876 ));
877 byte
878 })
879 .collect()
880 }
881
882 pub(crate) fn fresh_bounded_int(&mut self, cx: &mut SymCx, bits: U256) -> SymExpr {
883 let value = self.fresh_word(cx, "symbolic");
884 if bits.is_zero() {
885 self.constraints.push(SymBoolExpr::eq_word_const(cx, &value, U256::ZERO));
886 } else if bits < U256::from(256) {
887 let magnitude = U256::ONE << (usize::try_from(bits).expect("checked bit width") - 1);
888 let lt = SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &value, magnitude);
889 let ge = SymBoolExpr::cmp_word_const(
890 cx,
891 SymCmpOp::Uge,
892 &value,
893 U256::ZERO.wrapping_sub(magnitude),
894 );
895 let condition = SymBoolExpr::or(cx, vec![lt, ge]);
896 self.constraints.push(condition);
897 }
898 value
899 }
900
901 pub(crate) fn prank_for_next_call(&mut self) -> (Address, SymExpr, Option<(Address, SymExpr)>) {
902 if let Some((caller, caller_word)) = self.prank.next_caller.take() {
903 (caller, caller_word, self.prank.next_origin.take())
904 } else {
905 match self.prank.persistent_caller.clone() {
906 Some((caller, caller_word)) => {
907 (caller, caller_word, self.prank.persistent_origin.clone())
908 }
909 None => {
910 (self.address, self.address_word.clone(), self.prank.persistent_origin.clone())
911 }
912 }
913 }
914 }
915
916 pub(crate) fn read_callers_words(&self, cx: &mut SymCx) -> Vec<SymExpr> {
917 let (mode, caller, origin) = if let Some((_, caller_word)) = self.prank.next_caller.as_ref()
918 {
919 (
920 U256::from(3),
921 caller_word.clone(),
922 self.prank
923 .next_origin
924 .as_ref()
925 .map(|(_, origin_word)| origin_word.clone())
926 .unwrap_or_else(|| self.origin_word.clone()),
927 )
928 } else if let Some((_, caller_word)) = self.prank.persistent_caller.as_ref() {
929 (
930 U256::from(4),
931 caller_word.clone(),
932 self.prank
933 .persistent_origin
934 .as_ref()
935 .map(|(_, origin_word)| origin_word.clone())
936 .unwrap_or_else(|| self.origin_word.clone()),
937 )
938 } else {
939 (U256::ZERO, self.caller_word.clone(), self.origin_word.clone())
940 };
941 vec![SymExpr::constant(cx, mode), caller, origin]
942 }
943
944 pub(crate) fn record_log(&mut self, log: SymbolicLog) {
945 if let Some(logs) = &mut self.recorded_logs {
946 logs.push(log);
947 }
948 }
949
950 pub(crate) fn record_sload(&mut self, address: Address, slot: SymExpr) {
951 if let Some(record) = &mut self.access_record {
952 record.read(address, slot);
953 }
954 }
955
956 pub(crate) fn record_sstore(&mut self, address: Address, slot: SymExpr) {
957 if let Some(record) = &mut self.access_record {
958 record.write(address, slot);
959 }
960 }
961
962 pub(crate) fn expectations_satisfied(&self) -> bool {
963 self.expected_revert.is_none()
964 && self.expected_emit.as_ref().is_none_or(ExpectedEmit::is_satisfied)
965 && self.expected_calls.iter().all(ExpectedCall::is_satisfied)
966 && self.expected_creates.is_empty()
967 }
968}
969
970#[derive(Clone, Copy, Debug, PartialEq, Eq)]
971pub(crate) struct SymbolicStorageHook {
972 pub(crate) callback_target: Address,
973 pub(crate) callback_selector: [u8; 4],
974}
975
976#[derive(Clone, Debug, PartialEq, Eq)]
977pub(crate) struct SymbolicLog {
978 topics: Arc<[SymExpr]>,
979 data_len: SymExpr,
980 data: SymBytes,
981 emitter: Address,
982}
983
984impl SymbolicLog {
985 pub(crate) fn new(
986 topics: Vec<SymExpr>,
987 data_len: SymExpr,
988 data: SymBytes,
989 emitter: Address,
990 ) -> Self {
991 Self { topics: topics.into(), data_len, data, emitter }
992 }
993
994 pub(crate) fn into_parts(self) -> (Arc<[SymExpr]>, SymExpr, SymBytes, Address) {
995 (self.topics, self.data_len, self.data, self.emitter)
996 }
997}
998
999#[derive(Clone, Debug, Default, PartialEq, Eq)]
1000pub(crate) struct AccessRecord {
1001 reads: HashMap<Address, Vec<SymExpr>>,
1002 writes: HashMap<Address, Vec<SymExpr>>,
1003}
1004
1005impl AccessRecord {
1006 pub(crate) fn read(&mut self, address: Address, slot: SymExpr) {
1007 Self::push_unique_slot(self.reads.entry(address).or_default(), slot);
1008 }
1009
1010 pub(crate) fn write(&mut self, address: Address, slot: SymExpr) {
1011 self.read(address, slot.clone());
1012 Self::push_unique_slot(self.writes.entry(address).or_default(), slot);
1013 }
1014
1015 pub(crate) fn addresses(&self) -> Vec<Address> {
1016 let mut addresses = HashSet::<Address>::default();
1017 addresses.extend(self.reads.keys().copied());
1018 addresses.extend(self.writes.keys().copied());
1019 let mut addresses = addresses.into_iter().collect::<Vec<_>>();
1020 addresses.sort_unstable();
1021 addresses
1022 }
1023
1024 pub(crate) fn read_slots(&self, address: Address) -> Vec<SymExpr> {
1025 self.reads.get(&address).cloned().unwrap_or_default()
1026 }
1027
1028 pub(crate) fn write_slots(&self, address: Address) -> Vec<SymExpr> {
1029 self.writes.get(&address).cloned().unwrap_or_default()
1030 }
1031
1032 fn push_unique_slot(slots: &mut Vec<SymExpr>, slot: SymExpr) {
1033 if !slots.iter().any(|existing| existing == &slot) {
1034 slots.push(slot);
1035 }
1036 }
1037}
1038
1039#[derive(Clone, Debug, PartialEq, Eq)]
1040pub(crate) struct ExpectedRevert {
1041 data: ExpectedRevertData,
1042 reverter: Option<SymExpr>,
1043 remaining: u64,
1044}
1045
1046impl ExpectedRevert {
1047 pub(crate) fn new(data: ExpectedRevertData, reverter: Option<SymExpr>, remaining: u64) -> Self {
1048 Self { data, reverter, remaining: remaining.max(1) }
1049 }
1050
1051 pub(crate) const fn consume_one(&mut self) -> bool {
1052 self.remaining = self.remaining.saturating_sub(1);
1053 self.remaining == 0
1054 }
1055
1056 pub(crate) fn match_condition(
1057 &self,
1058 cx: &mut SymCx,
1059 reverter: Address,
1060 return_data: &SymReturnData,
1061 ) -> Option<SymBoolExpr> {
1062 let mut conditions = Vec::new();
1063 if let Some(expected_reverter) = &self.reverter {
1064 conditions.push(expected_reverter.address_match_condition(cx, reverter));
1065 }
1066 match &self.data {
1067 ExpectedRevertData::Any => {}
1068 ExpectedRevertData::Prefix(prefix) => {
1069 if return_data.len() < prefix.len() {
1070 return None;
1071 }
1072 let prefix_len = SymExpr::constant(cx, U256::from(prefix.len()));
1073 conditions.push(SymBoolExpr::cmp_word_expr(
1074 cx,
1075 SymCmpOp::Uge,
1076 &return_data.len_word,
1077 prefix_len,
1078 ));
1079 conditions.extend((0..prefix.len()).map(|offset| {
1080 let expected = prefix.byte(cx, offset);
1081 let actual = return_data.byte(cx, offset);
1082 SymBoolExpr::eq(cx, actual, expected)
1083 }));
1084 }
1085 ExpectedRevertData::Exact(data) => {
1086 if return_data.len() < data.len() {
1087 return None;
1088 }
1089 let len = SymExpr::constant(cx, U256::from(data.len()));
1090 conditions.push(SymBoolExpr::eq(cx, return_data.len_word.clone(), len));
1091 conditions.extend((0..data.len()).map(|offset| {
1092 let expected = data.byte(cx, offset);
1093 let actual = return_data.byte(cx, offset);
1094 SymBoolExpr::eq(cx, actual, expected)
1095 }));
1096 }
1097 }
1098 Some(SymBoolExpr::and(cx, conditions))
1099 }
1100}
1101
1102#[derive(Clone, Debug, PartialEq, Eq)]
1103pub(crate) enum ExpectedRevertData {
1104 Any,
1105 Prefix(SymBytes),
1106 Exact(SymBytes),
1107}
1108
1109#[derive(Clone, Debug, PartialEq, Eq)]
1110pub(crate) enum AssumeNoRevert {
1111 Any,
1112 Filtered(Vec<ExpectedRevert>),
1113}
1114
1115#[derive(Clone, Debug, PartialEq, Eq)]
1116pub(crate) struct ExpectedCall {
1117 callee: SymExpr,
1118 value: Option<U256>,
1119 gas: Option<u64>,
1120 min_gas: Option<u64>,
1121 data: SymBytes,
1122 expected: u64,
1123 observed: u64,
1124 exact: bool,
1125}
1126
1127#[derive(Clone, Debug, PartialEq, Eq)]
1128pub(crate) struct ExpectedCreate {
1129 bytecode: Vec<u8>,
1130 deployer: SymExpr,
1131 kind: CreateKind,
1132}
1133
1134impl ExpectedCreate {
1135 pub(crate) const fn new(bytecode: Vec<u8>, deployer: SymExpr, kind: CreateKind) -> Self {
1136 Self { bytecode, deployer, kind }
1137 }
1138
1139 pub(crate) fn match_condition(
1140 &self,
1141 cx: &mut SymCx,
1142 deployer: Address,
1143 kind: CreateKind,
1144 bytecode: &[u8],
1145 ) -> Option<SymBoolExpr> {
1146 (self.kind == kind && self.bytecode == bytecode)
1147 .then(|| self.deployer.address_match_condition(cx, deployer))
1148 }
1149}
1150
1151impl ExpectedCall {
1152 pub(crate) fn new(
1153 callee: SymExpr,
1154 value: Option<U256>,
1155 gas: Option<u64>,
1156 min_gas: Option<u64>,
1157 data: SymBytes,
1158 count: Option<u64>,
1159 ) -> Self {
1160 let (gas, min_gas) = if value.is_some_and(|value| !value.is_zero()) {
1161 (
1162 gas.map(|gas| gas.saturating_add(CALL_STIPEND)),
1163 min_gas.map(|gas| gas.saturating_add(CALL_STIPEND)),
1164 )
1165 } else {
1166 (gas, min_gas)
1167 };
1168 Self {
1169 callee,
1170 value,
1171 gas,
1172 min_gas,
1173 data,
1174 expected: count.unwrap_or(1),
1175 observed: 0,
1176 exact: count.is_some(),
1177 }
1178 }
1179
1180 pub(crate) const fn value(&self) -> Option<U256> {
1181 self.value
1182 }
1183
1184 pub(crate) fn match_condition(
1185 &self,
1186 cx: &mut SymCx,
1187 callee: Address,
1188 value: Option<U256>,
1189 gas: &SymExpr,
1190 calldata: &SymBytes,
1191 ) -> Result<Option<SymBoolExpr>, SymbolicError> {
1192 if !self.value.is_none_or(|expected| value.is_some_and(|value| expected == value))
1193 || !self.gas_matches(gas, value)?
1194 {
1195 return Ok(None);
1196 }
1197 let Some(data_condition) = calldata.prefix_condition(cx, &self.data) else {
1198 return Ok(None);
1199 };
1200 let callee_condition = self.callee.address_match_condition(cx, callee);
1201 Ok(Some(SymBoolExpr::and(cx, vec![callee_condition, data_condition])))
1202 }
1203
1204 fn gas_matches(&self, gas: &SymExpr, value: Option<U256>) -> Result<bool, SymbolicError> {
1205 if self.gas.is_none() && self.min_gas.is_none() {
1206 return Ok(true);
1207 }
1208 let mut gas = gas.as_const_or("symbolic expected call gas")?;
1209 if value.is_some_and(|value| !value.is_zero()) {
1210 gas = gas.saturating_add(U256::from(CALL_STIPEND));
1211 }
1212 Ok(self.gas.is_none_or(|expected| gas == U256::from(expected))
1213 && self.min_gas.is_none_or(|expected| gas >= U256::from(expected)))
1214 }
1215
1216 pub(crate) const fn observe(&mut self) -> bool {
1217 if self.exact && self.observed >= self.expected {
1218 return false;
1219 }
1220 self.observed = self.observed.saturating_add(1);
1221 true
1222 }
1223
1224 pub(crate) const fn is_satisfied(&self) -> bool {
1225 if self.exact { self.observed == self.expected } else { self.observed >= self.expected }
1226 }
1227}
1228
1229pub(crate) fn register_expected_call(
1231 expected_calls: &mut Vec<ExpectedCall>,
1232 cx: &mut SymCx,
1233 expected: ExpectedCall,
1234) -> Result<(), &'static str> {
1235 if let Some(existing) = expected_calls
1236 .iter_mut()
1237 .find(|call| call.callee == expected.callee && call.data.same_bytes(cx, &expected.data))
1238 {
1239 if expected.exact {
1240 return Err("counted expected calls can only bet set once");
1241 }
1242 if existing.exact {
1243 return Err("cannot overwrite a counted expectCall with a non-counted expectCall");
1244 }
1245 existing.expected += 1;
1246 } else {
1247 expected_calls.push(expected);
1248 }
1249 Ok(())
1250}
1251
1252#[derive(Clone, Debug)]
1253pub(crate) struct CallMock {
1254 pub(crate) callee: SymExpr,
1255 value: Option<U256>,
1256 pub(crate) data: SymBytes,
1257 returns: Vec<SymReturnData>,
1258 reverts: bool,
1259 calls: usize,
1260}
1261
1262impl CallMock {
1263 pub(crate) const fn new(
1264 callee: SymExpr,
1265 value: Option<U256>,
1266 data: SymBytes,
1267 returns: Vec<SymReturnData>,
1268 reverts: bool,
1269 ) -> Self {
1270 Self { callee, value, data, returns, reverts, calls: 0 }
1271 }
1272
1273 pub(crate) const fn value(&self) -> Option<U256> {
1274 self.value
1275 }
1276
1277 pub(crate) fn specificity(&self) -> (usize, bool) {
1278 (self.data.len(), self.value.is_some())
1279 }
1280
1281 pub(crate) fn match_condition(
1282 &self,
1283 cx: &mut SymCx,
1284 callee: Address,
1285 value: Option<U256>,
1286 calldata: &SymBytes,
1287 ) -> Option<SymBoolExpr> {
1288 if !self.value.is_none_or(|expected| value.is_some_and(|value| expected == value)) {
1289 return None;
1290 }
1291 let data_condition = calldata.prefix_condition(cx, &self.data)?;
1292 let callee_condition = self.callee.address_match_condition(cx, callee);
1293 Some(SymBoolExpr::and(cx, vec![callee_condition, data_condition]))
1294 }
1295
1296 pub(crate) fn next_outcome(&mut self, cx: &mut SymCx) -> CallMockOutcome {
1297 let idx = self.calls.min(self.returns.len().saturating_sub(1));
1298 self.calls = self.calls.saturating_add(1);
1299 CallMockOutcome {
1300 return_data: self.returns.get(idx).cloned().unwrap_or_else(|| SymReturnData::empty(cx)),
1301 reverts: self.reverts,
1302 }
1303 }
1304}
1305
1306#[derive(Clone, Debug)]
1307pub(crate) struct CallMockOutcome {
1308 return_data: SymReturnData,
1309 reverts: bool,
1310}
1311
1312impl CallMockOutcome {
1313 pub(crate) fn into_parts(self) -> (SymReturnData, bool) {
1314 (self.return_data, self.reverts)
1315 }
1316}
1317
1318#[derive(Clone, Debug, PartialEq, Eq)]
1319pub(crate) struct FunctionMock {
1320 callee: SymExpr,
1321 target: Address,
1322 data: SymBytes,
1323}
1324
1325impl FunctionMock {
1326 pub(crate) const fn new(callee: SymExpr, target: Address, data: SymBytes) -> Self {
1327 Self { callee, target, data }
1328 }
1329
1330 pub(crate) fn matches_definition(
1331 &self,
1332 cx: &mut SymCx,
1333 callee: &SymExpr,
1334 data: &SymBytes,
1335 ) -> bool {
1336 self.callee == *callee && self.data.same_bytes(cx, data)
1337 }
1338
1339 pub(crate) const fn set_target(&mut self, target: Address) {
1340 self.target = target;
1341 }
1342
1343 pub(crate) fn calldata_len(&self) -> usize {
1344 self.data.len()
1345 }
1346
1347 pub(crate) const fn target(&self) -> Address {
1348 self.target
1349 }
1350
1351 pub(crate) fn match_condition(
1352 &self,
1353 cx: &mut SymCx,
1354 callee: Address,
1355 calldata: &SymBytes,
1356 ) -> Option<SymBoolExpr> {
1357 let data_condition = calldata.prefix_condition(cx, &self.data)?;
1358 let callee_condition = self.callee.address_match_condition(cx, callee);
1359 Some(SymBoolExpr::and(cx, vec![callee_condition, data_condition]))
1360 }
1361}
1362
1363#[derive(Clone, Debug, PartialEq, Eq)]
1364pub(crate) struct ExpectedEmit {
1365 checks: ExpectedEmitChecks,
1366 emitter: Option<SymExpr>,
1367 remaining: u64,
1368 template: Option<SymbolicLog>,
1369}
1370
1371impl ExpectedEmit {
1372 pub(crate) fn new(
1373 checks: ExpectedEmitChecks,
1374 emitter: Option<SymExpr>,
1375 remaining: u64,
1376 ) -> Self {
1377 Self { checks, emitter, remaining: remaining.max(1), template: None }
1378 }
1379
1380 pub(crate) const fn is_satisfied(&self) -> bool {
1381 self.template.is_none() && self.remaining == 0
1382 }
1383
1384 pub(crate) const fn template(&self) -> Option<&SymbolicLog> {
1385 self.template.as_ref()
1386 }
1387
1388 pub(crate) fn set_template(&mut self, log: SymbolicLog) {
1389 self.template = Some(log);
1390 }
1391
1392 pub(crate) fn consume_one(&mut self) -> bool {
1393 self.remaining = self.remaining.saturating_sub(1);
1394 if self.remaining == 0 {
1395 self.template = None;
1396 true
1397 } else {
1398 false
1399 }
1400 }
1401
1402 pub(crate) fn match_condition(
1403 &self,
1404 cx: &mut SymCx,
1405 template: &SymbolicLog,
1406 actual: &SymbolicLog,
1407 ) -> Option<SymBoolExpr> {
1408 let mut conditions = Vec::new();
1409 if let Some(expected_emitter) = &self.emitter {
1410 conditions.push(expected_emitter.address_match_condition(cx, actual.emitter));
1411 }
1412 for (idx, &check_topic) in self.checks.topics.iter().enumerate() {
1413 if !check_topic {
1414 continue;
1415 }
1416 match (template.topics.get(idx), actual.topics.get(idx)) {
1417 (Some(left), Some(right)) => {
1418 conditions.push(SymBoolExpr::eq(cx, left.clone(), right.clone()));
1419 }
1420 (None, None) => {}
1421 _ => return None,
1422 }
1423 }
1424
1425 if self.checks.data {
1426 conditions.push(SymBoolExpr::eq(
1427 cx,
1428 template.data_len.clone(),
1429 actual.data_len.clone(),
1430 ));
1431 if template.data.len() != actual.data.len() {
1432 return None;
1433 }
1434 conditions.extend((0..template.data.len()).map(|idx| {
1435 let template = template.data.byte(cx, idx);
1436 let actual = actual.data.byte(cx, idx);
1437 SymBoolExpr::eq(cx, template, actual)
1438 }));
1439 }
1440
1441 Some(SymBoolExpr::and(cx, conditions))
1442 }
1443}
1444
1445#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1446pub(crate) struct ExpectedEmitChecks {
1447 topics: [bool; 4],
1448 data: bool,
1449}
1450
1451impl Default for ExpectedEmitChecks {
1452 fn default() -> Self {
1453 Self { topics: [true; 4], data: true }
1454 }
1455}
1456
1457impl ExpectedEmitChecks {
1458 pub(crate) fn from_non_anonymous_args(
1459 cx: &mut SymCx,
1460 memory: &SymMemory,
1461 args_offset: usize,
1462 ) -> Result<Self, SymbolicError> {
1463 Ok(Self {
1464 topics: [
1465 true,
1466 read_abi_bool_arg(cx, memory, args_offset, 0, "symbolic vm.expectEmit")?,
1467 read_abi_bool_arg(cx, memory, args_offset, 1, "symbolic vm.expectEmit")?,
1468 read_abi_bool_arg(cx, memory, args_offset, 2, "symbolic vm.expectEmit")?,
1469 ],
1470 data: read_abi_bool_arg(cx, memory, args_offset, 3, "symbolic vm.expectEmit")?,
1471 })
1472 }
1473
1474 pub(crate) fn from_anonymous_args(
1475 cx: &mut SymCx,
1476 memory: &SymMemory,
1477 args_offset: usize,
1478 ) -> Result<Self, SymbolicError> {
1479 Ok(Self {
1480 topics: [
1481 read_abi_bool_arg(cx, memory, args_offset, 0, "symbolic vm.expectEmitAnonymous")?,
1482 read_abi_bool_arg(cx, memory, args_offset, 1, "symbolic vm.expectEmitAnonymous")?,
1483 read_abi_bool_arg(cx, memory, args_offset, 2, "symbolic vm.expectEmitAnonymous")?,
1484 read_abi_bool_arg(cx, memory, args_offset, 3, "symbolic vm.expectEmitAnonymous")?,
1485 ],
1486 data: read_abi_bool_arg(cx, memory, args_offset, 4, "symbolic vm.expectEmitAnonymous")?,
1487 })
1488 }
1489}
1490
1491impl Deref for PathState {
1492 type Target = CallFrame;
1493
1494 fn deref(&self) -> &Self::Target {
1495 &self.frame
1496 }
1497}
1498
1499impl DerefMut for PathState {
1500 fn deref_mut(&mut self) -> &mut Self::Target {
1501 &mut self.frame
1502 }
1503}
1504
1505#[derive(Clone, Debug)]
1506pub(crate) struct CallFrame {
1507 pub(crate) pc: usize,
1508 pub(crate) address: Address,
1509 pub(crate) address_word: SymExpr,
1510 pub(crate) storage_address: Address,
1511 pub(crate) caller: Address,
1512 pub(crate) caller_word: SymExpr,
1513 pub(crate) callvalue: SymExpr,
1514 pub(crate) is_static: bool,
1515 pub(crate) calldata: SymCalldata,
1516 pub(crate) stack: SymStack,
1517 pub(crate) memory: SymMemory,
1518 pub(crate) return_data: SymReturnData,
1519}
1520
1521impl CallFrame {
1522 pub(crate) fn new(
1523 cx: &mut SymCx,
1524 address: Address,
1525 storage_address: Address,
1526 caller: Address,
1527 callvalue: SymExpr,
1528 is_static: bool,
1529 calldata: SymCalldata,
1530 ) -> Self {
1531 Self {
1532 pc: 0,
1533 address,
1534 address_word: SymExpr::constant(cx, address_word(address)),
1535 storage_address,
1536 caller,
1537 caller_word: SymExpr::constant(cx, address_word(caller)),
1538 callvalue,
1539 is_static,
1540 calldata,
1541 stack: SymStack::default(),
1542 memory: SymMemory::default(),
1543 return_data: SymReturnData::empty(cx),
1544 }
1545 }
1546}
1547
1548#[derive(Clone, Debug, Default, PartialEq, Eq)]
1549pub(crate) struct SymbolicPrank {
1550 next_caller: Option<(Address, SymExpr)>,
1551 next_origin: Option<(Address, SymExpr)>,
1552 persistent_caller: Option<(Address, SymExpr)>,
1553 persistent_origin: Option<(Address, SymExpr)>,
1554}
1555
1556impl SymbolicPrank {
1557 pub(crate) fn set_next(
1558 &mut self,
1559 caller: (Address, SymExpr),
1560 origin: Option<(Address, SymExpr)>,
1561 ) {
1562 self.next_caller = Some(caller);
1563 self.next_origin = origin;
1564 }
1565
1566 pub(crate) fn set_persistent(
1567 &mut self,
1568 caller: (Address, SymExpr),
1569 origin: Option<(Address, SymExpr)>,
1570 ) {
1571 self.persistent_caller = Some(caller);
1572 self.persistent_origin = origin;
1573 }
1574
1575 pub(crate) const fn has_active(&self) -> bool {
1576 self.next_caller.is_some()
1577 || self.next_origin.is_some()
1578 || self.persistent_caller.is_some()
1579 || self.persistent_origin.is_some()
1580 }
1581}
1582
1583#[derive(Clone, Debug, PartialEq, Eq)]
1584pub(crate) struct StorageWrite {
1585 address: Address,
1586 key: SymExpr,
1587 value: SymExpr,
1588}
1589
1590impl StorageWrite {
1591 pub(crate) const fn new(address: Address, key: SymExpr, value: SymExpr) -> Self {
1592 Self { address, key, value }
1593 }
1594
1595 pub(crate) fn select_from(
1596 cx: &mut SymCx,
1597 writes: &[Self],
1598 address: Address,
1599 key: SymExpr,
1600 base: SymExpr,
1601 ) -> SymExpr {
1602 let mut value = base;
1603 for write in writes.iter().filter(|write| write.address == address) {
1604 value =
1605 key.clone().select_storage_write(cx, write.key.clone(), write.value.clone(), value);
1606 }
1607 value
1608 }
1609}
1610
1611#[derive(Clone, Debug, Default)]
1613pub(crate) struct SymbolicWorldState {
1614 storage: Vec<StorageWrite>,
1615 transient_storage: Vec<StorageWrite>,
1616 created_accounts: HashSet<Address>,
1617 current_transaction_created_accounts: HashSet<Address>,
1618 balances: HashMap<Address, SymExpr>,
1619 code_cache: HashMap<Address, SymCode>,
1620 nonces: HashMap<Address, u64>,
1621 existing_accounts: HashSet<Address>,
1622 destroyed_accounts: HashSet<Address>,
1623 arbitrary_storage_accounts: HashMap<Address, bool>,
1624 arbitrary_storage_copies: HashMap<Address, Address>,
1625 arbitrary_storage_all: bool,
1626 zero_init_symbolic_storage: bool,
1627 symbolic_address_aliases: HashMap<SymExpr, Address>,
1628 replay_storage_slots: HashMap<Symbol, Vec<SymbolicReplayStorageSlot>>,
1629}
1630
1631#[derive(Clone, Debug)]
1632struct SymbolicReplayStorageSlot {
1633 address: Address,
1634 slot: U256,
1635}
1636
1637#[derive(Clone, Debug, Default)]
1638pub(crate) struct SymbolicWorld {
1639 state: Arc<SymbolicWorldState>,
1640 snapshots: HashMap<U256, Arc<SymbolicWorldState>>,
1641 next_snapshot_id: u64,
1642}
1643
1644impl Deref for SymbolicWorld {
1645 type Target = SymbolicWorldState;
1646
1647 fn deref(&self) -> &Self::Target {
1648 &self.state
1649 }
1650}
1651
1652impl SymbolicWorld {
1653 pub(crate) fn is_destroyed(&self, address: Address) -> bool {
1654 self.destroyed_accounts.contains(&address)
1655 }
1656
1657 pub(crate) fn set_storage_layout(&mut self, layout: SymbolicStorageLayout) {
1658 let state = Arc::make_mut(&mut self.state);
1659 state.arbitrary_storage_all = matches!(layout, SymbolicStorageLayout::Generic);
1660 state.zero_init_symbolic_storage = matches!(layout, SymbolicStorageLayout::ZeroInit);
1661 }
1662
1663 pub(crate) fn sload<FEN: FoundryEvmNetwork>(
1664 &mut self,
1665 cx: &mut SymCx,
1666 executor: &Executor<FEN>,
1667 address: Address,
1668 key: SymExpr,
1669 concrete_key: Option<U256>,
1670 ) -> Result<SymExpr, SymbolicError> {
1671 let base = self.storage_base(cx, executor, address, &key, concrete_key)?;
1672 let read_key = concrete_key.map(|key| SymExpr::constant(cx, key)).unwrap_or(key);
1673 Ok(StorageWrite::select_from(cx, &self.storage, address, read_key, base))
1674 }
1675
1676 pub(crate) fn sstore(&mut self, address: Address, key: SymExpr, value: SymExpr) {
1677 Arc::make_mut(&mut self.state).storage.push(StorageWrite::new(address, key, value));
1678 }
1679
1680 pub(crate) fn tload(&self, cx: &mut SymCx, address: Address, key: SymExpr) -> SymExpr {
1681 let base = SymExpr::zero(cx);
1682 StorageWrite::select_from(cx, &self.transient_storage, address, key, base)
1683 }
1684
1685 pub(crate) fn tstore(&mut self, address: Address, key: SymExpr, value: SymExpr) {
1686 Arc::make_mut(&mut self.state)
1687 .transient_storage
1688 .push(StorageWrite::new(address, key, value));
1689 }
1690
1691 pub(crate) fn clear_transaction_scoped_state(&mut self) {
1693 let state = Arc::make_mut(&mut self.state);
1694 state.transient_storage.clear();
1695 state.current_transaction_created_accounts.clear();
1696 }
1697
1698 pub(crate) fn mark_current_transaction_created(&mut self, address: Address) {
1699 let state = Arc::make_mut(&mut self.state);
1700 state.created_accounts.insert(address);
1701 state.current_transaction_created_accounts.insert(address);
1702 }
1703
1704 pub(crate) fn was_created_in_current_transaction(&self, address: Address) -> bool {
1706 self.current_transaction_created_accounts.contains(&address)
1707 }
1708
1709 pub(crate) fn enable_arbitrary_storage(&mut self, address: Address, overwrite: bool) {
1710 Arc::make_mut(&mut self.state).arbitrary_storage_accounts.insert(address, overwrite);
1711 }
1712
1713 pub(crate) fn replay_storage_symbols(&self) -> SymbolicVars {
1714 self.replay_storage_slots.keys().copied().collect()
1715 }
1716
1717 pub(crate) fn replay_storage_assignments(
1718 &self,
1719 model: &SymbolicModel,
1720 ) -> Result<Vec<SymbolicStorageAssignment>, SymbolicError> {
1721 let mut assignments = std::collections::BTreeMap::<(Address, U256), U256>::new();
1722 for (symbol, slots) in &self.replay_storage_slots {
1723 let Some(value) = model.get(symbol).copied() else { continue };
1724 for slot in slots {
1725 match assignments.entry((slot.address, slot.slot)) {
1726 std::collections::btree_map::Entry::Vacant(entry) => {
1727 entry.insert(value);
1728 }
1729 std::collections::btree_map::Entry::Occupied(entry)
1730 if *entry.get() == value => {}
1731 std::collections::btree_map::Entry::Occupied(_) => {
1732 return Err(SymbolicError::Solver(
1733 "conflicting symbolic storage replay assignments".to_string(),
1734 ));
1735 }
1736 }
1737 }
1738 }
1739 Ok(assignments
1740 .into_iter()
1741 .map(|((address, slot), value)| SymbolicStorageAssignment { address, slot, value })
1742 .collect())
1743 }
1744
1745 pub(crate) fn resolve_address(&self, expr: &SymExpr) -> Option<Address> {
1746 expr.as_const().map(word_to_address).or_else(|| {
1747 self.symbolic_address_aliases.get(expr).copied().or_else(|| {
1748 self.symbolic_address_aliases.iter().find_map(|(alias, address)| {
1749 expr.symbolic_address_equivalent(alias).then_some(*address)
1750 })
1751 })
1752 })
1753 }
1754
1755 pub(crate) fn symbolic_address_slot(&mut self, expr: SymExpr) -> Address {
1756 if let Some(address) = self.resolve_address(&expr) {
1757 return address;
1758 }
1759 let address = expr.representative_symbolic_address();
1760 Arc::make_mut(&mut self.state).symbolic_address_aliases.insert(expr, address);
1761 address
1762 }
1763
1764 pub(crate) fn symbolic_word_for_address(&self, address: Address) -> Option<SymExpr> {
1765 self.symbolic_address_aliases
1766 .iter()
1767 .find_map(|(word, slot)| (*slot == address).then(|| word.clone()))
1768 }
1769
1770 pub(crate) fn snapshot_state(&mut self) -> U256 {
1771 let id = U256::from(self.next_snapshot_id);
1772 self.next_snapshot_id = self.next_snapshot_id.saturating_add(1);
1773 self.snapshots.insert(id, Arc::clone(&self.state));
1774 id
1775 }
1776
1777 pub(crate) fn restore_snapshot(&mut self, id: U256) -> bool {
1778 let Some(snapshot) = self.snapshots.get(&id) else {
1779 return false;
1780 };
1781 self.state = Arc::clone(snapshot);
1782 true
1783 }
1784
1785 pub(crate) fn delete_snapshot(&mut self, id: U256) -> bool {
1786 self.snapshots.remove(&id).is_some()
1787 }
1788
1789 pub(crate) fn delete_snapshots(&mut self) {
1790 self.snapshots.clear();
1791 }
1792
1793 pub(crate) fn storage_base<FEN: FoundryEvmNetwork>(
1794 &mut self,
1795 cx: &mut SymCx,
1796 executor: &Executor<FEN>,
1797 address: Address,
1798 key: &SymExpr,
1799 concrete_key: Option<U256>,
1800 ) -> Result<SymExpr, SymbolicError> {
1801 if let Some(base) = self.arbitrary_storage_base(cx, executor, address, key, concrete_key)? {
1802 return Ok(base);
1803 }
1804 if self.created_accounts.contains(&address) {
1805 return Ok(SymExpr::zero(cx));
1806 }
1807 if let Some(key) = concrete_key {
1808 return executor
1809 .backend()
1810 .storage_ref(address, key)
1811 .map(|value| SymExpr::constant(cx, value))
1812 .map_err(|err| SymbolicError::Backend(err.to_string()));
1813 }
1814 if let Some(key) = key.as_const() {
1815 executor
1816 .backend()
1817 .storage_ref(address, key)
1818 .map(|value| SymExpr::constant(cx, value))
1819 .map_err(|err| SymbolicError::Backend(err.to_string()))
1820 } else if self.zero_init_symbolic_storage {
1821 Ok(SymExpr::zero(cx))
1822 } else {
1823 let name = symbolic_storage_symbol(cx, address, key);
1824 Ok(SymExpr::get_var(cx, name))
1825 }
1826 }
1827
1828 fn arbitrary_storage_base<FEN: FoundryEvmNetwork>(
1829 &mut self,
1830 cx: &mut SymCx,
1831 executor: &Executor<FEN>,
1832 address: Address,
1833 key: &SymExpr,
1834 concrete_key: Option<U256>,
1835 ) -> Result<Option<SymExpr>, SymbolicError> {
1836 if let Some(slot) = concrete_key.or_else(|| key.as_const())
1837 && !self.arbitrary_storage_all
1838 {
1839 let overwrite_arbitrary_storage =
1840 self.arbitrary_storage_accounts.get(&address).copied();
1841 let has_arbitrary_storage = overwrite_arbitrary_storage.is_some();
1842 let is_copied_storage =
1843 !has_arbitrary_storage && self.arbitrary_storage_copies.contains_key(&address);
1844 let preserve_nonzero_slot =
1845 overwrite_arbitrary_storage == Some(false) || is_copied_storage;
1846 if preserve_nonzero_slot {
1847 let concrete = executor
1848 .backend()
1849 .storage_ref(address, slot)
1850 .map_err(|err| SymbolicError::Backend(err.to_string()))?;
1851 if !concrete.is_zero() {
1852 return Ok(Some(SymExpr::constant(cx, concrete)));
1853 }
1854 }
1855 }
1856
1857 Ok(self.unchecked_arbitrary_storage_base(cx, address, key, concrete_key))
1858 }
1859
1860 fn unchecked_arbitrary_storage_base(
1861 &mut self,
1862 cx: &mut SymCx,
1863 address: Address,
1864 key: &SymExpr,
1865 concrete_key: Option<U256>,
1866 ) -> Option<SymExpr> {
1867 let overwrite_arbitrary_storage = self.arbitrary_storage_accounts.get(&address).copied();
1868 let has_arbitrary_storage = overwrite_arbitrary_storage.is_some();
1869 let copied_source = (!has_arbitrary_storage)
1870 .then(|| self.arbitrary_storage_copies.get(&address).copied())
1871 .flatten();
1872 let symbol_address = if self.arbitrary_storage_all || has_arbitrary_storage {
1873 address
1874 } else {
1875 copied_source?
1876 };
1877 let symbol = symbolic_storage_symbol(cx, symbol_address, key);
1878 if let Some(slot) = concrete_key.or_else(|| key.as_const()) {
1879 if has_arbitrary_storage {
1880 self.record_replay_storage_slot(symbol, address, slot);
1881 }
1882 if let Some(source) = copied_source {
1883 self.record_replay_storage_slot(symbol, source, slot);
1884 self.record_replay_storage_slot(symbol, address, slot);
1885 }
1886 }
1887 let value = SymExpr::get_var(cx, symbol);
1888 if let Some(source) = copied_source {
1889 self.sstore(source, key.clone(), value.clone());
1890 }
1891 Some(value)
1892 }
1893
1894 fn record_replay_storage_slot(&mut self, symbol: Symbol, address: Address, slot: U256) {
1895 if self.replay_storage_slots.get(&symbol).is_some_and(|slots| {
1896 slots.iter().any(|existing| existing.address == address && existing.slot == slot)
1897 }) {
1898 return;
1899 }
1900 Arc::make_mut(&mut self.state)
1901 .replay_storage_slots
1902 .entry(symbol)
1903 .or_default()
1904 .push(SymbolicReplayStorageSlot { address, slot });
1905 }
1906
1907 fn merge_replay_metadata_from(&mut self, other: &Self) {
1908 for (symbol, slots) in &other.replay_storage_slots {
1909 for slot in slots {
1910 self.record_replay_storage_slot(*symbol, slot.address, slot.slot);
1911 }
1912 }
1913 let state = Arc::make_mut(&mut self.state);
1914 for (expr, address) in &other.symbolic_address_aliases {
1915 state.symbolic_address_aliases.entry(expr.clone()).or_insert(*address);
1916 }
1917 }
1918
1919 pub(crate) fn backend_balance<FEN: FoundryEvmNetwork>(
1920 &self,
1921 executor: &Executor<FEN>,
1922 address: Address,
1923 ) -> U256 {
1924 executor
1925 .backend()
1926 .basic_ref(address)
1927 .ok()
1928 .flatten()
1929 .map(|account| account.balance)
1930 .unwrap_or_default()
1931 }
1932
1933 pub(crate) fn balance_word_for_address<FEN: FoundryEvmNetwork>(
1934 &self,
1935 cx: &mut SymCx,
1936 executor: &Executor<FEN>,
1937 address: Address,
1938 ) -> SymExpr {
1939 if self.destroyed_accounts.contains(&address) {
1940 return SymExpr::zero(cx);
1941 }
1942 self.balances
1943 .get(&address)
1944 .cloned()
1945 .unwrap_or_else(|| SymExpr::constant(cx, self.backend_balance(executor, address)))
1946 }
1947
1948 pub(crate) fn balance_word<FEN: FoundryEvmNetwork>(
1949 &mut self,
1950 cx: &mut SymCx,
1951 executor: &Executor<FEN>,
1952 address_expr: SymExpr,
1953 ) -> Result<SymExpr, SymbolicError> {
1954 if let Some(address) = self.resolve_address(&address_expr) {
1955 return Ok(self.balance_word_for_address(cx, executor, address));
1956 }
1957
1958 let expr = address_expr;
1959 let representative = expr.representative_symbolic_address();
1960 let mut result = self.balance_word_for_address(cx, executor, representative);
1961 for (address, balance) in &self.balances {
1962 if self.destroyed_accounts.contains(address) {
1963 continue;
1964 }
1965 let address = SymExpr::constant(cx, address_word(*address));
1966 let condition = SymBoolExpr::eq(cx, expr.clone(), address);
1967 result = SymExpr::ite(cx, condition, balance.clone(), result);
1968 }
1969
1970 Ok(result)
1971 }
1972
1973 pub(crate) fn set_balance_word(&mut self, address: Address, value: SymExpr) {
1974 let account_exists = !value.as_const().is_some_and(|value| value.is_zero());
1975 let state = Arc::make_mut(&mut self.state);
1976 state.balances.insert(address, value);
1977 if account_exists {
1978 state.existing_accounts.insert(address);
1979 state.destroyed_accounts.remove(&address);
1980 }
1981 }
1982
1983 pub(crate) fn transfer<FEN: FoundryEvmNetwork>(
1984 &mut self,
1985 cx: &mut SymCx,
1986 executor: &Executor<FEN>,
1987 from: Address,
1988 to: Address,
1989 value: SymExpr,
1990 ) {
1991 if from == to || value.as_const().is_some_and(|value| value.is_zero()) {
1992 return;
1993 }
1994 let from_balance = self.balance_word_for_address(cx, executor, from);
1995 let to_balance = self.balance_word_for_address(cx, executor, to);
1996 let from_balance = SymExpr::binop(cx, SymBinOp::Sub, from_balance, value.clone());
1997 let to_balance = SymExpr::binop(cx, SymBinOp::Add, to_balance, value);
1998 self.set_balance_word(from, from_balance);
1999 self.set_balance_word(to, to_balance);
2000 }
2001
2002 pub(crate) fn nonce<FEN: FoundryEvmNetwork>(
2003 &self,
2004 executor: &Executor<FEN>,
2005 address: Address,
2006 ) -> Result<u64, SymbolicError> {
2007 if self.destroyed_accounts.contains(&address) {
2008 return Ok(self.nonces.get(&address).copied().unwrap_or_default());
2009 }
2010 if let Some(nonce) = self.nonces.get(&address) {
2011 return Ok(*nonce);
2012 }
2013 executor
2014 .backend()
2015 .basic_ref(address)
2016 .map_err(|err| SymbolicError::Backend(err.to_string()))
2017 .map(|account| account.map(|account| account.nonce).unwrap_or_default())
2018 }
2019
2020 pub(crate) fn set_nonce(&mut self, address: Address, nonce: u64) {
2021 let state = Arc::make_mut(&mut self.state);
2022 state.nonces.insert(address, nonce);
2023 if nonce != 0 {
2024 state.existing_accounts.insert(address);
2025 state.destroyed_accounts.remove(&address);
2026 }
2027 }
2028
2029 pub(crate) fn increment_nonce<FEN: FoundryEvmNetwork>(
2030 &mut self,
2031 executor: &Executor<FEN>,
2032 address: Address,
2033 ) -> Result<(), SymbolicError> {
2034 let nonce = self.nonce(executor, address)?;
2035 self.set_nonce(address, nonce.saturating_add(1));
2036 Ok(())
2037 }
2038
2039 pub(crate) fn has_code_or_nonce<FEN: FoundryEvmNetwork>(
2040 &mut self,
2041 cx: &mut SymCx,
2042 executor: &Executor<FEN>,
2043 address: Address,
2044 ) -> Result<bool, SymbolicError> {
2045 if self.destroyed_accounts.contains(&address) {
2046 return Ok(false);
2047 }
2048 Ok(!self.extcode(cx, executor, address)?.is_empty() || self.nonce(executor, address)? != 0)
2049 }
2050
2051 pub(crate) fn install_code(&mut self, address: Address, code: SymCode) {
2052 let state = Arc::make_mut(&mut self.state);
2053 state.code_cache.insert(address, code);
2054 state.existing_accounts.insert(address);
2055 state.destroyed_accounts.remove(&address);
2056 }
2057
2058 pub(crate) fn selfdestruct_legacy<FEN: FoundryEvmNetwork>(
2060 &mut self,
2061 cx: &mut SymCx,
2062 executor: &Executor<FEN>,
2063 address: Address,
2064 beneficiary: Address,
2065 ) -> Result<(), SymbolicError> {
2066 let balance = self.balance_word_for_address(cx, executor, address);
2067 if beneficiary != address && !balance.as_const().is_some_and(|value| value.is_zero()) {
2068 let beneficiary_balance = self.balance_word_for_address(cx, executor, beneficiary);
2069 let beneficiary_balance =
2070 SymExpr::binop(cx, SymBinOp::Add, beneficiary_balance, balance);
2071 self.set_balance_word(beneficiary, beneficiary_balance);
2072 }
2073 let nonce = if self.nonces.contains_key(&address) {
2074 None
2075 } else {
2076 Some(self.nonce(executor, address)?)
2077 };
2078 let zero = SymExpr::zero(cx);
2079 let empty_code = SymCode::empty(cx);
2080 let state = Arc::make_mut(&mut self.state);
2081 state.balances.insert(address, zero);
2082 state.code_cache.insert(address, empty_code);
2083 if let Some(nonce) = nonce {
2084 state.nonces.insert(address, nonce);
2085 }
2086 state.storage.retain(|write| write.address != address);
2087 state.transient_storage.retain(|write| write.address != address);
2088 state.created_accounts.remove(&address);
2089 state.current_transaction_created_accounts.remove(&address);
2090 state.existing_accounts.remove(&address);
2091 state.destroyed_accounts.insert(address);
2092 Ok(())
2093 }
2094
2095 pub(crate) fn selfdestruct_cancun_existing<FEN: FoundryEvmNetwork>(
2097 &mut self,
2098 cx: &mut SymCx,
2099 executor: &Executor<FEN>,
2100 address: Address,
2101 beneficiary: Address,
2102 ) {
2103 let balance = self.balance_word_for_address(cx, executor, address);
2104 if beneficiary != address && !balance.as_const().is_some_and(|value| value.is_zero()) {
2105 let beneficiary_balance = self.balance_word_for_address(cx, executor, beneficiary);
2106 let beneficiary_balance =
2109 SymExpr::binop(cx, SymBinOp::Add, beneficiary_balance, balance);
2110 self.set_balance_word(beneficiary, beneficiary_balance);
2111 let zero = SymExpr::zero(cx);
2112 Arc::make_mut(&mut self.state).balances.insert(address, zero);
2113 }
2114 }
2115
2116 pub(crate) fn account_exists<FEN: FoundryEvmNetwork>(
2117 &mut self,
2118 cx: &mut SymCx,
2119 executor: &Executor<FEN>,
2120 address: Address,
2121 ) -> Result<bool, SymbolicError> {
2122 if address == CHEATCODE_ADDRESS || address == SYMBOLIC_VM_COMPAT_ADDRESS {
2123 return Ok(true);
2124 }
2125 if self.destroyed_accounts.contains(&address) {
2126 return Ok(false);
2127 }
2128 if self.existing_accounts.contains(&address) {
2129 return Ok(true);
2130 }
2131 if self
2132 .balances
2133 .get(&address)
2134 .is_some_and(|balance| !balance.as_const().is_some_and(|value| value.is_zero()))
2135 || self.nonces.get(&address).is_some_and(|nonce| *nonce != 0)
2136 || self.code_cache.get(&address).is_some_and(|code| !code.is_empty())
2137 {
2138 Arc::make_mut(&mut self.state).existing_accounts.insert(address);
2139 return Ok(true);
2140 }
2141
2142 let Some(account) = executor
2143 .backend()
2144 .basic_ref(address)
2145 .map_err(|err| SymbolicError::Backend(err.to_string()))?
2146 else {
2147 return Ok(false);
2148 };
2149
2150 if account.nonce != 0 || !account.balance.is_zero() {
2151 Arc::make_mut(&mut self.state).existing_accounts.insert(address);
2152 return Ok(true);
2153 }
2154
2155 if let Some(code) = account.code.as_ref()
2156 && !code.is_empty()
2157 {
2158 let code = SymCode::from_bytecode(cx, code);
2159 let state = Arc::make_mut(&mut self.state);
2160 state.code_cache.insert(address, code);
2161 state.existing_accounts.insert(address);
2162 return Ok(true);
2163 }
2164
2165 Ok(false)
2166 }
2167
2168 pub(crate) fn extcode<FEN: FoundryEvmNetwork>(
2169 &mut self,
2170 cx: &mut SymCx,
2171 executor: &Executor<FEN>,
2172 address: Address,
2173 ) -> Result<SymCode, SymbolicError> {
2174 if address == CHEATCODE_ADDRESS || address == SYMBOLIC_VM_COMPAT_ADDRESS {
2175 return Ok(SymCode::concrete(cx, vec![0]));
2176 }
2177 let spec_id: SpecId = executor.spec_id().into();
2178 if precompile_number_for_spec(address, spec_id).is_some() {
2179 return Ok(SymCode::empty(cx));
2180 }
2181 if self.destroyed_accounts.contains(&address) {
2182 return Ok(SymCode::empty(cx));
2183 }
2184 if let Some(code) = self.code_cache.get(&address) {
2185 return Ok(code.clone());
2186 }
2187 let account = executor
2188 .backend()
2189 .basic_ref(address)
2190 .map_err(|err| SymbolicError::Backend(err.to_string()))?;
2191 if let Some(account) = account.as_ref()
2192 && (account.nonce != 0
2193 || !account.balance.is_zero()
2194 || account.code.as_ref().is_some_and(|code| !code.is_empty()))
2195 {
2196 Arc::make_mut(&mut self.state).existing_accounts.insert(address);
2197 }
2198 let bytecode = account.as_ref().and_then(|account| account.code.as_ref());
2199 let code = bytecode
2200 .map(|bytecode| SymCode::from_bytecode(cx, bytecode))
2201 .unwrap_or_else(|| SymCode::empty(cx));
2202 Arc::make_mut(&mut self.state).code_cache.insert(address, code.clone());
2203 Ok(code)
2204 }
2205
2206 pub(crate) fn extcode_hash_for_address<FEN: FoundryEvmNetwork>(
2207 &mut self,
2208 cx: &mut SymCx,
2209 executor: &Executor<FEN>,
2210 address: Address,
2211 ) -> Result<SymExpr, SymbolicError> {
2212 if self.account_exists(cx, executor, address)? {
2213 let code = self.extcode(cx, executor, address)?;
2214 let bytes = code.read_byte_exprs(cx, 0, code.len());
2215 Ok(keccak_word(cx, bytes))
2216 } else {
2217 Ok(SymExpr::zero(cx))
2218 }
2219 }
2220
2221 pub(crate) fn extcode_size_word<FEN: FoundryEvmNetwork>(
2222 &mut self,
2223 cx: &mut SymCx,
2224 executor: &Executor<FEN>,
2225 address_expr: SymExpr,
2226 ) -> Result<SymExpr, SymbolicError> {
2227 if let Some(address) = self.resolve_address(&address_expr) {
2228 let len = self.extcode(cx, executor, address)?.len();
2229 return Ok(SymExpr::constant(cx, U256::from(len)));
2230 }
2231
2232 let expr = address_expr;
2233 let representative = expr.representative_symbolic_address();
2234 let len = self.extcode(cx, executor, representative)?.len();
2235 let mut result = SymExpr::constant(cx, U256::from(len));
2236 for (address, code) in &self.code_cache {
2237 if self.destroyed_accounts.contains(address) {
2238 continue;
2239 }
2240 let address = SymExpr::constant(cx, address_word(*address));
2241 let condition = SymBoolExpr::eq(cx, expr.clone(), address);
2242 let len = SymExpr::constant(cx, U256::from(code.len()));
2243 result = SymExpr::ite(cx, condition, len, result);
2244 }
2245
2246 Ok(result)
2247 }
2248
2249 pub(crate) fn extcode_hash_word<FEN: FoundryEvmNetwork>(
2250 &mut self,
2251 cx: &mut SymCx,
2252 executor: &Executor<FEN>,
2253 address_expr: SymExpr,
2254 ) -> Result<SymExpr, SymbolicError> {
2255 if let Some(address) = self.resolve_address(&address_expr) {
2256 return self.extcode_hash_for_address(cx, executor, address);
2257 }
2258
2259 let expr = address_expr;
2260 let representative = expr.representative_symbolic_address();
2261 let mut result = self.extcode_hash_for_address(cx, executor, representative)?;
2262 let cached_codes = self.code_cache.iter().collect::<Vec<_>>();
2263 for (address, code) in cached_codes.into_iter().rev() {
2264 let hash = if self.destroyed_accounts.contains(address) {
2265 SymExpr::zero(cx)
2266 } else {
2267 let bytes = code.read_byte_exprs(cx, 0, code.len());
2268 keccak_word(cx, bytes)
2269 };
2270 let address = SymExpr::constant(cx, address_word(*address));
2271 let condition = SymBoolExpr::eq(cx, expr.clone(), address);
2272 result = SymExpr::ite(cx, condition, hash, result);
2273 }
2274
2275 Ok(result)
2276 }
2277
2278 pub(crate) fn extcode_bytes_word<FEN: FoundryEvmNetwork>(
2279 &mut self,
2280 cx: &mut SymCx,
2281 executor: &Executor<FEN>,
2282 address_expr: SymExpr,
2283 offset: SymExpr,
2284 size: usize,
2285 ) -> Result<SymBytes, SymbolicError> {
2286 if let Some(address) = self.resolve_address(&address_expr) {
2287 return Ok(self.extcode(cx, executor, address)?.read_bytes_offset(cx, offset, size));
2288 }
2289
2290 let expr = address_expr;
2291 let representative = expr.representative_symbolic_address();
2292 let mut result = self.extcode(cx, executor, representative)?.read_byte_exprs_offset(
2293 cx,
2294 offset.clone(),
2295 size,
2296 );
2297 let cached_codes = self.code_cache.iter().collect::<Vec<_>>();
2298 for (address, code) in cached_codes.into_iter().rev() {
2299 let bytes = if self.destroyed_accounts.contains(address) {
2300 vec![SymExpr::zero(cx); size]
2301 } else {
2302 code.read_byte_exprs_offset(cx, offset.clone(), size)
2303 };
2304 let address = SymExpr::constant(cx, address_word(*address));
2305 let condition = SymBoolExpr::eq(cx, expr.clone(), address);
2306 for (idx, byte) in bytes.into_iter().enumerate() {
2307 result[idx] = SymExpr::ite(cx, condition.clone(), byte, result[idx].clone());
2308 }
2309 }
2310
2311 Ok(SymBytes::exprs(cx, result))
2312 }
2313
2314 pub(crate) fn symbolic_call_targets<FEN: FoundryEvmNetwork>(
2315 &mut self,
2316 cx: &mut SymCx,
2317 executor: &Executor<FEN>,
2318 ) -> Result<Vec<Address>, SymbolicError> {
2319 let mut addresses = HashSet::<Address>::default();
2320 addresses.extend(self.code_cache.keys().copied());
2321 addresses.extend(self.existing_accounts.iter().copied());
2322 addresses.extend(executor.backend().mem_db().cache.accounts.keys().copied());
2323 if let Some(db) = executor.backend().active_fork_db() {
2324 addresses.extend(db.cache.accounts.keys().copied());
2325 }
2326 let mut addresses = addresses.into_iter().collect::<Vec<_>>();
2327 addresses.sort_unstable();
2328
2329 let mut targets = Vec::new();
2330 let spec_id: SpecId = executor.spec_id().into();
2331 for address in addresses {
2332 if address == CHEATCODE_ADDRESS
2333 || address == SYMBOLIC_VM_COMPAT_ADDRESS
2334 || precompile_number_for_spec(address, spec_id).is_some()
2335 {
2336 continue;
2337 }
2338 if !self.extcode(cx, executor, address)?.is_empty() {
2339 targets.push(address);
2340 }
2341 }
2342 Ok(targets)
2343 }
2344}
2345
2346fn symbolic_storage_symbol(cx: &mut SymCx, address: Address, key: &SymExpr) -> Symbol {
2347 stable_symbol(
2348 cx,
2349 "storage",
2350 format!("{address:?}:{:?}", ExpressionDigests::identity([key])).as_bytes(),
2351 )
2352}
2353
2354#[derive(Clone, Debug)]
2355pub(crate) struct SymbolicBlock {
2356 pub(crate) chain_id: SymExpr,
2357 pub(crate) coinbase: Address,
2358 pub(crate) timestamp: SymExpr,
2359 pub(crate) number: SymExpr,
2360 pub(crate) difficulty: SymExpr,
2361 pub(crate) gaslimit: SymExpr,
2362 pub(crate) basefee: SymExpr,
2363 pub(crate) blob_basefee: SymExpr,
2364 pub(crate) block_hashes: HashMap<U256, SymExpr>,
2365 pub(crate) blob_hashes: Vec<B256>,
2366}
2367
2368impl SymbolicBlock {
2369 pub(crate) fn new(cx: &mut SymCx) -> Self {
2370 Self {
2371 chain_id: SymExpr::one(cx),
2372 coinbase: Address::ZERO,
2373 timestamp: SymExpr::zero(cx),
2374 number: SymExpr::zero(cx),
2375 difficulty: SymExpr::zero(cx),
2376 gaslimit: SymExpr::zero(cx),
2377 basefee: SymExpr::zero(cx),
2378 blob_basefee: SymExpr::zero(cx),
2379 block_hashes: HashMap::default(),
2380 blob_hashes: Vec::new(),
2381 }
2382 }
2383
2384 pub(crate) fn from_executor<FEN: FoundryEvmNetwork>(
2385 cx: &mut SymCx,
2386 executor: &Executor<FEN>,
2387 ) -> Self {
2388 let evm_env = executor.evm_env();
2389 let block = executor
2390 .inspector()
2391 .cheatcodes
2392 .as_ref()
2393 .and_then(|cheats| cheats.block.as_ref())
2394 .unwrap_or(evm_env.block_env());
2395 let difficulty = block.prevrandao().map(Into::into).unwrap_or_else(|| block.difficulty());
2396
2397 Self {
2398 chain_id: SymExpr::constant(cx, U256::from(evm_env.cfg_env.chain_id)),
2399 coinbase: block.beneficiary(),
2400 timestamp: SymExpr::constant(cx, block.timestamp()),
2401 number: SymExpr::constant(cx, block.number()),
2402 difficulty: SymExpr::constant(cx, difficulty),
2403 gaslimit: SymExpr::constant(cx, U256::from(block.gas_limit())),
2404 basefee: SymExpr::constant(cx, U256::from(block.basefee())),
2405 blob_basefee: SymExpr::constant(
2406 cx,
2407 U256::from(block.blob_gasprice().unwrap_or_default()),
2408 ),
2409 block_hashes: HashMap::default(),
2410 blob_hashes: executor.tx_env().blob_versioned_hashes().to_vec(),
2411 }
2412 }
2413
2414 pub(crate) fn set_block_hash(
2415 &mut self,
2416 block_number: U256,
2417 block_hash: SymExpr,
2418 ) -> Result<(), SymbolicError> {
2419 let current = self.number.as_const_or("symbolic vm.setBlockhash current number")?;
2420 if block_number < current && current - block_number <= U256::from(256) {
2421 self.block_hashes.insert(block_number, block_hash);
2422 }
2423 Ok(())
2424 }
2425
2426 pub(crate) fn block_hash<FEN: FoundryEvmNetwork>(
2427 &self,
2428 cx: &mut SymCx,
2429 executor: &Executor<FEN>,
2430 block_number: U256,
2431 ) -> Result<SymExpr, SymbolicError> {
2432 let current = self.number.as_const_or("symbolic BLOCKHASH current number")?;
2433 if block_number >= current || current - block_number > U256::from(256) {
2434 return Ok(SymExpr::zero(cx));
2435 }
2436 if let Some(hash) = self.block_hashes.get(&block_number) {
2437 return Ok(hash.clone());
2438 }
2439 let Ok(block_number) = u64::try_from(block_number) else {
2440 return Ok(SymExpr::zero(cx));
2441 };
2442 let hash = executor
2443 .backend()
2444 .block_hash_ref(block_number)
2445 .map_err(|err| SymbolicError::Backend(err.to_string()))?;
2446 Ok(SymExpr::constant(cx, U256::from_be_slice(hash.as_slice())))
2447 }
2448
2449 pub(crate) fn block_hash_word<FEN: FoundryEvmNetwork>(
2450 &self,
2451 cx: &mut SymCx,
2452 executor: &Executor<FEN>,
2453 block_number: SymExpr,
2454 ) -> Result<SymExpr, SymbolicError> {
2455 if let Some(block_number) = block_number.as_const() {
2456 return self.block_hash(cx, executor, block_number);
2457 }
2458 let current = self.number.as_const_or("symbolic BLOCKHASH current number")?;
2459 if current.is_zero() {
2460 return Ok(SymExpr::zero(cx));
2461 }
2462
2463 let mut result = SymExpr::zero(cx);
2464 let max_distance =
2465 usize::try_from(current.min(U256::from(256))).expect("checked blockhash distance");
2466 for distance in (1..=max_distance).rev() {
2467 let candidate = current - U256::from(distance);
2468 let hash = self.block_hash(cx, executor, candidate)?;
2469 if hash.as_const().is_some_and(|hash| hash.is_zero()) {
2470 continue;
2471 }
2472 let candidate = SymExpr::constant(cx, candidate);
2473 let condition = SymBoolExpr::eq(cx, block_number.clone(), candidate);
2474 result = SymExpr::ite(cx, condition, hash, result);
2475 }
2476
2477 Ok(result)
2478 }
2479}