Skip to main content

foundry_evm_symbolic/runtime/solver/
opt.rs

1use super::*;
2
3/// Normalizes path constraints into an equivalent, solver-friendlier form.
4pub(crate) fn normalize_constraints_for_solver(
5    cx: &mut SymCx,
6    constraints: &[SymBoolExpr],
7) -> Vec<SymBoolExpr> {
8    let normalized = normalize_constraint_batch(
9        constraints.iter().cloned().map(|constraint| normalize_bool_for_solver(cx, constraint)),
10        constraints.len(),
11    );
12    if matches!(normalized.as_slice(), [expr] if expr.as_const() == Some(false)) {
13        return normalized;
14    }
15
16    let context = ConstraintContext::new(&normalized);
17    let normalized_len = normalized.len();
18    normalize_constraint_batch(
19        normalized.into_iter().map(|constraint| context.normalize_bool(cx, constraint)),
20        normalized_len,
21    )
22}
23
24fn normalize_constraint_batch(
25    constraints: impl IntoIterator<Item = SymBoolExpr>,
26    capacity: usize,
27) -> Vec<SymBoolExpr> {
28    let mut normalized = Vec::with_capacity(capacity);
29    for constraint in constraints {
30        if constraint.as_const() == Some(false) {
31            return vec![constraint];
32        }
33        constraint.push_normalized_conjuncts(&mut normalized);
34    }
35    sort_dedup_bool_exprs(&mut normalized);
36    normalized
37}
38
39fn sort_dedup_bool_exprs(exprs: &mut Vec<SymBoolExpr>) {
40    exprs.sort_by_cached_key(bool_structural_key);
41    exprs.dedup();
42}
43
44fn bool_structural_key(expr: &SymBoolExpr) -> String {
45    let mut key = String::new();
46    write_bool_structural_key(&mut key, expr);
47    key
48}
49
50fn write_bool_structural_key(out: &mut String, expr: &SymBoolExpr) {
51    match expr.kind() {
52        SymBoolExprKind::Const(value) => {
53            let _ = write!(out, "0:{value}");
54        }
55        SymBoolExprKind::Not(value) => {
56            out.push_str("1:");
57            write_bool_structural_key(out, value);
58        }
59        SymBoolExprKind::And(values) => {
60            let _ = write!(out, "2:{}:", values.len());
61            for value in values.iter() {
62                write_bool_structural_key(out, value);
63                out.push(';');
64            }
65        }
66        SymBoolExprKind::Cmp(op, left, right) => {
67            let _ = write!(out, "3:{}:", cmp_op_key(*op));
68            write_expr_structural_key(out, left);
69            out.push(':');
70            write_expr_structural_key(out, right);
71        }
72    }
73}
74
75fn write_expr_structural_key(out: &mut String, expr: &SymExpr) {
76    match expr.kind() {
77        SymExprKind::Const(value) => {
78            let _ = write!(out, "0:{value:064x}");
79        }
80        SymExprKind::Var(name) => {
81            let _ = write!(out, "1:{}", name.id());
82        }
83        SymExprKind::GasLeft(symbol) => {
84            let _ = write!(out, "2:{}", symbol.id());
85        }
86        SymExprKind::Keccak { name, len, bytes } => {
87            let _ = write!(out, "3:{}:", name.id());
88            write_expr_structural_key(out, len);
89            write_exprs_structural_key(out, bytes);
90        }
91        SymExprKind::Hash { name, algorithm, bytes } => {
92            let _ = write!(out, "4:{}:{algorithm}:", name.id());
93            write_exprs_structural_key(out, bytes);
94        }
95        SymExprKind::Not(value) => {
96            out.push_str("5:");
97            write_expr_structural_key(out, value);
98        }
99        SymExprKind::BinOp(op, left, right) => {
100            let _ = write!(out, "6:{}:", expr_binop_key(*op));
101            write_expr_structural_key(out, left);
102            out.push(':');
103            write_expr_structural_key(out, right);
104        }
105        SymExprKind::TernOp(op, left, right, modulus) => {
106            let _ = write!(out, "7:{}:", expr_ternop_key(*op));
107            write_expr_structural_key(out, left);
108            out.push(':');
109            write_expr_structural_key(out, right);
110            out.push(':');
111            write_expr_structural_key(out, modulus);
112        }
113        SymExprKind::Ite(condition, then_expr, else_expr) => {
114            out.push_str("9:");
115            write_bool_structural_key(out, condition);
116            out.push(':');
117            write_expr_structural_key(out, then_expr);
118            out.push(':');
119            write_expr_structural_key(out, else_expr);
120        }
121    }
122}
123
124fn write_exprs_structural_key(out: &mut String, exprs: &[SymExpr]) {
125    let _ = write!(out, "{}:", exprs.len());
126    for expr in exprs {
127        write_expr_structural_key(out, expr);
128        out.push(';');
129    }
130}
131
132const fn cmp_op_key(op: SymCmpOp) -> u8 {
133    match op {
134        SymCmpOp::Eq => 0,
135        SymCmpOp::Ult => 1,
136        SymCmpOp::Ugt => 2,
137        SymCmpOp::Ule => 3,
138        SymCmpOp::Uge => 4,
139        SymCmpOp::Slt => 5,
140        SymCmpOp::Sgt => 6,
141    }
142}
143
144const fn expr_binop_key(op: SymBinOp) -> u8 {
145    match op {
146        SymBinOp::Add => 0,
147        SymBinOp::Sub => 1,
148        SymBinOp::Mul => 2,
149        SymBinOp::UDiv => 3,
150        SymBinOp::URem => 4,
151        SymBinOp::SDiv => 5,
152        SymBinOp::SRem => 6,
153        SymBinOp::And => 7,
154        SymBinOp::Or => 8,
155        SymBinOp::Xor => 9,
156        SymBinOp::Shl => 10,
157        SymBinOp::Shr => 11,
158        SymBinOp::Sar => 12,
159    }
160}
161
162const fn expr_ternop_key(op: SymTernOp) -> u8 {
163    match op {
164        SymTernOp::AddMod => 0,
165        SymTernOp::MulMod => 1,
166    }
167}
168
169/// Returns whether normalized conjunctive constraints contain a direct contradiction.
170pub(super) fn constraints_are_directly_unsat(cx: &mut SymCx, constraints: &[SymBoolExpr]) -> bool {
171    constraints.iter().any(|constraint| match constraint.kind() {
172        SymBoolExprKind::Const(false) => true,
173        SymBoolExprKind::Not(inner) => constraints.contains(inner),
174        _ => {
175            let negated = constraint.clone().not(cx);
176            constraints.contains(&negated)
177        }
178    })
179}
180
181/// Returns whether every expression in `subset` appears in `superset`.
182pub(super) fn sorted_bool_exprs_are_subset(
183    subset: &[SymBoolExpr],
184    superset: &[SymBoolExpr],
185) -> bool {
186    if subset.len() > superset.len() {
187        return false;
188    }
189
190    let superset: HashSet<_> = superset.iter().collect();
191    subset.iter().all(|expected| superset.contains(expected))
192}
193
194/// Normalizes one boolean expression into an equivalent, solver-friendlier form.
195pub(crate) fn normalize_bool_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
196    expr.fold(cx, &mut normalize_bool_node_for_solver)
197}
198
199impl SymBoolExpr {
200    fn push_normalized_conjuncts(self, out: &mut Vec<Self>) {
201        match self.kind() {
202            SymBoolExprKind::Const(true) => {}
203            SymBoolExprKind::And(values) => {
204                for value in values.iter().cloned() {
205                    value.push_normalized_conjuncts(out);
206                }
207            }
208            _ => out.push(self),
209        }
210    }
211}
212
213pub(super) fn write_smt_assertions(
214    cx: &SymCx,
215    out: &mut String,
216    constraints: &[SymBoolExpr],
217) -> Result<(), SymbolicError> {
218    if constraints.is_empty() {
219        return Ok(());
220    }
221    if constraints.iter().any(SymBoolExpr::contains_gasleft) {
222        return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
223    }
224
225    let plan = SmtCsePlan::new(constraints);
226    if plan.bindings.is_empty() {
227        for constraint in constraints {
228            let _ = writeln!(out, "(assert {})", constraint.smt(cx));
229        }
230        return Ok(());
231    }
232
233    let writer = SmtCseWriter { cx, plan: &plan };
234    // define binding_0 = term_0
235    // ...
236    // define binding_n = term_n
237    // assert constraint_0
238    // ...
239    // assert constraint_n
240    for (idx, binding) in plan.bindings.iter().enumerate() {
241        out.push_str("(define-fun ");
242        binding.write_definition_header(out, idx);
243        match binding {
244            SmtBinding::Expr(expr) => writer.write_expr(out, expr, Some(idx), None),
245            SmtBinding::Bool(expr) => writer.write_bool(out, expr, None, Some(idx)),
246        }
247        out.push_str(")\n");
248    }
249    for constraint in constraints {
250        out.push_str("(assert ");
251        writer.write_bool(out, constraint, None, None);
252        out.push_str(")\n");
253    }
254    Ok(())
255}
256
257#[derive(Default)]
258struct SmtCseVisit {
259    count: usize,
260    binding: Option<usize>,
261    collected: bool,
262}
263
264struct SmtCsePlan {
265    expr_visits: HashMap<SymExpr, SmtCseVisit>,
266    bool_visits: HashMap<SymBoolExpr, SmtCseVisit>,
267    bindings: Vec<SmtBinding>,
268}
269
270impl SmtCsePlan {
271    fn new(constraints: &[SymBoolExpr]) -> Self {
272        let mut plan = Self {
273            expr_visits: HashMap::default(),
274            bool_visits: HashMap::default(),
275            bindings: Vec::new(),
276        };
277        for constraint in constraints {
278            plan.count_bool(constraint);
279        }
280        for constraint in constraints {
281            plan.collect_bool_binding(constraint);
282        }
283        plan
284    }
285
286    fn count_expr(&mut self, expr: &SymExpr) {
287        let visit = self.expr_visits.entry(expr.clone()).or_default();
288        visit.count += 1;
289        if visit.count != 1 {
290            return;
291        }
292        match expr.kind() {
293            SymExprKind::Const(_)
294            | SymExprKind::Var(_)
295            | SymExprKind::GasLeft(_)
296            | SymExprKind::Keccak { .. }
297            | SymExprKind::Hash { .. } => {}
298            SymExprKind::Not(value) => self.count_expr(value),
299            SymExprKind::BinOp(_, left, right) => {
300                self.count_expr(left);
301                self.count_expr(right);
302            }
303            SymExprKind::TernOp(_, left, right, modulus) => {
304                self.count_expr(modulus);
305                self.count_expr(left);
306                self.count_expr(right);
307                self.count_expr(modulus);
308            }
309            SymExprKind::Ite(cond, left, right) => {
310                self.count_bool(cond);
311                self.count_expr(left);
312                self.count_expr(right);
313            }
314        }
315    }
316
317    fn count_bool(&mut self, expr: &SymBoolExpr) {
318        let visit = self.bool_visits.entry(expr.clone()).or_default();
319        visit.count += 1;
320        if visit.count != 1 {
321            return;
322        }
323        match expr.kind() {
324            SymBoolExprKind::Const(_) => {}
325            SymBoolExprKind::Not(value) => self.count_bool(value),
326            SymBoolExprKind::And(values) => {
327                for value in values.iter() {
328                    self.count_bool(value);
329                }
330            }
331            SymBoolExprKind::Cmp(_, left, right) => {
332                self.count_expr(left);
333                self.count_expr(right);
334            }
335        }
336    }
337
338    fn collect_expr_binding(&mut self, expr: &SymExpr) {
339        {
340            let Some(visit) = self.expr_visits.get_mut(expr) else { return };
341            if visit.collected {
342                return;
343            }
344            visit.collected = true;
345        }
346        match expr.kind() {
347            SymExprKind::Const(_)
348            | SymExprKind::Var(_)
349            | SymExprKind::GasLeft(_)
350            | SymExprKind::Keccak { .. }
351            | SymExprKind::Hash { .. } => {}
352            SymExprKind::Not(value) => self.collect_expr_binding(value),
353            SymExprKind::BinOp(_, left, right) => {
354                self.collect_expr_binding(left);
355                self.collect_expr_binding(right);
356            }
357            SymExprKind::TernOp(_, left, right, modulus) => {
358                self.collect_expr_binding(modulus);
359                self.collect_expr_binding(left);
360                self.collect_expr_binding(right);
361            }
362            SymExprKind::Ite(cond, left, right) => {
363                self.collect_bool_binding(cond);
364                self.collect_expr_binding(left);
365                self.collect_expr_binding(right);
366            }
367        }
368        self.bind_expr(expr);
369    }
370
371    fn collect_bool_binding(&mut self, expr: &SymBoolExpr) {
372        {
373            let Some(visit) = self.bool_visits.get_mut(expr) else { return };
374            if visit.collected {
375                return;
376            }
377            visit.collected = true;
378        }
379        match expr.kind() {
380            SymBoolExprKind::Const(_) => {}
381            SymBoolExprKind::Not(value) => self.collect_bool_binding(value),
382            SymBoolExprKind::And(values) => {
383                for value in values.iter() {
384                    self.collect_bool_binding(value);
385                }
386            }
387            SymBoolExprKind::Cmp(_, left, right) => {
388                self.collect_expr_binding(left);
389                self.collect_expr_binding(right);
390            }
391        }
392        self.bind_bool(expr);
393    }
394
395    fn bind_expr(&mut self, expr: &SymExpr) {
396        let Some(visit) = self.expr_visits.get_mut(expr) else { return };
397        if visit.count <= 1 || visit.binding.is_some() || !Self::expr_can_bind(expr) {
398            return;
399        }
400        let idx = self.bindings.len();
401        visit.binding = Some(idx);
402        self.bindings.push(SmtBinding::Expr(expr.clone()));
403    }
404
405    fn bind_bool(&mut self, expr: &SymBoolExpr) {
406        let Some(visit) = self.bool_visits.get_mut(expr) else { return };
407        if visit.count <= 1 || visit.binding.is_some() || !Self::bool_can_bind(expr) {
408            return;
409        }
410        let idx = self.bindings.len();
411        visit.binding = Some(idx);
412        self.bindings.push(SmtBinding::Bool(expr.clone()));
413    }
414
415    fn expr_binding(&self, expr: &SymExpr) -> Option<usize> {
416        self.expr_visits.get(expr).and_then(|visit| visit.binding)
417    }
418
419    fn bool_binding(&self, expr: &SymBoolExpr) -> Option<usize> {
420        self.bool_visits.get(expr).and_then(|visit| visit.binding)
421    }
422
423    fn expr_can_bind(expr: &SymExpr) -> bool {
424        !matches!(
425            expr.kind(),
426            SymExprKind::Const(_)
427                | SymExprKind::Var(_)
428                | SymExprKind::GasLeft(_)
429                | SymExprKind::Keccak { .. }
430                | SymExprKind::Hash { .. }
431        )
432    }
433
434    fn bool_can_bind(expr: &SymBoolExpr) -> bool {
435        !matches!(expr.kind(), SymBoolExprKind::Const(_))
436    }
437}
438
439enum SmtBinding {
440    Expr(SymExpr),
441    Bool(SymBoolExpr),
442}
443
444impl SmtBinding {
445    fn write_definition_header(&self, out: &mut String, idx: usize) {
446        match self {
447            Self::Expr(_) => {
448                Self::write_expr_name(out, idx);
449                out.push_str(" () (_ BitVec 256) ");
450            }
451            Self::Bool(_) => {
452                Self::write_bool_name(out, idx);
453                out.push_str(" () Bool ");
454            }
455        }
456    }
457
458    fn write_expr_name(out: &mut String, idx: usize) {
459        let _ = write!(out, "__sym_expr_{idx}");
460    }
461
462    fn write_bool_name(out: &mut String, idx: usize) {
463        let _ = write!(out, "__sym_bool_{idx}");
464    }
465}
466
467struct SmtCseWriter<'a> {
468    cx: &'a SymCx,
469    plan: &'a SmtCsePlan,
470}
471
472impl SmtCseWriter<'_> {
473    fn write_expr(
474        &self,
475        out: &mut String,
476        expr: &SymExpr,
477        skip_expr: Option<usize>,
478        skip_bool: Option<usize>,
479    ) {
480        if let Some(idx) = self.plan.expr_binding(expr)
481            && Some(idx) != skip_expr
482        {
483            SmtBinding::write_expr_name(out, idx);
484            return;
485        }
486
487        match expr.kind() {
488            SymExprKind::Const(value) => {
489                let _ = write!(out, "(_ bv{value} 256)");
490            }
491            SymExprKind::Var(symbol)
492            | SymExprKind::GasLeft(symbol)
493            | SymExprKind::Keccak { name: symbol, .. }
494            | SymExprKind::Hash { name: symbol, .. } => out.push_str(self.cx.symbol_name(*symbol)),
495            SymExprKind::Not(value) => {
496                out.push_str("(bvnot ");
497                self.write_expr(out, value, skip_expr, skip_bool);
498                out.push(')');
499            }
500            SymExprKind::BinOp(op, left, right) => {
501                let _ = write!(out, "({} ", op.smt());
502                self.write_expr(out, left, skip_expr, skip_bool);
503                out.push(' ');
504                self.write_expr(out, right, skip_expr, skip_bool);
505                out.push(')');
506            }
507            SymExprKind::TernOp(op, left, right, modulus) => {
508                self.write_wide_modular_arithmetic(out, op.smt(), left, right, modulus);
509            }
510            SymExprKind::Ite(cond, left, right) => {
511                out.push_str("(ite ");
512                self.write_bool(out, cond, skip_expr, skip_bool);
513                out.push(' ');
514                self.write_expr(out, left, skip_expr, skip_bool);
515                out.push(' ');
516                self.write_expr(out, right, skip_expr, skip_bool);
517                out.push(')');
518            }
519        }
520    }
521
522    fn write_wide_modular_arithmetic(
523        &self,
524        out: &mut String,
525        op: &'static str,
526        left: &SymExpr,
527        right: &SymExpr,
528        modulus: &SymExpr,
529    ) {
530        // if modulus == 0:
531        //   0
532        // else:
533        //   low_256((zext(left) op zext(right)) urem zext(modulus))
534        out.push_str("(ite (= ");
535        self.write_expr(out, modulus, None, None);
536        out.push_str(" (_ bv0 256)) (_ bv0 256) ((_ extract 255 0) (bvurem (");
537        out.push_str(op);
538        out.push_str(" ((_ zero_extend 256) ");
539        self.write_expr(out, left, None, None);
540        out.push_str(") ((_ zero_extend 256) ");
541        self.write_expr(out, right, None, None);
542        out.push_str(")) ((_ zero_extend 256) ");
543        self.write_expr(out, modulus, None, None);
544        out.push_str("))))");
545    }
546
547    fn write_bool(
548        &self,
549        out: &mut String,
550        expr: &SymBoolExpr,
551        skip_expr: Option<usize>,
552        skip_bool: Option<usize>,
553    ) {
554        if let Some(idx) = self.plan.bool_binding(expr)
555            && Some(idx) != skip_bool
556        {
557            SmtBinding::write_bool_name(out, idx);
558            return;
559        }
560
561        match expr.kind() {
562            SymBoolExprKind::Const(value) => out.push_str(if *value { "true" } else { "false" }),
563            SymBoolExprKind::Not(value) => {
564                out.push_str("(not ");
565                self.write_bool(out, value, skip_expr, skip_bool);
566                out.push(')');
567            }
568            SymBoolExprKind::And(values) => {
569                out.push_str("(and");
570                for value in values.iter() {
571                    out.push(' ');
572                    self.write_bool(out, value, skip_expr, skip_bool);
573                }
574                out.push(')');
575            }
576            SymBoolExprKind::Cmp(op, left, right) => {
577                let _ = write!(out, "({} ", op.smt());
578                self.write_expr(out, left, skip_expr, skip_bool);
579                out.push(' ');
580                self.write_expr(out, right, skip_expr, skip_bool);
581                out.push(')');
582            }
583        }
584    }
585}
586
587fn normalize_bool_node_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
588    if let Some(normalized) = expr.normalize_udiv_for_solver(cx) {
589        return normalized;
590    }
591
592    match expr.kind() {
593        SymBoolExprKind::Cmp(op, left, right) => {
594            let left = normalize_expr_for_solver(cx, left.clone());
595            let right = normalize_expr_for_solver(cx, right.clone());
596            let normalized = normalize_cmp_for_solver(cx, *op, left, right);
597            normalized.normalize_udiv_for_solver(cx).unwrap_or(normalized)
598        }
599        _ => expr,
600    }
601}
602
603fn normalize_cmp_for_solver(
604    cx: &mut SymCx,
605    op: SymCmpOp,
606    left: SymExpr,
607    right: SymExpr,
608) -> SymBoolExpr {
609    match op {
610        // `a > b => b < a`.
611        SymCmpOp::Ugt => SymBoolExpr::cmp(cx, SymCmpOp::Ult, right, left),
612        // `a >= b => b <= a`.
613        SymCmpOp::Uge => SymBoolExpr::cmp(cx, SymCmpOp::Ule, right, left),
614        // `a >s b => b <s a`.
615        SymCmpOp::Sgt => SymBoolExpr::cmp(cx, SymCmpOp::Slt, right, left),
616        SymCmpOp::Eq | SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Slt => {
617            SymBoolExpr::cmp(cx, op, left, right)
618        }
619    }
620}
621
622/// Simple facts learned from the normalized conjunction currently being queried.
623#[derive(Default)]
624pub(super) struct ConstraintContext {
625    upper_bounds: HashMap<SymExpr, U256>,
626    lower_bounds: HashMap<SymExpr, U256>,
627}
628
629#[derive(Clone, Copy)]
630struct WordInterval {
631    min: U256,
632    max: U256,
633}
634
635impl WordInterval {
636    fn new(min: U256, max: U256) -> Option<Self> {
637        (min <= max).then_some(Self { min, max })
638    }
639
640    const fn exact(value: U256) -> Self {
641        Self { min: value, max: value }
642    }
643
644    fn with_bounds(self, lower: Option<U256>, upper: Option<U256>) -> Option<Self> {
645        Self::new(
646            self.min.max(lower.unwrap_or(U256::ZERO)),
647            self.max.min(upper.unwrap_or(U256::MAX)),
648        )
649    }
650}
651
652impl ConstraintContext {
653    pub(super) fn new(constraints: &[SymBoolExpr]) -> Self {
654        let mut context = Self::default();
655        for constraint in constraints {
656            context.record_upper_bound_constraint(constraint);
657            context.record_lower_bound_constraint(constraint);
658        }
659        // A bounded number of rounds closes ordinary order chains. Relational propagation keeps
660        // strict comparisons weak (`a < b` propagates only `a <= upper(b)`), so inconsistent
661        // cycles cannot tighten a bound one integer at a time across the uint256 domain.
662        for _ in 0..constraints.len() {
663            let mut changed = false;
664            for constraint in constraints {
665                changed |= context.propagate_order_bounds(constraint);
666            }
667            if !changed {
668                break;
669            }
670        }
671        context
672    }
673
674    fn upper_bound(&self, expr: &SymExpr) -> Option<U256> {
675        self.upper_bounds.get(expr).copied()
676    }
677
678    fn lower_bound(&self, expr: &SymExpr) -> Option<U256> {
679        self.lower_bounds.get(expr).copied()
680    }
681
682    fn normalize_bool(&self, cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
683        match expr.kind() {
684            SymBoolExprKind::Not(value) if self.unsigned_bool_always_true(value) => {
685                SymBoolExpr::constant(cx, false)
686            }
687            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
688                if self.masked_word_eq_self(left, right) =>
689            {
690                // `x & mask == x => true` when the current context proves `x <= mask`.
691                SymBoolExpr::constant(cx, true)
692            }
693            SymBoolExprKind::Not(value) if self.masked_eq_self_condition(value) => {
694                // `x & mask != x => false` when the current context proves `x <= mask`.
695                SymBoolExpr::constant(cx, false)
696            }
697            _ if expr
698                .zero_check_operand()
699                .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
700            {
701                // `always_true_word == 0 => false`.
702                SymBoolExpr::constant(cx, false)
703            }
704            SymBoolExprKind::Not(value)
705                if value
706                    .zero_check_operand()
707                    .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
708            {
709                // `always_true_word != 0 => true`.
710                SymBoolExpr::constant(cx, true)
711            }
712            _ => expr,
713        }
714    }
715
716    fn masked_eq_self_condition(&self, expr: &SymBoolExpr) -> bool {
717        match expr.kind() {
718            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
719                self.masked_word_eq_self(left, right)
720            }
721            _ => false,
722        }
723    }
724
725    fn masked_word_eq_self(&self, left: &SymExpr, right: &SymExpr) -> bool {
726        self.masked_word_side_eq_self(left, right) || self.masked_word_side_eq_self(right, left)
727    }
728
729    fn masked_word_side_eq_self(&self, masked: &SymExpr, value: &SymExpr) -> bool {
730        let SymExprKind::BinOp(SymBinOp::And, left, right) = masked.kind() else { return false };
731        let Some((source, mask)) = right
732            .as_const()
733            .map(|mask| (left, mask))
734            .or_else(|| left.as_const().map(|mask| (right, mask)))
735        else {
736            return false;
737        };
738        let Some(bits) = mask_low_bits(mask) else { return false };
739        source == value && self.unsigned_bits(value) <= bits
740    }
741
742    fn record_upper_bound_constraint(&mut self, constraint: &SymBoolExpr) {
743        if let Some((expr, bound)) = self.upper_bound_constraint(constraint) {
744            self.record_upper_bound(expr.clone(), bound);
745        }
746    }
747
748    fn record_upper_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
749        match self.upper_bounds.entry(expr) {
750            alloy_primitives::map::Entry::Occupied(mut entry) if bound < *entry.get() => {
751                entry.insert(bound);
752                true
753            }
754            alloy_primitives::map::Entry::Vacant(entry) => {
755                entry.insert(bound);
756                true
757            }
758            alloy_primitives::map::Entry::Occupied(_) => false,
759        }
760    }
761
762    fn record_lower_bound_constraint(&mut self, constraint: &SymBoolExpr) {
763        if let Some((expr, bound)) = self.lower_bound_constraint(constraint) {
764            self.record_lower_bound(expr.clone(), bound);
765        }
766    }
767
768    fn record_lower_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
769        match self.lower_bounds.entry(expr) {
770            alloy_primitives::map::Entry::Occupied(mut entry) if bound > *entry.get() => {
771                entry.insert(bound);
772                true
773            }
774            alloy_primitives::map::Entry::Vacant(entry) => {
775                entry.insert(bound);
776                true
777            }
778            alloy_primitives::map::Entry::Occupied(_) => false,
779        }
780    }
781
782    fn propagate_order_bounds(&mut self, constraint: &SymBoolExpr) -> bool {
783        match constraint.kind() {
784            SymBoolExprKind::Cmp(op, left, right) => match op {
785                SymCmpOp::Ult | SymCmpOp::Ule => self.propagate_less_or_equal_bounds(left, right),
786                SymCmpOp::Ugt | SymCmpOp::Uge => self.propagate_less_or_equal_bounds(right, left),
787                SymCmpOp::Eq => {
788                    let changed = self.propagate_less_or_equal_bounds(left, right);
789                    self.propagate_less_or_equal_bounds(right, left) || changed
790                }
791                SymCmpOp::Slt | SymCmpOp::Sgt => false,
792            },
793            SymBoolExprKind::Not(value) => match value.kind() {
794                SymBoolExprKind::Cmp(op, left, right) => match op {
795                    SymCmpOp::Ult | SymCmpOp::Ule => {
796                        self.propagate_less_or_equal_bounds(right, left)
797                    }
798                    SymCmpOp::Ugt | SymCmpOp::Uge => {
799                        self.propagate_less_or_equal_bounds(left, right)
800                    }
801                    SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
802                },
803                _ => false,
804            },
805            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
806        }
807    }
808
809    /// Propagates interval bounds through the known unsigned relation `left <= right`.
810    fn propagate_less_or_equal_bounds(&mut self, left: &SymExpr, right: &SymExpr) -> bool {
811        let upper = self.upper_bound(right);
812        let lower = self.lower_bound(left);
813        let upper_changed = upper.is_some_and(|bound| self.record_upper_bound(left.clone(), bound));
814        let lower_changed =
815            lower.is_some_and(|bound| self.record_lower_bound(right.clone(), bound));
816        upper_changed || lower_changed
817    }
818
819    fn upper_bound_constraint<'a>(
820        &self,
821        constraint: &'a SymBoolExpr,
822    ) -> Option<(&'a SymExpr, U256)> {
823        match constraint.kind() {
824            SymBoolExprKind::Cmp(op, left, right) => match *op {
825                SymCmpOp::Eq => const_side_bound(left, right),
826                SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
827                    (_, Some(bound)) => (!bound.is_zero()).then(|| (left, bound - U256::from(1))),
828                    _ => None,
829                },
830                SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
831                    (_, Some(bound)) => Some((left, bound)),
832                    _ => None,
833                },
834                SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
835                    (Some(bound), _) => (!bound.is_zero()).then(|| (right, bound - U256::from(1))),
836                    _ => None,
837                },
838                SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
839                    (Some(bound), _) => Some((right, bound)),
840                    _ => None,
841                },
842                SymCmpOp::Slt | SymCmpOp::Sgt => None,
843            },
844            SymBoolExprKind::Not(value) => match value.kind() {
845                SymBoolExprKind::Cmp(op, left, right) => match *op {
846                    SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
847                        (_, Some(bound)) => Some((left, bound)),
848                        _ => None,
849                    },
850                    SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
851                        (_, Some(bound)) => {
852                            (!bound.is_zero()).then(|| (left, bound - U256::from(1)))
853                        }
854                        _ => None,
855                    },
856                    SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
857                        (Some(bound), _) => Some((right, bound)),
858                        _ => None,
859                    },
860                    SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
861                        (Some(bound), _) => {
862                            (!bound.is_zero()).then(|| (right, bound - U256::from(1)))
863                        }
864                        _ => None,
865                    },
866                    SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
867                },
868                _ => None,
869            },
870            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
871        }
872    }
873
874    fn lower_bound_constraint<'a>(
875        &self,
876        constraint: &'a SymBoolExpr,
877    ) -> Option<(&'a SymExpr, U256)> {
878        match constraint.kind() {
879            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => const_side_bound(left, right),
880            SymBoolExprKind::Not(value) => match value.kind() {
881                SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
882                    nonzero_bound(left, right).or_else(|| nonzero_bound(right, left))
883                }
884                _ => None,
885            },
886            _ => None,
887        }
888    }
889
890    fn unsigned_bool_always_true(&self, expr: &SymBoolExpr) -> bool {
891        match expr.kind() {
892            SymBoolExprKind::Cmp(op, left, right) => {
893                self.unsigned_cmp_always_true(*op, left, right)
894            }
895            _ => false,
896        }
897    }
898
899    fn unsigned_cmp_always_true(&self, op: SymCmpOp, left: &SymExpr, right: &SymExpr) -> bool {
900        let Some(left) = self.interval(left) else { return false };
901        let Some(right) = self.interval(right) else { return false };
902        match op {
903            SymCmpOp::Ult => left.max < right.min,
904            SymCmpOp::Ule => left.max <= right.min,
905            SymCmpOp::Ugt => left.min > right.max,
906            SymCmpOp::Uge => left.min >= right.max,
907            SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
908        }
909    }
910
911    fn interval(&self, expr: &SymExpr) -> Option<WordInterval> {
912        let lower = self.lower_bound(expr);
913        let upper = self.upper_bound(expr);
914        let interval = self.structural_interval(expr).or_else(|| {
915            (lower.is_some() || upper.is_some()).then(|| WordInterval {
916                min: lower.unwrap_or(U256::ZERO),
917                max: upper.unwrap_or(U256::MAX),
918            })
919        })?;
920        interval.with_bounds(lower, upper)
921    }
922
923    fn structural_interval(&self, expr: &SymExpr) -> Option<WordInterval> {
924        match expr.kind() {
925            SymExprKind::Const(value) => Some(WordInterval::exact(*value)),
926            SymExprKind::BinOp(SymBinOp::And, left, right) => {
927                let mask = left.as_const().or_else(|| right.as_const())?;
928                Some(WordInterval { min: U256::ZERO, max: mask })
929            }
930            SymExprKind::BinOp(SymBinOp::Add, left, right) => {
931                let left = self.interval(left)?;
932                let right = self.interval(right)?;
933                Some(WordInterval {
934                    min: left.min.checked_add(right.min)?,
935                    max: left.max.checked_add(right.max)?,
936                })
937            }
938            SymExprKind::BinOp(SymBinOp::Sub, left, right) => {
939                let left = self.interval(left)?;
940                let right = self.interval(right)?;
941                if left.min < right.max {
942                    return None;
943                }
944                Some(WordInterval {
945                    min: left.min.checked_sub(right.max)?,
946                    max: left.max.checked_sub(right.min)?,
947                })
948            }
949            SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
950                let left = self.interval(left)?;
951                let right = self.interval(right)?;
952                Some(WordInterval {
953                    min: left.min.checked_mul(right.min)?,
954                    max: left.max.checked_mul(right.max)?,
955                })
956            }
957            SymExprKind::Ite(_, left, right) => {
958                let left = self.interval(left)?;
959                let right = self.interval(right)?;
960                Some(WordInterval { min: left.min.min(right.min), max: left.max.max(right.max) })
961            }
962            _ => None,
963        }
964    }
965}
966
967fn const_side_bound<'a>(left: &'a SymExpr, right: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
968    right
969        .as_const()
970        .map(|value| (left, value))
971        .or_else(|| left.as_const().map(|value| (right, value)))
972}
973
974fn nonzero_bound<'a>(expr: &'a SymExpr, value: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
975    value.as_const().is_some_and(|value| value.is_zero()).then(|| (expr, U256::from(1)))
976}
977
978/// Normalizes one word expression into an equivalent, solver-friendlier form.
979pub(crate) fn normalize_expr_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
980    if !expr.contains_ite() {
981        return expr;
982    }
983    expr.fold(cx, &mut normalize_expr_node_for_solver)
984}
985
986fn normalize_expr_node_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
987    match expr.kind() {
988        SymExprKind::Ite(cond, left, right) => {
989            normalize_ite_expr_for_solver(cx, cond.clone(), left.clone(), right.clone())
990        }
991        _ => expr,
992    }
993}
994
995fn normalize_ite_expr_for_solver(
996    cx: &mut SymCx,
997    cond: SymBoolExpr,
998    left: SymExpr,
999    right: SymExpr,
1000) -> SymExpr {
1001    let cond = normalize_bool_for_solver(cx, cond);
1002    if left == right {
1003        // `ite(c, a, a) => a`.
1004        return left;
1005    }
1006    if left.as_const() == Some(U256::from(1))
1007        && right.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1008    {
1009        // `ite(c, 1, bool_word(c)) => bool_word(c)`.
1010        return right;
1011    }
1012    if right.as_const().is_some_and(|value| value.is_zero())
1013        && left.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1014    {
1015        // `ite(c, bool_word(c), 0) => bool_word(c)`.
1016        return left;
1017    }
1018    SymExpr::ite(cx, cond, left, right)
1019}
1020
1021impl SymExpr {
1022    fn add_cannot_overflow_256(&self, right: &Self) -> bool {
1023        self.unsigned_bits().max(right.unsigned_bits()).saturating_add(1) <= 256
1024    }
1025
1026    fn word_bool_always_true(&self, cx: &mut SymCx) -> bool {
1027        ConstraintContext::default().word_bool_always_true(cx, self)
1028    }
1029}
1030
1031impl SymBoolExpr {
1032    fn normalize_udiv_for_solver(&self, cx: &mut SymCx) -> Option<Self> {
1033        match self.kind() {
1034            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1035                if right.as_const().is_some_and(|value| value.is_zero()) =>
1036            {
1037                left.normalized_bool_word_condition(cx).map(|value| value.not(cx)).or_else(|| {
1038                    if left.word_bool_always_true(cx) {
1039                        // `always_true_word == 0 => false`.
1040                        Some(Self::constant(cx, false))
1041                    } else {
1042                        let zero = SymExpr::zero(cx);
1043                        Self::normalize_udiv_eq_zero(cx, left, &zero)
1044                    }
1045                })
1046            }
1047            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1048                if right.as_const() == Some(U256::from(1)) =>
1049            {
1050                // `bool_word(c) == 1 => c`.
1051                left.normalized_bool_word_condition(cx)
1052            }
1053            SymBoolExprKind::Not(value) => match value.kind() {
1054                SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1055                    if right.as_const().is_some_and(|value| value.is_zero()) =>
1056                {
1057                    if left.word_bool_always_true(cx) {
1058                        // `always_true_word != 0 => true`.
1059                        Some(Self::constant(cx, true))
1060                    } else {
1061                        let zero = SymExpr::zero(cx);
1062                        Self::normalize_udiv_eq_zero(cx, left, &zero).map(|value| value.not(cx))
1063                    }
1064                }
1065                SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1066                    Self::normalize_udiv_eq_zero(cx, left, right).map(|value| value.not(cx))
1067                }
1068                SymBoolExprKind::Cmp(op, left, right) => {
1069                    Self::normalize_add_overflow_cmp(cx, *op, left, right)
1070                        .map(|value| value.not(cx))
1071                        .or_else(|| {
1072                            Self::normalize_udiv_cmp(cx, *op, left, right)
1073                                .map(|value| value.not(cx))
1074                        })
1075                }
1076                _ => None,
1077            },
1078            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1079                Self::normalize_udiv_eq_zero(cx, left, right)
1080            }
1081            SymBoolExprKind::Cmp(op, left, right) => {
1082                Self::normalize_add_overflow_cmp(cx, *op, left, right)
1083                    .or_else(|| Self::normalize_udiv_cmp(cx, *op, left, right))
1084            }
1085            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
1086        }
1087    }
1088
1089    fn normalize_add_overflow_cmp(
1090        cx: &mut SymCx,
1091        op: SymCmpOp,
1092        left: &SymExpr,
1093        right: &SymExpr,
1094    ) -> Option<Self> {
1095        match op {
1096            // `a + b > a => false` when `a + b` cannot overflow.
1097            SymCmpOp::Ugt if left.add_overflow_check(right) => Some(Self::constant(cx, false)),
1098            // `a < a + b => false` when `a + b` cannot overflow.
1099            SymCmpOp::Ult if right.add_overflow_check(left) => Some(Self::constant(cx, false)),
1100            _ => None,
1101        }
1102    }
1103
1104    fn normalize_udiv_eq_zero(cx: &mut SymCx, left: &SymExpr, right: &SymExpr) -> Option<Self> {
1105        if right.as_const().is_some_and(|value| value.is_zero())
1106            && let Some(condition) = left.normalize_eq_zero_for_solver(cx)
1107        {
1108            // `word_bool(c) == 0 => !c`.
1109            return Some(condition);
1110        }
1111        None
1112    }
1113
1114    fn normalize_udiv_cmp(
1115        cx: &mut SymCx,
1116        op: SymCmpOp,
1117        left: &SymExpr,
1118        right: &SymExpr,
1119    ) -> Option<Self> {
1120        match op {
1121            SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1122                // `a > 0 => a != 0`.
1123                (_, Some(value)) if value.is_zero() => left
1124                    .normalize_ne_zero_for_solver(cx)
1125                    .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1126                // `1 > a => a == 0`.
1127                (Some(value), _) if value == U256::from(1) => right
1128                    .normalize_eq_zero_for_solver(cx)
1129                    .or_else(|| Some(Self::eq_zero(cx, right))),
1130                _ => None,
1131            },
1132            SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1133                // `a >= 1 => a != 0`.
1134                (_, Some(value)) if value == U256::from(1) => left
1135                    .normalize_ne_zero_for_solver(cx)
1136                    .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1137                // `0 >= a => a == 0`.
1138                (Some(value), _) if value.is_zero() => right
1139                    .normalize_eq_zero_for_solver(cx)
1140                    .or_else(|| Some(Self::eq_zero(cx, right))),
1141                _ => None,
1142            },
1143            SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1144                // `a <= 0 => a == 0`.
1145                (_, Some(value)) if value.is_zero() => {
1146                    left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1147                }
1148                // `1 <= a => a != 0`.
1149                (Some(value), _) if value == U256::from(1) => right
1150                    .normalize_ne_zero_for_solver(cx)
1151                    .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1152                _ => None,
1153            },
1154            SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1155                // `a < 1 => a == 0`.
1156                (_, Some(value)) if value == U256::from(1) => {
1157                    left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1158                }
1159                // `0 < a => a != 0`.
1160                (Some(value), _) if value.is_zero() => right
1161                    .normalize_ne_zero_for_solver(cx)
1162                    .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1163                _ => None,
1164            },
1165            SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1166        }
1167    }
1168
1169    fn eq_zero(cx: &mut SymCx, expr: &SymExpr) -> Self {
1170        let zero = SymExpr::zero(cx);
1171        Self::eq(cx, expr.clone(), zero)
1172    }
1173}
1174
1175impl SymExpr {
1176    fn normalized_bool_word_condition(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1177        self.strip_low_byte_mask()
1178            .bool_word_condition()
1179            .map(|condition| normalize_bool_for_solver(cx, condition))
1180    }
1181
1182    fn add_overflow_check(&self, right: &Self) -> bool {
1183        let Some((base, increment)) = right.add_with_operand(self) else { return false };
1184        base == self && base.add_cannot_overflow_256(increment)
1185    }
1186
1187    fn add_with_operand<'a>(&'a self, operand: &Self) -> Option<(&'a Self, &'a Self)> {
1188        let SymExprKind::BinOp(SymBinOp::Add, left, right) = self.kind() else { return None };
1189        if left == operand {
1190            Some((left, right))
1191        } else if right == operand {
1192            Some((right, left))
1193        } else {
1194            None
1195        }
1196    }
1197
1198    fn normalize_eq_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1199        if let Some((numerator, denominator)) = self.udiv_operands() {
1200            // `a / b == 0 => b == 0 || a < b`.
1201            return Some(Self::udiv_zero_condition(cx, numerator, denominator));
1202        }
1203        if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1204            let then_zero = match then_expr.normalize_eq_zero_for_solver(cx) {
1205                Some(condition) => condition,
1206                None => {
1207                    let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1208                    let zero = Self::zero(cx);
1209                    SymBoolExpr::eq(cx, then_expr, zero)
1210                }
1211            };
1212            let else_zero = match else_expr.normalize_eq_zero_for_solver(cx) {
1213                Some(condition) => condition,
1214                None => {
1215                    let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1216                    let zero = Self::zero(cx);
1217                    SymBoolExpr::eq(cx, else_expr, zero)
1218                }
1219            };
1220            if then_zero.contains_udiv() || else_zero.contains_udiv() {
1221                return None;
1222            }
1223            // `ite(c, a, b) == 0 => (c && a == 0) || (!c && b == 0)`.
1224            let condition = normalize_bool_for_solver(cx, condition.clone());
1225            let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_zero]);
1226            let not_condition = condition.not(cx);
1227            let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_zero]);
1228            return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1229        }
1230        None
1231    }
1232
1233    fn normalize_ne_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1234        if let Some((numerator, denominator)) = self.udiv_operands() {
1235            // `a / b != 0 => b != 0 && a >= b`.
1236            return Some(Self::udiv_nonzero_condition(cx, numerator, denominator));
1237        }
1238        if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1239            let then_nonzero = match then_expr.normalize_ne_zero_for_solver(cx) {
1240                Some(condition) => condition,
1241                None => {
1242                    let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1243                    let zero = Self::zero(cx);
1244                    SymBoolExpr::eq(cx, then_expr, zero).not(cx)
1245                }
1246            };
1247            let else_nonzero = match else_expr.normalize_ne_zero_for_solver(cx) {
1248                Some(condition) => condition,
1249                None => {
1250                    let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1251                    let zero = Self::zero(cx);
1252                    SymBoolExpr::eq(cx, else_expr, zero).not(cx)
1253                }
1254            };
1255            if then_nonzero.contains_udiv() || else_nonzero.contains_udiv() {
1256                return None;
1257            }
1258            // `ite(c, a, b) != 0 => (c && a != 0) || (!c && b != 0)`.
1259            let condition = normalize_bool_for_solver(cx, condition.clone());
1260            let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_nonzero]);
1261            let not_condition = condition.not(cx);
1262            let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_nonzero]);
1263            return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1264        }
1265        None
1266    }
1267
1268    fn udiv_zero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1269        let numerator = normalize_expr_for_solver(cx, numerator.clone());
1270        let denominator = normalize_expr_for_solver(cx, denominator.clone());
1271        let zero = Self::zero(cx);
1272        let denominator_zero = SymBoolExpr::eq(cx, denominator.clone(), zero);
1273        let below_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Ult, numerator, denominator);
1274        SymBoolExpr::or(cx, vec![denominator_zero, below_denominator])
1275    }
1276
1277    fn udiv_nonzero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1278        let numerator = normalize_expr_for_solver(cx, numerator.clone());
1279        let denominator = normalize_expr_for_solver(cx, denominator.clone());
1280        let zero = Self::zero(cx);
1281        let denominator_nonzero = SymBoolExpr::eq(cx, denominator.clone(), zero).not(cx);
1282        let at_least_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Uge, numerator, denominator);
1283        SymBoolExpr::and(cx, vec![denominator_nonzero, at_least_denominator])
1284    }
1285}
1286
1287impl ConstraintContext {
1288    fn word_bool_always_true(&self, cx: &mut SymCx, expr: &SymExpr) -> bool {
1289        let mut terms = Vec::new();
1290        expr.push_or_terms(&mut terms);
1291        if terms.len() <= 1 {
1292            return false;
1293        }
1294
1295        let bool_terms = terms
1296            .iter()
1297            .filter_map(|term| term.normalized_bool_word_condition(cx))
1298            .collect::<Vec<_>>();
1299        if bool_terms.iter().any(|term| {
1300            let negated = term.clone().not(cx);
1301            bool_terms.contains(&negated)
1302        }) {
1303            // `c || !c => true`.
1304            return true;
1305        }
1306        for zero_term in &bool_terms {
1307            let Some(zero_operand) = zero_term.zero_check_operand() else { continue };
1308            if bool_terms.iter().any(|term| self.checked_mul_guard_for_operand(term, zero_operand))
1309            {
1310                // `a == 0 || guarded_mul_div(a) => true`.
1311                return true;
1312            }
1313        }
1314        false
1315    }
1316
1317    fn checked_mul_guard_for_operand(&self, expr: &SymBoolExpr, zero_operand: &SymExpr) -> bool {
1318        let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = expr.kind() else {
1319            return false;
1320        };
1321        self.checked_mul_guard_side(left, right, zero_operand)
1322            || self.checked_mul_guard_side(right, left, zero_operand)
1323    }
1324
1325    fn checked_mul_guard_side(
1326        &self,
1327        div_expr: &SymExpr,
1328        expected: &SymExpr,
1329        zero_operand: &SymExpr,
1330    ) -> bool {
1331        let SymExprKind::Ite(condition, then_expr, else_expr) = div_expr.kind() else {
1332            return false;
1333        };
1334        if condition.zero_check_operand().is_none_or(|operand| operand != zero_operand) {
1335            return false;
1336        }
1337        if !then_expr.as_const().is_some_and(|value| value.is_zero()) {
1338            return false;
1339        }
1340        let Some((numerator, denominator)) = else_expr.udiv_operands() else { return false };
1341        if denominator != zero_operand {
1342            return false;
1343        }
1344        let SymExprKind::BinOp(SymBinOp::Mul, left, right) = numerator.kind() else {
1345            return false;
1346        };
1347        let other = if left == zero_operand {
1348            right
1349        } else if right == zero_operand {
1350            left
1351        } else {
1352            return false;
1353        };
1354        other == expected && self.mul_cannot_overflow_256(zero_operand, other)
1355    }
1356
1357    pub(super) fn mul_cannot_overflow_256(&self, left: &SymExpr, right: &SymExpr) -> bool {
1358        self.unsigned_bits(left).saturating_add(self.unsigned_bits(right)) <= 256
1359    }
1360
1361    pub(super) fn unsigned_bits(&self, expr: &SymExpr) -> usize {
1362        let bits = match expr.kind() {
1363            SymExprKind::Const(_)
1364            | SymExprKind::Var(_)
1365            | SymExprKind::GasLeft(_)
1366            | SymExprKind::Keccak { .. }
1367            | SymExprKind::Hash { .. }
1368            | SymExprKind::Not(_) => expr.unsigned_bits(),
1369            SymExprKind::BinOp(SymBinOp::And, left, right) => {
1370                if let Some(mask) = right.as_const() {
1371                    self.unsigned_bits(left).min(mask.bit_len())
1372                } else {
1373                    256
1374                }
1375            }
1376            SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1377                self.unsigned_bits(left).max(self.unsigned_bits(right)).saturating_add(1).min(256)
1378            }
1379            SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1380                self.unsigned_bits(left).saturating_add(self.unsigned_bits(right)).min(256)
1381            }
1382            SymExprKind::BinOp(SymBinOp::UDiv, left, _) => self.unsigned_bits(left),
1383            SymExprKind::Ite(_, left, right) => {
1384                self.unsigned_bits(left).max(self.unsigned_bits(right))
1385            }
1386            _ => 256,
1387        };
1388
1389        self.upper_bound(expr).map(|bound| bits.min(bound.bit_len().max(1))).unwrap_or(bits)
1390    }
1391}