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
697 .visit_exprs(&mut |_| {
698 if *remaining_support_visits == 0 {
699 return ControlFlow::Break(());
700 }
701 *remaining_support_visits -= 1;
702 ControlFlow::Continue(())
703 })
704 .is_break()
705}
706
707fn complete_model_with_zeroes(
708 constraints: &[SymBoolExpr],
709 model: &mut SymbolicModel,
710 remaining_support_visits: &mut usize,
711) -> bool {
712 let mut vars = SymbolicVars::default();
713 for constraint in constraints {
714 if !charge_support_constraint(constraint, remaining_support_visits) {
715 return false;
716 }
717 constraint.collect_eval_vars(&mut vars);
718 }
719 for var in vars {
720 model.entry(var).or_default();
721 }
722 true
723}
724
725fn complete_default_support_constraint(
726 constraint: &SymBoolExpr,
727 model: &mut SymbolicModel,
728) -> bool {
729 complete_support_bool(constraint, model, false, true)
730}
731
732fn complete_support_bool(
733 constraint: &SymBoolExpr,
734 model: &mut SymbolicModel,
735 inverted: bool,
736 defaults_only: bool,
737) -> bool {
738 match constraint.kind() {
739 SymBoolExprKind::Const(_) => false,
740 SymBoolExprKind::Not(value) => {
741 complete_support_bool(value, model, !inverted, defaults_only)
742 }
743 SymBoolExprKind::And(values) if !inverted => {
744 let mut changed = false;
745 for value in values.iter() {
746 changed |= complete_support_bool(value, model, false, defaults_only);
747 }
748 changed
749 }
750 SymBoolExprKind::Cmp(op, left, right) => {
751 let Some(op) = support_cmp_op(*op, inverted) else {
752 return false;
753 };
754 if defaults_only {
755 complete_default_support_comparison(op, left, right, model)
756 } else {
757 complete_support_comparison(op, left, right, model)
758 }
759 }
760 SymBoolExprKind::And(_) => false,
761 }
762}
763
764const fn support_cmp_op(op: SymCmpOp, inverted: bool) -> Option<SymCmpOp> {
765 if !inverted {
766 return Some(op);
767 }
768
769 match op {
770 SymCmpOp::Ult => Some(SymCmpOp::Uge),
771 SymCmpOp::Ugt => Some(SymCmpOp::Ule),
772 SymCmpOp::Ule => Some(SymCmpOp::Ugt),
773 SymCmpOp::Uge => Some(SymCmpOp::Ult),
774 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
775 }
776}
777
778fn complete_support_comparison(
779 op: SymCmpOp,
780 left: &SymExpr,
781 right: &SymExpr,
782 model: &mut SymbolicModel,
783) -> bool {
784 if complete_checked_sub_guard(op, left, right, model) {
785 return true;
786 }
787 if let Ok(Some(value)) = left.eval_model_if_complete(model)
788 && let Some(target) = support_target_for_known_left(op, value)
789 {
790 return right.assign_model_value(model, target);
791 }
792 if let Ok(Some(value)) = right.eval_model_if_complete(model)
793 && let Some(target) = support_target_for_known_right(op, value)
794 {
795 return left.assign_model_value(model, target);
796 }
797 false
798}
799
800fn complete_default_support_comparison(
801 op: SymCmpOp,
802 left: &SymExpr,
803 right: &SymExpr,
804 model: &mut SymbolicModel,
805) -> bool {
806 complete_checked_add_guard(op, left, right, model)
807}
808
809fn complete_checked_sub_guard(
810 op: SymCmpOp,
811 left: &SymExpr,
812 right: &SymExpr,
813 model: &mut SymbolicModel,
814) -> bool {
815 match op {
816 SymCmpOp::Uge => assign_checked_sub_minuend(left, right, model),
817 SymCmpOp::Ule => assign_checked_sub_minuend(right, left, model),
818 _ => false,
819 }
820}
821
822fn assign_checked_sub_minuend(
823 minuend: &SymExpr,
824 sub_expr: &SymExpr,
825 model: &mut SymbolicModel,
826) -> bool {
827 let SymExprKind::BinOp(SymBinOp::Sub, sub_minuend, amount) = sub_expr.kind() else {
828 return false;
829 };
830 if sub_minuend != minuend {
831 return false;
832 }
833 let Ok(Some(amount)) = amount.eval_model_if_complete(model) else {
834 return false;
835 };
836 minuend.assign_model_value(model, amount)
837}
838
839fn complete_checked_add_guard(
840 op: SymCmpOp,
841 left: &SymExpr,
842 right: &SymExpr,
843 model: &mut SymbolicModel,
844) -> bool {
845 match op {
846 SymCmpOp::Uge => assign_checked_add_base(left, right, model),
847 SymCmpOp::Ule => assign_checked_add_base(right, left, model),
848 _ => false,
849 }
850}
851
852fn assign_checked_add_base(sum: &SymExpr, base: &SymExpr, model: &mut SymbolicModel) -> bool {
853 let SymExprKind::BinOp(SymBinOp::Add, left, right) = sum.kind() else {
854 return false;
855 };
856 if left == base && right.eval_model_if_complete(model).ok().flatten().is_some() {
857 return base.assign_model_value(model, U256::ZERO);
858 }
859 if right == base && left.eval_model_if_complete(model).ok().flatten().is_some() {
860 return base.assign_model_value(model, U256::ZERO);
861 }
862 false
863}
864
865const fn support_target_for_known_left(op: SymCmpOp, value: U256) -> Option<U256> {
866 match op {
867 SymCmpOp::Eq | SymCmpOp::Ule | SymCmpOp::Uge => Some(value),
868 SymCmpOp::Ult => value.checked_add(U256::ONE),
869 SymCmpOp::Ugt => value.checked_sub(U256::ONE),
870 SymCmpOp::Slt | SymCmpOp::Sgt => None,
871 }
872}
873
874const fn support_target_for_known_right(op: SymCmpOp, value: U256) -> Option<U256> {
875 match op {
876 SymCmpOp::Eq | SymCmpOp::Ule | SymCmpOp::Uge => Some(value),
877 SymCmpOp::Ult => value.checked_sub(U256::ONE),
878 SymCmpOp::Ugt => value.checked_add(U256::ONE),
879 SymCmpOp::Slt | SymCmpOp::Sgt => None,
880 }
881}
882
883fn fallback_partial_model_satisfies_known_constraints(
884 constraints: &[SymBoolExpr],
885 constraint_vars: &[SymbolicVars],
886 searched_vars: &SymbolicVars,
887 model: &SymbolicModel,
888) -> bool {
889 constraints.iter().zip(constraint_vars).all(|(constraint, vars)| {
890 !vars.is_subset(searched_vars)
891 || !vars.iter().all(|var| model.contains_name(*var))
892 || constraint.eval_model(model).unwrap_or(false)
893 })
894}
895
896fn collect_bool_fallback_vars(expr: &SymBoolExpr, vars: &mut SymbolicVars) {
897 let _ = expr.visit_exprs::<()>(&mut |expr| {
898 vars.extend(expr.kind().get_eval_var());
899 ControlFlow::Continue(())
900 });
901}
902
903fn collect_bool_hard_arith_vars(expr: &SymBoolExpr, vars: &mut SymbolicVars) {
904 let _ = expr.visit_exprs::<()>(&mut |expr| {
905 if is_hard_arith_node(expr) {
906 expr.collect_eval_vars(vars);
907 }
908 ControlFlow::Continue(())
909 });
910}
911
912pub(crate) fn fallback_single_var_model(constraints: &[SymBoolExpr]) -> Option<SymbolicModel> {
913 let mut vars = SymbolicVars::default();
914 let mut constants = HashSet::<U256>::default();
915 for constraint in constraints {
916 constraint.collect_vars(&mut vars);
917 collect_bool_constants(constraint, &mut constants);
918 }
919 let mut constants = constants.into_iter().collect::<Vec<_>>();
920 constants.sort_unstable();
921
922 let var = if vars.len() == 1 { *vars.iter().next()? } else { return None };
923 let hints = MaskHints::for_var(&var, constraints);
924 if !(hints.one & hints.zero).is_zero() {
925 return None;
926 }
927
928 let mut model = SymbolicModel::default();
929 let mut remaining_support_visits = usize::MAX;
930 if complete_fallback_support_model(constraints, &mut model, &mut remaining_support_visits)
931 && model.len() == 1
932 && model.contains_key(&var)
933 {
934 return Some(model);
935 }
936
937 for candidate in [
938 U256::ZERO,
939 U256::ONE,
940 U256::from(2),
941 U256::MAX,
942 U256::MAX - U256::ONE,
943 U256::MAX - U256::from(2),
944 ] {
945 let mut model = SymbolicModel::default();
946 model.insert(var, (candidate | hints.one) & !hints.zero);
947 if eval_model_constraints(constraints, &model) {
948 return Some(model);
949 }
950 }
951
952 let mut candidates = HashSet::<U256>::default();
953 for constant in constants.iter().copied() {
954 push_fallback_candidate(&mut candidates, constant, hints);
955 push_fallback_candidate(&mut candidates, constant.wrapping_add(U256::ONE), hints);
956 push_fallback_candidate(&mut candidates, constant.wrapping_sub(U256::ONE), hints);
957 }
958
959 for bit in 0..256 {
960 let power = U256::ONE << bit;
961 push_fallback_candidate(&mut candidates, power, hints);
962 for constant in constants.iter().copied().take(64) {
963 push_fallback_candidate(&mut candidates, power | constant, hints);
964 push_fallback_candidate(&mut candidates, power.wrapping_add(constant), hints);
965 }
966 }
967
968 let mut candidates = candidates.into_iter().collect::<Vec<_>>();
969 candidates.sort_unstable();
970 for candidate in candidates {
971 let mut model = SymbolicModel::default();
972 model.insert(var, candidate);
973 if eval_model_constraints(constraints, &model) {
974 return Some(model);
975 }
976 }
977
978 None
979}
980
981pub(crate) fn fallback_bounded_model(constraints: &[SymBoolExpr]) -> Option<SymbolicModel> {
986 if constraints.iter().any(SymBoolExpr::contains_hard_arith) {
987 return None;
988 }
989
990 let mut vars = SymbolicVars::default();
991 for constraint in constraints {
992 collect_bool_fallback_vars(constraint, &mut vars);
993 if vars.len() > FALLBACK_MODEL_MAX_VARS {
994 return None;
995 }
996 }
997 if vars.len() < 2 {
998 return None;
999 }
1000 if constraints.iter().any(SymBoolExpr::contains_symbolic_hash)
1001 || constraints.iter().any(SymBoolExpr::contains_gasleft)
1002 {
1003 return None;
1004 }
1005 let mut constants = HashSet::<U256>::default();
1006 for constraint in constraints {
1007 collect_bool_constants(constraint, &mut constants);
1008 }
1009 let mut constants = constants.into_iter().collect::<Vec<_>>();
1010 constants.sort_unstable();
1011 let vars = vars.into_iter().collect::<Vec<_>>();
1012 let candidates = vars
1013 .iter()
1014 .map(|var| fallback_candidates_for_var(var, constraints, &constants))
1015 .collect::<Option<Vec<_>>>()?;
1016 let searched_vars = vars.iter().copied().collect::<SymbolicVars>();
1017 let constraint_vars = constraints
1018 .iter()
1019 .map(|constraint| {
1020 let mut vars = SymbolicVars::default();
1021 constraint.collect_vars(&mut vars);
1022 vars
1023 })
1024 .collect::<Vec<_>>();
1025 let search = FallbackSearch {
1026 constraints,
1027 constraint_vars: &constraint_vars,
1028 searched_vars: &searched_vars,
1029 vars: &vars,
1030 candidates: &candidates,
1031 max_assignments: FALLBACK_MODEL_MAX_ASSIGNMENTS,
1032 };
1033 let mut model = SymbolicModel::default();
1034 let mut assignments = 0usize;
1035 search.model(0, &mut model, &mut assignments)
1036}
1037
1038fn push_fallback_candidate(candidates: &mut HashSet<U256>, candidate: U256, hints: MaskHints) {
1039 candidates.insert((candidate | hints.one) & !hints.zero);
1040}
1041
1042fn collect_bool_constants(expr: &SymBoolExpr, constants: &mut HashSet<U256>) {
1043 let _ = expr.visit_exprs::<()>(&mut |expr| {
1044 if let SymExprKind::Const(value) = expr.kind() {
1045 constants.insert(*value);
1046 }
1047 ControlFlow::Continue(())
1048 });
1049}
1050
1051#[derive(Clone, Copy, Debug, Default)]
1052struct MaskHints {
1053 one: U256,
1054 zero: U256,
1055}
1056
1057impl MaskHints {
1058 fn for_var(var: &Symbol, constraints: &[SymBoolExpr]) -> Self {
1059 let mut hints = Self::default();
1060 for constraint in constraints {
1061 hints.apply_bool(var, constraint, false);
1062 }
1063 hints
1064 }
1065
1066 fn apply_bool(&mut self, var: &Symbol, expr: &SymBoolExpr, inverted: bool) {
1067 match expr.kind() {
1068 SymBoolExprKind::Const(_) => {}
1069 SymBoolExprKind::Not(value) => self.apply_bool(var, value, !inverted),
1070 SymBoolExprKind::And(values) if !inverted => {
1071 for value in values.iter() {
1072 self.apply_bool(var, value, false);
1073 }
1074 }
1075 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1076 self.apply_equality(var, left, right, inverted)
1077 }
1078 SymBoolExprKind::Cmp(_, _, _) | SymBoolExprKind::And(_) => {}
1079 }
1080 }
1081
1082 fn apply_equality(&mut self, var: &Symbol, left: &SymExpr, right: &SymExpr, inverted: bool) {
1083 if let Some(mask) =
1084 zero_mask_equality(var, left, right).or_else(|| zero_mask_equality(var, right, left))
1085 {
1086 if inverted {
1087 if mask.is_power_of_two() {
1088 self.one |= mask;
1089 }
1090 } else {
1091 self.zero |= mask;
1092 }
1093 }
1094 }
1095}
1096
1097fn zero_mask_equality(var: &Symbol, masked: &SymExpr, zero: &SymExpr) -> Option<U256> {
1098 if !zero.as_const().is_some_and(|value| value.is_zero()) {
1099 return None;
1100 }
1101 match masked.kind() {
1102 SymExprKind::BinOp(SymBinOp::And, left, right)
1103 if left.kind().get_var().is_some_and(|name| &name == var) =>
1104 {
1105 right.as_const()
1106 }
1107 _ => None,
1108 }
1109}