1use super::{hashcons::HashConsed, *};
2use foundry_evm::revm::interpreter::instructions::i256::i256_cmp;
3
4const 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 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 _ if left == right => Self::constant(cx, true),
105 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
106 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 return Self::constant(cx, left_value == *right_value);
116 }
117 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 return Self::constant(cx, *left_value == right_value);
128 }
129 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 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 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 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 _ if left == right => Self::constant(cx, false),
176 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
177 Self::constant(cx, op.eval(*left, *right))
179 }
180 (_, SymExprKind::Const(value)) if value.is_zero() => Self::constant(cx, false),
182 (SymExprKind::Const(value), _) if *value == U256::MAX => Self::constant(cx, false),
184 _ 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 _ if left == right => Self::constant(cx, false),
191 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
192 Self::constant(cx, op.eval(*left, *right))
194 }
195 (SymExprKind::Const(value), _) if value.is_zero() => Self::constant(cx, false),
197 (_, SymExprKind::Const(value)) if *value == U256::MAX => Self::constant(cx, false),
199 _ 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 _ if left == right => Self::constant(cx, true),
206 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
207 Self::constant(cx, op.eval(*left, *right))
209 }
210 (SymExprKind::Const(value), _) if value.is_zero() => Self::constant(cx, true),
212 (_, SymExprKind::Const(value)) if *value == U256::MAX => Self::constant(cx, true),
214 _ 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 _ if left == right => Self::constant(cx, true),
221 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
222 Self::constant(cx, op.eval(*left, *right))
224 }
225 (_, SymExprKind::Const(value)) if value.is_zero() => Self::constant(cx, true),
227 (SymExprKind::Const(value), _) if *value == U256::MAX => Self::constant(cx, true),
229 _ 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 _ if left == right => Self::constant(cx, false),
236 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
237 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 SymBoolExprKind::Const(true) => {}
251 SymBoolExprKind::Const(false) => return Self::constant(cx, false),
253 SymBoolExprKind::And(values) => out.extend(values.iter().cloned()),
255 _ => out.push(value),
256 }
257 }
258 if out.is_empty() {
259 Self::constant(cx, true)
261 } else if out.len() == 1 {
262 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 SymBoolExprKind::Const(false) => {}
275 SymBoolExprKind::Const(true) => return Self::constant(cx, true),
277 _ => out.push(value),
278 }
279 }
280 if out.is_empty() {
281 Self::constant(cx, false)
283 } else if out.len() == 1 {
284 out.pop().expect("single item exists")
286 } else {
287 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 SymBoolExprKind::Const(value) => Self::constant(cx, !*value),
298 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 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 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 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 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 pub(crate) fn visit_bool(&self, visitor: impl FnMut(&SymExpr) -> bool) -> bool {
661 visit_unique(vec![self.clone()], Vec::new(), visitor)
662 }
663
664 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 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
852pub(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}