Skip to main content

foundry_evm_symbolic/runtime/solver/normalize/
mod.rs

1//! Constraint and expression normalization for solver queries.
2
3use super::*;
4
5mod polynomial;
6mod rounding;
7
8use polynomial::polynomial_identity;
9
10/// Normalizes path constraints into an equivalent, solver-friendlier form.
11#[cfg(test)]
12pub(crate) fn normalize_constraints_for_solver(
13    cx: &mut SymCx,
14    constraints: &[SymBoolExpr],
15) -> Vec<SymBoolExpr> {
16    normalize_constraints_for_solver_with(cx, constraints, |cx, constraint| {
17        normalize_bool_for_solver(cx, constraint.clone())
18    })
19}
20
21/// Reuses context-free normalization results while retaining per-query contextual rewrites.
22pub(super) fn normalize_constraints_for_solver_cached(
23    cx: &mut SymCx,
24    constraints: &[SymBoolExpr],
25    normalization_cache: &mut HashMap<SymBoolExpr, SymBoolExpr>,
26) -> Vec<SymBoolExpr> {
27    normalize_constraints_for_solver_with(cx, constraints, |cx, constraint| {
28        if let Some(normalized) = normalization_cache.get(constraint) {
29            return normalized.clone();
30        }
31        let normalized = normalize_bool_for_solver(cx, constraint.clone());
32        // These are strong hash-consed handles, so bound their lifetime like the SAT cache.
33        if normalization_cache.len() < SYMBOLIC_SOLVER_SAT_CACHE_MAX_ENTRIES {
34            normalization_cache.insert(constraint.clone(), normalized.clone());
35        }
36        normalized
37    })
38}
39
40fn normalize_constraints_for_solver_with(
41    cx: &mut SymCx,
42    constraints: &[SymBoolExpr],
43    mut normalize: impl FnMut(&mut SymCx, &SymBoolExpr) -> SymBoolExpr,
44) -> Vec<SymBoolExpr> {
45    let mut changed_conjuncts = HashSet::default();
46    let normalized = normalize_constraint_batch(
47        constraints.iter().map(|constraint| {
48            let normalized = normalize(cx, constraint);
49            if normalized != *constraint {
50                mark_conjuncts(&normalized, &mut changed_conjuncts);
51            }
52            normalized
53        }),
54        constraints.len(),
55    );
56    if matches!(normalized.as_slice(), [expr] if expr.as_const() == Some(false)) {
57        return normalized;
58    }
59
60    // Context-dependent rewrites must not contribute facts to the context that proves them. Mark
61    // candidates by syntax rather than by whether the full context happens to prove a rewrite:
62    // contradictory bounds can make an interval unavailable until another candidate is removed.
63    let retained_count = normalized
64        .iter()
65        .filter(|constraint| !ConstraintContext::requires_independent_context(constraint))
66        .count();
67    let retained = normalized
68        .iter()
69        .filter(|constraint| !ConstraintContext::requires_independent_context(constraint));
70    let context =
71        ConstraintContext::from_constraints_with_lower_bounds(retained, retained_count, false);
72    let normalized_len = normalized.len();
73    let normalized = normalize_constraint_batch(
74        normalized.into_iter().map(|constraint| {
75            let changed = changed_conjuncts.contains(&constraint);
76            context.normalize_bool(cx, constraint, changed)
77        }),
78        normalized_len,
79    );
80    normalize_bounded_comparisons(cx, normalized)
81}
82
83/// Simplifies predicates using only the other, still-retained conjuncts.
84fn normalize_bounded_comparisons(
85    cx: &mut SymCx,
86    mut constraints: Vec<SymBoolExpr>,
87) -> Vec<SymBoolExpr> {
88    // Later predicates can expose guards needed by earlier ones. Revisit the retained
89    // conjunction, but bound the work; unfinished simplification is still sound SMT input.
90    for _ in 0..MAX_CONTEXTUAL_PASSES {
91        let previous = constraints.clone();
92        let mut index = 0;
93        while index < constraints.len() {
94            let context = ConstraintContext::for_rewrite(cx, &constraints, index);
95            constraints[index] = context.normalize_bool(cx, constraints[index].clone(), false);
96            // Revisit each newly exposed conjunct with the retained supporting facts. Otherwise a
97            // division rewrite can leave a simple contradiction hidden until the SMT fallback.
98            if let SymBoolExprKind::And(terms) = constraints[index].kind() {
99                let terms = terms.to_vec();
100                constraints.splice(index..=index, terms);
101                continue;
102            }
103            match context.bounded_bool_value(&constraints[index]) {
104                Some(false) => return vec![SymBoolExpr::constant(cx, false)],
105                Some(true) => {
106                    constraints.remove(index);
107                }
108                None => index += 1,
109            }
110        }
111        // Contextual rewrites may change sort order or expose a conjunction.
112        let count = constraints.len();
113        constraints = normalize_constraint_batch(constraints, count);
114        if constraints == previous || constraints.iter().any(|c| c.as_const() == Some(false)) {
115            break;
116        }
117    }
118    constraints
119}
120
121fn mark_conjuncts(expr: &SymBoolExpr, out: &mut HashSet<SymBoolExpr>) {
122    let mut pending = vec![expr.clone()];
123    while let Some(expr) = pending.pop() {
124        if !out.insert(expr.clone()) {
125            continue;
126        }
127        if let SymBoolExprKind::And(values) = expr.kind() {
128            pending.extend(values.iter().cloned());
129        }
130    }
131}
132
133fn normalize_constraint_batch(
134    constraints: impl IntoIterator<Item = SymBoolExpr>,
135    capacity: usize,
136) -> Vec<SymBoolExpr> {
137    let mut normalized = Vec::with_capacity(capacity);
138    for constraint in constraints {
139        if constraint.as_const() == Some(false) {
140            return vec![constraint];
141        }
142        constraint.push_normalized_conjuncts(&mut normalized);
143    }
144    sort_dedup_bool_exprs(&mut normalized);
145    normalized
146}
147
148fn sort_dedup_bool_exprs(exprs: &mut Vec<SymBoolExpr>) {
149    // Hash-consing already caches deterministic structural hashes. Only render full structural
150    // keys for the exceedingly rare case where two distinct expressions collide.
151    exprs.sort_unstable_by(bool_expr_cmp);
152    exprs.dedup();
153}
154
155fn bool_expr_cmp(left: &SymBoolExpr, right: &SymBoolExpr) -> std::cmp::Ordering {
156    if left == right {
157        return std::cmp::Ordering::Equal;
158    }
159    left.stable_hash_cmp(right)
160        .then_with(|| bool_structural_key(left).cmp(&bool_structural_key(right)))
161}
162
163fn bool_structural_key(expr: &SymBoolExpr) -> String {
164    let mut key = String::new();
165    write_bool_structural_key(&mut key, expr);
166    key
167}
168
169fn write_bool_structural_key(out: &mut String, expr: &SymBoolExpr) {
170    match expr.kind() {
171        SymBoolExprKind::Const(value) => {
172            let _ = write!(out, "0:{value}");
173        }
174        SymBoolExprKind::Not(value) => {
175            out.push_str("1:");
176            write_bool_structural_key(out, value);
177        }
178        SymBoolExprKind::And(values) => {
179            let _ = write!(out, "2:{}:", values.len());
180            for value in values.iter() {
181                write_bool_structural_key(out, value);
182                out.push(';');
183            }
184        }
185        SymBoolExprKind::Cmp(op, left, right) => {
186            let _ = write!(out, "3:{}:", cmp_op_key(*op));
187            write_expr_structural_key(out, left);
188            out.push(':');
189            write_expr_structural_key(out, right);
190        }
191    }
192}
193
194fn write_expr_structural_key(out: &mut String, expr: &SymExpr) {
195    match expr.kind() {
196        SymExprKind::Const(value) => {
197            let _ = write!(out, "0:{value:064x}");
198        }
199        SymExprKind::Var(name) => {
200            let _ = write!(out, "1:{}", name.id());
201        }
202        SymExprKind::GasLeft(symbol) => {
203            let _ = write!(out, "2:{}", symbol.id());
204        }
205        SymExprKind::Keccak { name, len, bytes } => {
206            let _ = write!(out, "3:{}:", name.id());
207            write_expr_structural_key(out, len);
208            write_exprs_structural_key(out, bytes);
209        }
210        SymExprKind::Hash { name, algorithm, bytes } => {
211            let _ = write!(out, "4:{}:{algorithm}:", name.id());
212            write_exprs_structural_key(out, bytes);
213        }
214        SymExprKind::Not(value) => {
215            out.push_str("5:");
216            write_expr_structural_key(out, value);
217        }
218        SymExprKind::BinOp(op, left, right) => {
219            let _ = write!(out, "6:{}:", expr_binop_key(*op));
220            write_expr_structural_key(out, left);
221            out.push(':');
222            write_expr_structural_key(out, right);
223        }
224        SymExprKind::TernOp(op, left, right, modulus) => {
225            let _ = write!(out, "7:{}:", expr_ternop_key(*op));
226            write_expr_structural_key(out, left);
227            out.push(':');
228            write_expr_structural_key(out, right);
229            out.push(':');
230            write_expr_structural_key(out, modulus);
231        }
232        SymExprKind::Ite(condition, then_expr, else_expr) => {
233            out.push_str("9:");
234            write_bool_structural_key(out, condition);
235            out.push(':');
236            write_expr_structural_key(out, then_expr);
237            out.push(':');
238            write_expr_structural_key(out, else_expr);
239        }
240    }
241}
242
243fn write_exprs_structural_key(out: &mut String, exprs: &[SymExpr]) {
244    let _ = write!(out, "{}:", exprs.len());
245    for expr in exprs {
246        write_expr_structural_key(out, expr);
247        out.push(';');
248    }
249}
250
251const fn cmp_op_key(op: SymCmpOp) -> u8 {
252    match op {
253        SymCmpOp::Eq => 0,
254        SymCmpOp::Ult => 1,
255        SymCmpOp::Ugt => 2,
256        SymCmpOp::Ule => 3,
257        SymCmpOp::Uge => 4,
258        SymCmpOp::Slt => 5,
259        SymCmpOp::Sgt => 6,
260    }
261}
262
263const fn expr_binop_key(op: SymBinOp) -> u8 {
264    match op {
265        SymBinOp::Add => 0,
266        SymBinOp::Sub => 1,
267        SymBinOp::Mul => 2,
268        SymBinOp::UDiv => 3,
269        SymBinOp::URem => 4,
270        SymBinOp::SDiv => 5,
271        SymBinOp::SRem => 6,
272        SymBinOp::And => 7,
273        SymBinOp::Or => 8,
274        SymBinOp::Xor => 9,
275        SymBinOp::Shl => 10,
276        SymBinOp::Shr => 11,
277        SymBinOp::Sar => 12,
278    }
279}
280
281const fn expr_ternop_key(op: SymTernOp) -> u8 {
282    match op {
283        SymTernOp::AddMod => 0,
284        SymTernOp::MulMod => 1,
285    }
286}
287
288/// Returns whether canonically ordered normalized constraints contain a direct contradiction.
289pub(super) fn constraints_are_directly_unsat(cx: &mut SymCx, constraints: &[SymBoolExpr]) -> bool {
290    let mut derived = Vec::new();
291    for constraint in constraints {
292        let Some(fact) = bitwise_bool_word_fact(cx, constraint) else {
293            continue;
294        };
295        if let SymBoolExprKind::And(values) = fact.kind() {
296            // A positive conjunction implies each member independently. Retain the aggregate for
297            // exact matches, but expose its members to the direct contradiction check as well.
298            derived.extend(values.iter().cloned());
299        }
300        derived.push(fact);
301    }
302    let contains = |expected: &SymBoolExpr| {
303        constraints.binary_search_by(|candidate| bool_expr_cmp(candidate, expected)).is_ok()
304            || derived.contains(expected)
305    };
306    constraints.iter().chain(&derived).any(|constraint| match constraint.kind() {
307        SymBoolExprKind::Const(false) => true,
308        SymBoolExprKind::Not(inner)
309            if let SymBoolExprKind::And(values) = inner.kind()
310                && values.iter().all(&contains) =>
311        {
312            true
313        }
314        SymBoolExprKind::Not(inner) => contains(inner),
315        _ => {
316            let negated = constraint.clone().not(cx);
317            contains(&negated)
318        }
319    })
320}
321
322fn bitwise_bool_word_fact(cx: &mut SymCx, constraint: &SymBoolExpr) -> Option<SymBoolExpr> {
323    match constraint.kind() {
324        SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
325            if right.as_const().is_some_and(|value| value.is_zero()) =>
326        {
327            left.bitwise_bool_word_condition(cx).map(|condition| condition.not(cx))
328        }
329        SymBoolExprKind::Not(inner) => {
330            let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = inner.kind() else {
331                return None;
332            };
333            if !right.as_const().is_some_and(|value| value.is_zero()) {
334                return None;
335            }
336            left.bitwise_bool_word_condition(cx)
337        }
338        _ => None,
339    }
340}
341
342/// Returns whether every expression in `subset` appears in `superset`.
343pub(super) fn sorted_bool_exprs_are_subset(
344    subset: &[SymBoolExpr],
345    superset: &[SymBoolExpr],
346) -> bool {
347    if subset.len() > superset.len() {
348        return false;
349    }
350
351    let superset: HashSet<_> = superset.iter().collect();
352    subset.iter().all(|expected| superset.contains(expected))
353}
354
355/// Normalizes one boolean expression into an equivalent, solver-friendlier form.
356pub(crate) fn normalize_bool_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
357    expr.fold(cx, &mut normalize_bool_node_for_solver)
358}
359
360impl SymBoolExpr {
361    fn push_normalized_conjuncts(self, out: &mut Vec<Self>) {
362        match self.kind() {
363            SymBoolExprKind::Const(true) => {}
364            SymBoolExprKind::And(values) => {
365                for value in values.iter().cloned() {
366                    value.push_normalized_conjuncts(out);
367                }
368            }
369            _ => out.push(self),
370        }
371    }
372}
373
374fn normalize_bool_node_for_solver(cx: &mut SymCx, expr: SymBoolExpr) -> SymBoolExpr {
375    if let Some(normalized) = expr.normalize_udiv_for_solver(cx) {
376        return normalized;
377    }
378
379    match expr.kind() {
380        SymBoolExprKind::Not(value) => match value.kind() {
381            SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right)
382                if matches!(left.kind(), SymExprKind::Not(_)) =>
383            {
384                normalize_cmp_for_solver(cx, SymCmpOp::Ule, right.clone(), left.clone())
385            }
386            _ => expr,
387        },
388        SymBoolExprKind::Cmp(op, left, right) => {
389            let left = normalize_expr_for_solver(cx, left.clone());
390            let right = normalize_expr_for_solver(cx, right.clone());
391            if *op == SymCmpOp::Eq && polynomial_identity(&left, &right) {
392                return SymBoolExpr::constant(cx, true);
393            }
394            let normalized = normalize_cmp_for_solver(cx, *op, left, right);
395            normalized.normalize_udiv_for_solver(cx).unwrap_or(normalized)
396        }
397        _ => expr,
398    }
399}
400
401fn normalize_cmp_for_solver(
402    cx: &mut SymCx,
403    op: SymCmpOp,
404    left: SymExpr,
405    right: SymExpr,
406) -> SymBoolExpr {
407    if op == SymCmpOp::Eq {
408        if right.as_const().is_some_and(|value| value.is_zero())
409            && let SymExprKind::BinOp(SymBinOp::Sub, minuend, subtrahend) = left.kind()
410        {
411            // Word subtraction is zero exactly when both operands are equal, including at the
412            // modular boundary. Solc commonly lowers optimized equality checks to this shape.
413            return SymBoolExpr::eq(cx, minuend.clone(), subtrahend.clone());
414        }
415        if left.as_const().is_some_and(|value| value.is_zero())
416            && let SymExprKind::BinOp(SymBinOp::Sub, minuend, subtrahend) = right.kind()
417        {
418            return SymBoolExpr::eq(cx, minuend.clone(), subtrahend.clone());
419        }
420    }
421
422    let (left, right) =
423        if matches!(op, SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Ugt | SymCmpOp::Uge) {
424            // Complement reverses unsigned order: ~x = MAX - x. Move it onto
425            // the constant so interval analysis can see Solidity's addition guard.
426            match (left.kind(), right.kind()) {
427                (SymExprKind::Not(value), SymExprKind::Const(limit)) => {
428                    (SymExpr::constant(cx, !*limit), value.clone())
429                }
430                (SymExprKind::Const(limit), SymExprKind::Not(value)) => {
431                    (value.clone(), SymExpr::constant(cx, !*limit))
432                }
433                _ => (left, right),
434            }
435        } else {
436            (left, right)
437        };
438
439    match op {
440        // `a > b => b < a`.
441        SymCmpOp::Ugt => SymBoolExpr::cmp(cx, SymCmpOp::Ult, right, left),
442        // `a >= b => b <= a`.
443        SymCmpOp::Uge => SymBoolExpr::cmp(cx, SymCmpOp::Ule, right, left),
444        // `a >s b => b <s a`.
445        SymCmpOp::Sgt => SymBoolExpr::cmp(cx, SymCmpOp::Slt, right, left),
446        SymCmpOp::Eq | SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Slt => {
447            SymBoolExpr::cmp(cx, op, left, right)
448        }
449    }
450}
451
452/// Simple facts learned from the normalized conjunction currently being queried.
453#[derive(Default)]
454pub(super) struct ConstraintContext {
455    upper_bounds: HashMap<SymExpr, U256>,
456    lower_bounds: HashMap<SymExpr, U256>,
457    unsigned_lower_bounds: HashMap<SymExpr, U256>,
458    exact_values: HashMap<SymExpr, U256>,
459    conflicting_exact_values: HashSet<SymExpr>,
460    non_wrapping_products: HashSet<(SymExpr, SymExpr)>,
461}
462
463#[derive(Clone, Copy)]
464struct WordInterval {
465    min: U256,
466    max: U256,
467}
468
469// These analyses are solver optimizations, so exceeding their local work budget must only make
470// them decline a rewrite. Keeping the bound shared and private prevents deeply nested bytecode
471// expressions from turning a proof shortcut into unbounded Rust recursion.
472const MAX_LOCAL_ANALYSIS_NODES: usize = 256;
473const MAX_CONTEXTUAL_PASSES: usize = 4;
474
475impl WordInterval {
476    fn new(min: U256, max: U256) -> Option<Self> {
477        (min <= max).then_some(Self { min, max })
478    }
479
480    const fn exact(value: U256) -> Self {
481        Self { min: value, max: value }
482    }
483
484    fn with_bounds(self, lower: Option<U256>, upper: Option<U256>) -> Option<Self> {
485        Self::new(
486            self.min.max(lower.unwrap_or(U256::ZERO)),
487            self.max.min(upper.unwrap_or(U256::MAX)),
488        )
489    }
490}
491
492impl ConstraintContext {
493    pub(super) fn new(constraints: &[SymBoolExpr]) -> Self {
494        Self::from_constraints(constraints.iter(), constraints.len())
495    }
496
497    fn from_constraints<'a>(
498        constraints: impl Clone + Iterator<Item = &'a SymBoolExpr>,
499        constraint_count: usize,
500    ) -> Self {
501        Self::from_constraints_with_lower_bounds(constraints, constraint_count, true)
502    }
503
504    /// Builds a rewrite context from the other retained conjuncts, never the predicate itself.
505    fn for_rewrite(cx: &mut SymCx, constraints: &[SymBoolExpr], index: usize) -> Self {
506        let supporting = constraints
507            .iter()
508            .enumerate()
509            .filter_map(|(i, constraint)| (i != index).then_some(constraint));
510        let mut context = Self::from_constraints(supporting.clone(), constraints.len() - 1);
511        // One successful product guard may bound an operand used in another guard.
512        for _ in 0..MAX_CONTEXTUAL_PASSES {
513            let mut changed = false;
514            for constraint in supporting.clone() {
515                changed |= context.record_non_wrapping_product(cx, constraint);
516            }
517            if !changed {
518                break;
519            }
520        }
521        // Product bounds can then turn scaled zero checks into exact operand facts.
522        for constraint in supporting {
523            context.record_scaled_zero_fact(cx, constraint);
524        }
525        context
526    }
527
528    fn from_constraints_with_lower_bounds<'a>(
529        constraints: impl Clone + Iterator<Item = &'a SymBoolExpr>,
530        constraint_count: usize,
531        promote_unsigned_bounds: bool,
532    ) -> Self {
533        let mut context = Self::default();
534        for constraint in constraints.clone() {
535            context.record_exact_value_constraint(constraint);
536            context.record_upper_bound_constraint(constraint);
537            context.record_lower_bound_constraint(constraint);
538            context.record_unsigned_lower_bound_constraint(constraint, promote_unsigned_bounds);
539        }
540        // A bounded number of rounds closes ordinary order chains. Relational propagation keeps
541        // strict comparisons weak (`a < b` propagates only `a <= upper(b)`), so inconsistent
542        // cycles cannot tighten a bound one integer at a time across the uint256 domain.
543        for _ in 0..constraint_count {
544            let mut changed = false;
545            for constraint in constraints.clone() {
546                changed |= context.propagate_order_bounds(constraint);
547            }
548            if !changed {
549                break;
550            }
551        }
552        context
553    }
554
555    fn upper_bound(&self, expr: &SymExpr) -> Option<U256> {
556        self.upper_bounds.get(expr).copied()
557    }
558
559    fn lower_bound(&self, expr: &SymExpr) -> Option<U256> {
560        self.lower_bounds.get(expr).copied()
561    }
562
563    /// Conservatively identifies every conjunct that path facts may rewrite.
564    fn requires_independent_context(expr: &SymBoolExpr) -> bool {
565        let root_candidate = match expr.kind() {
566            SymBoolExprKind::Cmp(op, left, right) => match op {
567                SymCmpOp::Eq => {
568                    Self::mul_div_identity_operands(left, right).is_some()
569                        || Self::mul_div_identity_operands(right, left).is_some()
570                        || Self::masked_word_side_eq_self_shape(left, right).is_some()
571                        || Self::masked_word_side_eq_self_shape(right, left).is_some()
572                }
573                SymCmpOp::Ult | SymCmpOp::Ule => {
574                    Self::udiv_comparison_operands(*op, left, right).is_some()
575                }
576                SymCmpOp::Slt | SymCmpOp::Sgt => true,
577                SymCmpOp::Ugt | SymCmpOp::Uge => false,
578            },
579            SymBoolExprKind::Not(value) => match value.kind() {
580                SymBoolExprKind::Cmp(op, left, right)
581                    if Self::udiv_comparison_operands(*op, left, right).is_some() =>
582                {
583                    true
584                }
585                SymBoolExprKind::Cmp(SymCmpOp::Slt | SymCmpOp::Sgt, _, _) => true,
586                _ => value.zero_check_operand().is_some_and(|word| {
587                    matches!(word.kind(), SymExprKind::BinOp(SymBinOp::Or, _, _))
588                }),
589            },
590            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
591        };
592        root_candidate
593            || expr.contains_udiv()
594            || expr.visit_unique_bool(|word| matches!(word.kind(), SymExprKind::Ite(_, _, _)))
595    }
596
597    fn normalize_bool(
598        &self,
599        cx: &mut SymCx,
600        expr: SymBoolExpr,
601        context_free_changed: bool,
602    ) -> SymBoolExpr {
603        let may_normalize_word = !self.is_exact_value_constraint(&expr)
604            && expr.visit_unique_bool(|word| self.may_normalize_word(word));
605        let expr = if may_normalize_word {
606            let expr = expr.fold_exprs(cx, &mut |cx, expr| self.normalize_word(cx, expr));
607            normalize_bool_for_solver(cx, expr)
608        } else if context_free_changed {
609            // The first pass can create new Boolean predicates, such as an overflow comparison
610            // while eliminating a division. Normalize those predicates before applying facts.
611            normalize_bool_for_solver(cx, expr)
612        } else {
613            expr
614        };
615        if let Some(normalized) = self.normalize_signed_add_comparison(cx, &expr) {
616            return normalized;
617        }
618        if let Some(value) = self.rounding_comparison_value(&expr) {
619            return SymBoolExpr::constant(cx, value);
620        }
621        if let SymBoolExprKind::Not(value) = expr.kind()
622            && let Some(normalized) = self.normalize_signed_add_comparison(cx, value)
623        {
624            return normalized.not(cx);
625        }
626        if let SymBoolExprKind::Cmp(op, left, right) = expr.kind()
627            && let Some(normalized) = self.normalize_udiv_comparison(cx, *op, left, right)
628        {
629            return normalized;
630        }
631        if let SymBoolExprKind::Not(value) = expr.kind()
632            && let SymBoolExprKind::Cmp(op, left, right) = value.kind()
633            && let Some(normalized) = self.normalize_udiv_comparison(cx, *op, left, right)
634        {
635            return normalized.not(cx);
636        }
637
638        match expr.kind() {
639            SymBoolExprKind::Not(value) if self.unsigned_bool_always_true(value) => {
640                SymBoolExpr::constant(cx, false)
641            }
642            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
643                if self.mul_div_identity(left, right) || self.mul_div_identity(right, left) =>
644            {
645                SymBoolExpr::constant(cx, true)
646            }
647            SymBoolExprKind::Not(value)
648                if matches!(
649                    value.kind(),
650                    SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
651                        if self.mul_div_identity(left, right)
652                            || self.mul_div_identity(right, left)
653                ) =>
654            {
655                SymBoolExpr::constant(cx, false)
656            }
657            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
658                if self.masked_word_eq_self(left, right) =>
659            {
660                // `x & mask == x => true` when the current context proves `x <= mask`.
661                SymBoolExpr::constant(cx, true)
662            }
663            SymBoolExprKind::Not(value) if self.masked_eq_self_condition(value) => {
664                // `x & mask != x => false` when the current context proves `x <= mask`.
665                SymBoolExpr::constant(cx, false)
666            }
667            _ if expr
668                .zero_check_operand()
669                .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
670            {
671                // `always_true_word == 0 => false`.
672                SymBoolExpr::constant(cx, false)
673            }
674            SymBoolExprKind::Not(value)
675                if value
676                    .zero_check_operand()
677                    .is_some_and(|left| self.word_bool_always_true(cx, left)) =>
678            {
679                // `always_true_word != 0 => true`.
680                SymBoolExpr::constant(cx, true)
681            }
682            _ => expr,
683        }
684    }
685
686    fn record_exact_value_constraint(&mut self, constraint: &SymBoolExpr) {
687        let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = constraint.kind() else {
688            return;
689        };
690        let Some((expr, value)) = const_side_bound(left, right) else {
691            return;
692        };
693        if !matches!(expr.kind(), SymExprKind::Var(_))
694            || self.conflicting_exact_values.contains(expr)
695        {
696            return;
697        }
698        if self.exact_values.get(expr).is_some_and(|current| *current != value) {
699            self.exact_values.remove(expr);
700            self.conflicting_exact_values.insert(expr.clone());
701        } else {
702            self.exact_values.insert(expr.clone(), value);
703        }
704    }
705
706    fn exact_value(&self, expr: &SymExpr) -> Option<U256> {
707        self.exact_values.get(expr).copied()
708    }
709
710    fn may_normalize_word(&self, expr: &SymExpr) -> bool {
711        if self.exact_values.contains_key(expr)
712            || Self::mul_div_operands(expr).is_some()
713            || Self::ceil_div_product(expr).is_some()
714        {
715            return true;
716        }
717        match expr.kind() {
718            SymExprKind::Ite(_, _, _) => true,
719            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
720                left.as_const() == Some(U256::ONE) || right.as_const() == Some(U256::ONE)
721            }
722            SymExprKind::BinOp(SymBinOp::Mul, _, _) => Self::constant_mul_operands(expr)
723                .is_some_and(|(value, _)| Self::constant_mul_operands(value).is_some()),
724            SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
725                Self::rounded_product_operands(numerator).is_some()
726                    || (denominator.as_const().is_some_and(|value| !value.is_zero())
727                        && Self::constant_mul_operands(numerator).is_some())
728            }
729            _ => false,
730        }
731    }
732
733    fn normalize_word(&self, cx: &mut SymCx, expr: SymExpr) -> SymExpr {
734        if let Some(value) = self.exact_value(&expr) {
735            return SymExpr::constant(cx, value);
736        }
737        if let SymExprKind::Ite(condition, then_value, else_value) = expr.kind() {
738            if let Some(value) = self.bounded_bool_value(condition) {
739                return if value { then_value.clone() } else { else_value.clone() };
740            }
741            if let Some(condition) = self.normalize_signed_add_comparison(cx, condition) {
742                return SymExpr::ite(cx, condition, then_value.clone(), else_value.clone());
743            }
744        }
745        if let Some(value) = self.quotient_of_rounded_product(&expr) {
746            return value.clone();
747        }
748        if let SymExprKind::BinOp(SymBinOp::And, value, mask) = expr.kind()
749            && mask.as_const() == Some(U256::ONE)
750            && value.normalized_bool_word_condition(cx).is_some()
751        {
752            return value.clone();
753        }
754        if let SymExprKind::BinOp(SymBinOp::Or, left, right) = expr.kind()
755            && ((left.as_const() == Some(U256::from(1))
756                && right.normalized_bool_word_condition(cx).is_some())
757                || (right.as_const() == Some(U256::from(1))
758                    && left.normalized_bool_word_condition(cx).is_some()))
759        {
760            return SymExpr::one(cx);
761        }
762        if let Some((value, outer_factor)) = Self::constant_mul_operands(&expr)
763            && let Some((value, inner_factor)) = Self::constant_mul_operands(value)
764        {
765            let factor = SymExpr::constant(cx, inner_factor.wrapping_mul(outer_factor));
766            return SymExpr::binop(cx, SymBinOp::Mul, value.clone(), factor);
767        }
768        if let Some((value, factor)) =
769            self.exact_ceil_div_factor(&expr).or_else(|| self.exact_scaled_div_factor(&expr))
770        {
771            let factor = SymExpr::constant(cx, factor);
772            return SymExpr::binop(cx, SymBinOp::Mul, value.clone(), factor);
773        }
774        if let Some((denominator, other)) = Self::mul_div_operands(&expr)
775            && self.interval(denominator).is_some_and(|interval| !interval.min.is_zero())
776            && self.mul_cannot_overflow_256(denominator, other)
777        {
778            return other.clone();
779        }
780        expr
781    }
782
783    fn bounded_bool_value(&self, expr: &SymBoolExpr) -> Option<bool> {
784        match expr.kind() {
785            SymBoolExprKind::Const(value) => Some(*value),
786            SymBoolExprKind::Not(value) => self.bounded_bool_value(value).map(|value| !value),
787            SymBoolExprKind::Cmp(op, left, right) => {
788                let left = self.interval(left)?;
789                let right = self.interval(right)?;
790                if *op == SymCmpOp::Eq {
791                    return if left.max < right.min || right.max < left.min {
792                        Some(false)
793                    } else if left.min == left.max && right.min == right.max {
794                        Some(left.min == right.min)
795                    } else {
796                        None
797                    };
798                }
799                if matches!(op, SymCmpOp::Slt | SymCmpOp::Sgt)
800                    && (left.min.bit(255) != left.max.bit(255)
801                        || right.min.bit(255) != right.max.bit(255))
802                {
803                    return None;
804                }
805                let (always, possible) =
806                    if matches!(op, SymCmpOp::Ult | SymCmpOp::Ule | SymCmpOp::Slt) {
807                        (op.eval(left.max, right.min), op.eval(left.min, right.max))
808                    } else {
809                        (op.eval(left.min, right.max), op.eval(left.max, right.min))
810                    };
811                if always {
812                    Some(true)
813                } else if !possible {
814                    Some(false)
815                } else {
816                    None
817                }
818            }
819            SymBoolExprKind::And(_) => None,
820        }
821    }
822
823    /// Simplifies signed addition guards once the signs of the summands are established.
824    fn normalize_signed_add_comparison(
825        &self,
826        cx: &mut SymCx,
827        expr: &SymBoolExpr,
828    ) -> Option<SymBoolExpr> {
829        let SymBoolExprKind::Cmp(op, left, right) = expr.kind() else { return None };
830        let (sum, base) = match op {
831            SymCmpOp::Slt => (left, right),
832            SymCmpOp::Sgt => (right, left),
833            _ => return None,
834        };
835        let signed_max = U256::MAX >> 1;
836        if let Some((_, increment)) = sum.add_with_operand(base) {
837            let base_range = self.interval(base)?;
838            let increment_range = self.interval(increment)?;
839            // Opposite-sign addition cannot overflow: adding a nonnegative value cannot make
840            // a negative summand smaller, and adding a negative value makes a nonnegative one
841            // smaller.
842            if base_range.min > signed_max && increment_range.max <= signed_max {
843                return Some(SymBoolExpr::constant(cx, false));
844            }
845            if base_range.max <= signed_max && increment_range.min > signed_max {
846                return Some(SymBoolExpr::constant(cx, true));
847            }
848        } else if base.as_const() != Some(U256::ZERO) {
849            return None;
850        }
851        if base.as_const() == Some(U256::ZERO)
852            && let SymExprKind::BinOp(SymBinOp::Add, left, right) = sum.kind()
853        {
854            for (positive, negative) in [(left, right), (right, left)] {
855                if let SymExprKind::BinOp(SymBinOp::Sub, zero, amount) = negative.kind()
856                    && zero.as_const() == Some(U256::ZERO)
857                    && self.interval(positive).is_some_and(|range| range.max <= signed_max)
858                    && self.interval(amount).is_some_and(|range| range.max <= signed_max)
859                {
860                    // For a,b in [0, int256::MAX], signed(a + (-b)) < 0 iff a < b.
861                    return Some(SymBoolExpr::cmp(
862                        cx,
863                        SymCmpOp::Ult,
864                        positive.clone(),
865                        amount.clone(),
866                    ));
867                }
868            }
869        }
870        // Use the sum's canonical operand order for both the overflow guard and a subsequent
871        // signed-to-unsigned cast. Their conditions must normalize to the same predicate.
872        let SymExprKind::BinOp(SymBinOp::Add, increment, base) = sum.kind() else {
873            return None;
874        };
875        if self.interval(base)?.max > signed_max || self.interval(increment)?.max > signed_max {
876            return None;
877        }
878        // With nonnegative summands their unsigned sum cannot wrap. It is signed-less than a
879        // summand exactly when it crosses the signed maximum; equality (zero increment) is safe.
880        let limit = SymExpr::constant(cx, signed_max);
881        let remaining = SymExpr::binop(cx, SymBinOp::Sub, limit, base.clone());
882        Some(SymBoolExpr::cmp(cx, SymCmpOp::Ult, remaining, increment.clone()))
883    }
884
885    fn exact_ceil_div_factor<'a>(&self, expr: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
886        let (product, denominator) = Self::ceil_div_product(expr)?;
887        let (value, multiplier) = Self::constant_mul_operands(product)?;
888        self.max_scaled_product(value, multiplier)?.checked_add(denominator)?;
889        let factor = multiplier.checked_div(denominator)?;
890        (multiplier % denominator).is_zero().then_some((value, factor))
891    }
892
893    /// Recognizes `(product + scale - 1) / scale` without assuming the arithmetic cannot wrap.
894    fn ceil_div_product(expr: &SymExpr) -> Option<(&SymExpr, U256)> {
895        let (numerator, denominator) = expr.udiv_operands()?;
896        let scale = denominator.as_const().filter(|value| !value.is_zero())?;
897        if let SymExprKind::BinOp(SymBinOp::Sub, sum, one) = numerator.kind()
898            && one.as_const() == Some(U256::ONE)
899            && let SymExprKind::BinOp(SymBinOp::Add, product, rounding) = sum.kind()
900            && rounding.as_const() == Some(scale)
901            && matches!(product.kind(), SymExprKind::BinOp(SymBinOp::Mul, _, _))
902        {
903            Some((product, scale))
904        } else {
905            None
906        }
907    }
908
909    fn exact_scaled_div_factor<'a>(&self, expr: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
910        let (numerator, denominator) = expr.udiv_operands()?;
911        let denominator = denominator.as_const().filter(|value| !value.is_zero())?;
912        let (value, multiplier) = Self::constant_mul_operands(numerator)?;
913        if !(multiplier % denominator).is_zero()
914            || self.max_scaled_product(value, multiplier).is_none()
915        {
916            return None;
917        }
918        Some((value, multiplier / denominator))
919    }
920
921    fn max_scaled_product(&self, value: &SymExpr, multiplier: U256) -> Option<U256> {
922        self.interval(value)?.max.checked_mul(multiplier)
923    }
924
925    fn constant_mul_operands(expr: &SymExpr) -> Option<(&SymExpr, U256)> {
926        let SymExprKind::BinOp(SymBinOp::Mul, left, right) = expr.kind() else {
927            return None;
928        };
929        right
930            .as_const()
931            .map(|factor| (left, factor))
932            .or_else(|| left.as_const().map(|factor| (right, factor)))
933    }
934
935    fn is_exact_value_constraint(&self, constraint: &SymBoolExpr) -> bool {
936        let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = constraint.kind() else {
937            return false;
938        };
939        const_side_bound(left, right)
940            .is_some_and(|(expr, value)| self.exact_value(expr) == Some(value))
941    }
942
943    fn masked_eq_self_condition(&self, expr: &SymBoolExpr) -> bool {
944        match expr.kind() {
945            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
946                self.masked_word_eq_self(left, right)
947            }
948            _ => false,
949        }
950    }
951
952    fn masked_word_eq_self(&self, left: &SymExpr, right: &SymExpr) -> bool {
953        self.masked_word_side_eq_self(left, right) || self.masked_word_side_eq_self(right, left)
954    }
955
956    fn masked_word_side_eq_self(&self, masked: &SymExpr, value: &SymExpr) -> bool {
957        Self::masked_word_side_eq_self_shape(masked, value)
958            .is_some_and(|bits| self.unsigned_bits(value) <= bits)
959    }
960
961    fn masked_word_side_eq_self_shape(masked: &SymExpr, value: &SymExpr) -> Option<usize> {
962        let SymExprKind::BinOp(SymBinOp::And, left, right) = masked.kind() else {
963            return None;
964        };
965        let (source, mask) = right
966            .as_const()
967            .map(|mask| (left, mask))
968            .or_else(|| left.as_const().map(|mask| (right, mask)))?;
969        let bits = mask_low_bits(mask)?;
970        (source == value).then_some(bits)
971    }
972
973    fn record_upper_bound_constraint(&mut self, constraint: &SymBoolExpr) {
974        if let Some((expr, bound)) = self.upper_bound_constraint(constraint) {
975            self.record_upper_bound(expr.clone(), bound);
976        }
977    }
978
979    fn record_upper_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
980        match self.upper_bounds.entry(expr) {
981            alloy_primitives::map::Entry::Occupied(mut entry) if bound < *entry.get() => {
982                entry.insert(bound);
983                true
984            }
985            alloy_primitives::map::Entry::Vacant(entry) => {
986                entry.insert(bound);
987                true
988            }
989            alloy_primitives::map::Entry::Occupied(_) => false,
990        }
991    }
992
993    fn record_lower_bound_constraint(&mut self, constraint: &SymBoolExpr) {
994        if let Some((expr, bound)) = self.lower_bound_constraint(constraint) {
995            self.record_lower_bound(expr.clone(), bound);
996        }
997    }
998
999    fn record_unsigned_lower_bound_constraint(
1000        &mut self,
1001        constraint: &SymBoolExpr,
1002        promote_to_interval: bool,
1003    ) {
1004        if let Some((expr, bound)) = self.unsigned_lower_bound_constraint(constraint) {
1005            let entry = self.unsigned_lower_bounds.entry(expr.clone()).or_default();
1006            *entry = (*entry).max(bound);
1007            // Batch normalization keeps theorem bounds separate from general intervals.
1008            // A rewrite supported only by other retained conjuncts may also use these bounds
1009            // for interval deductions.
1010            if promote_to_interval {
1011                self.record_lower_bound(expr.clone(), bound);
1012            }
1013        }
1014    }
1015
1016    fn unsigned_lower_bound_constraint<'a>(
1017        &self,
1018        constraint: &'a SymBoolExpr,
1019    ) -> Option<(&'a SymExpr, U256)> {
1020        match constraint.kind() {
1021            SymBoolExprKind::Cmp(op, left, right) => match *op {
1022                SymCmpOp::Eq => const_side_bound(left, right),
1023                SymCmpOp::Ult => {
1024                    left.as_const()?.checked_add(U256::ONE).map(|bound| (right, bound))
1025                }
1026                SymCmpOp::Ule => left.as_const().map(|bound| (right, bound)),
1027                SymCmpOp::Ugt => {
1028                    right.as_const()?.checked_add(U256::ONE).map(|bound| (left, bound))
1029                }
1030                SymCmpOp::Uge => right.as_const().map(|bound| (left, bound)),
1031                SymCmpOp::Slt | SymCmpOp::Sgt => None,
1032            },
1033            SymBoolExprKind::Not(value) => match value.kind() {
1034                SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) => {
1035                    right.as_const().map(|bound| (left, bound))
1036                }
1037                SymBoolExprKind::Cmp(SymCmpOp::Ule, left, right) => {
1038                    right.as_const()?.checked_add(U256::ONE).map(|bound| (left, bound))
1039                }
1040                SymBoolExprKind::Cmp(SymCmpOp::Ugt, left, right) => {
1041                    left.as_const().map(|bound| (right, bound))
1042                }
1043                SymBoolExprKind::Cmp(SymCmpOp::Uge, left, right) => {
1044                    left.as_const()?.checked_add(U256::ONE).map(|bound| (right, bound))
1045                }
1046                _ => None,
1047            },
1048            _ => None,
1049        }
1050    }
1051
1052    fn record_lower_bound(&mut self, expr: SymExpr, bound: U256) -> bool {
1053        match self.lower_bounds.entry(expr) {
1054            alloy_primitives::map::Entry::Occupied(mut entry) if bound > *entry.get() => {
1055                entry.insert(bound);
1056                true
1057            }
1058            alloy_primitives::map::Entry::Vacant(entry) => {
1059                entry.insert(bound);
1060                true
1061            }
1062            alloy_primitives::map::Entry::Occupied(_) => false,
1063        }
1064    }
1065
1066    fn propagate_order_bounds(&mut self, constraint: &SymBoolExpr) -> bool {
1067        match constraint.kind() {
1068            SymBoolExprKind::Cmp(op, left, right) => match op {
1069                SymCmpOp::Ult | SymCmpOp::Ule => self.propagate_less_or_equal_bounds(left, right),
1070                SymCmpOp::Ugt | SymCmpOp::Uge => self.propagate_less_or_equal_bounds(right, left),
1071                SymCmpOp::Eq => {
1072                    let changed = self.propagate_less_or_equal_bounds(left, right);
1073                    self.propagate_less_or_equal_bounds(right, left) || changed
1074                }
1075                SymCmpOp::Slt | SymCmpOp::Sgt => false,
1076            },
1077            SymBoolExprKind::Not(value) => match value.kind() {
1078                SymBoolExprKind::Cmp(op, left, right) => match op {
1079                    SymCmpOp::Ult | SymCmpOp::Ule => {
1080                        self.propagate_less_or_equal_bounds(right, left)
1081                    }
1082                    SymCmpOp::Ugt | SymCmpOp::Uge => {
1083                        self.propagate_less_or_equal_bounds(left, right)
1084                    }
1085                    SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
1086                },
1087                _ => false,
1088            },
1089            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
1090        }
1091    }
1092
1093    /// Propagates interval bounds through the known unsigned relation `left <= right`.
1094    fn propagate_less_or_equal_bounds(&mut self, left: &SymExpr, right: &SymExpr) -> bool {
1095        let upper = self.upper_bound(right);
1096        let lower = self.lower_bound(left);
1097        let upper_changed = upper.is_some_and(|bound| self.record_upper_bound(left.clone(), bound));
1098        let lower_changed =
1099            lower.is_some_and(|bound| self.record_lower_bound(right.clone(), bound));
1100        upper_changed || lower_changed
1101    }
1102
1103    fn upper_bound_constraint<'a>(
1104        &self,
1105        constraint: &'a SymBoolExpr,
1106    ) -> Option<(&'a SymExpr, U256)> {
1107        match constraint.kind() {
1108            SymBoolExprKind::Cmp(op, left, right) => match *op {
1109                SymCmpOp::Eq => const_side_bound(left, right),
1110                SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1111                    (_, Some(bound)) => (!bound.is_zero()).then(|| (left, bound - U256::from(1))),
1112                    _ => None,
1113                },
1114                SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1115                    (_, Some(bound)) => Some((left, bound)),
1116                    _ => None,
1117                },
1118                SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1119                    (Some(bound), _) => (!bound.is_zero()).then(|| (right, bound - U256::from(1))),
1120                    _ => None,
1121                },
1122                SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1123                    (Some(bound), _) => Some((right, bound)),
1124                    _ => None,
1125                },
1126                SymCmpOp::Slt | SymCmpOp::Sgt => None,
1127            },
1128            SymBoolExprKind::Not(value) => match value.kind() {
1129                SymBoolExprKind::Cmp(op, left, right) => match *op {
1130                    SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1131                        (_, Some(bound)) => Some((left, bound)),
1132                        _ => None,
1133                    },
1134                    SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1135                        (_, Some(bound)) => {
1136                            (!bound.is_zero()).then(|| (left, bound - U256::from(1)))
1137                        }
1138                        _ => None,
1139                    },
1140                    SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1141                        (Some(bound), _) => Some((right, bound)),
1142                        _ => None,
1143                    },
1144                    SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1145                        (Some(bound), _) => {
1146                            (!bound.is_zero()).then(|| (right, bound - U256::from(1)))
1147                        }
1148                        _ => None,
1149                    },
1150                    SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1151                },
1152                _ => None,
1153            },
1154            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
1155        }
1156    }
1157
1158    fn lower_bound_constraint<'a>(
1159        &self,
1160        constraint: &'a SymBoolExpr,
1161    ) -> Option<(&'a SymExpr, U256)> {
1162        match constraint.kind() {
1163            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => const_side_bound(left, right),
1164            SymBoolExprKind::Not(value) => match value.kind() {
1165                SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1166                    if right.as_const().is_some_and(|value| value.is_zero()) {
1167                        Some((left, U256::from(1)))
1168                    } else if left.as_const().is_some_and(|value| value.is_zero()) {
1169                        Some((right, U256::from(1)))
1170                    } else {
1171                        None
1172                    }
1173                }
1174                _ => None,
1175            },
1176            _ => None,
1177        }
1178    }
1179
1180    fn unsigned_bool_always_true(&self, expr: &SymBoolExpr) -> bool {
1181        match expr.kind() {
1182            SymBoolExprKind::Cmp(op, left, right) => {
1183                self.unsigned_cmp_always_true(*op, left, right)
1184            }
1185            _ => false,
1186        }
1187    }
1188
1189    fn unsigned_cmp_always_true(&self, op: SymCmpOp, left: &SymExpr, right: &SymExpr) -> bool {
1190        if op == SymCmpOp::Eq
1191            && (self.mul_div_identity(left, right) || self.mul_div_identity(right, left))
1192        {
1193            return true;
1194        }
1195        let Some(left) = self.interval(left) else {
1196            return false;
1197        };
1198        let Some(right) = self.interval(right) else {
1199            return false;
1200        };
1201        match op {
1202            SymCmpOp::Ult => left.max < right.min,
1203            SymCmpOp::Ule => left.max <= right.min,
1204            SymCmpOp::Ugt => left.min > right.max,
1205            SymCmpOp::Uge => left.min >= right.max,
1206            SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => false,
1207        }
1208    }
1209
1210    fn mul_div_identity(&self, quotient: &SymExpr, expected: &SymExpr) -> bool {
1211        let Some((denominator, other)) = Self::mul_div_identity_operands(quotient, expected) else {
1212            return false;
1213        };
1214
1215        self.interval(denominator).is_some_and(|interval| !interval.min.is_zero())
1216            && self.mul_cannot_overflow_256(denominator, other)
1217    }
1218
1219    fn mul_div_identity_operands<'a>(
1220        quotient: &'a SymExpr,
1221        expected: &SymExpr,
1222    ) -> Option<(&'a SymExpr, &'a SymExpr)> {
1223        let (denominator, other) = Self::mul_div_operands(quotient)?;
1224        (other == expected).then_some((denominator, other))
1225    }
1226
1227    fn mul_div_operands(quotient: &SymExpr) -> Option<(&SymExpr, &SymExpr)> {
1228        let (numerator, denominator) = quotient.udiv_operands()?;
1229        let SymExprKind::BinOp(SymBinOp::Mul, left, right) = numerator.kind() else {
1230            return None;
1231        };
1232        let other = if left == denominator {
1233            right
1234        } else if right == denominator {
1235            left
1236        } else {
1237            return None;
1238        };
1239        Some((denominator, other))
1240    }
1241
1242    fn udiv_comparison_operands<'a>(
1243        op: SymCmpOp,
1244        left: &'a SymExpr,
1245        right: &'a SymExpr,
1246    ) -> Option<(&'a SymExpr, &'a SymExpr, &'a SymExpr, bool)> {
1247        if !matches!(op, SymCmpOp::Ult | SymCmpOp::Ule) {
1248            return None;
1249        }
1250        if let Some((numerator, denominator)) = left.udiv_operands()
1251            && denominator.as_const().is_some_and(|value| !value.is_zero())
1252            && !right.contains_udiv()
1253        {
1254            return Some((numerator, denominator, right, true));
1255        }
1256        if let Some((numerator, denominator)) = right.udiv_operands()
1257            && denominator.as_const().is_some_and(|value| !value.is_zero())
1258            && !left.contains_udiv()
1259        {
1260            return Some((numerator, denominator, left, false));
1261        }
1262        None
1263    }
1264
1265    fn normalize_udiv_comparison(
1266        &self,
1267        cx: &mut SymCx,
1268        op: SymCmpOp,
1269        left: &SymExpr,
1270        right: &SymExpr,
1271    ) -> Option<SymBoolExpr> {
1272        let (numerator, denominator, threshold, quotient_on_left) =
1273            Self::udiv_comparison_operands(op, left, right)?;
1274        let increment_threshold =
1275            matches!((op, quotient_on_left), (SymCmpOp::Ule, true) | (SymCmpOp::Ult, false));
1276        let threshold = if increment_threshold {
1277            // Prove the successor cannot wrap before constructing the word addition.
1278            self.interval(threshold)?.max.checked_add(U256::ONE)?;
1279            let one = SymExpr::one(cx);
1280            SymExpr::binop(cx, SymBinOp::Add, threshold.clone(), one)
1281        } else {
1282            threshold.clone()
1283        };
1284        if !self.mul_cannot_overflow_256(&threshold, denominator) {
1285            return None;
1286        }
1287
1288        let scaled_threshold = SymExpr::binop(cx, SymBinOp::Mul, threshold, denominator.clone());
1289        Some(if quotient_on_left {
1290            // `n / d < k => n < k * d`; `n / d <= k => n < (k + 1) * d`.
1291            SymBoolExpr::cmp(cx, SymCmpOp::Ult, numerator.clone(), scaled_threshold)
1292        } else {
1293            // `k <= n / d => k * d <= n`; `k < n / d => (k + 1) * d <= n`.
1294            SymBoolExpr::cmp(cx, SymCmpOp::Ule, scaled_threshold, numerator.clone())
1295        })
1296    }
1297
1298    fn interval(&self, expr: &SymExpr) -> Option<WordInterval> {
1299        let mut intervals = HashMap::default();
1300        let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
1301        self.interval_cached(expr, &mut intervals, &mut remaining)
1302    }
1303
1304    fn interval_cached(
1305        &self,
1306        expr: &SymExpr,
1307        intervals: &mut HashMap<SymExpr, Option<WordInterval>>,
1308        remaining: &mut usize,
1309    ) -> Option<WordInterval> {
1310        if let Some(interval) = intervals.get(expr) {
1311            return *interval;
1312        }
1313
1314        let lower = self.lower_bound(expr);
1315        let upper = self.upper_bound(expr);
1316        let explicit_bounds = || {
1317            if lower.is_none() && upper.is_none() {
1318                return None;
1319            }
1320            WordInterval::new(lower.unwrap_or(U256::ZERO), upper.unwrap_or(U256::MAX))
1321        };
1322        if *remaining == 0 {
1323            let interval = explicit_bounds();
1324            intervals.insert(expr.clone(), interval);
1325            return interval;
1326        }
1327        *remaining -= 1;
1328
1329        let interval =
1330            self.structural_interval(expr, intervals, remaining).or_else(explicit_bounds);
1331        let interval = interval.and_then(|interval| interval.with_bounds(lower, upper));
1332        intervals.insert(expr.clone(), interval);
1333        interval
1334    }
1335
1336    fn structural_interval(
1337        &self,
1338        expr: &SymExpr,
1339        intervals: &mut HashMap<SymExpr, Option<WordInterval>>,
1340        remaining: &mut usize,
1341    ) -> Option<WordInterval> {
1342        match expr.kind() {
1343            SymExprKind::Const(value) => Some(WordInterval::exact(*value)),
1344            SymExprKind::BinOp(SymBinOp::And, left, right) => {
1345                let mask = left.as_const().or_else(|| right.as_const())?;
1346                Some(WordInterval { min: U256::ZERO, max: mask })
1347            }
1348            SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1349                let left = self.interval_cached(left, intervals, remaining)?;
1350                let right = self.interval_cached(right, intervals, remaining)?;
1351                Some(WordInterval {
1352                    min: left.min.checked_add(right.min)?,
1353                    max: left.max.checked_add(right.max)?,
1354                })
1355            }
1356            SymExprKind::BinOp(SymBinOp::Sub, left, right) => {
1357                if let Some(interval) =
1358                    self.rounding_error_interval(left, right, intervals, remaining)
1359                {
1360                    return Some(interval);
1361                }
1362                let left = self.interval_cached(left, intervals, remaining)?;
1363                let right = self.interval_cached(right, intervals, remaining)?;
1364                if left.max < right.min {
1365                    // Every subtraction wraps exactly once, so the unsigned image is contiguous.
1366                    return Some(WordInterval {
1367                        min: left.min.wrapping_sub(right.max),
1368                        max: left.max.wrapping_sub(right.min),
1369                    });
1370                }
1371                if left.min < right.max {
1372                    return None;
1373                }
1374                Some(WordInterval {
1375                    min: left.min.checked_sub(right.max)?,
1376                    max: left.max.checked_sub(right.min)?,
1377                })
1378            }
1379            SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1380                let guarded = self.has_non_wrapping_product(left, right);
1381                let left = self.interval_cached(left, intervals, remaining)?;
1382                let right = self.interval_cached(right, intervals, remaining)?;
1383                Some(WordInterval {
1384                    min: left.min.checked_mul(right.min)?,
1385                    // A retained overflow guard correlates the factors. Their independent
1386                    // maxima can overflow even though every feasible product fits.
1387                    max: left
1388                        .max
1389                        .checked_mul(right.max)
1390                        .or_else(|| guarded.then_some(U256::MAX))?,
1391                })
1392            }
1393            SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
1394                // Even an otherwise unbounded numerator is a uint256 word. Division by a
1395                // positive denominator bounds the quotient regardless of numerator wrapping.
1396                let numerator = self
1397                    .interval_cached(numerator, intervals, remaining)
1398                    .unwrap_or(WordInterval { min: U256::ZERO, max: U256::MAX });
1399                let denominator = self.interval_cached(denominator, intervals, remaining)?;
1400                if denominator.min.is_zero() {
1401                    return None;
1402                }
1403                Some(WordInterval {
1404                    min: numerator.min / denominator.max,
1405                    max: numerator.max / denominator.min,
1406                })
1407            }
1408            SymExprKind::BinOp(SymBinOp::Shr, value, shift) => {
1409                let shift = shift.as_const()?;
1410                if shift >= U256::from(256) {
1411                    return Some(WordInterval::exact(U256::ZERO));
1412                }
1413                let value = self.interval_cached(value, intervals, remaining)?;
1414                let shift = shift.to::<usize>();
1415                Some(WordInterval { min: value.min >> shift, max: value.max >> shift })
1416            }
1417            SymExprKind::Ite(_, left, right) => {
1418                let left = self.interval_cached(left, intervals, remaining)?;
1419                let right = self.interval_cached(right, intervals, remaining)?;
1420                Some(WordInterval { min: left.min.min(right.min), max: left.max.max(right.max) })
1421            }
1422            _ => None,
1423        }
1424    }
1425}
1426
1427fn const_side_bound<'a>(left: &'a SymExpr, right: &'a SymExpr) -> Option<(&'a SymExpr, U256)> {
1428    right
1429        .as_const()
1430        .map(|value| (left, value))
1431        .or_else(|| left.as_const().map(|value| (right, value)))
1432}
1433
1434/// Normalizes one word expression into an equivalent, solver-friendlier form.
1435pub(crate) fn normalize_expr_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
1436    if expr.contains_ite() { expr.fold(cx, &mut normalize_expr_node_for_solver) } else { expr }
1437}
1438
1439fn normalize_expr_node_for_solver(cx: &mut SymCx, expr: SymExpr) -> SymExpr {
1440    match expr.kind() {
1441        SymExprKind::Ite(cond, left, right) => {
1442            normalize_ite_expr_for_solver(cx, cond.clone(), left.clone(), right.clone())
1443        }
1444        _ => expr,
1445    }
1446}
1447
1448fn normalize_ite_expr_for_solver(
1449    cx: &mut SymCx,
1450    cond: SymBoolExpr,
1451    left: SymExpr,
1452    right: SymExpr,
1453) -> SymExpr {
1454    let cond = normalize_bool_for_solver(cx, cond);
1455    if left == right {
1456        // `ite(c, a, a) => a`.
1457        return left;
1458    }
1459    if left.as_const() == Some(U256::from(1))
1460        && right.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1461    {
1462        // `ite(c, 1, bool_word(c)) => bool_word(c)`.
1463        return right;
1464    }
1465    if right.as_const().is_some_and(|value| value.is_zero())
1466        && left.normalized_bool_word_condition(cx).as_ref() == Some(&cond)
1467    {
1468        // `ite(c, bool_word(c), 0) => bool_word(c)`.
1469        return left;
1470    }
1471    SymExpr::ite(cx, cond, left, right)
1472}
1473
1474impl SymExpr {
1475    fn add_cannot_overflow_256(&self, right: &Self) -> bool {
1476        self.unsigned_bits().max(right.unsigned_bits()).saturating_add(1) <= 256
1477    }
1478
1479    fn word_bool_always_true(&self, cx: &mut SymCx) -> bool {
1480        ConstraintContext::default().word_bool_always_true(cx, self)
1481    }
1482}
1483
1484impl SymBoolExpr {
1485    fn normalize_udiv_for_solver(&self, cx: &mut SymCx) -> Option<Self> {
1486        if let SymBoolExprKind::Cmp(op, left, right) = self.kind()
1487            && let Some(normalized) = Self::normalize_const_over_self_udiv_cmp(cx, *op, left, right)
1488        {
1489            return Some(normalized);
1490        }
1491        if let SymBoolExprKind::Not(value) = self.kind()
1492            && let SymBoolExprKind::Cmp(op, left, right) = value.kind()
1493            && let Some(normalized) = Self::normalize_const_over_self_udiv_cmp(cx, *op, left, right)
1494        {
1495            return Some(normalized.not(cx));
1496        }
1497
1498        match self.kind() {
1499            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1500                if right.as_const().is_some_and(|value| value.is_zero()) =>
1501            {
1502                left.normalized_bool_word_condition(cx).map(|value| value.not(cx)).or_else(|| {
1503                    if left.word_bool_always_true(cx) {
1504                        // `always_true_word == 0 => false`.
1505                        Some(Self::constant(cx, false))
1506                    } else {
1507                        let zero = SymExpr::zero(cx);
1508                        Self::normalize_udiv_eq_zero(cx, left, &zero)
1509                    }
1510                })
1511            }
1512            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1513                if right.as_const() == Some(U256::from(1)) =>
1514            {
1515                // `bool_word(c) == 1 => c`.
1516                left.normalized_bool_word_condition(cx)
1517            }
1518            SymBoolExprKind::Not(value) => match value.kind() {
1519                SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
1520                    if right.as_const().is_some_and(|value| value.is_zero()) =>
1521                {
1522                    if left.word_bool_always_true(cx) {
1523                        // `always_true_word != 0 => true`.
1524                        Some(Self::constant(cx, true))
1525                    } else {
1526                        let zero = SymExpr::zero(cx);
1527                        Self::normalize_udiv_eq_zero(cx, left, &zero).map(|value| value.not(cx))
1528                    }
1529                }
1530                SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1531                    Self::normalize_udiv_eq_zero(cx, left, right).map(|value| value.not(cx))
1532                }
1533                SymBoolExprKind::Cmp(op, left, right) => {
1534                    Self::normalize_add_overflow_cmp(cx, *op, left, right)
1535                        .map(|value| value.not(cx))
1536                        .or_else(|| {
1537                            Self::normalize_udiv_cmp(cx, *op, left, right)
1538                                .map(|value| value.not(cx))
1539                        })
1540                }
1541                _ => None,
1542            },
1543            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
1544                Self::normalize_udiv_eq_zero(cx, left, right)
1545            }
1546            SymBoolExprKind::Cmp(op, left, right) => {
1547                Self::normalize_add_overflow_cmp(cx, *op, left, right)
1548                    .or_else(|| Self::normalize_udiv_cmp(cx, *op, left, right))
1549            }
1550            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => None,
1551        }
1552    }
1553
1554    fn normalize_add_overflow_cmp(
1555        cx: &mut SymCx,
1556        op: SymCmpOp,
1557        left: &SymExpr,
1558        right: &SymExpr,
1559    ) -> Option<Self> {
1560        if let Some(normalized) = Self::normalize_sub_underflow_cmp(cx, op, left, right) {
1561            return Some(normalized);
1562        }
1563        // Strict forms test overflow and non-strict forms its complement; addition wraps iff the
1564        // increment exceeds `~base`.
1565        let (base, increment, overflow) = match op {
1566            SymCmpOp::Ugt => {
1567                right.add_with_operand(left).map(|(_, increment)| (left, increment, true))
1568            }
1569            SymCmpOp::Ult => {
1570                left.add_with_operand(right).map(|(_, increment)| (right, increment, true))
1571            }
1572            SymCmpOp::Uge => {
1573                left.add_with_operand(right).map(|(_, increment)| (right, increment, false))
1574            }
1575            SymCmpOp::Ule => {
1576                right.add_with_operand(left).map(|(_, increment)| (left, increment, false))
1577            }
1578            SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1579        }?;
1580        if base.add_cannot_overflow_256(increment) {
1581            return Some(Self::constant(cx, !overflow));
1582        }
1583
1584        let limit = match base.kind() {
1585            SymExprKind::BinOp(SymBinOp::Sub, max, value) if max.as_const() == Some(U256::MAX) => {
1586                value.clone()
1587            }
1588            _ => SymExpr::not(cx, base.clone()),
1589        };
1590        Some(if overflow {
1591            Self::cmp(cx, SymCmpOp::Ult, limit, increment.clone())
1592        } else {
1593            Self::cmp(cx, SymCmpOp::Ule, increment.clone(), limit)
1594        })
1595    }
1596
1597    fn normalize_sub_underflow_cmp(
1598        cx: &mut SymCx,
1599        op: SymCmpOp,
1600        left: &SymExpr,
1601        right: &SymExpr,
1602    ) -> Option<Self> {
1603        let (base, difference, underflow) = match op {
1604            SymCmpOp::Ult => (left, right, true),
1605            SymCmpOp::Ugt => (right, left, true),
1606            SymCmpOp::Ule => (right, left, false),
1607            SymCmpOp::Uge => (left, right, false),
1608            _ => return None,
1609        };
1610        let SymExprKind::BinOp(SymBinOp::Sub, minuend, subtrahend) = difference.kind() else {
1611            return None;
1612        };
1613        if minuend != base {
1614            return None;
1615        }
1616        // Unsigned modular subtraction wraps exactly when the subtrahend exceeds the minuend.
1617        Some(if underflow {
1618            Self::cmp(cx, SymCmpOp::Ult, base.clone(), subtrahend.clone())
1619        } else {
1620            Self::cmp(cx, SymCmpOp::Ule, subtrahend.clone(), base.clone())
1621        })
1622    }
1623
1624    fn normalize_udiv_eq_zero(cx: &mut SymCx, left: &SymExpr, right: &SymExpr) -> Option<Self> {
1625        if right.as_const().is_some_and(|value| value.is_zero())
1626            && let Some(condition) = left.normalize_eq_zero_for_solver(cx)
1627        {
1628            // `word_bool(c) == 0 => !c`.
1629            return Some(condition);
1630        }
1631        None
1632    }
1633
1634    fn normalize_udiv_cmp(
1635        cx: &mut SymCx,
1636        op: SymCmpOp,
1637        left: &SymExpr,
1638        right: &SymExpr,
1639    ) -> Option<Self> {
1640        match op {
1641            SymCmpOp::Ugt => match (left.as_const(), right.as_const()) {
1642                // `a > 0 => a != 0`.
1643                (_, Some(value)) if value.is_zero() => left
1644                    .normalize_ne_zero_for_solver(cx)
1645                    .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1646                // `1 > a => a == 0`.
1647                (Some(value), _) if value == U256::from(1) => right
1648                    .normalize_eq_zero_for_solver(cx)
1649                    .or_else(|| Some(Self::eq_zero(cx, right))),
1650                _ => None,
1651            },
1652            SymCmpOp::Uge => match (left.as_const(), right.as_const()) {
1653                // `a >= 1 => a != 0`.
1654                (_, Some(value)) if value == U256::from(1) => left
1655                    .normalize_ne_zero_for_solver(cx)
1656                    .or_else(|| Some(Self::eq_zero(cx, left).not(cx))),
1657                // `0 >= a => a == 0`.
1658                (Some(value), _) if value.is_zero() => right
1659                    .normalize_eq_zero_for_solver(cx)
1660                    .or_else(|| Some(Self::eq_zero(cx, right))),
1661                _ => None,
1662            },
1663            SymCmpOp::Ule => match (left.as_const(), right.as_const()) {
1664                // `a <= 0 => a == 0`.
1665                (_, Some(value)) if value.is_zero() => {
1666                    left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1667                }
1668                // `1 <= a => a != 0`.
1669                (Some(value), _) if value == U256::from(1) => right
1670                    .normalize_ne_zero_for_solver(cx)
1671                    .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1672                _ => None,
1673            },
1674            SymCmpOp::Ult => match (left.as_const(), right.as_const()) {
1675                // `a < 1 => a == 0`.
1676                (_, Some(value)) if value == U256::from(1) => {
1677                    left.normalize_eq_zero_for_solver(cx).or_else(|| Some(Self::eq_zero(cx, left)))
1678                }
1679                // `0 < a => a != 0`.
1680                (Some(value), _) if value.is_zero() => right
1681                    .normalize_ne_zero_for_solver(cx)
1682                    .or_else(|| Some(Self::eq_zero(cx, right).not(cx))),
1683                _ => None,
1684            },
1685            SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => None,
1686        }
1687    }
1688
1689    fn normalize_const_over_self_udiv_cmp(
1690        cx: &mut SymCx,
1691        op: SymCmpOp,
1692        left: &SymExpr,
1693        right: &SymExpr,
1694    ) -> Option<Self> {
1695        let (value, quotient, complement) = match op {
1696            // `a <= c / a`.
1697            SymCmpOp::Ule => (left, right, false),
1698            // `c / a < a`, the complement of `a <= c / a`.
1699            SymCmpOp::Ult => (right, left, true),
1700            SymCmpOp::Eq | SymCmpOp::Ugt | SymCmpOp::Uge | SymCmpOp::Slt | SymCmpOp::Sgt => {
1701                return None;
1702            }
1703        };
1704        let (numerator, denominator) = match quotient.kind() {
1705            SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => (numerator, denominator),
1706            SymExprKind::Ite(condition, zero, division)
1707                if zero.as_const().is_some_and(|value| value.is_zero()) =>
1708            {
1709                let (numerator, denominator) = division.udiv_operands()?;
1710                if condition.zero_check_operand() != Some(denominator) {
1711                    return None;
1712                }
1713                (numerator, denominator)
1714            }
1715            _ => return None,
1716        };
1717        if denominator != value {
1718            return None;
1719        }
1720
1721        let threshold = numerator.as_const()?.root(2);
1722        let threshold = SymExpr::constant(cx, threshold);
1723        Some(if complement {
1724            Self::cmp(cx, SymCmpOp::Ult, threshold, value.clone())
1725        } else {
1726            Self::cmp(cx, SymCmpOp::Ule, value.clone(), threshold)
1727        })
1728    }
1729
1730    fn eq_zero(cx: &mut SymCx, expr: &SymExpr) -> Self {
1731        let zero = SymExpr::zero(cx);
1732        Self::eq(cx, expr.clone(), zero)
1733    }
1734}
1735
1736impl SymExpr {
1737    fn normalized_bool_word_condition(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1738        self.strip_low_byte_mask()
1739            .bool_word_condition()
1740            .map(|condition| normalize_bool_for_solver(cx, condition))
1741    }
1742
1743    fn add_with_operand<'a>(&'a self, operand: &Self) -> Option<(&'a Self, &'a Self)> {
1744        let SymExprKind::BinOp(SymBinOp::Add, left, right) = self.kind() else {
1745            return None;
1746        };
1747        if left == operand {
1748            Some((left, right))
1749        } else if right == operand {
1750            Some((right, left))
1751        } else {
1752            None
1753        }
1754    }
1755
1756    fn normalize_eq_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1757        if let Some((numerator, denominator)) = self.udiv_operands() {
1758            // `a / b == 0 => b == 0 || a < b`.
1759            return Some(Self::udiv_zero_condition(cx, numerator, denominator));
1760        }
1761        if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1762            let then_zero = match then_expr.normalize_eq_zero_for_solver(cx) {
1763                Some(condition) => condition,
1764                None => {
1765                    let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1766                    let zero = Self::zero(cx);
1767                    SymBoolExpr::eq(cx, then_expr, zero)
1768                }
1769            };
1770            let else_zero = match else_expr.normalize_eq_zero_for_solver(cx) {
1771                Some(condition) => condition,
1772                None => {
1773                    let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1774                    let zero = Self::zero(cx);
1775                    SymBoolExpr::eq(cx, else_expr, zero)
1776                }
1777            };
1778            if then_zero.contains_udiv() || else_zero.contains_udiv() {
1779                return None;
1780            }
1781            // `ite(c, a, b) == 0 => (c && a == 0) || (!c && b == 0)`.
1782            let condition = normalize_bool_for_solver(cx, condition.clone());
1783            let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_zero]);
1784            let not_condition = condition.not(cx);
1785            let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_zero]);
1786            return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1787        }
1788        None
1789    }
1790
1791    fn normalize_ne_zero_for_solver(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1792        if let Some((numerator, denominator)) = self.udiv_operands() {
1793            // `a / b != 0 => b != 0 && a >= b`.
1794            return Some(Self::udiv_nonzero_condition(cx, numerator, denominator));
1795        }
1796        if let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() {
1797            let then_nonzero = match then_expr.normalize_ne_zero_for_solver(cx) {
1798                Some(condition) => condition,
1799                None => {
1800                    let then_expr = normalize_expr_for_solver(cx, then_expr.clone());
1801                    let zero = Self::zero(cx);
1802                    SymBoolExpr::eq(cx, then_expr, zero).not(cx)
1803                }
1804            };
1805            let else_nonzero = match else_expr.normalize_ne_zero_for_solver(cx) {
1806                Some(condition) => condition,
1807                None => {
1808                    let else_expr = normalize_expr_for_solver(cx, else_expr.clone());
1809                    let zero = Self::zero(cx);
1810                    SymBoolExpr::eq(cx, else_expr, zero).not(cx)
1811                }
1812            };
1813            if then_nonzero.contains_udiv() || else_nonzero.contains_udiv() {
1814                return None;
1815            }
1816            // `ite(c, a, b) != 0 => (c && a != 0) || (!c && b != 0)`.
1817            let condition = normalize_bool_for_solver(cx, condition.clone());
1818            let then_condition = SymBoolExpr::and(cx, vec![condition.clone(), then_nonzero]);
1819            let not_condition = condition.not(cx);
1820            let else_condition = SymBoolExpr::and(cx, vec![not_condition, else_nonzero]);
1821            return Some(SymBoolExpr::or(cx, vec![then_condition, else_condition]));
1822        }
1823        None
1824    }
1825
1826    fn udiv_zero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1827        let numerator = normalize_expr_for_solver(cx, numerator.clone());
1828        let denominator = normalize_expr_for_solver(cx, denominator.clone());
1829        let zero = Self::zero(cx);
1830        let denominator_zero = SymBoolExpr::eq(cx, denominator.clone(), zero);
1831        let below_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Ult, numerator, denominator);
1832        SymBoolExpr::or(cx, vec![denominator_zero, below_denominator])
1833    }
1834
1835    fn udiv_nonzero_condition(cx: &mut SymCx, numerator: &Self, denominator: &Self) -> SymBoolExpr {
1836        let numerator = normalize_expr_for_solver(cx, numerator.clone());
1837        let denominator = normalize_expr_for_solver(cx, denominator.clone());
1838        let zero = Self::zero(cx);
1839        let denominator_nonzero = SymBoolExpr::eq(cx, denominator.clone(), zero).not(cx);
1840        let at_least_denominator = SymBoolExpr::cmp(cx, SymCmpOp::Uge, numerator, denominator);
1841        SymBoolExpr::and(cx, vec![denominator_nonzero, at_least_denominator])
1842    }
1843}
1844
1845impl ConstraintContext {
1846    fn word_bool_always_true(&self, cx: &mut SymCx, expr: &SymExpr) -> bool {
1847        let mut terms = Vec::new();
1848        expr.push_or_terms(&mut terms);
1849        if terms.len() <= 1 {
1850            return false;
1851        }
1852
1853        let bool_terms = terms
1854            .iter()
1855            .filter_map(|term| term.normalized_bool_word_condition(cx))
1856            .collect::<Vec<_>>();
1857        if bool_terms.iter().any(|term| {
1858            let negated = term.clone().not(cx);
1859            bool_terms.contains(&negated)
1860        }) {
1861            // `c || !c => true`.
1862            return true;
1863        }
1864        for zero_term in &bool_terms {
1865            if bool_terms
1866                .iter()
1867                .any(|term| self.checked_mul_guard_for_zero_condition(term, zero_term))
1868            {
1869                // `a == 0 || guarded_mul_div(a) => true`.
1870                return true;
1871            }
1872        }
1873        false
1874    }
1875
1876    /// Records operand facts from independently justified, non-wrapping scaled zero checks.
1877    fn record_scaled_zero_fact(&mut self, cx: &mut SymCx, constraint: &SymBoolExpr) {
1878        let (condition, nonzero) = match constraint.kind() {
1879            SymBoolExprKind::Not(inner) => (inner, true),
1880            _ => (constraint, false),
1881        };
1882        if matches!(condition.kind(), SymBoolExprKind::Cmp(SymCmpOp::Ult, _, _))
1883            && let Some(value) = self.bounded_zero_check_operand(condition).cloned()
1884        {
1885            if nonzero {
1886                self.record_lower_bound(value, U256::ONE);
1887            } else {
1888                self.record_upper_bound(value.clone(), U256::ZERO);
1889                let zero = SymExpr::zero(cx);
1890                let exact = SymBoolExpr::eq(cx, value, zero);
1891                self.record_exact_value_constraint(&exact);
1892            }
1893        }
1894    }
1895
1896    /// Recognizes zero checks exposed by normalizing a scaled balance division.
1897    fn bounded_zero_check_operand<'a>(&self, expr: &'a SymBoolExpr) -> Option<&'a SymExpr> {
1898        if let Some(value) = expr.zero_check_operand() {
1899            return Some(value);
1900        }
1901        let SymBoolExprKind::Cmp(SymCmpOp::Ult, product, limit) = expr.kind() else {
1902            return None;
1903        };
1904        let (value, scale) = Self::constant_mul_operands(product)?;
1905        // x * scale < scale iff x == 0, but only for a positive scale and no wrap.
1906        if scale.is_zero() || limit.as_const() != Some(scale) {
1907            return None;
1908        }
1909        self.max_scaled_product(value, scale)?;
1910        Some(value)
1911    }
1912
1913    /// Checks a zero predicate against the actual divisor of a multiplication guard.
1914    fn zero_check_for_operand(&self, condition: &SymBoolExpr, operand: &SymExpr) -> bool {
1915        if self.bounded_zero_check_operand(condition) == Some(operand) {
1916            return true;
1917        }
1918        // For a positive constant d, n / d == 0 iff n < d. Normalization
1919        // exposes this comparison before the Solidity multiplication guard.
1920        if let Some((numerator, denominator)) = operand.udiv_operands()
1921            && denominator.as_const().is_some_and(|d| !d.is_zero())
1922            && let SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) = condition.kind()
1923        {
1924            return left == numerator && right == denominator;
1925        }
1926        false
1927    }
1928
1929    fn checked_mul_guard_for_zero_condition(
1930        &self,
1931        expr: &SymBoolExpr,
1932        zero_condition: &SymBoolExpr,
1933    ) -> bool {
1934        let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = expr.kind() else {
1935            return false;
1936        };
1937        [(left, right), (right, left)].into_iter().any(|(quotient, expected)| {
1938            matches!(quotient.kind(), SymExprKind::Ite(_, _, _))
1939                && self
1940                    .checked_quotient_factors(quotient, expected, Some(zero_condition))
1941                    .is_some_and(|(left, right)| self.mul_cannot_overflow_256(left, right))
1942        })
1943    }
1944
1945    /// Matches the quotient side shared by multiplication guard proofs and retained guard facts.
1946    fn checked_quotient_factors<'a>(
1947        &self,
1948        quotient: &'a SymExpr,
1949        expected: &SymExpr,
1950        zero_condition: Option<&SymBoolExpr>,
1951    ) -> Option<(&'a SymExpr, &'a SymExpr)> {
1952        let (quotient, branch_condition) =
1953            if let SymExprKind::Ite(condition, zero, quotient) = quotient.kind() {
1954                if zero.as_const() != Some(U256::ZERO) || zero_condition.is_none() {
1955                    return None;
1956                }
1957                (quotient, Some(condition))
1958            } else {
1959                (quotient, None)
1960            };
1961        let (divisor, other) = Self::mul_div_identity_operands(quotient, expected)?;
1962        (zero_condition.is_none_or(|condition| self.zero_check_for_operand(condition, divisor))
1963            && branch_condition
1964                .is_none_or(|condition| self.zero_check_for_operand(condition, divisor)))
1965        .then_some((divisor, other))
1966    }
1967
1968    /// Learns multiplication safety from a retained successful Solidity overflow check.
1969    fn record_non_wrapping_product(&mut self, cx: &mut SymCx, constraint: &SymBoolExpr) -> bool {
1970        let fact = bitwise_bool_word_fact(cx, constraint).unwrap_or_else(|| constraint.clone());
1971        let factors = if let Some(factors) = self.checked_product_factors(&fact, None) {
1972            Some(factors)
1973        } else if let SymBoolExprKind::Not(inner) = fact.kind()
1974            && let SymBoolExprKind::And(terms) = inner.kind()
1975            && terms.len() == 2
1976        {
1977            // Only the exact two-way disjunction is a multiplication guard. An additional
1978            // alternative would let this predicate hold even when the product wraps.
1979            let first = terms[0].clone().not(cx);
1980            let second = terms[1].clone().not(cx);
1981            self.checked_product_factors(&second, Some(&first))
1982                .or_else(|| self.checked_product_factors(&first, Some(&second)))
1983        } else {
1984            None
1985        };
1986        if let Some((left, right)) = factors {
1987            // A successful product fits in one word. A positive lower bound on either
1988            // factor therefore bounds the other, even if it started as a full-width word.
1989            // The supporting guard remains in the conjunction; it cannot prove itself.
1990            let mut changed = false;
1991            for (value, factor) in [(&left, &right), (&right, &left)] {
1992                if let Some(range) = self.interval(factor)
1993                    && !range.min.is_zero()
1994                {
1995                    changed |= self.record_upper_bound(value.clone(), U256::MAX / range.min);
1996                }
1997            }
1998            self.non_wrapping_products.insert((left, right)) || changed
1999        } else {
2000            false
2001        }
2002    }
2003
2004    fn checked_product_factors(
2005        &self,
2006        predicate: &SymBoolExpr,
2007        zero_condition: Option<&SymBoolExpr>,
2008    ) -> Option<(SymExpr, SymExpr)> {
2009        let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = predicate.kind() else {
2010            return None;
2011        };
2012        for (quotient, expected) in [(left, right), (right, left)] {
2013            if let Some((divisor, other)) =
2014                self.checked_quotient_factors(quotient, expected, zero_condition)
2015            {
2016                // For divisor > 0, (divisor * other mod 2^256) / divisor == other
2017                // implies the true product fits. For divisor == 0 the product is zero.
2018                return Some((divisor.clone(), other.clone()));
2019            }
2020        }
2021        None
2022    }
2023
2024    fn has_non_wrapping_product(&self, left: &SymExpr, right: &SymExpr) -> bool {
2025        self.non_wrapping_products.contains(&(left.clone(), right.clone()))
2026            || self.non_wrapping_products.contains(&(right.clone(), left.clone()))
2027    }
2028
2029    pub(super) fn mul_cannot_overflow_256(&self, left: &SymExpr, right: &SymExpr) -> bool {
2030        if self.has_non_wrapping_product(left, right) {
2031            return true;
2032        }
2033        let mut intervals = HashMap::default();
2034        let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
2035        if self
2036            .interval_cached(left, &mut intervals, &mut remaining)
2037            .zip(self.interval_cached(right, &mut intervals, &mut remaining))
2038            .is_some_and(|(left, right)| left.max.checked_mul(right.max).is_some())
2039        {
2040            return true;
2041        }
2042
2043        let mut bit_widths = HashMap::default();
2044        let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
2045        self.unsigned_bits_cached(left, &mut bit_widths, &mut remaining)
2046            .zip(self.unsigned_bits_cached(right, &mut bit_widths, &mut remaining))
2047            .is_some_and(|(left, right)| left.saturating_add(right) <= 256)
2048    }
2049
2050    pub(super) fn unsigned_bits(&self, expr: &SymExpr) -> usize {
2051        let mut bit_widths = HashMap::default();
2052        let mut remaining = MAX_LOCAL_ANALYSIS_NODES;
2053        self.unsigned_bits_cached(expr, &mut bit_widths, &mut remaining).unwrap_or(256)
2054    }
2055
2056    fn unsigned_bits_cached(
2057        &self,
2058        expr: &SymExpr,
2059        bit_widths: &mut HashMap<SymExpr, usize>,
2060        remaining: &mut usize,
2061    ) -> Option<usize> {
2062        if let Some(bits) = bit_widths.get(expr) {
2063            return Some(*bits);
2064        }
2065        if *remaining == 0 {
2066            return None;
2067        }
2068        *remaining -= 1;
2069
2070        let bits = match expr.kind() {
2071            SymExprKind::Const(value) => value.bit_len().max(1),
2072            SymExprKind::Var(_)
2073            | SymExprKind::GasLeft(_)
2074            | SymExprKind::Keccak { .. }
2075            | SymExprKind::Hash { .. }
2076            | SymExprKind::Not(_) => 256,
2077            SymExprKind::BinOp(SymBinOp::And, left, right) => {
2078                if let Some(mask) = right.as_const() {
2079                    self.unsigned_bits_cached(left, bit_widths, remaining)?.min(mask.bit_len())
2080                } else {
2081                    256
2082                }
2083            }
2084            SymExprKind::BinOp(SymBinOp::Add, left, right) => self
2085                .unsigned_bits_cached(left, bit_widths, remaining)?
2086                .max(self.unsigned_bits_cached(right, bit_widths, remaining)?)
2087                .saturating_add(1)
2088                .min(256),
2089            SymExprKind::BinOp(SymBinOp::Mul, left, right) => self
2090                .unsigned_bits_cached(left, bit_widths, remaining)?
2091                .saturating_add(self.unsigned_bits_cached(right, bit_widths, remaining)?)
2092                .min(256),
2093            SymExprKind::BinOp(SymBinOp::UDiv, left, _) => {
2094                self.unsigned_bits_cached(left, bit_widths, remaining)?
2095            }
2096            SymExprKind::Ite(_, left, right) => self
2097                .unsigned_bits_cached(left, bit_widths, remaining)?
2098                .max(self.unsigned_bits_cached(right, bit_widths, remaining)?),
2099            _ => 256,
2100        };
2101
2102        let bits =
2103            self.upper_bound(expr).map(|bound| bits.min(bound.bit_len().max(1))).unwrap_or(bits);
2104        bit_widths.insert(expr.clone(), bits);
2105        Some(bits)
2106    }
2107}
2108
2109#[cfg(test)]
2110mod tests;