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