1use super::*;
4
5impl SymBoolExpr {
6 pub(crate) fn contains_hard_arith(&self) -> bool {
7 self.visit_bool(is_hard_arith_node)
8 }
9
10 fn contains_symbolic_hash(&self) -> bool {
11 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Hash { .. }))
12 }
13}
14
15impl SymExpr {
16 fn contains_var(&self) -> bool {
17 self.visit_bool(|expr| {
18 matches!(
19 expr.kind(),
20 SymExprKind::Var(_) | SymExprKind::Keccak { .. } | SymExprKind::Hash { .. }
21 )
22 })
23 }
24}
25
26fn is_hard_arith_node(expr: &SymExpr) -> bool {
27 match expr.kind() {
28 SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
29 left.contains_var() && right.contains_var()
30 }
31 SymExprKind::BinOp(
32 SymBinOp::UDiv | SymBinOp::URem | SymBinOp::SDiv | SymBinOp::SRem,
33 left,
34 right,
35 ) => left.contains_var() || right.contains_var(),
36 SymExprKind::TernOp(_, left, right, modulus) => {
37 left.contains_var() || right.contains_var() || modulus.contains_var()
38 }
39 _ => false,
40 }
41}
42
43pub(crate) fn constraints_prefer_hard_arith_fallback_first(
45 cx: &SymCx,
46 constraints: &[SymBoolExpr],
47) -> bool {
48 if !constraints.iter().any(SymBoolExpr::contains_hard_arith)
49 || constraints.iter().any(SymBoolExpr::contains_symbolic_hash)
50 {
51 return false;
52 }
53
54 let mut vars = SymbolicVars::default();
55 for constraint in constraints {
56 collect_bool_fallback_vars(constraint, &mut vars);
57 }
58 let vars = fallback_search_vars(cx, vars, constraints);
59 !vars.is_empty() && vars.len() <= HARD_ARITH_FALLBACK_MAX_VARS
60}
61
62pub(crate) fn hard_arith_fallback_model(
63 cx: &SymCx,
64 constraints: &[SymBoolExpr],
65) -> Option<SymbolicModel> {
66 if !constraints.iter().any(SymBoolExpr::contains_hard_arith)
67 || constraints.iter().any(SymBoolExpr::contains_symbolic_hash)
68 {
69 return None;
70 }
71
72 let mut vars = SymbolicVars::default();
73 let mut constants = HashSet::<U256>::default();
74 for constraint in constraints {
75 collect_bool_fallback_vars(constraint, &mut vars);
76 collect_bool_constants(constraint, &mut constants);
77 }
78 let mut constants = constants.into_iter().collect::<Vec<_>>();
79 constants.sort_unstable();
80 let vars = fallback_search_vars(cx, vars, constraints);
81 if vars.is_empty() || vars.len() > HARD_ARITH_FALLBACK_MAX_VARS {
82 return None;
83 }
84
85 let candidates = vars
86 .iter()
87 .map(|var| fallback_candidates_for_var(var, constraints, &constants))
88 .collect::<Option<Vec<_>>>()?;
89 let searched_vars = vars.iter().copied().collect::<SymbolicVars>();
90 let constraint_vars = constraints
91 .iter()
92 .map(|constraint| {
93 let mut vars = SymbolicVars::default();
94 constraint.collect_vars(&mut vars);
95 vars
96 })
97 .collect::<Vec<_>>();
98 let mut model = SymbolicModel::default();
99 let mut assignments = 0usize;
100 let search = FallbackSearch {
101 constraints,
102 constraint_vars: &constraint_vars,
103 searched_vars: &searched_vars,
104 vars: &vars,
105 candidates: &candidates,
106 max_assignments: HARD_ARITH_FALLBACK_MAX_ASSIGNMENTS,
107 };
108 search.model(0, &mut model, &mut assignments)
109}
110
111const MAX_CHECKED_MUL_SUPPORT_VISITS: usize = 256;
114
115pub(super) fn checked_mul_guard_branch_model(
124 cx: &SymCx,
125 constraints: &[SymBoolExpr],
126 original_constraints: &[SymBoolExpr],
127 replayable_storage: &SymbolicVars,
128) -> Option<SymbolicModel> {
129 let mut eval_vars = SymbolicVars::default();
130 for constraint in original_constraints {
131 constraint.collect_eval_vars(&mut eval_vars);
132 }
133 if eval_vars
134 .iter()
135 .any(|var| !cx.is_replayable_input(*var) && !replayable_storage.contains(var))
136 {
137 return None;
138 }
139
140 let mut remaining_support_visits = MAX_CHECKED_MUL_SUPPORT_VISITS;
141 let mut candidates = Vec::new();
142 let mut seen = HashSet::<&SymBoolExpr>::default();
143 let mut pending = Vec::new();
144 for constraint in constraints {
145 if !seen.insert(constraint) {
146 continue;
147 }
148 if remaining_support_visits == 0 {
149 return None;
150 }
151 remaining_support_visits -= 1;
152 if let Some(candidate) = checked_mul_guard_branch(constraint) {
153 candidates.push(candidate);
154 }
155 match constraint.kind() {
156 SymBoolExprKind::Not(inner) => pending.push(inner),
157 SymBoolExprKind::And(values) => pending.extend(values.iter()),
158 SymBoolExprKind::Const(_) | SymBoolExprKind::Cmp(_, _, _) => {}
159 }
160 }
161
162 let mut nested = Vec::new();
163 while let Some(constraint) = pending.pop() {
164 if !seen.insert(constraint) {
165 continue;
166 }
167 if remaining_support_visits == 0 {
168 return None;
169 }
170 remaining_support_visits -= 1;
171 nested.push(constraint);
172 match constraint.kind() {
173 SymBoolExprKind::Not(inner) => pending.push(inner),
174 SymBoolExprKind::And(values) => pending.extend(values.iter()),
175 SymBoolExprKind::Const(_) | SymBoolExprKind::Cmp(_, _, _) => {}
176 }
177 }
178 for constraint in nested.into_iter().rev() {
179 if let Some(candidate) = checked_mul_guard_branch(constraint) {
180 candidates.push(candidate);
181 }
182 }
183
184 for (zero_operand, expected, guard_is_true) in candidates {
185 let assignments = if guard_is_true {
186 [(U256::ZERO, U256::ZERO), (U256::ONE, U256::ONE)]
187 } else {
188 [(U256::MAX, U256::from(2)), (U256::from(2), U256::MAX)]
189 };
190 for (zero_default, expected_default) in assignments {
191 let seed_orders = [
192 [(&zero_operand, zero_default), (&expected, expected_default)],
193 [(&expected, expected_default), (&zero_operand, zero_default)],
194 ];
195 for seeds in seed_orders {
196 let mut model = SymbolicModel::default();
197 if !propagate_fallback_support_constraints(
198 constraints,
199 &mut model,
200 &mut remaining_support_visits,
201 ) {
202 if remaining_support_visits == 0 {
203 return None;
204 }
205 continue;
206 }
207 let mut valid = true;
208 for (operand, default) in seeds {
209 let assigned = match operand.eval_model_if_complete(&model) {
210 Ok(Some(_)) => true,
211 Ok(None) => operand.assign_model_value(&mut model, default),
212 Err(_) => false,
213 };
214 if !assigned {
215 valid = false;
216 break;
217 }
218 if !propagate_fallback_support_constraints(
219 constraints,
220 &mut model,
221 &mut remaining_support_visits,
222 ) {
223 if remaining_support_visits == 0 {
224 return None;
225 }
226 valid = false;
227 break;
228 }
229 }
230 if valid {
231 if eval_vars.iter().all(|var| model.contains_name(*var)) {
232 let valid = original_constraints.iter().all(|constraint| {
233 charge_support_constraint(constraint, &mut remaining_support_visits)
234 && constraint.eval_model(&model).unwrap_or(false)
235 });
236 if valid {
237 return Some(model);
238 }
239 if remaining_support_visits == 0 {
240 return None;
241 }
242 continue;
243 }
244 if complete_fallback_support_model(
245 constraints,
246 &mut model,
247 &mut remaining_support_visits,
248 ) && complete_model_with_zeroes(
249 original_constraints,
250 &mut model,
251 &mut remaining_support_visits,
252 ) {
253 let valid = original_constraints.iter().all(|constraint| {
254 charge_support_constraint(constraint, &mut remaining_support_visits)
255 && constraint.eval_model(&model).unwrap_or(false)
256 });
257 if valid {
258 return Some(model);
259 }
260 }
261 if remaining_support_visits == 0 {
262 return None;
263 }
264 }
265 }
266 }
267 }
268 None
269}
270
271fn checked_mul_guard_branch(constraint: &SymBoolExpr) -> Option<(SymExpr, SymExpr, bool)> {
272 match constraint.kind() {
273 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
274 checked_mul_guard_word_comparison(left, right)
275 .map(|(zero_operand, expected)| (zero_operand, expected, false))
276 }
277 SymBoolExprKind::Not(inner) => match inner.kind() {
278 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
279 checked_mul_guard_word_comparison(left, right)
280 .map(|(zero_operand, expected)| (zero_operand, expected, true))
281 }
282 SymBoolExprKind::And(values) => checked_mul_guard_conjunction(values)
283 .map(|(zero_operand, expected)| (zero_operand, expected, true)),
284 _ => None,
285 },
286 SymBoolExprKind::And(values) => checked_mul_guard_conjunction(values)
287 .map(|(zero_operand, expected)| (zero_operand, expected, false)),
288 _ => None,
289 }
290}
291
292fn checked_mul_guard_word_comparison(
293 left: &SymExpr,
294 right: &SymExpr,
295) -> Option<(SymExpr, SymExpr)> {
296 let guard_word = if right.as_const().is_some_and(|value| value.is_zero()) {
297 left
298 } else if left.as_const().is_some_and(|value| value.is_zero()) {
299 right
300 } else {
301 return None;
302 };
303 let SymExprKind::BinOp(SymBinOp::Or, left, right) = guard_word.kind() else {
304 return None;
305 };
306
307 for (quotient_word, zero_word) in [(left, right), (right, left)] {
308 let Some(quotient_matches) = quotient_word.bool_word_condition() else {
309 continue;
310 };
311 let Some(zero_condition) = zero_word.bool_word_condition() else {
312 continue;
313 };
314 let Some((zero_operand, expected, quotient_zero_condition)) =
315 checked_mul_guard_operands("ient_matches)
316 else {
317 continue;
318 };
319 if zero_condition == quotient_zero_condition {
320 return Some((zero_operand, expected));
321 }
322 }
323 None
324}
325
326fn checked_mul_guard_conjunction(values: &[SymBoolExpr]) -> Option<(SymExpr, SymExpr)> {
327 for value in values {
328 let SymBoolExprKind::Not(quotient_matches) = value.kind() else {
329 continue;
330 };
331 let Some((zero_operand, expected, zero_condition)) =
332 checked_mul_guard_operands(quotient_matches)
333 else {
334 continue;
335 };
336 let contains_negated_zero_condition = values.iter().any(
337 |value| matches!(value.kind(), SymBoolExprKind::Not(inner) if inner == &zero_condition),
338 );
339 if contains_negated_zero_condition {
340 return Some((zero_operand, expected));
341 }
342 }
343 None
344}
345
346fn checked_mul_guard_operands(condition: &SymBoolExpr) -> Option<(SymExpr, SymExpr, SymBoolExpr)> {
347 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = condition.kind() else {
348 return None;
349 };
350 for (guarded_quotient, expected) in [(left, right), (right, left)] {
351 let SymExprKind::Ite(zero_condition, zero, quotient) = guarded_quotient.kind() else {
352 continue;
353 };
354 if !zero.as_const().is_some_and(|value| value.is_zero()) {
355 continue;
356 }
357 let Some(zero_operand) = zero_condition.zero_check_operand() else {
358 continue;
359 };
360 let Some((numerator, denominator)) = quotient.udiv_operands() else {
361 continue;
362 };
363 if denominator != zero_operand {
364 continue;
365 }
366 let SymExprKind::BinOp(SymBinOp::Mul, product_left, product_right) = numerator.kind()
367 else {
368 continue;
369 };
370 if (product_left == denominator && product_right == expected)
371 || (product_right == denominator && product_left == expected)
372 {
373 return Some((zero_operand.clone(), expected.clone(), zero_condition.clone()));
374 }
375 }
376 None
377}
378
379fn fallback_search_vars(
380 cx: &SymCx,
381 vars: SymbolicVars,
382 constraints: &[SymBoolExpr],
383) -> Vec<Symbol> {
384 if vars.len() <= HARD_ARITH_FALLBACK_MAX_VARS {
385 return vars.into_iter().collect();
386 }
387
388 let hard_arith_vars = hard_arith_fallback_vars(constraints);
389 if !hard_arith_vars.is_empty() && hard_arith_vars.len() <= HARD_ARITH_FALLBACK_MAX_VARS {
390 let mut vars = hard_arith_vars;
391 add_zero_invalid_support_vars(&mut vars, constraints);
392 return vars.into_iter().collect();
393 }
394
395 vars.into_iter()
396 .filter(|var| {
397 let var = cx.symbol_name(*var);
398 var.starts_with("calldata")
399 || var.starts_with("sequence")
400 || var.starts_with("create_address")
401 || var.starts_with("create2_address")
402 || !var.contains('_')
403 })
404 .collect()
405}
406
407fn hard_arith_fallback_vars(constraints: &[SymBoolExpr]) -> SymbolicVars {
408 let mut vars = SymbolicVars::default();
409 for constraint in constraints {
410 collect_bool_hard_arith_vars(constraint, &mut vars);
411 }
412 vars
413}
414
415fn add_zero_invalid_support_vars(vars: &mut SymbolicVars, constraints: &[SymBoolExpr]) {
416 let zero_model = SymbolicModel::default();
417 for constraint in constraints {
418 if constraint.eval_model(&zero_model).unwrap_or(false) {
419 continue;
420 }
421 let (inner, inverted) = match constraint.kind() {
424 SymBoolExprKind::Not(inner) => (inner, true),
425 _ => (constraint, false),
426 };
427 if let SymBoolExprKind::Cmp(op, left, right) = inner.kind()
428 && support_cmp_op(*op, inverted)
429 .is_some_and(|op| !matches!(op, SymCmpOp::Slt | SymCmpOp::Sgt))
430 && [(left, right), (right, left)].into_iter().any(|(variable, bound)| {
431 if !matches!(variable.kind(), SymExprKind::Var(_)) {
432 return false;
433 }
434 let mut dependencies = SymbolicVars::default();
435 bound.collect_eval_vars(&mut dependencies);
436 dependencies.is_subset(vars)
437 })
438 {
439 continue;
440 }
441
442 let mut constraint_vars = SymbolicVars::default();
443 constraint.collect_vars(&mut constraint_vars);
444 let missing =
445 constraint_vars.iter().filter(|var| !vars.contains(*var)).copied().collect::<Vec<_>>();
446 if vars.len() + missing.len() > HARD_ARITH_FALLBACK_MAX_VARS {
447 continue;
448 }
449 vars.extend(missing);
450 }
451}
452
453fn fallback_candidates_for_var(
454 var: &Symbol,
455 constraints: &[SymBoolExpr],
456 constants: &[U256],
457) -> Option<Vec<U256>> {
458 let hints = MaskHints::for_var(var, constraints);
459 if !(hints.one & hints.zero).is_zero() {
460 return None;
461 }
462
463 let mut candidates = HashSet::<U256>::default();
464 for candidate in [
465 U256::ZERO,
466 U256::ONE,
467 U256::from(2),
468 U256::from(3),
469 U256::MAX,
470 U256::MAX - U256::ONE,
471 U256::MAX - U256::from(2),
472 ] {
473 push_fallback_candidate(&mut candidates, candidate, hints);
474 }
475
476 for constant in constants.iter().copied() {
477 push_fallback_candidate(&mut candidates, constant, hints);
478 push_fallback_candidate(&mut candidates, constant.wrapping_add(U256::ONE), hints);
479 push_fallback_candidate(&mut candidates, constant.wrapping_sub(U256::ONE), hints);
480 if candidates.len() >= FALLBACK_MODEL_MAX_CANDIDATES_PER_VAR {
481 break;
482 }
483 }
484
485 for bit in 0..256 {
486 let power = U256::ONE << bit;
487 push_fallback_candidate(&mut candidates, power, hints);
488 if candidates.len() >= FALLBACK_MODEL_MAX_CANDIDATES_PER_VAR {
489 break;
490 }
491 }
492
493 let mut candidates = candidates.into_iter().collect::<Vec<_>>();
494 candidates.sort_unstable();
495 candidates.truncate(FALLBACK_MODEL_MAX_CANDIDATES_PER_VAR);
496 Some(candidates)
497}
498
499struct FallbackSearch<'a> {
500 constraints: &'a [SymBoolExpr],
501 constraint_vars: &'a [SymbolicVars],
502 searched_vars: &'a SymbolicVars,
503 vars: &'a [Symbol],
504 candidates: &'a [Vec<U256>],
505 max_assignments: usize,
506}
507
508impl FallbackSearch<'_> {
509 fn model(
510 &self,
511 index: usize,
512 model: &mut SymbolicModel,
513 assignments: &mut usize,
514 ) -> Option<SymbolicModel> {
515 if index == self.vars.len() {
516 let mut completed = model.clone();
517 let mut remaining_support_visits = usize::MAX;
518 if complete_fallback_support_model(
519 self.constraints,
520 &mut completed,
521 &mut remaining_support_visits,
522 ) {
523 return Some(completed);
524 }
525 let mut completed = model.clone();
528 return (seed_bounded_support_vars(self.constraints, &mut completed)
529 && complete_fallback_support_model(
530 self.constraints,
531 &mut completed,
532 &mut remaining_support_visits,
533 ))
534 .then_some(completed);
535 }
536
537 for candidate in &self.candidates[index] {
538 if *assignments >= self.max_assignments {
539 return None;
540 }
541 *assignments += 1;
542 model.insert(self.vars[index], *candidate);
543 if fallback_partial_model_satisfies_known_constraints(
544 self.constraints,
545 self.constraint_vars,
546 self.searched_vars,
547 model,
548 ) && let Some(model) = self.model(index + 1, model, assignments)
549 {
550 return Some(model);
551 }
552 }
553 model.remove(&self.vars[index]);
554 None
555 }
556}
557
558fn seed_bounded_support_vars(constraints: &[SymBoolExpr], model: &mut SymbolicModel) -> bool {
560 let mut bounds: HashMap<Symbol, (U256, U256)> = HashMap::default();
561 for constraint in constraints {
562 let (constraint, inverted) = match constraint.kind() {
563 SymBoolExprKind::Not(inner) => (inner, true),
564 _ => (constraint, false),
565 };
566 let SymBoolExprKind::Cmp(op, left, right) = constraint.kind() else { continue };
567 let Some(mut op) = support_cmp_op(*op, inverted) else { continue };
568 if matches!(op, SymCmpOp::Slt | SymCmpOp::Sgt) {
569 continue;
570 }
571 let (var, known) = if let SymExprKind::Var(var) = left.kind()
572 && !model.contains_name(*var)
573 && let Ok(Some(value)) = right.eval_model_if_complete(model)
574 {
575 (*var, value)
576 } else if let SymExprKind::Var(var) = right.kind()
577 && !model.contains_name(*var)
578 && let Ok(Some(value)) = left.eval_model_if_complete(model)
579 {
580 op = match op {
581 SymCmpOp::Ult => SymCmpOp::Ugt,
582 SymCmpOp::Ule => SymCmpOp::Uge,
583 SymCmpOp::Ugt => SymCmpOp::Ult,
584 SymCmpOp::Uge => SymCmpOp::Ule,
585 other => other,
586 };
587 (*var, value)
588 } else {
589 continue;
590 };
591 let Some(value) = support_target_for_known_right(op, known) else { return false };
592 let (lower, upper) = bounds.entry(var).or_insert((U256::ZERO, U256::MAX));
593 match op {
594 SymCmpOp::Eq => {
595 *lower = (*lower).max(value);
596 *upper = (*upper).min(value);
597 }
598 SymCmpOp::Ule | SymCmpOp::Ult => *upper = (*upper).min(value),
599 SymCmpOp::Uge | SymCmpOp::Ugt => *lower = (*lower).max(value),
600 SymCmpOp::Slt | SymCmpOp::Sgt => continue,
601 }
602 if lower > upper {
603 return false;
604 }
605 }
606 if bounds.is_empty() {
607 return false;
608 }
609 for (var, (lower, _)) in bounds {
610 model.insert(var, lower);
611 }
612 true
613}
614
615fn complete_fallback_support_model(
616 constraints: &[SymBoolExpr],
617 model: &mut SymbolicModel,
618 remaining_support_visits: &mut usize,
619) -> bool {
620 for _ in 0..constraints.len() {
621 let Some(mut changed) =
622 complete_support_constraints_once(constraints, model, remaining_support_visits)
623 else {
624 return false;
625 };
626 if changed {
627 continue;
628 }
629 for constraint in constraints {
632 if !charge_support_constraint(constraint, remaining_support_visits) {
633 return false;
634 }
635 match constraint.eval_model_if_complete(model) {
636 Ok(Some(true)) => {}
637 Ok(Some(false)) | Err(_) => return false,
638 Ok(None) => {
639 changed |= complete_default_support_constraint(constraint, model);
640 }
641 }
642 }
643 if !changed {
644 break;
645 }
646 }
647 constraints.iter().all(|constraint| {
648 charge_support_constraint(constraint, remaining_support_visits)
649 && constraint.eval_model(model).unwrap_or(false)
650 })
651}
652
653fn propagate_fallback_support_constraints(
654 constraints: &[SymBoolExpr],
655 model: &mut SymbolicModel,
656 remaining_support_visits: &mut usize,
657) -> bool {
658 for _ in 0..constraints.len() {
659 match complete_support_constraints_once(constraints, model, remaining_support_visits) {
660 Some(true) => {}
661 Some(false) => return true,
662 None => return false,
663 }
664 }
665 true
666}
667
668fn complete_support_constraints_once(
669 constraints: &[SymBoolExpr],
670 model: &mut SymbolicModel,
671 remaining_support_visits: &mut usize,
672) -> Option<bool> {
673 let mut changed = false;
674 for constraint in constraints {
675 if !charge_support_constraint(constraint, remaining_support_visits) {
676 return None;
677 }
678 match constraint.eval_model_if_complete(model) {
679 Ok(Some(true)) => {}
680 Ok(Some(false)) | Err(_) => return None,
681 Ok(None) => changed |= complete_support_bool(constraint, model, false, false),
682 }
683 }
684 Some(changed)
685}
686
687fn charge_support_constraint(
688 constraint: &SymBoolExpr,
689 remaining_support_visits: &mut usize,
690) -> bool {
691 if *remaining_support_visits == 0 {
692 return false;
693 }
694 *remaining_support_visits -= 1;
695 !constraint
696 .visit_exprs(&mut |_| {
697 if *remaining_support_visits == 0 {
698 return ControlFlow::Break(());
699 }
700 *remaining_support_visits -= 1;
701 ControlFlow::Continue(())
702 })
703 .is_break()
704}
705
706fn complete_model_with_zeroes(
707 constraints: &[SymBoolExpr],
708 model: &mut SymbolicModel,
709 remaining_support_visits: &mut usize,
710) -> bool {
711 let mut vars = SymbolicVars::default();
712 for constraint in constraints {
713 if !charge_support_constraint(constraint, remaining_support_visits) {
714 return false;
715 }
716 constraint.collect_eval_vars(&mut vars);
717 }
718 for var in vars {
719 model.entry(var).or_default();
720 }
721 true
722}
723
724fn complete_default_support_constraint(
725 constraint: &SymBoolExpr,
726 model: &mut SymbolicModel,
727) -> bool {
728 complete_support_bool(constraint, model, false, true)
729}
730
731fn complete_support_bool(
732 constraint: &SymBoolExpr,
733 model: &mut SymbolicModel,
734 inverted: bool,
735 defaults_only: bool,
736) -> bool {
737 match constraint.kind() {
738 SymBoolExprKind::Const(_) => false,
739 SymBoolExprKind::Not(value) => {
740 complete_support_bool(value, model, !inverted, defaults_only)
741 }
742 SymBoolExprKind::And(values) if !inverted => {
743 let mut changed = false;
744 for value in values.iter() {
745 changed |= complete_support_bool(value, model, false, defaults_only);
746 }
747 changed
748 }
749 SymBoolExprKind::Cmp(op, left, right) => {
750 let Some(op) = support_cmp_op(*op, inverted) else {
751 return false;
752 };
753 if defaults_only {
754 complete_default_support_comparison(op, left, right, model)
755 } else {
756 complete_support_comparison(op, left, right, model)
757 }
758 }
759 SymBoolExprKind::And(_) => false,
760 }
761}
762
763const fn support_cmp_op(op: SymCmpOp, inverted: bool) -> Option<SymCmpOp> {
764 if !inverted {
765 return Some(op);
766 }
767
768 match op {
769 SymCmpOp::Ult => Some(SymCmpOp::Uge),
770 SymCmpOp::Ugt => Some(SymCmpOp::Ule),
771 SymCmpOp::Ule => Some(SymCmpOp::Ugt),
772 SymCmpOp::Uge => Some(SymCmpOp::Ult),
773 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
774 }
775}
776
777fn complete_support_comparison(
778 op: SymCmpOp,
779 left: &SymExpr,
780 right: &SymExpr,
781 model: &mut SymbolicModel,
782) -> bool {
783 if complete_checked_sub_guard(op, left, right, model) {
784 return true;
785 }
786 if let Ok(Some(value)) = left.eval_model_if_complete(model)
787 && let Some(target) = support_target_for_known_left(op, value)
788 {
789 return right.assign_model_value(model, target);
790 }
791 if let Ok(Some(value)) = right.eval_model_if_complete(model)
792 && let Some(target) = support_target_for_known_right(op, value)
793 {
794 return left.assign_model_value(model, target);
795 }
796 false
797}
798
799fn complete_default_support_comparison(
800 op: SymCmpOp,
801 left: &SymExpr,
802 right: &SymExpr,
803 model: &mut SymbolicModel,
804) -> bool {
805 complete_checked_add_guard(op, left, right, model)
806}
807
808fn complete_checked_sub_guard(
809 op: SymCmpOp,
810 left: &SymExpr,
811 right: &SymExpr,
812 model: &mut SymbolicModel,
813) -> bool {
814 match op {
815 SymCmpOp::Uge => assign_checked_sub_minuend(left, right, model),
816 SymCmpOp::Ule => assign_checked_sub_minuend(right, left, model),
817 _ => false,
818 }
819}
820
821fn assign_checked_sub_minuend(
822 minuend: &SymExpr,
823 sub_expr: &SymExpr,
824 model: &mut SymbolicModel,
825) -> bool {
826 let SymExprKind::BinOp(SymBinOp::Sub, sub_minuend, amount) = sub_expr.kind() else {
827 return false;
828 };
829 if sub_minuend != minuend {
830 return false;
831 }
832 let Ok(Some(amount)) = amount.eval_model_if_complete(model) else {
833 return false;
834 };
835 minuend.assign_model_value(model, amount)
836}
837
838fn complete_checked_add_guard(
839 op: SymCmpOp,
840 left: &SymExpr,
841 right: &SymExpr,
842 model: &mut SymbolicModel,
843) -> bool {
844 match op {
845 SymCmpOp::Uge => assign_checked_add_base(left, right, model),
846 SymCmpOp::Ule => assign_checked_add_base(right, left, model),
847 _ => false,
848 }
849}
850
851fn assign_checked_add_base(sum: &SymExpr, base: &SymExpr, model: &mut SymbolicModel) -> bool {
852 let SymExprKind::BinOp(SymBinOp::Add, left, right) = sum.kind() else {
853 return false;
854 };
855 if left == base && right.eval_model_if_complete(model).ok().flatten().is_some() {
856 return base.assign_model_value(model, U256::ZERO);
857 }
858 if right == base && left.eval_model_if_complete(model).ok().flatten().is_some() {
859 return base.assign_model_value(model, U256::ZERO);
860 }
861 false
862}
863
864const fn support_target_for_known_left(op: SymCmpOp, value: U256) -> Option<U256> {
865 match op {
866 SymCmpOp::Eq | SymCmpOp::Ule | SymCmpOp::Uge => Some(value),
867 SymCmpOp::Ult => value.checked_add(U256::ONE),
868 SymCmpOp::Ugt => value.checked_sub(U256::ONE),
869 SymCmpOp::Slt | SymCmpOp::Sgt => None,
870 }
871}
872
873const fn support_target_for_known_right(op: SymCmpOp, value: U256) -> Option<U256> {
874 match op {
875 SymCmpOp::Eq | SymCmpOp::Ule | SymCmpOp::Uge => Some(value),
876 SymCmpOp::Ult => value.checked_sub(U256::ONE),
877 SymCmpOp::Ugt => value.checked_add(U256::ONE),
878 SymCmpOp::Slt | SymCmpOp::Sgt => None,
879 }
880}
881
882fn fallback_partial_model_satisfies_known_constraints(
883 constraints: &[SymBoolExpr],
884 constraint_vars: &[SymbolicVars],
885 searched_vars: &SymbolicVars,
886 model: &SymbolicModel,
887) -> bool {
888 constraints.iter().zip(constraint_vars).all(|(constraint, vars)| {
889 !vars.is_subset(searched_vars)
890 || !vars.iter().all(|var| model.contains_name(*var))
891 || constraint.eval_model(model).unwrap_or(false)
892 })
893}
894
895fn collect_bool_fallback_vars(expr: &SymBoolExpr, vars: &mut SymbolicVars) {
896 let _ = expr.visit_exprs(&mut |expr| {
897 if let Some(var) = expr.kind().get_eval_var() {
898 vars.insert(var);
899 }
900 ControlFlow::<()>::Continue(())
901 });
902}
903
904fn collect_bool_hard_arith_vars(expr: &SymBoolExpr, vars: &mut SymbolicVars) {
905 let _ = expr.visit_exprs(&mut |expr| {
906 if is_hard_arith_node(expr) {
907 expr.collect_eval_vars(vars);
908 }
909 ControlFlow::<()>::Continue(())
910 });
911}
912
913pub(crate) fn fallback_single_var_model(constraints: &[SymBoolExpr]) -> Option<SymbolicModel> {
914 let mut vars = SymbolicVars::default();
915 let mut constants = HashSet::<U256>::default();
916 for constraint in constraints {
917 constraint.collect_vars(&mut vars);
918 collect_bool_constants(constraint, &mut constants);
919 }
920 let mut constants = constants.into_iter().collect::<Vec<_>>();
921 constants.sort_unstable();
922
923 let var = if vars.len() == 1 { *vars.iter().next()? } else { return None };
924 let hints = MaskHints::for_var(&var, constraints);
925 if !(hints.one & hints.zero).is_zero() {
926 return None;
927 }
928
929 let mut model = SymbolicModel::default();
930 let mut remaining_support_visits = usize::MAX;
931 if complete_fallback_support_model(constraints, &mut model, &mut remaining_support_visits)
932 && model.len() == 1
933 && model.contains_key(&var)
934 {
935 return Some(model);
936 }
937
938 for candidate in [
939 U256::ZERO,
940 U256::ONE,
941 U256::from(2),
942 U256::MAX,
943 U256::MAX - U256::ONE,
944 U256::MAX - U256::from(2),
945 ] {
946 let mut model = SymbolicModel::default();
947 model.insert(var, (candidate | hints.one) & !hints.zero);
948 if eval_model_constraints(constraints, &model) {
949 return Some(model);
950 }
951 }
952
953 let mut candidates = HashSet::<U256>::default();
954 for constant in constants.iter().copied() {
955 push_fallback_candidate(&mut candidates, constant, hints);
956 push_fallback_candidate(&mut candidates, constant.wrapping_add(U256::ONE), hints);
957 push_fallback_candidate(&mut candidates, constant.wrapping_sub(U256::ONE), hints);
958 }
959
960 for bit in 0..256 {
961 let power = U256::ONE << bit;
962 push_fallback_candidate(&mut candidates, power, hints);
963 for constant in constants.iter().copied().take(64) {
964 push_fallback_candidate(&mut candidates, power | constant, hints);
965 push_fallback_candidate(&mut candidates, power.wrapping_add(constant), hints);
966 }
967 }
968
969 let mut candidates = candidates.into_iter().collect::<Vec<_>>();
970 candidates.sort_unstable();
971 for candidate in candidates {
972 let mut model = SymbolicModel::default();
973 model.insert(var, candidate);
974 if eval_model_constraints(constraints, &model) {
975 return Some(model);
976 }
977 }
978
979 None
980}
981
982pub(crate) fn fallback_bounded_model(constraints: &[SymBoolExpr]) -> Option<SymbolicModel> {
987 if constraints.iter().any(SymBoolExpr::contains_hard_arith) {
988 return None;
989 }
990
991 let mut vars = SymbolicVars::default();
992 for constraint in constraints {
993 collect_bool_fallback_vars(constraint, &mut vars);
994 if vars.len() > FALLBACK_MODEL_MAX_VARS {
995 return None;
996 }
997 }
998 if vars.len() < 2 {
999 return None;
1000 }
1001 if constraints.iter().any(SymBoolExpr::contains_symbolic_hash)
1002 || constraints.iter().any(SymBoolExpr::contains_gasleft)
1003 {
1004 return None;
1005 }
1006 let mut constants = HashSet::<U256>::default();
1007 for constraint in constraints {
1008 collect_bool_constants(constraint, &mut constants);
1009 }
1010 let mut constants = constants.into_iter().collect::<Vec<_>>();
1011 constants.sort_unstable();
1012 let vars = vars.into_iter().collect::<Vec<_>>();
1013 let candidates = vars
1014 .iter()
1015 .map(|var| fallback_candidates_for_var(var, constraints, &constants))
1016 .collect::<Option<Vec<_>>>()?;
1017 let searched_vars = vars.iter().copied().collect::<SymbolicVars>();
1018 let constraint_vars = constraints
1019 .iter()
1020 .map(|constraint| {
1021 let mut vars = SymbolicVars::default();
1022 constraint.collect_vars(&mut vars);
1023 vars
1024 })
1025 .collect::<Vec<_>>();
1026 let search = FallbackSearch {
1027 constraints,
1028 constraint_vars: &constraint_vars,
1029 searched_vars: &searched_vars,
1030 vars: &vars,
1031 candidates: &candidates,
1032 max_assignments: FALLBACK_MODEL_MAX_ASSIGNMENTS,
1033 };
1034 let mut model = SymbolicModel::default();
1035 let mut assignments = 0usize;
1036 search.model(0, &mut model, &mut assignments)
1037}
1038
1039fn push_fallback_candidate(candidates: &mut HashSet<U256>, candidate: U256, hints: MaskHints) {
1040 candidates.insert((candidate | hints.one) & !hints.zero);
1041}
1042
1043fn collect_bool_constants(expr: &SymBoolExpr, constants: &mut HashSet<U256>) {
1044 let _ = expr.visit_exprs(&mut |expr| {
1045 if let SymExprKind::Const(value) = expr.kind() {
1046 constants.insert(*value);
1047 }
1048 ControlFlow::<()>::Continue(())
1049 });
1050}
1051
1052#[derive(Clone, Copy, Debug, Default)]
1053struct MaskHints {
1054 one: U256,
1055 zero: U256,
1056}
1057
1058impl MaskHints {
1059 fn for_var(var: &Symbol, constraints: &[SymBoolExpr]) -> Self {
1060 let mut hints = Self::default();
1061 for constraint in constraints {
1062 hints.apply_bool(var, constraint, false);
1063 }
1064 hints
1065 }
1066
1067 fn apply_bool(&mut self, var: &Symbol, expr: &SymBoolExpr, inverted: bool) {
1068 match expr.kind() {
1069 SymBoolExprKind::Const(_) => {}
1070 SymBoolExprKind::Not(value) => self.apply_bool(var, value, !inverted),
1071 SymBoolExprKind::And(values) if !inverted => {
1072 for value in values.iter() {
1073 self.apply_bool(var, value, false);
1074 }
1075 }
1076 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1077 self.apply_equality(var, left, right, inverted)
1078 }
1079 SymBoolExprKind::Cmp(_, _, _) | SymBoolExprKind::And(_) => {}
1080 }
1081 }
1082
1083 fn apply_equality(&mut self, var: &Symbol, left: &SymExpr, right: &SymExpr, inverted: bool) {
1084 if let Some(mask) =
1085 zero_mask_equality(var, left, right).or_else(|| zero_mask_equality(var, right, left))
1086 {
1087 if inverted {
1088 if mask.is_power_of_two() {
1089 self.one |= mask;
1090 }
1091 } else {
1092 self.zero |= mask;
1093 }
1094 }
1095 }
1096}
1097
1098fn zero_mask_equality(var: &Symbol, masked: &SymExpr, zero: &SymExpr) -> Option<U256> {
1099 if !zero.as_const().is_some_and(|value| value.is_zero()) {
1100 return None;
1101 }
1102 match masked.kind() {
1103 SymExprKind::BinOp(SymBinOp::And, left, right)
1104 if left.kind().get_var().is_some_and(|name| &name == var) =>
1105 {
1106 right.as_const()
1107 }
1108 _ => None,
1109 }
1110}