Skip to main content

foundry_evm_symbolic/runtime/expr/
bool.rs

1use super::{hashcons::HashConsed, *};
2use foundry_evm::revm::interpreter::instructions::i256::i256_cmp;
3
4/// Bounds both the number of distinct word nodes inspected by constant-ITE equality expansion and
5/// the unfolded size of the Boolean expression it could produce.
6const MAX_CONSTANT_ITE_EQ_NODES: usize = 128;
7const MAX_CONSTANT_ITE_EQ_UNFOLDED_NODES: usize = 8 * 1024;
8
9#[derive(Clone, PartialEq, Eq, Hash)]
10pub(crate) struct SymBoolExpr {
11    pub(in crate::runtime::expr) kind: HashConsed<SymBoolExprKind>,
12}
13
14impl fmt::Debug for SymBoolExpr {
15    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16        self.kind().fmt(f)
17    }
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Hash)]
21pub(in crate::runtime) enum SymBoolExprKind {
22    Const(bool),
23    Not(SymBoolExpr),
24    And(Arc<[SymBoolExpr]>),
25    Cmp(SymCmpOp, SymExpr, SymExpr),
26}
27
28impl SymBoolExpr {
29    #[inline]
30    pub(in crate::runtime) fn stable_hash_cmp(&self, other: &Self) -> std::cmp::Ordering {
31        self.kind.stable_hash_cmp(&other.kind)
32    }
33
34    pub(in crate::runtime) fn kind(&self) -> &SymBoolExprKind {
35        self.kind.value()
36    }
37
38    pub(in crate::runtime) fn from_kind(cx: &mut SymCx, kind: SymBoolExprKind) -> Self {
39        cx.mk_bool_kind(kind)
40    }
41
42    pub(crate) fn constant(cx: &mut SymCx, value: bool) -> Self {
43        cx.cached_bool(value)
44    }
45
46    pub(crate) fn cmp_word_const(
47        cx: &mut SymCx,
48        op: SymCmpOp,
49        word: &SymExpr,
50        value: U256,
51    ) -> Self {
52        if let Some(word) = word.as_const() {
53            Self::constant(cx, op.eval(word, value))
54        } else {
55            let value = SymExpr::constant(cx, value);
56            Self::cmp_word_expr(cx, op, word, value)
57        }
58    }
59
60    pub(crate) fn eq_word_const(cx: &mut SymCx, word: &SymExpr, value: U256) -> Self {
61        if let Some(word) = word.as_const() {
62            Self::constant(cx, word == value)
63        } else {
64            let value = SymExpr::constant(cx, value);
65            Self::eq(cx, word.clone(), value)
66        }
67    }
68
69    pub(crate) fn eq(cx: &mut SymCx, left: SymExpr, right: SymExpr) -> Self {
70        Self::cmp(cx, SymCmpOp::Eq, left, right)
71    }
72
73    pub(crate) fn cmp(cx: &mut SymCx, op: SymCmpOp, left: SymExpr, right: SymExpr) -> Self {
74        if let (
75            SymExprKind::Ite(left_condition, left_then, left_else),
76            SymExprKind::Ite(right_condition, right_then, right_else),
77        ) = (left.kind(), right.kind())
78            && left_condition == right_condition
79            && let (Some(left_then), Some(left_else), Some(right_then), Some(right_else)) = (
80                left_then.as_const(),
81                left_else.as_const(),
82                right_then.as_const(),
83                right_else.as_const(),
84            )
85        {
86            // Compare aligned constant-arm ITEs pointwise without expanding either expression.
87            return match (op.eval(left_then, right_then), op.eval(left_else, right_else)) {
88                (true, true) => Self::constant(cx, true),
89                (false, false) => Self::constant(cx, false),
90                (true, false) => left_condition.clone(),
91                (false, true) => Self::not_bool(cx, left_condition.clone()),
92            };
93        }
94
95        match op {
96            SymCmpOp::Eq => {
97                if let Some(condition) = Self::ite_eq_arm(cx, &left, &right)
98                    .or_else(|| Self::ite_eq_arm(cx, &right, &left))
99                {
100                    return condition;
101                }
102                match (left.kind(), right.kind()) {
103                    // `a == a => true`.
104                    _ if left == right => Self::constant(cx, true),
105                    (SymExprKind::Const(left), SymExprKind::Const(right)) => {
106                        // `const == const => const`.
107                        Self::constant(cx, left == right)
108                    }
109                    (_, SymExprKind::Const(right_value)) => {
110                        if let Some(condition) = Self::bool_word_eq_const(cx, &left, *right_value) {
111                            return condition;
112                        }
113                        if let Some(left_value) = left.known_word() {
114                            // `known(a) == const => const`.
115                            return Self::constant(cx, left_value == *right_value);
116                        }
117                        // `a == b => ordered(a, b)`.
118                        let (left, right) = SymExpr::ordered_commutative_operands(left, right);
119                        Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right))
120                    }
121                    (SymExprKind::Const(left_value), _) => {
122                        if let Some(condition) = Self::bool_word_eq_const(cx, &right, *left_value) {
123                            return condition;
124                        }
125                        if let Some(right_value) = right.known_word() {
126                            // `const == known(a) => const`.
127                            return Self::constant(cx, *left_value == right_value);
128                        }
129                        // `a == b => ordered(a, b)`.
130                        let (left, right) = SymExpr::ordered_commutative_operands(left, right);
131                        Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right))
132                    }
133                    (
134                        SymExprKind::Keccak { len: left_len, bytes: left_bytes, .. },
135                        SymExprKind::Keccak { len: right_len, bytes: right_bytes, .. },
136                    ) if left_bytes.len() == right_bytes.len() => {
137                        // `keccak(a) == keccak(b) => len(a) == len(b) && bytes(a) == bytes(b)`.
138                        let mut conditions =
139                            vec![Self::eq(cx, left_len.clone(), right_len.clone())];
140                        conditions.extend(
141                            left_bytes
142                                .iter()
143                                .cloned()
144                                .zip(right_bytes.iter().cloned())
145                                .map(|(left, right)| Self::eq(cx, left, right)),
146                        );
147                        Self::and(cx, conditions)
148                    }
149                    (
150                        SymExprKind::Hash { algorithm: left_algorithm, bytes: left_bytes, .. },
151                        SymExprKind::Hash {
152                            algorithm: right_algorithm, bytes: right_bytes, ..
153                        },
154                    ) if left_algorithm == right_algorithm
155                        && left_bytes.len() == right_bytes.len() =>
156                    {
157                        // `hash(a) == hash(b) => bytes(a) == bytes(b)`.
158                        let conditions = left_bytes
159                            .iter()
160                            .cloned()
161                            .zip(right_bytes.iter().cloned())
162                            .map(|(left, right)| Self::eq(cx, left, right))
163                            .collect();
164                        Self::and(cx, conditions)
165                    }
166                    _ => {
167                        // `a == b => ordered(a, b)`.
168                        let (left, right) = SymExpr::ordered_commutative_operands(left, right);
169                        Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right))
170                    }
171                }
172            }
173            SymCmpOp::Ult => match (left.kind(), right.kind()) {
174                // `a < a => false`.
175                _ if left == right => Self::constant(cx, false),
176                (SymExprKind::Const(left), SymExprKind::Const(right)) => {
177                    // `const < const => const`.
178                    Self::constant(cx, op.eval(*left, *right))
179                }
180                // `a < 0 => false`.
181                (_, SymExprKind::Const(value)) if value.is_zero() => Self::constant(cx, false),
182                // `MAX < a => false`.
183                (SymExprKind::Const(value), _) if *value == U256::MAX => Self::constant(cx, false),
184                // `a < a & low_mask => false`.
185                _ if low_masked_source_any(&right) == Some(&left) => Self::constant(cx, false),
186                _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
187            },
188            SymCmpOp::Ugt => match (left.kind(), right.kind()) {
189                // `a > a => false`.
190                _ if left == right => Self::constant(cx, false),
191                (SymExprKind::Const(left), SymExprKind::Const(right)) => {
192                    // `const > const => const`.
193                    Self::constant(cx, op.eval(*left, *right))
194                }
195                // `0 > a => false`.
196                (SymExprKind::Const(value), _) if value.is_zero() => Self::constant(cx, false),
197                // `a > MAX => false`.
198                (_, SymExprKind::Const(value)) if *value == U256::MAX => Self::constant(cx, false),
199                // `a & low_mask > a => false`.
200                _ if low_masked_source_any(&left) == Some(&right) => Self::constant(cx, false),
201                _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
202            },
203            SymCmpOp::Ule => match (left.kind(), right.kind()) {
204                // `a <= a => true`.
205                _ if left == right => Self::constant(cx, true),
206                (SymExprKind::Const(left), SymExprKind::Const(right)) => {
207                    // `const <= const => const`.
208                    Self::constant(cx, op.eval(*left, *right))
209                }
210                // `0 <= a => true`.
211                (SymExprKind::Const(value), _) if value.is_zero() => Self::constant(cx, true),
212                // `a <= MAX => true`.
213                (_, SymExprKind::Const(value)) if *value == U256::MAX => Self::constant(cx, true),
214                // `a & low_mask <= a => true`.
215                _ if low_masked_source_any(&left) == Some(&right) => Self::constant(cx, true),
216                _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
217            },
218            SymCmpOp::Uge => match (left.kind(), right.kind()) {
219                // `a >= a => true`.
220                _ if left == right => Self::constant(cx, true),
221                (SymExprKind::Const(left), SymExprKind::Const(right)) => {
222                    // `const >= const => const`.
223                    Self::constant(cx, op.eval(*left, *right))
224                }
225                // `a >= 0 => true`.
226                (_, SymExprKind::Const(value)) if value.is_zero() => Self::constant(cx, true),
227                // `MAX >= a => true`.
228                (SymExprKind::Const(value), _) if *value == U256::MAX => Self::constant(cx, true),
229                // `a >= a & low_mask => true`.
230                _ if low_masked_source_any(&right) == Some(&left) => Self::constant(cx, true),
231                _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
232            },
233            SymCmpOp::Slt | SymCmpOp::Sgt => match (left.kind(), right.kind()) {
234                // `a <s a => false`, `a >s a => false`.
235                _ if left == right => Self::constant(cx, false),
236                (SymExprKind::Const(left), SymExprKind::Const(right)) => {
237                    // `const <s const => const`.
238                    Self::constant(cx, op.eval(*left, *right))
239                }
240                _ => Self::from_kind(cx, SymBoolExprKind::Cmp(op, left, right)),
241            },
242        }
243    }
244
245    pub(crate) fn and(cx: &mut SymCx, values: Vec<Self>) -> Self {
246        let mut out = Vec::new();
247        for value in values {
248            match value.kind() {
249                // `true && a => a`.
250                SymBoolExprKind::Const(true) => {}
251                // `false && a => false`.
252                SymBoolExprKind::Const(false) => return Self::constant(cx, false),
253                // `(a && b) && c => a && b && c`.
254                SymBoolExprKind::And(values) => out.extend(values.iter().cloned()),
255                _ => out.push(value),
256            }
257        }
258        if out.is_empty() {
259            // `and() => true`.
260            Self::constant(cx, true)
261        } else if out.len() == 1 {
262            // `and(a) => a`.
263            out.pop().expect("single item exists")
264        } else {
265            Self::from_kind(cx, SymBoolExprKind::And(out.into()))
266        }
267    }
268
269    pub(crate) fn or(cx: &mut SymCx, values: Vec<Self>) -> Self {
270        let mut out = Vec::new();
271        for value in values {
272            match value.kind() {
273                // `false || a => a`.
274                SymBoolExprKind::Const(false) => {}
275                // `true || a => true`.
276                SymBoolExprKind::Const(true) => return Self::constant(cx, true),
277                _ => out.push(value),
278            }
279        }
280        if out.is_empty() {
281            // `or() => false`.
282            Self::constant(cx, false)
283        } else if out.len() == 1 {
284            // `or(a) => a`.
285            out.pop().expect("single item exists")
286        } else {
287            // `a || b => !(!a && !b)`.
288            let values = out.into_iter().map(|value| Self::not_bool(cx, value)).collect();
289            let and = Self::and(cx, values);
290            Self::not_bool(cx, and)
291        }
292    }
293
294    pub(crate) fn not_bool(cx: &mut SymCx, value: Self) -> Self {
295        match value.kind() {
296            // `!const => const`.
297            SymBoolExprKind::Const(value) => Self::constant(cx, !*value),
298            // `!!a => a`.
299            SymBoolExprKind::Not(value) => value.clone(),
300            _ => Self::from_kind(cx, SymBoolExprKind::Not(value)),
301        }
302    }
303
304    fn bool_word_eq_const(cx: &mut SymCx, word: &SymExpr, value: U256) -> Option<Self> {
305        let SymExprKind::Ite(condition, then_expr, else_expr) = word.kind() else { return None };
306        match (then_expr.as_const(), else_expr.as_const()) {
307            (Some(then_value), Some(else_value))
308                if then_value == U256::ONE && else_value.is_zero() =>
309            {
310                Some(if value.is_zero() {
311                    Self::not_bool(cx, condition.clone())
312                } else if value == U256::ONE {
313                    condition.clone()
314                } else {
315                    Self::constant(cx, false)
316                })
317            }
318            (Some(then_value), Some(else_value))
319                if then_value.is_zero() && else_value == U256::ONE =>
320            {
321                Some(if value.is_zero() {
322                    condition.clone()
323                } else if value == U256::ONE {
324                    Self::not_bool(cx, condition.clone())
325                } else {
326                    Self::constant(cx, false)
327                })
328            }
329            _ => None,
330        }
331    }
332
333    fn ite_eq_arm(cx: &mut SymCx, conditional: &SymExpr, expected: &SymExpr) -> Option<Self> {
334        let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
335            return None;
336        };
337        if then_expr == expected {
338            let else_matches = Self::eq(cx, else_expr.clone(), expected.clone());
339            return Some(Self::or(cx, vec![condition.clone(), else_matches]));
340        }
341        if else_expr == expected {
342            let then_matches = Self::eq(cx, then_expr.clone(), expected.clone());
343            let condition = Self::not_bool(cx, condition.clone());
344            return Some(Self::or(cx, vec![condition, then_matches]));
345        }
346        if let Some(expected_value) = expected.as_const() {
347            let mut cost_cache = HashMap::default();
348            let mut remaining = MAX_CONSTANT_ITE_EQ_NODES;
349            if Self::constant_ite_eq_unfolded_nodes(conditional, &mut cost_cache, &mut remaining)
350                .is_none()
351            {
352                let (left, right) =
353                    SymExpr::ordered_commutative_operands(conditional.clone(), expected.clone());
354                return Some(Self::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)));
355            }
356
357            let mut cache = HashMap::default();
358            let mut remaining = MAX_CONSTANT_ITE_EQ_NODES;
359            return Self::constant_ite_eq(
360                cx,
361                conditional,
362                expected_value,
363                &mut cache,
364                &mut remaining,
365            );
366        }
367        None
368    }
369
370    /// Computes a conservative upper bound for the unfolded Boolean result without interning it.
371    ///
372    /// Cached child costs are deliberately added again at each use. This keeps construction of a
373    /// small shared word DAG from creating a Boolean DAG that becomes exponential when a later
374    /// consumer traverses occurrences instead of identities.
375    fn constant_ite_eq_unfolded_nodes(
376        expr: &SymExpr,
377        cache: &mut HashMap<SymExpr, Option<usize>>,
378        remaining: &mut usize,
379    ) -> Option<usize> {
380        if let Some(cached) = cache.get(expr) {
381            return *cached;
382        }
383        if *remaining == 0 {
384            cache.insert(expr.clone(), None);
385            return None;
386        }
387        *remaining -= 1;
388
389        let result = match expr.kind() {
390            SymExprKind::Const(_) => Some(1),
391            SymExprKind::Ite(condition, then_expr, else_expr) => {
392                let then_nodes = Self::constant_ite_eq_unfolded_nodes(then_expr, cache, remaining)?;
393                let else_nodes = Self::constant_ite_eq_unfolded_nodes(else_expr, cache, remaining)?;
394
395                let mut pending = vec![condition];
396                let mut condition_nodes = 0usize;
397                while let Some(condition) = pending.pop() {
398                    condition_nodes = condition_nodes.checked_add(1)?;
399                    if condition_nodes > MAX_CONSTANT_ITE_EQ_UNFOLDED_NODES {
400                        return None;
401                    }
402                    match condition.kind() {
403                        SymBoolExprKind::Not(value) => pending.push(value),
404                        SymBoolExprKind::And(values) => pending.extend(values.iter()),
405                        SymBoolExprKind::Const(_) | SymBoolExprKind::Cmp(_, _, _) => {}
406                    }
407                }
408
409                // Two selected branches and the `or` encoding add at most seven Boolean wrapper
410                // occurrences around both children and two occurrences of the condition.
411                then_nodes
412                    .checked_add(else_nodes)
413                    .and_then(|nodes| nodes.checked_add(2 * condition_nodes))
414                    .and_then(|nodes| nodes.checked_add(7))
415                    .filter(|nodes| *nodes <= MAX_CONSTANT_ITE_EQ_UNFOLDED_NODES)
416            }
417            _ => None,
418        };
419        cache.insert(expr.clone(), result);
420        result
421    }
422
423    fn constant_ite_eq(
424        cx: &mut SymCx,
425        expr: &SymExpr,
426        expected: U256,
427        cache: &mut HashMap<SymExpr, Option<Self>>,
428        remaining: &mut usize,
429    ) -> Option<Self> {
430        if let Some(cached) = cache.get(expr) {
431            return cached.clone();
432        }
433        if *remaining == 0 {
434            cache.insert(expr.clone(), None);
435            return None;
436        }
437        *remaining -= 1;
438
439        let result = match expr.kind() {
440            SymExprKind::Const(value) => Some(Self::constant(cx, *value == expected)),
441            SymExprKind::Ite(condition, then_expr, else_expr) => {
442                let condition = condition.clone();
443                let then_matches = Self::constant_ite_eq(cx, then_expr, expected, cache, remaining);
444                let else_matches = Self::constant_ite_eq(cx, else_expr, expected, cache, remaining);
445                match (then_matches, else_matches) {
446                    (Some(then_matches), Some(else_matches)) => {
447                        let then_selected =
448                            Self::and_ite_branch(cx, condition.clone(), then_matches);
449                        let condition = Self::not_bool(cx, condition);
450                        let else_selected = Self::and_ite_branch(cx, condition, else_matches);
451                        Some(Self::or(cx, vec![then_selected, else_selected]))
452                    }
453                    _ => None,
454                }
455            }
456            _ => None,
457        };
458        cache.insert(expr.clone(), result.clone());
459        result
460    }
461
462    /// Builds one selected ITE branch without flattening a cached child conjunction.
463    ///
464    /// Flattening here makes a linear ITE chain retain conjunctions of lengths `1..n` in the
465    /// per-call cache. Keeping this pair binary preserves the shared DAG and leaves flattening to
466    /// solver constraint normalization, where only the final expression is expanded.
467    fn and_ite_branch(cx: &mut SymCx, condition: Self, branch: Self) -> Self {
468        match (condition.as_const(), branch.as_const()) {
469            (Some(false), _) | (_, Some(false)) => Self::constant(cx, false),
470            (Some(true), _) => branch,
471            (_, Some(true)) => condition,
472            _ if condition == branch => condition,
473            _ => Self::from_kind(cx, SymBoolExprKind::And(vec![condition, branch].into())),
474        }
475    }
476
477    pub(crate) fn as_const(&self) -> Option<bool> {
478        match self.kind() {
479            SymBoolExprKind::Const(value) => Some(*value),
480            _ => None,
481        }
482    }
483
484    pub(in crate::runtime) fn zero_check_operand(&self) -> Option<&SymExpr> {
485        match self.kind() {
486            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right)
487                if right.as_const().is_some_and(|value| value.is_zero()) =>
488            {
489                Some(left)
490            }
491            _ => None,
492        }
493    }
494
495    pub(crate) fn contains_keccak(&self) -> bool {
496        self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Keccak { .. }))
497    }
498
499    pub(crate) fn contains_gasleft(&self) -> bool {
500        self.visit_bool(|expr| expr.is_raw_gasleft())
501    }
502
503    pub(crate) fn contains_udiv(&self) -> bool {
504        self.visit_bool(|expr| expr.contains_udiv())
505    }
506
507    pub(crate) fn implies_unsigned_less_or_equal(
508        &self,
509        expected: bool,
510        left: &SymExpr,
511        right: &SymExpr,
512        remaining: &mut usize,
513    ) -> bool {
514        if left == right {
515            return true;
516        }
517        let Some(next) = remaining.checked_sub(1) else { return false };
518        *remaining = next;
519
520        match self.kind() {
521            SymBoolExprKind::Not(value) => {
522                value.implies_unsigned_less_or_equal(!expected, left, right, remaining)
523            }
524            SymBoolExprKind::And(values) if expected => values
525                .iter()
526                .any(|value| value.implies_unsigned_less_or_equal(true, left, right, remaining)),
527            SymBoolExprKind::Cmp(op, fact_left, fact_right) => match (*op, expected) {
528                (SymCmpOp::Ult | SymCmpOp::Ule, true) => fact_left == left && fact_right == right,
529                (SymCmpOp::Uge | SymCmpOp::Ugt, true) => fact_right == left && fact_left == right,
530                (SymCmpOp::Ult | SymCmpOp::Ule, false) => fact_right == left && fact_left == right,
531                (SymCmpOp::Uge | SymCmpOp::Ugt, false) => fact_left == left && fact_right == right,
532                (SymCmpOp::Eq, true) => {
533                    (fact_left == left && fact_right == right)
534                        || (fact_right == left && fact_left == right)
535                }
536                (SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt, _) => false,
537            },
538            SymBoolExprKind::Const(_) | SymBoolExprKind::And(_) => false,
539        }
540    }
541
542    pub(crate) fn forces_expr_const_with_context(
543        &self,
544        expr: &SymExpr,
545        context: &[Self],
546    ) -> Option<U256> {
547        match self.kind() {
548            SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => match right.kind() {
549                SymExprKind::Const(value) => left.equality_forces_const(*value, expr, context),
550                _ => None,
551            },
552            SymBoolExprKind::Not(value) => match value.kind() {
553                SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => match right.kind() {
554                    SymExprKind::Const(value) if value.is_zero() => {
555                        left.nonzero_forces_const(expr, context)
556                    }
557                    _ => None,
558                },
559                SymBoolExprKind::Not(value) => value.forces_expr_const_with_context(expr, context),
560                _ => None,
561            },
562            SymBoolExprKind::And(values) => {
563                values.iter().find_map(|value| value.forces_expr_const_with_context(expr, context))
564            }
565            _ => None,
566        }
567    }
568
569    pub(crate) fn upper_bound_usize(&self, expr: &SymExpr) -> Option<usize> {
570        match self.kind() {
571            SymBoolExprKind::Const(_) | SymBoolExprKind::Not(_) => None,
572            SymBoolExprKind::And(values) => {
573                let mut bound: Option<usize> = None;
574                for value in values.iter() {
575                    if let Some(candidate) = value.upper_bound_usize(expr) {
576                        bound = Some(bound.map_or(candidate, |bound| bound.min(candidate)));
577                    }
578                }
579                bound
580            }
581            SymBoolExprKind::Cmp(op, left, right) => {
582                if *op == SymCmpOp::Eq {
583                    return match (left == expr, right == expr) {
584                        (true, _) => right.as_const().and_then(|value| usize::try_from(value).ok()),
585                        (_, true) => left.as_const().and_then(|value| usize::try_from(value).ok()),
586                        _ => None,
587                    };
588                }
589                if left == expr {
590                    match *op {
591                        SymCmpOp::Ult => right
592                            .as_const()
593                            .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::ONE))
594                            .and_then(|value| usize::try_from(value).ok()),
595                        SymCmpOp::Ule => {
596                            right.as_const().and_then(|value| usize::try_from(value).ok())
597                        }
598                        _ => None,
599                    }
600                } else if right == expr {
601                    match *op {
602                        SymCmpOp::Ugt => left
603                            .as_const()
604                            .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::ONE))
605                            .and_then(|value| usize::try_from(value).ok()),
606                        SymCmpOp::Uge => {
607                            left.as_const().and_then(|value| usize::try_from(value).ok())
608                        }
609                        _ => None,
610                    }
611                } else {
612                    None
613                }
614            }
615        }
616    }
617
618    pub(crate) fn eval_model<M: SymbolicModelLookup + ?Sized>(
619        &self,
620        model: &M,
621    ) -> Result<bool, SymbolicError> {
622        ModelEvaluator::new(model).eval_bool(self)
623    }
624
625    pub(crate) fn eval_model_if_complete<M: SymbolicModelLookup + ?Sized>(
626        &self,
627        model: &M,
628    ) -> Result<Option<bool>, SymbolicError> {
629        let mut vars = SymbolicVars::default();
630        self.collect_eval_vars(&mut vars);
631        if vars.iter().copied().all(|var| model.contains_name(var)) {
632            self.eval_model(model).map(Some)
633        } else {
634            Ok(None)
635        }
636    }
637
638    /// Visits all word expressions contained in this boolean expression.
639    pub(crate) fn visit_exprs<B>(
640        &self,
641        visitor: &mut impl FnMut(&SymExpr) -> ControlFlow<B>,
642    ) -> ControlFlow<B> {
643        match self.kind() {
644            SymBoolExprKind::Const(_) => {}
645            SymBoolExprKind::Not(value) => value.visit_exprs(visitor)?,
646            SymBoolExprKind::And(values) => {
647                for value in values.iter() {
648                    value.visit_exprs(visitor)?;
649                }
650            }
651            SymBoolExprKind::Cmp(_, left, right) => {
652                left.visit(visitor)?;
653                right.visit(visitor)?;
654            }
655        }
656        ControlFlow::Continue(())
657    }
658
659    /// Returns whether any word node satisfies `visitor`, visiting each distinct node once.
660    pub(crate) fn visit_bool(&self, visitor: impl FnMut(&SymExpr) -> bool) -> bool {
661        visit_unique(vec![self.clone()], Vec::new(), visitor)
662    }
663
664    /// Rewrites each distinct Boolean node once in bottom-up order.
665    ///
666    /// The folder must return the same result for every occurrence of one hash-consed node.
667    pub(crate) fn fold(
668        &self,
669        cx: &mut SymCx,
670        folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
671    ) -> Self {
672        let mut folded = HashMap::default();
673        self.fold_cached(cx, folder, &mut folded)
674    }
675
676    fn fold_cached<'a>(
677        &'a self,
678        cx: &mut SymCx,
679        folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
680        folded: &mut HashMap<&'a Self, Self>,
681    ) -> Self {
682        if let Some(expr) = folded.get(self) {
683            return expr.clone();
684        }
685
686        let expr = match self.kind() {
687            SymBoolExprKind::Const(_) => self.clone(),
688            SymBoolExprKind::Not(value) => {
689                let value = value.fold_cached(cx, folder, folded);
690                Self::not_bool(cx, value)
691            }
692            SymBoolExprKind::And(values) => {
693                let values =
694                    values.iter().map(|value| value.fold_cached(cx, folder, folded)).collect();
695                Self::and(cx, values)
696            }
697            SymBoolExprKind::Cmp(op, left, right) => {
698                Self::cmp_word_expr(cx, *op, left, right.clone())
699            }
700        };
701        let expr = folder(cx, expr);
702        folded.insert(self, expr.clone());
703        expr
704    }
705
706    /// Rewrites each distinct word node once while rebuilding this Boolean expression.
707    ///
708    /// The folder must return the same result for every occurrence of one hash-consed node.
709    pub(crate) fn fold_exprs(
710        &self,
711        cx: &mut SymCx,
712        folder: &mut impl FnMut(&mut SymCx, SymExpr) -> SymExpr,
713    ) -> Self {
714        let mut folded = ExpressionFoldCache::default();
715        self.fold_exprs_cached(cx, folder, &mut folded)
716    }
717
718    pub(in crate::runtime::expr) fn fold_exprs_cached<'a>(
719        &'a self,
720        cx: &mut SymCx,
721        folder: &mut impl FnMut(&mut SymCx, SymExpr) -> SymExpr,
722        folded: &mut ExpressionFoldCache<'a>,
723    ) -> Self {
724        if let Some(expr) = folded.bools.get(self) {
725            return expr.clone();
726        }
727
728        let expr = match self.kind() {
729            SymBoolExprKind::Const(_) => self.clone(),
730            SymBoolExprKind::Not(value) => {
731                let value = value.fold_exprs_cached(cx, folder, folded);
732                Self::not_bool(cx, value)
733            }
734            SymBoolExprKind::And(values) => {
735                let values = values
736                    .iter()
737                    .map(|value| value.fold_exprs_cached(cx, folder, folded))
738                    .collect();
739                Self::and(cx, values)
740            }
741            SymBoolExprKind::Cmp(op, left, right) => {
742                let left = left.fold_cached(cx, folder, folded);
743                let right = right.fold_cached(cx, folder, folded);
744                Self::cmp(cx, *op, left, right)
745            }
746        };
747        folded.bools.insert(self, expr.clone());
748        expr
749    }
750
751    pub(crate) fn cmp_word_expr(
752        cx: &mut SymCx,
753        op: SymCmpOp,
754        word: &SymExpr,
755        expr: SymExpr,
756    ) -> Self {
757        Self::cmp(cx, op, word.clone(), expr)
758    }
759
760    pub(crate) fn not(self, cx: &mut SymCx) -> Self {
761        Self::not_bool(cx, self)
762    }
763
764    pub(crate) fn collect_vars(&self, vars: &mut SymbolicVars) {
765        let _ = self.visit_exprs(&mut |expr| {
766            if let Some(var) = expr.kind().get_var() {
767                vars.insert(var);
768            }
769            ControlFlow::<()>::Continue(())
770        });
771    }
772
773    pub(crate) fn collect_eval_vars(&self, vars: &mut SymbolicVars) {
774        let _ = self.visit_exprs(&mut |expr| {
775            if let Some(var) = expr.kind().get_eval_var() {
776                vars.insert(var);
777            }
778            ControlFlow::<()>::Continue(())
779        });
780    }
781
782    pub(crate) fn smt(&self, cx: &SymCx) -> String {
783        let mut smt = String::new();
784        self.write_smt(cx, &mut smt);
785        smt
786    }
787
788    pub(in crate::runtime::expr) fn write_smt(&self, cx: &SymCx, out: &mut String) {
789        match self.kind() {
790            SymBoolExprKind::Const(value) => out.push_str(if *value { "true" } else { "false" }),
791            SymBoolExprKind::Not(value) => {
792                out.push_str("(not ");
793                value.write_smt(cx, out);
794                out.push(')');
795            }
796            SymBoolExprKind::And(values) => {
797                out.push_str("(and");
798                for value in values.iter() {
799                    out.push(' ');
800                    value.write_smt(cx, out);
801                }
802                out.push(')');
803            }
804            SymBoolExprKind::Cmp(op, left, right) => {
805                let _ = write!(out, "({} ", op.smt());
806                left.write_smt(cx, out);
807                out.push(' ');
808                right.write_smt(cx, out);
809                out.push(')');
810            }
811        }
812    }
813}
814
815#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
816pub(crate) enum SymCmpOp {
817    Eq,
818    Ult,
819    Ugt,
820    Ule,
821    Uge,
822    Slt,
823    Sgt,
824}
825
826impl SymCmpOp {
827    pub(crate) const fn smt(self) -> &'static str {
828        match self {
829            Self::Eq => "=",
830            Self::Ult => "bvult",
831            Self::Ugt => "bvugt",
832            Self::Ule => "bvule",
833            Self::Uge => "bvuge",
834            Self::Slt => "bvslt",
835            Self::Sgt => "bvsgt",
836        }
837    }
838
839    pub(crate) fn eval(self, left: U256, right: U256) -> bool {
840        match self {
841            Self::Eq => left == right,
842            Self::Ult => left < right,
843            Self::Ugt => left > right,
844            Self::Ule => left <= right,
845            Self::Uge => left >= right,
846            Self::Slt => i256_cmp(&left, &right).is_lt(),
847            Self::Sgt => i256_cmp(&left, &right).is_gt(),
848        }
849    }
850}
851
852/// Returns whether any word node reachable from the pending expressions satisfies `visitor`.
853///
854/// Expressions are hash-consed DAGs, so each distinct node is visited once. A tree walk would
855/// revisit shared subexpressions, which grows exponentially along chains such as nested hashes.
856pub(crate) fn visit_unique(
857    mut pending_bools: Vec<SymBoolExpr>,
858    mut pending_words: Vec<SymExpr>,
859    mut visitor: impl FnMut(&SymExpr) -> bool,
860) -> bool {
861    let mut visited_bools = HashSet::<SymBoolExpr>::default();
862    let mut visited_words = HashSet::<SymExpr>::default();
863
864    loop {
865        if let Some(expr) = pending_bools.pop() {
866            if !visited_bools.insert(expr.clone()) {
867                continue;
868            }
869            match expr.kind() {
870                SymBoolExprKind::Const(_) => {}
871                SymBoolExprKind::Not(value) => pending_bools.push(value.clone()),
872                SymBoolExprKind::And(values) => {
873                    pending_bools.extend(values.iter().cloned());
874                }
875                SymBoolExprKind::Cmp(_, left, right) => {
876                    pending_words.push(left.clone());
877                    pending_words.push(right.clone());
878                }
879            }
880            continue;
881        }
882
883        let Some(expr) = pending_words.pop() else { return false };
884        if !visited_words.insert(expr.clone()) {
885            continue;
886        }
887        if visitor(&expr) {
888            return true;
889        }
890        match expr.kind() {
891            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => {}
892            SymExprKind::Keccak { len, bytes, .. } => {
893                pending_words.push(len.clone());
894                pending_words.extend(bytes.iter().cloned());
895            }
896            SymExprKind::Hash { bytes, .. } => {
897                pending_words.extend(bytes.iter().cloned());
898            }
899            SymExprKind::Not(value) => pending_words.push(value.clone()),
900            SymExprKind::BinOp(_, left, right) => {
901                pending_words.push(left.clone());
902                pending_words.push(right.clone());
903            }
904            SymExprKind::TernOp(_, left, right, modulus) => {
905                pending_words.push(left.clone());
906                pending_words.push(right.clone());
907                pending_words.push(modulus.clone());
908            }
909            SymExprKind::Ite(condition, left, right) => {
910                pending_bools.push(condition.clone());
911                pending_words.push(left.clone());
912                pending_words.push(right.clone());
913            }
914        }
915    }
916}