Skip to main content

foundry_evm_symbolic/runtime/expr/
expr.rs

1use super::{hashcons::HashConsed, *};
2use foundry_evm::revm::interpreter::instructions::i256::{i256_div, i256_mod};
3
4// Boolean selector recovery is an optional expression rewrite. Bound it to one word's worth of
5// unique nodes so adversarial expression trees cannot make construction unbounded.
6const MAX_BITWISE_BOOL_WORD_VISITS: usize = 256;
7const MAX_CONSTANT_DIFFERENCE_VISITS: usize = 256;
8
9impl SymExpr {
10    pub(crate) fn select_storage_write(
11        self,
12        cx: &mut SymCx,
13        write_key: Self,
14        write_value: Self,
15        base: Self,
16    ) -> Self {
17        if write_value == base {
18            return base;
19        }
20        let condition = self.storage_key_eq(cx, &write_key);
21        match condition.as_const() {
22            Some(true) => write_value,
23            Some(false) => base,
24            None => Self::ite(cx, condition, write_value, base),
25        }
26    }
27
28    pub(crate) fn storage_key_eq(&self, cx: &mut SymCx, write_key: &Self) -> SymBoolExpr {
29        if let (Some(read_root), Some(write_root)) =
30            (self.storage_mapping_root_slot(cx), write_key.storage_mapping_root_slot(cx))
31            && read_root != write_root
32        {
33            return SymBoolExpr::constant(cx, false);
34        }
35        match (self.storage_layout_key(cx), write_key.storage_layout_key(cx)) {
36            (Some((read_base, read_offset)), Some((write_base, write_offset))) => {
37                let read_base = read_base
38                    .storage_base_eq(cx, &write_base)
39                    .unwrap_or_else(|| SymBoolExpr::eq(cx, read_base, write_base));
40                let read_offset = SymBoolExpr::eq(cx, read_offset, write_offset);
41                SymBoolExpr::and(cx, vec![read_base, read_offset])
42            }
43            (Some(_), None) if write_key.as_const().is_some() => SymBoolExpr::constant(cx, false),
44            (None, Some(_)) if self.as_const().is_some() => SymBoolExpr::constant(cx, false),
45            _ => SymBoolExpr::eq(cx, self.clone(), write_key.clone()),
46        }
47    }
48
49    fn storage_base_eq(&self, cx: &mut SymCx, other: &Self) -> Option<SymBoolExpr> {
50        let read = self.storage_mapping_key(cx)?;
51        let write = other.storage_mapping_key(cx)?;
52
53        let key_eq = storage_mapping_key_eq(cx, &read, &write);
54        let slot_eq = read
55            .slot
56            .storage_base_eq(cx, &write.slot)
57            .unwrap_or_else(|| SymBoolExpr::eq(cx, read.slot, write.slot));
58        Some(SymBoolExpr::and(cx, vec![key_eq, slot_eq]))
59    }
60
61    pub(crate) fn storage_mapping_key(&self, cx: &mut SymCx) -> Option<StorageMappingKey> {
62        let bytes = self.storage_mapping_key_bytes(cx)?;
63        let key_bytes = &bytes[..32];
64        let preserve_key_bytes = (!key_bytes.iter().all(|byte| byte.as_const().is_some())
65            && word_from_extracted_bytes(key_bytes).is_none())
66        .then(|| key_bytes.to_vec());
67        let key = Self::from_bytes(cx, key_bytes.iter().cloned());
68        let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
69        Some(StorageMappingKey { key, key_bytes: preserve_key_bytes, slot })
70    }
71
72    pub(crate) fn storage_mapping_provenance_observed_with(
73        &self,
74        cx: &mut SymCx,
75        mut observed_preimage: impl FnMut(&Self) -> Option<Arc<[Self]>>,
76    ) -> Option<SymbolicMappingProvenance> {
77        let mut current = self.clone();
78        let mut keys = Vec::new();
79        let mut visited = Vec::new();
80        loop {
81            if visited.contains(&current) {
82                return None;
83            }
84            visited.push(current.clone());
85            let bytes = observed_preimage(&current)?;
86            if bytes.len() != 64 {
87                return None;
88            }
89            let key = Self::from_bytes(cx, bytes[..32].iter().cloned());
90            keys.push(key);
91            current = Self::from_bytes(cx, bytes[32..64].iter().cloned());
92            match current.kind() {
93                SymExprKind::Const(root_slot) if observed_preimage(&current).is_none() => {
94                    keys.reverse();
95                    return Some(SymbolicMappingProvenance { root_slot: *root_slot, keys });
96                }
97                SymExprKind::Const(_) | SymExprKind::Keccak { .. } => {}
98                _ => return None,
99            }
100        }
101    }
102
103    fn storage_mapping_root_slot(&self, cx: &mut SymCx) -> Option<U256> {
104        let bytes = self.storage_mapping_key_bytes(cx)?;
105        let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
106        match slot.kind() {
107            SymExprKind::Const(value) if cx.concrete_keccak_preimage(*value).is_some() => {
108                slot.storage_mapping_root_slot(cx)
109            }
110            SymExprKind::Const(slot) => Some(*slot),
111            SymExprKind::Keccak { .. } => slot.storage_mapping_root_slot(cx),
112            _ => None,
113        }
114    }
115
116    fn storage_mapping_key_bytes(&self, cx: &SymCx) -> Option<Arc<[Self]>> {
117        match self.kind() {
118            SymExprKind::Keccak { len, bytes, .. }
119                if len.as_const() == Some(U256::from(64)) && bytes.len() >= 64 =>
120            {
121                Some(bytes.clone())
122            }
123            SymExprKind::Const(hash) => cx.concrete_keccak_preimage(*hash),
124            _ => None,
125        }
126    }
127
128    fn storage_layout_key(&self, cx: &mut SymCx) -> Option<(Self, Self)> {
129        match self.kind() {
130            SymExprKind::Keccak { .. } => Some((self.clone(), Self::zero(cx))),
131            SymExprKind::Const(hash) if cx.concrete_keccak_preimage(*hash).is_some() => {
132                Some((self.clone(), Self::zero(cx)))
133            }
134            SymExprKind::BinOp(SymBinOp::Add, left, right) => {
135                if let Some((base, offset)) = left.storage_layout_key(cx)
136                    && !right.contains_keccak()
137                {
138                    let offset = Self::binop(cx, SymBinOp::Add, offset, right.clone());
139                    return Some((base, offset));
140                }
141                if let Some((base, offset)) = right.storage_layout_key(cx)
142                    && !left.contains_keccak()
143                {
144                    let offset = Self::binop(cx, SymBinOp::Add, offset, left.clone());
145                    return Some((base, offset));
146                }
147                None
148            }
149            _ => None,
150        }
151    }
152}
153
154pub(crate) struct StorageMappingKey {
155    key: SymExpr,
156    key_bytes: Option<Vec<SymExpr>>,
157    slot: SymExpr,
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub(crate) struct SymbolicMappingProvenance {
162    pub(crate) root_slot: U256,
163    pub(crate) keys: Vec<SymExpr>,
164}
165
166fn storage_mapping_key_eq(
167    cx: &mut SymCx,
168    read: &StorageMappingKey,
169    write: &StorageMappingKey,
170) -> SymBoolExpr {
171    if read.key_bytes.is_some() || write.key_bytes.is_some() {
172        let read_owned;
173        let read_bytes = if let Some(bytes) = read.key_bytes.as_deref() {
174            bytes
175        } else {
176            read_owned = read.key.clone().into_byte_exprs(cx);
177            &read_owned
178        };
179        let write_owned;
180        let write_bytes = if let Some(bytes) = write.key_bytes.as_deref() {
181            bytes
182        } else {
183            write_owned = write.key.clone().into_byte_exprs(cx);
184            &write_owned
185        };
186        let byte_equalities = read_bytes
187            .iter()
188            .zip(write_bytes)
189            .map(|(read, write)| {
190                let read = read.byte_term(cx, 31).unwrap_or_else(|| read.clone().low_byte(cx));
191                let write = write.byte_term(cx, 31).unwrap_or_else(|| write.clone().low_byte(cx));
192                SymBoolExpr::eq(cx, read, write)
193            })
194            .collect();
195        SymBoolExpr::and(cx, byte_equalities)
196    } else {
197        SymBoolExpr::eq(cx, read.key.clone(), write.key.clone())
198    }
199}
200
201fn masked_expr_matches(candidate: &SymExprKind, target: &SymExpr) -> Option<U256> {
202    match candidate {
203        SymExprKind::BinOp(SymBinOp::And, left, right) if left == target => right.eval(),
204        SymExprKind::BinOp(SymBinOp::And, left, right) if right == target => left.eval(),
205        _ => None,
206    }
207}
208
209fn context_forces_masked_expr(context: &[SymBoolExpr], target: &SymExpr, mask: U256) -> bool {
210    context.iter().any(|condition| match condition.kind() {
211        SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
212            (left == target && masked_expr_matches(right.kind(), target) == Some(mask))
213                || (right == target && masked_expr_matches(left.kind(), target) == Some(mask))
214        }
215        SymBoolExprKind::And(values) => context_forces_masked_expr(values, target, mask),
216        _ => false,
217    })
218}
219
220pub(crate) fn concrete_expr_bytes(
221    bytes: &[SymExpr],
222    reason: &'static str,
223) -> Result<Vec<u8>, SymbolicError> {
224    bytes
225        .iter()
226        .map(|byte| match byte.as_const() {
227            Some(value) => Ok(value.to::<u8>()),
228            None => Err(SymbolicError::Unsupported(reason)),
229        })
230        .collect()
231}
232
233pub(crate) fn mask_low_bits(mask: U256) -> Option<usize> {
234    let bits = mask.bit_len();
235    (mask == mask_bits(U256::MAX, bits)).then_some(bits)
236}
237
238fn power_of_two_shift(value: U256) -> Option<usize> {
239    if value <= U256::ONE || !value.is_power_of_two() {
240        return None;
241    }
242    Some(value.bit_len() - 1)
243}
244
245pub(in crate::runtime::expr) fn low_masked_source(expr: &SymExpr, bits: usize) -> Option<&SymExpr> {
246    match expr.kind() {
247        // `a & low_mask => a`.
248        SymExprKind::BinOp(SymBinOp::And, left, right)
249            if right.as_const().and_then(mask_low_bits) == Some(bits) =>
250        {
251            Some(left)
252        }
253        _ => None,
254    }
255}
256
257pub(in crate::runtime::expr) fn low_masked_source_any(expr: &SymExpr) -> Option<&SymExpr> {
258    match expr.kind() {
259        // `a & low_mask => a`.
260        SymExprKind::BinOp(SymBinOp::And, left, right)
261            if right.as_const().and_then(mask_low_bits).is_some() =>
262        {
263            Some(left)
264        }
265        _ => None,
266    }
267}
268
269fn word_from_extracted_bytes(bytes: &[SymExpr]) -> Option<SymExpr> {
270    if bytes.len() < 32 {
271        return None;
272    }
273
274    let source = bytes
275        .iter()
276        .take(32)
277        .enumerate()
278        .find_map(|(idx, byte)| byte.extracted_byte_source(idx))?;
279
280    for (idx, byte) in bytes.iter().take(32).enumerate() {
281        if let Some(byte_source) = byte.extracted_byte_source(idx) {
282            if byte_source != source {
283                return None;
284            }
285            continue;
286        }
287
288        let byte = byte.as_const()?;
289        if source.known_byte(idx) != Some(byte.to::<u8>()) {
290            return None;
291        }
292    }
293    Some(source)
294}
295
296#[derive(Clone, PartialEq, Eq, Hash)]
297pub(crate) struct SymExpr {
298    pub(in crate::runtime::expr) kind: HashConsed<SymExprKind>,
299}
300
301impl fmt::Debug for SymExpr {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        self.kind().fmt(f)
304    }
305}
306
307#[derive(Clone, PartialEq, Eq, Hash)]
308pub(in crate::runtime) enum SymExprKind {
309    Const(U256),
310    Var(Symbol),
311    GasLeft(Symbol),
312    Keccak { name: Symbol, len: SymExpr, bytes: Arc<[SymExpr]> },
313    Hash { name: Symbol, algorithm: &'static str, bytes: Arc<[SymExpr]> },
314    Not(SymExpr),
315    BinOp(SymBinOp, SymExpr, SymExpr),
316    TernOp(SymTernOp, SymExpr, SymExpr, SymExpr),
317    Ite(SymBoolExpr, SymExpr, SymExpr),
318}
319
320/// Formats an expression as a tree.
321///
322/// A hash node prints only its name, because the name is a digest of the preimage. Printing the
323/// preimage again would repeat every nested preimage, so a chain of hashes over the previous hash
324/// would grow exponentially.
325impl fmt::Debug for SymExprKind {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327        match self {
328            Self::Const(value) => f.debug_tuple("Const").field(value).finish(),
329            Self::Var(symbol) => f.debug_tuple("Var").field(symbol).finish(),
330            Self::GasLeft(symbol) => f.debug_tuple("GasLeft").field(symbol).finish(),
331            Self::Keccak { name, len, .. } => f
332                .debug_struct("Keccak")
333                .field("name", name)
334                .field("len", len)
335                .finish_non_exhaustive(),
336            Self::Hash { name, algorithm, .. } => f
337                .debug_struct("Hash")
338                .field("name", name)
339                .field("algorithm", algorithm)
340                .finish_non_exhaustive(),
341            Self::Not(expr) => f.debug_tuple("Not").field(expr).finish(),
342            Self::BinOp(op, left, right) => {
343                f.debug_tuple("BinOp").field(op).field(left).field(right).finish()
344            }
345            Self::TernOp(op, first, second, third) => {
346                f.debug_tuple("TernOp").field(op).field(first).field(second).field(third).finish()
347            }
348            Self::Ite(condition, then, otherwise) => {
349                f.debug_tuple("Ite").field(condition).field(then).field(otherwise).finish()
350            }
351        }
352    }
353}
354
355impl SymExprKind {
356    pub(in crate::runtime) const fn get_var(&self) -> Option<Symbol> {
357        match self {
358            Self::Var(symbol)
359            | Self::GasLeft(symbol)
360            | Self::Keccak { name: symbol, .. }
361            | Self::Hash { name: symbol, .. } => Some(*symbol),
362            _ => None,
363        }
364    }
365
366    pub(in crate::runtime) const fn get_eval_var(&self) -> Option<Symbol> {
367        match self {
368            Self::Var(symbol) | Self::GasLeft(symbol) | Self::Hash { name: symbol, .. } => {
369                Some(*symbol)
370            }
371            _ => None,
372        }
373    }
374}
375
376impl SymExpr {
377    pub(in crate::runtime) fn kind(&self) -> &SymExprKind {
378        self.kind.value()
379    }
380
381    pub(in crate::runtime) fn from_kind(cx: &mut SymCx, kind: SymExprKind) -> Self {
382        cx.mk_expr_kind(kind)
383    }
384
385    pub(crate) fn zero(cx: &mut SymCx) -> Self {
386        Self::constant(cx, U256::ZERO)
387    }
388
389    pub(crate) fn one(cx: &mut SymCx) -> Self {
390        Self::constant(cx, U256::ONE)
391    }
392
393    pub(crate) fn constant(cx: &mut SymCx, value: U256) -> Self {
394        if value.is_zero() {
395            return cx.cached_zero();
396        }
397        if value == U256::ONE {
398            return cx.cached_one();
399        }
400        Self::from_kind(cx, SymExprKind::Const(value))
401    }
402
403    pub(crate) fn var(cx: &mut SymCx, name: &str) -> Self {
404        let symbol = cx.intern(name);
405        Self::get_var(cx, symbol)
406    }
407
408    pub(crate) fn get_var(cx: &mut SymCx, symbol: Symbol) -> Self {
409        Self::from_kind(cx, SymExprKind::Var(symbol))
410    }
411
412    pub(crate) fn gas_left(cx: &mut SymCx, id: usize) -> Self {
413        let symbol = cx.intern(&format!("gasleft_{id}"));
414        Self::from_kind(cx, SymExprKind::GasLeft(symbol))
415    }
416
417    pub(crate) fn not(cx: &mut SymCx, value: Self) -> Self {
418        match value.kind() {
419            SymExprKind::Const(value) => Self::constant(cx, !*value),
420            SymExprKind::Not(value) => value.clone(),
421            _ => Self::from_kind(cx, SymExprKind::Not(value)),
422        }
423    }
424
425    pub(crate) fn binop(cx: &mut SymCx, binop: SymBinOp, left: Self, right: Self) -> Self {
426        match binop {
427            SymBinOp::Add => match (left.kind(), right.kind()) {
428                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
429                    // `const + const => const`.
430                    Self::constant(cx, binop.eval(*left_value, *right_value))
431                }
432                // `0 + a => a`.
433                (SymExprKind::Const(value), _) if value.is_zero() => right,
434                // `a + 0 => a`.
435                (_, SymExprKind::Const(value)) if value.is_zero() => left,
436                // `bool_word(c) + MAX => ite(c, 0, MAX)`.
437                (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
438                    if *value == U256::MAX
439                        && let Some(condition) = if left.as_const() == Some(U256::MAX) {
440                            right.bitwise_bool_word_condition(cx)
441                        } else {
442                            left.bitwise_bool_word_condition(cx)
443                        } =>
444                {
445                    let zero = Self::zero(cx);
446                    let max = Self::constant(cx, U256::MAX);
447                    Self::ite(cx, condition, zero, max)
448                }
449                // `one_bit_word(c) + k => ite(c, k + 1, k)`.
450                (SymExprKind::Const(value), _)
451                    if let Some(condition) = right.bitwise_bool_word_condition(cx) =>
452                {
453                    let incremented = Self::constant(cx, value.wrapping_add(U256::ONE));
454                    let value = Self::constant(cx, *value);
455                    Self::ite(cx, condition, incremented, value)
456                }
457                (_, SymExprKind::Const(value))
458                    if let Some(condition) = left.bitwise_bool_word_condition(cx) =>
459                {
460                    let incremented = Self::constant(cx, value.wrapping_add(U256::ONE));
461                    let value = Self::constant(cx, *value);
462                    Self::ite(cx, condition, incremented, value)
463                }
464                _ => {
465                    let (left, right) = Self::ordered_commutative_operands(left, right);
466                    if let Some(value) = Self::add_with_const_ite(cx, &left, &right) {
467                        value
468                    } else if let Some(value) = Self::add_with_const_ite(cx, &right, &left) {
469                        value
470                    } else {
471                        Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
472                    }
473                }
474            },
475            SymBinOp::Sub => match (left.kind(), right.kind()) {
476                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
477                    // `const - const => const`.
478                    Self::constant(cx, binop.eval(*left_value, *right_value))
479                }
480                // `a - 0 => a`.
481                (_, SymExprKind::Const(value)) if value.is_zero() => left,
482                // `a - a => 0`.
483                _ if left == right => Self::zero(cx),
484                // `bool_word(c) - 1 => ite(c, 0, MAX)`.
485                (_, SymExprKind::Const(value))
486                    if *value == U256::ONE
487                        && let Some(condition) = left.bitwise_bool_word_condition(cx) =>
488                {
489                    let zero = Self::zero(cx);
490                    let max = Self::constant(cx, U256::MAX);
491                    Self::ite(cx, condition, zero, max)
492                }
493                // `k - bool_word(c) => ite(c, k - 1, k)`.
494                (SymExprKind::Const(value), _)
495                    if let Some(condition) = right.bitwise_bool_word_condition(cx) =>
496                {
497                    let decremented = Self::constant(cx, value.wrapping_sub(U256::ONE));
498                    let value = Self::constant(cx, *value);
499                    Self::ite(cx, condition, decremented, value)
500                }
501                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
502            },
503            SymBinOp::Mul => match (left.kind(), right.kind()) {
504                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
505                    // `const * const => const`.
506                    Self::constant(cx, binop.eval(*left_value, *right_value))
507                }
508                // `0 * a => 0`.
509                (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
510                    if value.is_zero() =>
511                {
512                    Self::zero(cx)
513                }
514                // `1 * a => a`.
515                (SymExprKind::Const(value), _) if *value == U256::ONE => right,
516                // `a * 1 => a`.
517                (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
518                _ => {
519                    let (left, right) = Self::ordered_commutative_operands(left, right);
520                    if let Some(condition) = left.direct_bool_word_condition(cx) {
521                        // `bool_word(c) * a => ite(c, a, 0)`.
522                        let zero = Self::zero(cx);
523                        Self::ite(cx, condition, right, zero)
524                    } else if let Some(condition) = right.direct_bool_word_condition(cx) {
525                        // `a * bool_word(c) => ite(c, a, 0)`.
526                        let zero = Self::zero(cx);
527                        Self::ite(cx, condition, left, zero)
528                    } else if let Some(shift) = right.as_const().and_then(power_of_two_shift) {
529                        // `a * 2**n => a << n`.
530                        let shift = Self::constant(cx, U256::from(shift));
531                        Self::binop(cx, SymBinOp::Shl, left, shift)
532                    } else {
533                        Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
534                    }
535                }
536            },
537            SymBinOp::UDiv | SymBinOp::SDiv => match (left.kind(), right.kind()) {
538                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
539                    // `const / const => const`.
540                    Self::constant(cx, binop.eval(*left_value, *right_value))
541                }
542                // `a / 0 => 0`.
543                (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
544                // `a / 1 => a`.
545                (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
546                // `(a - (a & (2**n - 1))) u/ 2**n => a >> n`.
547                (
548                    SymExprKind::BinOp(SymBinOp::Sub, value, low_bits),
549                    SymExprKind::Const(divisor),
550                ) if binop == SymBinOp::UDiv
551                    && let Some(shift) = power_of_two_shift(*divisor)
552                    && low_masked_source(low_bits, shift) == Some(value) =>
553                {
554                    let shift = Self::constant(cx, U256::from(shift));
555                    Self::binop(cx, SymBinOp::Shr, value.clone(), shift)
556                }
557                // `a u/ 2**n => a >> n`.
558                (_, SymExprKind::Const(divisor))
559                    if binop == SymBinOp::UDiv
560                        && let Some(shift) = power_of_two_shift(*divisor) =>
561                {
562                    let shift = Self::constant(cx, U256::from(shift));
563                    Self::binop(cx, SymBinOp::Shr, left, shift)
564                }
565                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
566            },
567            SymBinOp::URem | SymBinOp::SRem => match (left.kind(), right.kind()) {
568                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
569                    // `const % const => const`.
570                    Self::constant(cx, binop.eval(*left_value, *right_value))
571                }
572                // `a % 0 => 0`.
573                (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
574                // `a % 1 => 0`.
575                (_, SymExprKind::Const(value)) if *value == U256::ONE => Self::zero(cx),
576                // `a u% 2**n => a & (2**n - 1)`.
577                (_, SymExprKind::Const(divisor))
578                    if binop == SymBinOp::URem
579                        && let Some(bits) = power_of_two_shift(*divisor) =>
580                {
581                    Self::and_const(cx, left, mask_bits(U256::MAX, bits))
582                }
583                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
584            },
585            SymBinOp::And => match (left.kind(), right.kind()) {
586                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
587                    // `const & const => const`.
588                    Self::constant(cx, binop.eval(*left_value, *right_value))
589                }
590                // `0 & a => 0`.
591                (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
592                    if value.is_zero() =>
593                {
594                    Self::zero(cx)
595                }
596                // `MAX & a => a`.
597                (SymExprKind::Const(value), _) if *value == U256::MAX => right,
598                // `a & MAX => a`.
599                (_, SymExprKind::Const(value)) if *value == U256::MAX => left,
600                // `a & a => a`.
601                _ if left == right => left,
602                (SymExprKind::Const(mask), _) => Self::and_const(cx, right, *mask),
603                (_, SymExprKind::Const(mask)) => Self::and_const(cx, left, *mask),
604                _ => Self::commutative_binop(cx, binop, left, right),
605            },
606            SymBinOp::Or => match (left.kind(), right.kind()) {
607                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
608                    // `const | const => const`.
609                    Self::constant(cx, binop.eval(*left_value, *right_value))
610                }
611                // `0 | a => a`.
612                (SymExprKind::Const(value), _) if value.is_zero() => right,
613                // `a | 0 => a`.
614                (_, SymExprKind::Const(value)) if value.is_zero() => left,
615                // `a | a => a`.
616                _ if left == right => left,
617                _ if let Some(value) = Self::or_with_absorbing_ite(cx, &left, right.clone()) => {
618                    value
619                }
620                _ if let Some(value) = Self::or_with_absorbing_ite(cx, &right, left.clone()) => {
621                    value
622                }
623                _ => Self::or(cx, left, right),
624            },
625            SymBinOp::Xor => match (left.kind(), right.kind()) {
626                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
627                    // `const ^ const => const`.
628                    Self::constant(cx, binop.eval(*left_value, *right_value))
629                }
630                // `0 ^ a => a`.
631                (SymExprKind::Const(value), _) if value.is_zero() => right,
632                // `a ^ 0 => a`.
633                (_, SymExprKind::Const(value)) if value.is_zero() => left,
634                // `a ^ a => 0`.
635                _ if left == right => Self::zero(cx),
636                _ => {
637                    let (left, right) = Self::ordered_commutative_operands(left, right);
638                    // `a ^ (a ^ b) => b`.
639                    if let Some(value) = Self::xor_with_shared_operand(&left, &right)
640                        .or_else(|| Self::xor_with_shared_operand(&right, &left))
641                    {
642                        value
643                    // `a ^ ((a ^ b) * bool_word(c)) => ite(c, b, a)`.
644                    } else if let Some(value) = Self::xor_with_bool_select(cx, &left, &right) {
645                        value
646                    } else if let Some(value) = Self::xor_with_bool_select(cx, &right, &left) {
647                        value
648                    // `a ^ ite(c, b, 0) => ite(c, a ^ b, a)`.
649                    } else if let Some(value) = Self::xor_with_zero_ite(cx, &left, &right) {
650                        value
651                    } else if let Some(value) = Self::xor_with_zero_ite(cx, &right, &left) {
652                        value
653                    } else {
654                        Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
655                    }
656                }
657            },
658            SymBinOp::Shl => match (left.kind(), right.kind()) {
659                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
660                    // `const << const => const`.
661                    Self::constant(cx, binop.eval(*left_value, *right_value))
662                }
663                // `a << 0 => a`.
664                (_, SymExprKind::Const(value)) if value.is_zero() => left,
665                // `0 << a => 0`.
666                (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
667                // `a << 256 => 0`.
668                (_, SymExprKind::Const(value)) if *value >= U256::from(256) => Self::zero(cx),
669                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
670            },
671            SymBinOp::Shr => match (left.kind(), right.kind()) {
672                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
673                    // `const >> const => const`.
674                    Self::constant(cx, binop.eval(*left_value, *right_value))
675                }
676                // `a >> 0 => a`.
677                (_, SymExprKind::Const(value)) if value.is_zero() => left,
678                // `0 >> a => 0`.
679                (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
680                (_, SymExprKind::Const(value)) => Self::shr_const(cx, left, *value),
681                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
682            },
683            SymBinOp::Sar => match (left.kind(), right.kind()) {
684                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
685                    // `const >>s const => const`.
686                    Self::constant(cx, binop.eval(*left_value, *right_value))
687                }
688                // `a >>s 0 => a`.
689                (_, SymExprKind::Const(value)) if value.is_zero() => left,
690                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
691            },
692        }
693    }
694
695    pub(crate) fn ternop(
696        cx: &mut SymCx,
697        ternop: SymTernOp,
698        left: Self,
699        right: Self,
700        modulus: Self,
701    ) -> Self {
702        match (left.kind(), right.kind(), modulus.kind()) {
703            (_, _, SymExprKind::Const(modulus)) if modulus.is_zero() || *modulus == U256::ONE => {
704                // `addmod/mulmod(a, b, 0) => 0`.
705                Self::zero(cx)
706            }
707            (SymExprKind::Const(left), SymExprKind::Const(right), SymExprKind::Const(modulus)) => {
708                // `addmod/mulmod(const, const, const) => const`.
709                Self::constant(cx, ternop.eval(*left, *right, *modulus))
710            }
711            // `addmod/mulmod(a, b, 2**n) => op(a, b) & (2**n - 1)`.
712            (_, _, SymExprKind::Const(modulus))
713                if let Some(bits) = power_of_two_shift(*modulus) =>
714            {
715                let binop = match ternop {
716                    SymTernOp::AddMod => SymBinOp::Add,
717                    SymTernOp::MulMod => SymBinOp::Mul,
718                };
719                let value = Self::binop(cx, binop, left, right);
720                Self::and_const(cx, value, mask_bits(U256::MAX, bits))
721            }
722            _ => {
723                // `addmod/mulmod(a, b, m) => addmod/mulmod(ordered(a, b), m)`.
724                let (left, right) = Self::ordered_commutative_operands(left, right);
725                Self::from_kind(cx, SymExprKind::TernOp(ternop, left, right, modulus))
726            }
727        }
728    }
729
730    pub(crate) fn ite(
731        cx: &mut SymCx,
732        condition: SymBoolExpr,
733        then_expr: Self,
734        else_expr: Self,
735    ) -> Self {
736        match condition.as_const() {
737            // `ite(true, a, b) => a`.
738            Some(true) => then_expr,
739            // `ite(false, a, b) => b`.
740            Some(false) => else_expr,
741            // `ite(c, a, a) => a`.
742            None if then_expr == else_expr => then_expr,
743            // `ite(a == 0, 0, a / a) => a != 0`.
744            None if then_expr.as_const().is_some_and(|value| value.is_zero())
745                && Self::self_div_expr_matches_zero_check(&condition, &else_expr) =>
746            {
747                let condition = condition.not(cx);
748                Self::bool_word(cx, condition)
749            }
750            // `ite(c, 1, bool_word(c)) => bool_word(c)`.
751            None if then_expr.as_const() == Some(U256::ONE)
752                && else_expr.bool_word_condition().as_ref() == Some(&condition) =>
753            {
754                else_expr
755            }
756            // `ite(c, bool_word(c), 0) => bool_word(c)`.
757            None if else_expr.as_const().is_some_and(|value| value.is_zero())
758                && then_expr.bool_word_condition().as_ref() == Some(&condition) =>
759            {
760                then_expr
761            }
762            None => Self::from_kind(cx, SymExprKind::Ite(condition, then_expr, else_expr)),
763        }
764    }
765
766    pub(crate) fn bool_word(cx: &mut SymCx, value: SymBoolExpr) -> Self {
767        let one = Self::one(cx);
768        let zero = Self::zero(cx);
769        Self::ite(cx, value, one, zero)
770    }
771
772    fn self_div_expr_matches_zero_check(cond: &SymBoolExpr, expr: &Self) -> bool {
773        let Some(zero_operand) = cond.zero_check_operand() else { return false };
774        let Some((numerator, denominator)) = expr.udiv_operands() else { return false };
775        numerator == zero_operand && denominator == zero_operand
776    }
777
778    pub(crate) fn keccak_symbol(cx: &mut SymCx, name: Symbol, len: Self, bytes: Vec<Self>) -> Self {
779        Self::from_kind(cx, SymExprKind::Keccak { name, len, bytes: bytes.into() })
780    }
781
782    pub(crate) fn hash_symbol(
783        cx: &mut SymCx,
784        name: Symbol,
785        algorithm: &'static str,
786        bytes: Vec<Self>,
787    ) -> Self {
788        Self::from_kind(cx, SymExprKind::Hash { name, algorithm, bytes: bytes.into() })
789    }
790
791    fn or(cx: &mut SymCx, left: Self, right: Self) -> Self {
792        if let Some(rebuilt) = Self::rebuild_from_or_terms(&left, &right) {
793            // `byte_parts(a) | byte_parts(a) => a`.
794            return rebuilt;
795        }
796        Self::commutative_binop(cx, SymBinOp::Or, left, right)
797    }
798
799    fn or_with_absorbing_ite(cx: &mut SymCx, conditional: &Self, other: Self) -> Option<Self> {
800        let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
801            return None;
802        };
803        if then_expr.as_const().is_some_and(|value| value.is_zero())
804            && else_expr.as_const() == Some(U256::MAX)
805        {
806            return Some(Self::ite(cx, condition.clone(), other, else_expr.clone()));
807        }
808        if then_expr.as_const() == Some(U256::MAX)
809            && else_expr.as_const().is_some_and(|value| value.is_zero())
810        {
811            return Some(Self::ite(cx, condition.clone(), then_expr.clone(), other));
812        }
813        None
814    }
815
816    fn add_with_const_ite(cx: &mut SymCx, other: &Self, conditional: &Self) -> Option<Self> {
817        if other.as_const().is_some() {
818            return None;
819        }
820        let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
821            return None;
822        };
823        if then_expr.as_const().is_none() || else_expr.as_const().is_none() {
824            return None;
825        }
826        if !Self::duplicating_branchless_rewrite_fits(other, conditional) {
827            return None;
828        }
829        let then_expr = Self::binop(cx, SymBinOp::Add, other.clone(), then_expr.clone());
830        let else_expr = Self::binop(cx, SymBinOp::Add, other.clone(), else_expr.clone());
831        Some(Self::ite(cx, condition.clone(), then_expr, else_expr))
832    }
833
834    fn xor_with_bool_select(cx: &mut SymCx, base: &Self, selector: &Self) -> Option<Self> {
835        let SymExprKind::BinOp(SymBinOp::Mul, left, right) = selector.kind() else { return None };
836        let (condition_word, selected) = match left.kind() {
837            SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right) if delta_left == base => {
838                (right, delta_right.clone())
839            }
840            SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right) if delta_right == base => {
841                (right, delta_left.clone())
842            }
843            _ => match right.kind() {
844                SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right)
845                    if delta_left == base =>
846                {
847                    (left, delta_right.clone())
848                }
849                SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right)
850                    if delta_right == base =>
851                {
852                    (left, delta_left.clone())
853                }
854                _ => return None,
855            },
856        };
857        let condition = condition_word.bitwise_bool_word_condition(cx)?;
858        Some(Self::ite(cx, condition, selected, base.clone()))
859    }
860
861    fn xor_with_shared_operand(base: &Self, nested: &Self) -> Option<Self> {
862        let SymExprKind::BinOp(SymBinOp::Xor, left, right) = nested.kind() else { return None };
863        if left == base {
864            Some(right.clone())
865        } else if right == base {
866            Some(left.clone())
867        } else {
868            None
869        }
870    }
871
872    fn xor_with_zero_ite(cx: &mut SymCx, base: &Self, conditional: &Self) -> Option<Self> {
873        let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
874            return None;
875        };
876        if then_expr.as_const().is_some_and(|value| value.is_zero()) {
877            if !Self::duplicating_branchless_rewrite_fits(base, conditional) {
878                return None;
879            }
880            let selected = Self::binop(cx, SymBinOp::Xor, base.clone(), else_expr.clone());
881            return Some(Self::ite(cx, condition.clone(), base.clone(), selected));
882        }
883        if else_expr.as_const().is_some_and(|value| value.is_zero()) {
884            if !Self::duplicating_branchless_rewrite_fits(base, conditional) {
885                return None;
886            }
887            let selected = Self::binop(cx, SymBinOp::Xor, base.clone(), then_expr.clone());
888            return Some(Self::ite(cx, condition.clone(), selected, base.clone()));
889        }
890        None
891    }
892
893    /// Checks the occurrence cost of a rewrite that places one operand in both ITE arms.
894    ///
895    /// Hash-consing keeps the stored DAG compact, but the rewrite still places a shared operand in
896    /// both arms for downstream consumers. Count that unfolded result before constructing it so a
897    /// linear series of branchless operations cannot become exponential.
898    fn duplicating_branchless_rewrite_fits(operand: &Self, conditional: &Self) -> bool {
899        let mut counter = UnfoldedNodeCounter::new();
900        let Some(operand_nodes) = counter.expr_nodes(operand) else {
901            return false;
902        };
903        let Some(conditional_nodes) = counter.expr_nodes(conditional) else {
904            return false;
905        };
906
907        operand_nodes
908            .checked_mul(2)
909            .and_then(|duplicated| conditional_nodes.checked_add(duplicated))
910            // The rewritten ITE adds at most two operation wrappers around the original arms.
911            .and_then(|nodes| nodes.checked_add(2))
912            .is_some_and(|nodes| nodes <= MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES)
913    }
914
915    fn commutative_binop(cx: &mut SymCx, op: SymBinOp, left: Self, right: Self) -> Self {
916        // `a + b => b + a`.
917        let (left, right) = Self::ordered_commutative_operands(left, right);
918        Self::from_kind(cx, SymExprKind::BinOp(op, left, right))
919    }
920
921    pub(in crate::runtime::expr) fn ordered_commutative_operands(
922        left: Self,
923        right: Self,
924    ) -> (Self, Self) {
925        match left.complexity().cmp(&right.complexity()) {
926            // Put less complex operands, like constants, on the RHS.
927            std::cmp::Ordering::Less => (right, left),
928            std::cmp::Ordering::Greater => (left, right),
929            std::cmp::Ordering::Equal if right.kind.stable_hash_cmp(&left.kind).is_lt() => {
930                (right, left)
931            }
932            std::cmp::Ordering::Equal => (left, right),
933        }
934    }
935
936    /// Canonically orders factors that belong to the same symbolic context.
937    ///
938    /// Polynomial normalization only needs a stable order within one context, where hash-consed
939    /// identity is both total and substantially cheaper than rendering structural string keys.
940    pub(in crate::runtime) fn sort_interned_factors(factors: &mut [Self]) {
941        factors.sort_unstable_by(|left, right| left.kind.identity_cmp(&right.kind));
942    }
943
944    fn complexity(&self) -> usize {
945        match self.kind() {
946            SymExprKind::Const(_) => 0,
947            SymExprKind::Not(_) => 1,
948            SymExprKind::BinOp(..) => 2,
949            SymExprKind::TernOp(..) => 3,
950            _ => 4,
951        }
952    }
953
954    fn and_const(cx: &mut SymCx, expr: Self, mask: U256) -> Self {
955        if mask.is_zero() {
956            // `a & 0 => 0`.
957            return Self::zero(cx);
958        }
959        if mask == U256::MAX {
960            // `a & MAX => a`.
961            return expr;
962        }
963
964        match expr.kind() {
965            // `const & mask => const`.
966            SymExprKind::Const(value) => Self::constant(cx, *value & mask),
967            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
968                // `(a | b) & mask => (a & mask) | (b & mask)`.
969                let left = Self::and_const(cx, left.clone(), mask);
970                let right = Self::and_const(cx, right.clone(), mask);
971                Self::binop(cx, SymBinOp::Or, left, right)
972            }
973            SymExprKind::BinOp(SymBinOp::Shl, _, shift)
974                if mask_low_bits(mask).is_some_and(|bits| {
975                    shift
976                        .as_const()
977                        .and_then(|shift| usize::try_from(shift).ok())
978                        .is_some_and(|shift| bits <= shift)
979                }) =>
980            {
981                // `(a << n) & low_mask(n) => 0`.
982                Self::zero(cx)
983            }
984            SymExprKind::BinOp(SymBinOp::And, left, right) => {
985                if right.as_const() == Some(mask) {
986                    // `(a & mask) & mask => a & mask`.
987                    Self::and_const(cx, left.clone(), mask)
988                } else if left == right {
989                    // `(a & a) & mask => a & mask`.
990                    Self::and_const(cx, left.clone(), mask)
991                } else {
992                    let mask = Self::constant(cx, mask);
993                    Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
994                }
995            }
996            _ => {
997                let mask = Self::constant(cx, mask);
998                Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
999            }
1000        }
1001    }
1002
1003    fn shr_const(cx: &mut SymCx, expr: Self, shift: U256) -> Self {
1004        if shift.is_zero() {
1005            // `a >> 0 => a`.
1006            return expr;
1007        }
1008        if shift >= U256::from(256) {
1009            // `a >> 256 => 0`.
1010            return Self::zero(cx);
1011        }
1012
1013        let shift = usize::try_from(shift).expect("shift is less than 256");
1014        if expr.unsigned_bits() <= shift {
1015            // `small(a) >> bits(a) => 0`.
1016            return Self::zero(cx);
1017        }
1018
1019        if let SymExprKind::BinOp(SymBinOp::Shl, inner, left_shift) = expr.kind()
1020            && left_shift.as_const() == Some(U256::from(shift))
1021            && inner.unsigned_bits() <= 256 - shift
1022        {
1023            // `(a << n) >> n => a`.
1024            return inner.clone();
1025        }
1026
1027        if let SymExprKind::BinOp(SymBinOp::Or, left, right) = expr.kind() {
1028            // Distribute only when it collapses part of the OR; expanding broad
1029            // bit-smearing chains eagerly makes SMT CSE much larger.
1030            let left = Self::shr_const(cx, left.clone(), U256::from(shift));
1031            let right = Self::shr_const(cx, right.clone(), U256::from(shift));
1032            if left.as_const().is_some_and(|value| value.is_zero()) {
1033                return right;
1034            }
1035            if right.as_const().is_some_and(|value| value.is_zero()) {
1036                return left;
1037            }
1038        }
1039
1040        let shift = Self::constant(cx, U256::from(shift));
1041        Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::Shr, expr, shift))
1042    }
1043
1044    fn rebuild_from_or_terms(left: &Self, right: &Self) -> Option<Self> {
1045        let mut terms = Vec::new();
1046        left.push_or_terms(&mut terms);
1047        right.push_or_terms(&mut terms);
1048        Self::rebuild_from_extracted_byte_terms(&terms)
1049            .or_else(|| Self::rebuild_from_shifted_word_fragments(&terms))
1050    }
1051
1052    pub(in crate::runtime) fn push_or_terms<'a>(&'a self, terms: &mut Vec<&'a Self>) {
1053        match self.kind() {
1054            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1055                left.push_or_terms(terms);
1056                right.push_or_terms(terms);
1057            }
1058            _ => terms.push(self),
1059        }
1060    }
1061
1062    fn rebuild_from_extracted_byte_terms(terms: &[&Self]) -> Option<Self> {
1063        if terms.len() <= 1 {
1064            return None;
1065        }
1066
1067        let mut source = None;
1068        let mut seen = [false; 32];
1069        for term in terms {
1070            if term.as_const().is_some_and(|value| value.is_zero()) {
1071                continue;
1072            }
1073            let (term_source, index) = term.extracted_shifted_byte_term()?;
1074            match &source {
1075                Some(source) if source != &term_source => return None,
1076                Some(_) => {}
1077                None => source = Some(term_source),
1078            }
1079            seen[index] = true;
1080        }
1081
1082        let source = source?;
1083        for (index, seen) in seen.into_iter().enumerate() {
1084            if !seen && source.known_byte(index) != Some(0) {
1085                return None;
1086            }
1087        }
1088        Some(source)
1089    }
1090
1091    fn extracted_shifted_byte_term(&self) -> Option<(Self, usize)> {
1092        match self.kind() {
1093            SymExprKind::BinOp(SymBinOp::Shl, byte, shift) => {
1094                let shift = shift.as_const()?;
1095                let Ok(shift) = usize::try_from(shift) else { return None };
1096                if shift % 8 != 0 || shift > 248 {
1097                    return None;
1098                }
1099                let index = 31 - shift / 8;
1100                let source = byte.extracted_unshifted_byte_source(index)?;
1101                Some((source, index))
1102            }
1103            _ => self.extracted_unshifted_byte_source(31).map(|source| (source, 31)),
1104        }
1105    }
1106
1107    fn extracted_unshifted_byte_source(&self, index: usize) -> Option<Self> {
1108        let expr = self.strip_low_byte_mask();
1109        if index == 31 {
1110            return Some(expr.clone());
1111        }
1112        let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1113        let shift = shift.as_const()?;
1114        (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1115    }
1116
1117    fn rebuild_from_shifted_word_fragments(terms: &[&Self]) -> Option<Self> {
1118        if terms.len() != 2 {
1119            return None;
1120        }
1121
1122        let left_low = terms[0].low_word_fragment();
1123        let right_low = terms[1].low_word_fragment();
1124        let left_high = terms[0].shifted_high_word_fragment();
1125        let right_high = terms[1].shifted_high_word_fragment();
1126        match (left_low, right_low, left_high, right_high) {
1127            (Some((low_source, low_bits)), None, None, Some((high_source, high_bits)))
1128            | (None, Some((low_source, low_bits)), Some((high_source, high_bits)), None)
1129                if low_source == high_source && low_bits == high_bits =>
1130            {
1131                Some(low_source)
1132            }
1133            _ => None,
1134        }
1135    }
1136
1137    fn low_word_fragment(&self) -> Option<(Self, usize)> {
1138        let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1139        let mask = right.as_const()?;
1140        mask_low_bits(mask).map(|bits| (left.clone(), bits))
1141    }
1142
1143    fn shifted_high_word_fragment(&self) -> Option<(Self, usize)> {
1144        let SymExprKind::BinOp(SymBinOp::Shl, value, shift) = self.kind() else { return None };
1145        let bits = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1146        if bits == 0 || bits >= 256 {
1147            return None;
1148        }
1149
1150        let (source, source_shift, width) = value.shifted_low_fragment_source()?;
1151        (source_shift == bits && width == 256 - bits).then_some((source, bits))
1152    }
1153
1154    fn shifted_low_fragment_source(&self) -> Option<(Self, usize, usize)> {
1155        let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1156        let mask = right.as_const()?;
1157        Self::shifted_low_fragment_source_with_mask(left, mask)
1158    }
1159
1160    fn shifted_low_fragment_source_with_mask(
1161        value: &Self,
1162        mask: U256,
1163    ) -> Option<(Self, usize, usize)> {
1164        let width = mask_low_bits(mask)?;
1165        match value.kind() {
1166            SymExprKind::BinOp(SymBinOp::Shr, source, shift) => {
1167                let shift = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1168                Some((source.clone(), shift, width))
1169            }
1170            _ => Some((value.clone(), 0, width)),
1171        }
1172    }
1173
1174    pub(crate) fn low_byte(self, cx: &mut SymCx) -> Self {
1175        if let Some(word) = self.as_const() {
1176            return Self::constant(cx, U256::from(word.to::<u8>()));
1177        }
1178        let mask = Self::constant(cx, U256::from(0xff));
1179        Self::binop(cx, SymBinOp::And, self, mask)
1180    }
1181
1182    pub(crate) fn into_byte_exprs(self, cx: &mut SymCx) -> Vec<Self> {
1183        SymBytes::word(cx, self).materialize(cx)
1184    }
1185
1186    pub(crate) fn into_bytes(self, cx: &mut SymCx) -> SymBytes {
1187        SymBytes::word(cx, self)
1188    }
1189
1190    pub(crate) fn from_bytes(cx: &mut SymCx, bytes: impl IntoIterator<Item = Self>) -> Self {
1191        let bytes = bytes.into_iter().collect::<Vec<_>>();
1192        if let Ok(concrete) = concrete_expr_bytes(&bytes, "symbolic word bytes") {
1193            let mut word = [0u8; 32];
1194            for (idx, byte) in concrete.into_iter().take(32).enumerate() {
1195                word[idx] = byte;
1196            }
1197            return Self::constant(cx, U256::from_be_bytes(word));
1198        }
1199
1200        if let Some(expr) = word_from_extracted_bytes(&bytes) {
1201            return expr;
1202        }
1203
1204        let mut expr = Self::zero(cx);
1205        for (idx, byte) in bytes.into_iter().take(32).enumerate() {
1206            let shift = (31 - idx) * 8;
1207            let byte = byte.low_byte(cx);
1208            let byte = if shift == 0 {
1209                byte
1210            } else {
1211                let shift = Self::constant(cx, U256::from(shift));
1212                Self::binop(cx, SymBinOp::Shl, byte, shift)
1213            };
1214            expr = Self::binop(cx, SymBinOp::Or, expr, byte);
1215        }
1216        expr
1217    }
1218
1219    pub(crate) fn as_const(&self) -> Option<U256> {
1220        match self.kind() {
1221            SymExprKind::Const(value) => Some(*value),
1222            _ => None,
1223        }
1224    }
1225
1226    pub(crate) fn eval(&self) -> Option<U256> {
1227        self.eval_model_if_complete(&NoopModel).ok().flatten()
1228    }
1229
1230    pub(crate) fn eval_model<M: SymbolicModelLookup + ?Sized>(
1231        &self,
1232        model: &M,
1233    ) -> Result<U256, SymbolicError> {
1234        ModelEvaluator::new(model).eval_word(self)
1235    }
1236
1237    pub(crate) fn eval_model_if_complete<M: SymbolicModelLookup + ?Sized>(
1238        &self,
1239        model: &M,
1240    ) -> Result<Option<U256>, SymbolicError> {
1241        let mut vars = SymbolicVars::default();
1242        self.collect_eval_vars(&mut vars);
1243        if vars.iter().copied().all(|var| model.contains_name(var)) {
1244            self.eval_model(model).map(Some)
1245        } else {
1246            Ok(None)
1247        }
1248    }
1249
1250    pub(crate) fn assign_model_value(&self, model: &mut SymbolicModel, value: U256) -> bool {
1251        match self.kind() {
1252            SymExprKind::Const(existing) => *existing == value,
1253            SymExprKind::Var(var) => {
1254                if let Some(existing) = model.get(var) {
1255                    *existing == value
1256                } else {
1257                    model.insert(*var, value);
1258                    true
1259                }
1260            }
1261            SymExprKind::GasLeft(symbol) => {
1262                if let Some(existing) = model.get(symbol) {
1263                    *existing == value
1264                } else {
1265                    model.insert(*symbol, value);
1266                    true
1267                }
1268            }
1269            _ => false,
1270        }
1271    }
1272
1273    pub(crate) fn bool_word_condition(&self) -> Option<SymBoolExpr> {
1274        let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() else {
1275            return None;
1276        };
1277        Self::bool_word_condition_from_parts(condition, then_expr, else_expr)
1278    }
1279
1280    pub(in crate::runtime) fn bitwise_bool_word_condition(
1281        &self,
1282        cx: &mut SymCx,
1283    ) -> Option<SymBoolExpr> {
1284        let mut pending = vec![self.clone()];
1285        let mut seen_words = HashSet::<Self>::default();
1286        let mut leaf_conditions = IndexSet::<SymBoolExpr>::default();
1287        let mut bit_widths = HashMap::default();
1288        let mut remaining = MAX_BITWISE_BOOL_WORD_VISITS;
1289        while let Some(word) = pending.pop() {
1290            if !seen_words.insert(word.clone()) {
1291                continue;
1292            }
1293            if remaining == 0 {
1294                return None;
1295            }
1296            remaining -= 1;
1297
1298            if let Some(condition) = word.direct_bool_word_condition(cx) {
1299                leaf_conditions.insert(condition);
1300                continue;
1301            }
1302            if let SymExprKind::BinOp(SymBinOp::Or, left, right) = word.kind() {
1303                pending.push(right.clone());
1304                pending.push(left.clone());
1305                continue;
1306            }
1307
1308            if word.unsigned_bits_cached(&mut bit_widths, &mut remaining) == Some(1) {
1309                let zero = Self::zero(cx);
1310                let (word, zero) = Self::ordered_commutative_operands(word, zero);
1311                let zero_check =
1312                    SymBoolExpr::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, word, zero));
1313                leaf_conditions.insert(zero_check.not(cx));
1314                continue;
1315            }
1316            return None;
1317        }
1318
1319        Some(SymBoolExpr::or(cx, leaf_conditions.into_iter().collect()))
1320    }
1321
1322    /// Returns the condition represented by a direct `0`/`1` ITE, preserving its polarity.
1323    ///
1324    /// Unlike [`Self::bitwise_bool_word_condition`], this deliberately does not infer a boolean
1325    /// word from arbitrary one-bit expressions. Callers that run during expression construction
1326    /// use this bounded structural check to avoid recursively sizing a shared expression DAG.
1327    fn direct_bool_word_condition(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1328        let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() else {
1329            return None;
1330        };
1331        match (then_expr.as_const(), else_expr.as_const()) {
1332            (Some(then_value), Some(else_value))
1333                if then_value == U256::ONE && else_value.is_zero() =>
1334            {
1335                Some(condition.clone())
1336            }
1337            (Some(then_value), Some(else_value))
1338                if then_value.is_zero() && else_value == U256::ONE =>
1339            {
1340                Some(condition.clone().not(cx))
1341            }
1342            _ => None,
1343        }
1344    }
1345
1346    fn bool_word_condition_from_parts(
1347        condition: &SymBoolExpr,
1348        then_expr: &Self,
1349        else_expr: &Self,
1350    ) -> Option<SymBoolExpr> {
1351        match (then_expr.as_const(), else_expr.as_const()) {
1352            (Some(then_value), Some(else_value))
1353                if then_value == U256::ONE && else_value.is_zero() =>
1354            {
1355                Some(condition.clone())
1356            }
1357            (Some(then_value), Some(else_value))
1358                if then_value.is_zero() && else_value == U256::ONE =>
1359            {
1360                None
1361            }
1362            _ => None,
1363        }
1364    }
1365
1366    pub(crate) fn into_zero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1367        match self.kind() {
1368            SymExprKind::Const(value) => SymBoolExpr::constant(cx, value.is_zero()),
1369            SymExprKind::Ite(condition, then_expr, else_expr) => {
1370                match Self::bool_word_condition_from_parts(condition, then_expr, else_expr) {
1371                    Some(condition) => SymBoolExpr::not_bool(cx, condition),
1372                    None => {
1373                        let zero = Self::zero(cx);
1374                        SymBoolExpr::eq(cx, self, zero)
1375                    }
1376                }
1377            }
1378            _ => {
1379                let zero = Self::zero(cx);
1380                SymBoolExpr::eq(cx, self, zero)
1381            }
1382        }
1383    }
1384
1385    pub(crate) fn nonzero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1386        let zero = self.into_zero_bool(cx);
1387        SymBoolExpr::not_bool(cx, zero)
1388    }
1389
1390    pub(crate) fn as_const_or(&self, reason: &'static str) -> Result<U256, SymbolicError> {
1391        self.as_const().ok_or(SymbolicError::Unsupported(reason))
1392    }
1393
1394    pub(crate) fn as_usize_or(&self, reason: &'static str) -> Result<usize, SymbolicError> {
1395        let value = self.as_const_or(reason)?;
1396        usize::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
1397    }
1398
1399    pub(crate) fn contains_keccak(&self) -> bool {
1400        self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Keccak { .. }))
1401    }
1402
1403    pub(crate) fn contains_gasleft(&self) -> bool {
1404        self.visit_bool(|expr| expr.is_raw_gasleft())
1405    }
1406
1407    pub(crate) fn contains_udiv(&self) -> bool {
1408        self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::BinOp(SymBinOp::UDiv, _, _)))
1409    }
1410
1411    pub(crate) fn contains_ite(&self) -> bool {
1412        let mut visited = HashSet::<&Self>::default();
1413        self.contains_ite_cached(&mut visited, false)
1414    }
1415
1416    fn contains_ite_cached<'a>(&'a self, visited: &mut HashSet<&'a Self>, memoize: bool) -> bool {
1417        match self.kind() {
1418            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => return false,
1419            SymExprKind::Ite(_, _, _) => return true,
1420            _ => {}
1421        }
1422        if memoize && !visited.insert(self) {
1423            return false;
1424        }
1425
1426        match self.kind() {
1427            SymExprKind::Keccak { len, bytes, .. } => {
1428                len.contains_ite_cached(visited, true)
1429                    || bytes.iter().any(|byte| byte.contains_ite_cached(visited, true))
1430            }
1431            SymExprKind::Hash { bytes, .. } => {
1432                bytes.iter().any(|byte| byte.contains_ite_cached(visited, true))
1433            }
1434            SymExprKind::Not(value) => value.contains_ite_cached(visited, true),
1435            SymExprKind::BinOp(_, left, right) => {
1436                left.contains_ite_cached(visited, true) || right.contains_ite_cached(visited, true)
1437            }
1438            SymExprKind::TernOp(_, left, right, modulus) => {
1439                left.contains_ite_cached(visited, true)
1440                    || right.contains_ite_cached(visited, true)
1441                    || modulus.contains_ite_cached(visited, true)
1442            }
1443            SymExprKind::Const(_)
1444            | SymExprKind::Var(_)
1445            | SymExprKind::GasLeft(_)
1446            | SymExprKind::Ite(_, _, _) => unreachable!("leaf expression handled before descent"),
1447        }
1448    }
1449
1450    pub(in crate::runtime) fn udiv_operands(&self) -> Option<(&Self, &Self)> {
1451        match self.kind() {
1452            SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
1453                Some((numerator, denominator))
1454            }
1455            _ => None,
1456        }
1457    }
1458
1459    pub(crate) fn collect_eval_vars(&self, vars: &mut SymbolicVars) {
1460        let _ = self.visit::<()>(&mut |expr| {
1461            vars.extend(expr.kind().get_eval_var());
1462            ControlFlow::Continue(())
1463        });
1464    }
1465
1466    pub(crate) fn known_byte(&self, index: usize) -> Option<u8> {
1467        debug_assert!(index < 32);
1468        match self.kind() {
1469            SymExprKind::Const(value) => Some(value.to_be_bytes::<32>()[index]),
1470            SymExprKind::Var(_)
1471            | SymExprKind::GasLeft(_)
1472            | SymExprKind::Keccak { .. }
1473            | SymExprKind::Hash { .. } => None,
1474            SymExprKind::Not(value) => value.known_byte(index).map(|byte| !byte),
1475            SymExprKind::Ite(_, then_expr, else_expr) => {
1476                let then_byte = then_expr.known_byte(index)?;
1477                let else_byte = else_expr.known_byte(index)?;
1478                (then_byte == else_byte).then_some(then_byte)
1479            }
1480            SymExprKind::BinOp(op, left, right) => match op {
1481                SymBinOp::And => match (left.known_byte(index), right.known_byte(index)) {
1482                    (Some(left), Some(right)) => Some(left & right),
1483                    (Some(0), _) | (_, Some(0)) => Some(0),
1484                    _ => None,
1485                },
1486                SymBinOp::Or => Some(left.known_byte(index)? | right.known_byte(index)?),
1487                SymBinOp::Xor => Some(left.known_byte(index)? ^ right.known_byte(index)?),
1488                SymBinOp::Shl => {
1489                    let shift = right.as_const()?;
1490                    if shift >= U256::from(256) {
1491                        return Some(0);
1492                    }
1493                    let shift = usize::try_from(shift).expect("checked byte shift");
1494                    if shift % 8 != 0 {
1495                        return None;
1496                    }
1497                    let source_index = index + shift / 8;
1498                    if source_index >= 32 { Some(0) } else { left.known_byte(source_index) }
1499                }
1500                SymBinOp::Shr => {
1501                    let shift = right.as_const()?;
1502                    if shift >= U256::from(256) {
1503                        return Some(0);
1504                    }
1505                    let shift = usize::try_from(shift).expect("checked byte shift");
1506                    if shift % 8 != 0 {
1507                        return None;
1508                    }
1509                    let byte_shift = shift / 8;
1510                    if index < byte_shift { Some(0) } else { left.known_byte(index - byte_shift) }
1511                }
1512                SymBinOp::Add
1513                | SymBinOp::Sub
1514                | SymBinOp::Mul
1515                | SymBinOp::UDiv
1516                | SymBinOp::URem
1517                | SymBinOp::SDiv
1518                | SymBinOp::SRem
1519                | SymBinOp::Sar => None,
1520            },
1521            SymExprKind::TernOp(_, _, _, _) => None,
1522        }
1523    }
1524
1525    pub(crate) fn known_word(&self) -> Option<U256> {
1526        let mut word = [0u8; 32];
1527        for (idx, byte) in word.iter_mut().enumerate() {
1528            *byte = self.known_byte(idx)?;
1529        }
1530        Some(U256::from_be_bytes(word))
1531    }
1532
1533    pub(crate) fn unsigned_bits(&self) -> usize {
1534        let mut bit_widths = HashMap::default();
1535        let mut remaining = usize::MAX;
1536        self.unsigned_bits_cached(&mut bit_widths, &mut remaining).unwrap_or(256)
1537    }
1538
1539    fn unsigned_bits_cached(
1540        &self,
1541        bit_widths: &mut HashMap<Self, usize>,
1542        remaining: &mut usize,
1543    ) -> Option<usize> {
1544        if let Some(bits) = bit_widths.get(self) {
1545            return Some(*bits);
1546        }
1547
1548        let mut pending = vec![(self.clone(), false)];
1549        while let Some((expr, children_visited)) = pending.pop() {
1550            if bit_widths.contains_key(&expr) {
1551                continue;
1552            }
1553            if !children_visited {
1554                if *remaining == 0 {
1555                    return None;
1556                }
1557                *remaining -= 1;
1558                pending.push((expr.clone(), true));
1559                match expr.kind() {
1560                    SymExprKind::BinOp(SymBinOp::And, left, right)
1561                        if right.as_const().is_some() =>
1562                    {
1563                        pending.push((left.clone(), false));
1564                    }
1565                    SymExprKind::BinOp(SymBinOp::Add | SymBinOp::Mul, left, right)
1566                    | SymExprKind::Ite(_, left, right) => {
1567                        pending.push((right.clone(), false));
1568                        pending.push((left.clone(), false));
1569                    }
1570                    SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, left, right)
1571                        if right
1572                            .as_const()
1573                            .and_then(|shift| usize::try_from(shift).ok())
1574                            .is_some() =>
1575                    {
1576                        pending.push((left.clone(), false));
1577                    }
1578                    SymExprKind::BinOp(SymBinOp::UDiv, left, _) => {
1579                        pending.push((left.clone(), false));
1580                    }
1581                    SymExprKind::TernOp(_, _, _, modulus) => {
1582                        pending.push((modulus.clone(), false));
1583                    }
1584                    _ => {}
1585                }
1586                continue;
1587            }
1588
1589            let bits = match expr.kind() {
1590                SymExprKind::Const(value) => value.bit_len().max(1),
1591                SymExprKind::BinOp(SymBinOp::And, left, right) => {
1592                    if let Some(mask) = right.as_const() {
1593                        bit_widths[left].min(mask.bit_len())
1594                    } else {
1595                        256
1596                    }
1597                }
1598                SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1599                    bit_widths[left].max(bit_widths[right]).saturating_add(1).min(256)
1600                }
1601                SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1602                    bit_widths[left].saturating_add(bit_widths[right]).min(256)
1603                }
1604                SymExprKind::BinOp(SymBinOp::Shl, left, right) => right
1605                    .as_const()
1606                    .and_then(|shift| usize::try_from(shift).ok())
1607                    .map_or(256, |shift| bit_widths[left].saturating_add(shift).min(256)),
1608                SymExprKind::BinOp(SymBinOp::Shr, left, right) => right
1609                    .as_const()
1610                    .and_then(|shift| usize::try_from(shift).ok())
1611                    .map_or(256, |shift| bit_widths[left].saturating_sub(shift).max(1)),
1612                SymExprKind::BinOp(SymBinOp::UDiv, left, _) => bit_widths[left],
1613                SymExprKind::TernOp(_, _, _, modulus) => bit_widths[modulus],
1614                SymExprKind::Ite(_, left, right) => bit_widths[left].max(bit_widths[right]),
1615                _ => 256,
1616            };
1617            bit_widths.insert(expr, bits);
1618        }
1619        bit_widths.get(self).copied()
1620    }
1621
1622    pub(crate) fn extracted_byte(&self, cx: &mut SymCx, index: usize) -> Self {
1623        debug_assert!(index < 32);
1624        let shift = Self::constant(cx, U256::from((31 - index) * 8));
1625        let shifted = Self::binop(cx, SymBinOp::Shr, self.clone(), shift);
1626        let mask = Self::constant(cx, U256::from(0xff));
1627        Self::binop(cx, SymBinOp::And, shifted, mask)
1628    }
1629
1630    pub(crate) fn extracted_byte_source(&self, index: usize) -> Option<Self> {
1631        let expr = self.strip_low_byte_mask();
1632        if index == 31 {
1633            return Some(expr.clone());
1634        }
1635        let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1636        let shift = shift.as_const()?;
1637        (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1638    }
1639
1640    pub(crate) fn strip_low_byte_mask(&self) -> &Self {
1641        match self.kind() {
1642            SymExprKind::BinOp(SymBinOp::And, left, right)
1643                if right.as_const() == Some(U256::from(0xff)) =>
1644            {
1645                left.strip_low_byte_mask()
1646            }
1647            _ => self,
1648        }
1649    }
1650
1651    pub(crate) fn byte_term(&self, cx: &mut SymCx, index: usize) -> Option<Self> {
1652        debug_assert!(index < 32);
1653
1654        match self.kind() {
1655            SymExprKind::Const(value) => {
1656                Some(Self::constant(cx, U256::from(value.to_be_bytes::<32>()[index])))
1657            }
1658            SymExprKind::Var(_)
1659            | SymExprKind::GasLeft(_)
1660            | SymExprKind::Keccak { .. }
1661            | SymExprKind::Hash { .. } => Some(self.extracted_byte(cx, index)),
1662            SymExprKind::Not(value) => {
1663                let value = value.byte_term(cx, index)?;
1664                Some(Self::not(cx, value))
1665            }
1666            SymExprKind::Ite(cond, then_expr, else_expr) => {
1667                let then_expr = then_expr.byte_term(cx, index)?;
1668                let else_expr = else_expr.byte_term(cx, index)?;
1669                Some(Self::ite(cx, cond.clone(), then_expr, else_expr))
1670            }
1671            SymExprKind::BinOp(op, left, right) => match op {
1672                SymBinOp::And => Self::binary_byte_term(
1673                    cx,
1674                    left,
1675                    right,
1676                    index,
1677                    SymBinOp::And,
1678                    |byte| byte == 0xff,
1679                    |byte| byte == 0,
1680                ),
1681                SymBinOp::Or => Self::binary_byte_term(
1682                    cx,
1683                    left,
1684                    right,
1685                    index,
1686                    SymBinOp::Or,
1687                    |byte| byte == 0,
1688                    |_| false,
1689                ),
1690                SymBinOp::Xor => Self::binary_byte_term(
1691                    cx,
1692                    left,
1693                    right,
1694                    index,
1695                    SymBinOp::Xor,
1696                    |byte| byte == 0,
1697                    |_| false,
1698                ),
1699                SymBinOp::Shl => {
1700                    let shift = right.eval()?;
1701                    if shift >= U256::from(256) {
1702                        return Some(Self::zero(cx));
1703                    }
1704                    let shift = usize::try_from(shift).expect("checked byte shift");
1705                    if shift % 8 != 0 {
1706                        return None;
1707                    }
1708                    let source_index = index + shift / 8;
1709                    if source_index >= 32 {
1710                        Some(Self::zero(cx))
1711                    } else {
1712                        left.byte_term(cx, source_index)
1713                    }
1714                }
1715                SymBinOp::Shr => {
1716                    let shift = right.eval()?;
1717                    if shift >= U256::from(256) {
1718                        return Some(Self::zero(cx));
1719                    }
1720                    let shift = usize::try_from(shift).expect("checked byte shift");
1721                    if shift % 8 != 0 {
1722                        return None;
1723                    }
1724                    let byte_shift = shift / 8;
1725                    if index < byte_shift {
1726                        Some(Self::zero(cx))
1727                    } else {
1728                        left.byte_term(cx, index - byte_shift)
1729                    }
1730                }
1731                SymBinOp::Add
1732                | SymBinOp::Sub
1733                | SymBinOp::Mul
1734                | SymBinOp::UDiv
1735                | SymBinOp::URem
1736                | SymBinOp::SDiv
1737                | SymBinOp::SRem
1738                | SymBinOp::Sar => None,
1739            },
1740            SymExprKind::TernOp(_, _, _, _) => None,
1741        }
1742    }
1743
1744    fn binary_byte_term(
1745        cx: &mut SymCx,
1746        left: &Self,
1747        right: &Self,
1748        index: usize,
1749        op: SymBinOp,
1750        identity: impl Fn(u8) -> bool,
1751        absorbing: impl Fn(u8) -> bool,
1752    ) -> Option<Self> {
1753        let left = left.byte_term(cx, index)?;
1754        let right = right.byte_term(cx, index)?;
1755        match (
1756            left.as_const().map(|value| value.to::<u8>()),
1757            right.as_const().map(|value| value.to::<u8>()),
1758        ) {
1759            (Some(left), _) if absorbing(left) => Some(Self::constant(cx, U256::from(left))),
1760            (_, Some(right)) if absorbing(right) => Some(Self::constant(cx, U256::from(right))),
1761            (Some(left), _) if identity(left) => Some(right),
1762            (_, Some(right)) if identity(right) => Some(left),
1763            _ => Some(Self::binop(cx, op, left, right)),
1764        }
1765    }
1766
1767    pub(crate) fn equality_forces_const(
1768        &self,
1769        value: U256,
1770        expr: &Self,
1771        context: &[SymBoolExpr],
1772    ) -> Option<U256> {
1773        if self == expr {
1774            return Some(value);
1775        }
1776        self.equality_forces_const_inner(value, expr, context)
1777    }
1778
1779    fn equality_forces_const_inner(
1780        &self,
1781        value: U256,
1782        expr: &Self,
1783        context: &[SymBoolExpr],
1784    ) -> Option<U256> {
1785        let mask = masked_expr_matches(self.kind(), expr)?;
1786        if !(value & !mask).is_zero() || !context_forces_masked_expr(context, expr, mask) {
1787            return None;
1788        }
1789        Some(value)
1790    }
1791
1792    pub(crate) fn nonzero_forces_const(
1793        &self,
1794        target: &Self,
1795        context: &[SymBoolExpr],
1796    ) -> Option<U256> {
1797        match self.kind() {
1798            SymExprKind::Const(_)
1799            | SymExprKind::Var(_)
1800            | SymExprKind::GasLeft(_)
1801            | SymExprKind::Keccak { .. }
1802            | SymExprKind::Hash { .. }
1803            | SymExprKind::Not(_) => None,
1804            SymExprKind::Ite(cond, then_expr, else_expr) => {
1805                if then_expr.eval().is_some_and(|value| !value.is_zero())
1806                    && else_expr.eval().is_some_and(|value| value.is_zero())
1807                {
1808                    cond.forces_expr_const_with_context(target, context)
1809                } else {
1810                    None
1811                }
1812            }
1813            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1814                if left.eval().is_some_and(|value| value.is_zero()) {
1815                    return right.nonzero_forces_const(target, context);
1816                }
1817                if right.eval().is_some_and(|value| value.is_zero()) {
1818                    return left.nonzero_forces_const(target, context);
1819                }
1820                None
1821            }
1822            SymExprKind::BinOp(SymBinOp::And, left, right) => {
1823                if left.eval().is_some_and(|value| !value.is_zero()) {
1824                    return right.nonzero_forces_const(target, context);
1825                }
1826                if right.eval().is_some_and(|value| !value.is_zero()) {
1827                    return left.nonzero_forces_const(target, context);
1828                }
1829                None
1830            }
1831            SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, value, shift)
1832                if shift.eval().is_some_and(|shift| shift.is_zero()) =>
1833            {
1834                value.nonzero_forces_const(target, context)
1835            }
1836            SymExprKind::TernOp(_, _, _, _) => None,
1837            SymExprKind::BinOp(_, _, _) => None,
1838        }
1839    }
1840
1841    pub(crate) fn is_raw_gasleft(&self) -> bool {
1842        matches!(self.kind(), SymExprKind::GasLeft(_))
1843    }
1844
1845    pub(crate) fn add_const(cx: &mut SymCx, expr: Self, value: U256) -> Self {
1846        if value.is_zero() {
1847            return expr;
1848        }
1849        match expr.kind() {
1850            SymExprKind::Const(expr) => Self::constant(cx, expr.wrapping_add(value)),
1851            _ => {
1852                let value = Self::constant(cx, value);
1853                Self::binop(cx, SymBinOp::Add, expr, value)
1854            }
1855        }
1856    }
1857
1858    /// Returns a constant `self - other` modulo the EVM word size when it follows from the
1859    /// expression structure on every branch.
1860    pub(crate) fn constant_difference(&self, other: &Self) -> Option<U256> {
1861        let mut differences = HashMap::default();
1862        let mut remaining = MAX_CONSTANT_DIFFERENCE_VISITS;
1863        self.constant_difference_cached(other, &mut differences, &mut remaining)
1864    }
1865
1866    fn constant_difference_cached(
1867        &self,
1868        other: &Self,
1869        differences: &mut HashMap<(Self, Self), Option<U256>>,
1870        remaining: &mut usize,
1871    ) -> Option<U256> {
1872        if self == other {
1873            return Some(U256::ZERO);
1874        }
1875        let key = (self.clone(), other.clone());
1876        if let Some(difference) = differences.get(&key) {
1877            return *difference;
1878        }
1879        *remaining = remaining.checked_sub(1)?;
1880
1881        let difference = match (self.kind(), other.kind()) {
1882            (SymExprKind::Const(left), SymExprKind::Const(right)) => {
1883                Some(left.wrapping_sub(*right))
1884            }
1885            (
1886                SymExprKind::Ite(left_condition, left_then, left_else),
1887                SymExprKind::Ite(right_condition, right_then, right_else),
1888            ) if left_condition == right_condition => {
1889                let then_difference =
1890                    left_then.constant_difference_cached(right_then, differences, remaining)?;
1891                let else_difference =
1892                    left_else.constant_difference_cached(right_else, differences, remaining)?;
1893                (then_difference == else_difference).then_some(then_difference)
1894            }
1895            (SymExprKind::BinOp(SymBinOp::Add, value, constant), _)
1896                if let Some(constant) = constant.as_const() =>
1897            {
1898                value
1899                    .constant_difference_cached(other, differences, remaining)
1900                    .map(|difference| difference.wrapping_add(constant))
1901            }
1902            (SymExprKind::BinOp(SymBinOp::Sub, value, constant), _)
1903                if let Some(constant) = constant.as_const() =>
1904            {
1905                value
1906                    .constant_difference_cached(other, differences, remaining)
1907                    .map(|difference| difference.wrapping_sub(constant))
1908            }
1909            (_, SymExprKind::BinOp(SymBinOp::Add, value, constant))
1910                if let Some(constant) = constant.as_const() =>
1911            {
1912                self.constant_difference_cached(value, differences, remaining)
1913                    .map(|difference| difference.wrapping_sub(constant))
1914            }
1915            (_, SymExprKind::BinOp(SymBinOp::Sub, value, constant))
1916                if let Some(constant) = constant.as_const() =>
1917            {
1918                self.constant_difference_cached(value, differences, remaining)
1919                    .map(|difference| difference.wrapping_add(constant))
1920            }
1921            _ => None,
1922        };
1923        differences.insert(key, difference);
1924        difference
1925    }
1926
1927    /// Visits each distinct word expression once, in depth-first, left-to-right order.
1928    pub(crate) fn visit<B>(
1929        &self,
1930        visitor: &mut impl FnMut(&Self) -> ControlFlow<B>,
1931    ) -> ControlFlow<B> {
1932        visit_unique(Some(self), None, visitor)
1933    }
1934
1935    /// Returns whether any node satisfies `visitor`, visiting each distinct node once.
1936    pub(crate) fn visit_bool(&self, mut visitor: impl FnMut(&Self) -> bool) -> bool {
1937        self.visit(&mut |expr| {
1938            if visitor(expr) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
1939        })
1940        .is_break()
1941    }
1942
1943    /// Rewrites each distinct word or nested Boolean node once in bottom-up order.
1944    ///
1945    /// The folder must return the same result for every occurrence of one hash-consed node.
1946    pub(crate) fn fold(
1947        &self,
1948        cx: &mut SymCx,
1949        folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1950    ) -> Self {
1951        let mut folded = ExpressionFoldCache::default();
1952        self.fold_cached(cx, folder, &mut folded)
1953    }
1954
1955    pub(in crate::runtime::expr) fn fold_cached<'a>(
1956        &'a self,
1957        cx: &mut SymCx,
1958        folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1959        folded: &mut ExpressionFoldCache<'a>,
1960    ) -> Self {
1961        if let Some(expr) = folded.words.get(self) {
1962            return expr.clone();
1963        }
1964
1965        let expr = match self.kind() {
1966            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => self.clone(),
1967            SymExprKind::Keccak { name, len, bytes } => {
1968                let len = len.fold_cached(cx, folder, folded);
1969                let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
1970                Self::keccak_symbol(cx, *name, len, bytes)
1971            }
1972            SymExprKind::Hash { name, algorithm, bytes } => {
1973                let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
1974                Self::hash_symbol(cx, *name, algorithm, bytes)
1975            }
1976            SymExprKind::Not(value) => {
1977                let value = value.fold_cached(cx, folder, folded);
1978                Self::not(cx, value)
1979            }
1980            SymExprKind::BinOp(op, left, right) => {
1981                let left = left.fold_cached(cx, folder, folded);
1982                let right = right.fold_cached(cx, folder, folded);
1983                Self::binop(cx, *op, left, right)
1984            }
1985            SymExprKind::TernOp(op, left, right, modulus) => {
1986                let left = left.fold_cached(cx, folder, folded);
1987                let right = right.fold_cached(cx, folder, folded);
1988                let modulus = modulus.fold_cached(cx, folder, folded);
1989                Self::ternop(cx, *op, left, right, modulus)
1990            }
1991            SymExprKind::Ite(condition, then_expr, else_expr) => {
1992                let condition = condition.fold_exprs_cached(cx, folder, folded);
1993                let then_expr = then_expr.fold_cached(cx, folder, folded);
1994                let else_expr = else_expr.fold_cached(cx, folder, folded);
1995                Self::ite(cx, condition, then_expr, else_expr)
1996            }
1997        };
1998        let expr = folder(cx, expr);
1999        folded.words.insert(self, expr.clone());
2000        expr
2001    }
2002
2003    pub(in crate::runtime::expr) fn write_smt(&self, cx: &SymCx, out: &mut String) {
2004        match self.kind() {
2005            SymExprKind::Const(value) => {
2006                let _ = write!(out, "(_ bv{value} 256)");
2007            }
2008            SymExprKind::Var(symbol)
2009            | SymExprKind::GasLeft(symbol)
2010            | SymExprKind::Keccak { name: symbol, .. }
2011            | SymExprKind::Hash { name: symbol, .. } => out.push_str(cx.symbol_name(*symbol)),
2012            SymExprKind::Not(value) => {
2013                out.push_str("(bvnot ");
2014                value.write_smt(cx, out);
2015                out.push(')');
2016            }
2017            SymExprKind::BinOp(op, left, right) => {
2018                let _ = write!(out, "({} ", op.smt());
2019                left.write_smt(cx, out);
2020                out.push(' ');
2021                right.write_smt(cx, out);
2022                out.push(')');
2023            }
2024            SymExprKind::TernOp(op, left, right, modulus) => {
2025                write_smt_wide_modular_arithmetic(cx, out, op.smt(), left, right, modulus);
2026            }
2027            SymExprKind::Ite(cond, left, right) => {
2028                out.push_str("(ite ");
2029                cond.write_smt(cx, out);
2030                out.push(' ');
2031                left.write_smt(cx, out);
2032                out.push(' ');
2033                right.write_smt(cx, out);
2034                out.push(')');
2035            }
2036        }
2037    }
2038}
2039
2040// Branchless expression rewrites are optional. Bound both the distinct DAG nodes inspected while
2041// deciding whether to rewrite and the unfolded size of the expression they could produce.
2042const MAX_BRANCHLESS_REWRITE_NODES: usize = 256;
2043const MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES: usize = 8 * 1024;
2044
2045struct UnfoldedNodeCounter {
2046    expr_nodes: HashMap<SymExpr, usize>,
2047    bool_nodes: HashMap<SymBoolExpr, usize>,
2048    remaining_unique_nodes: usize,
2049}
2050
2051impl UnfoldedNodeCounter {
2052    fn new() -> Self {
2053        Self {
2054            expr_nodes: HashMap::default(),
2055            bool_nodes: HashMap::default(),
2056            remaining_unique_nodes: MAX_BRANCHLESS_REWRITE_NODES,
2057        }
2058    }
2059
2060    /// Counts unfolded occurrences while memoizing the cost of each distinct expression node.
2061    /// Reusing a cached cost still adds every occurrence, which exposes exponential shared DAGs.
2062    fn expr_nodes(&mut self, expr: &SymExpr) -> Option<usize> {
2063        if let Some(nodes) = self.expr_nodes.get(expr) {
2064            return Some(*nodes);
2065        }
2066        if self.remaining_unique_nodes == 0 {
2067            return None;
2068        }
2069        self.remaining_unique_nodes -= 1;
2070
2071        let nodes = match expr.kind() {
2072            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => 1,
2073            SymExprKind::Keccak { len, bytes, .. } => {
2074                let mut nodes = 1usize.checked_add(self.expr_nodes(len)?)?;
2075                for byte in bytes.iter() {
2076                    nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2077                }
2078                nodes
2079            }
2080            SymExprKind::Hash { bytes, .. } => {
2081                let mut nodes = 1usize;
2082                for byte in bytes.iter() {
2083                    nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2084                }
2085                nodes
2086            }
2087            SymExprKind::Not(value) => 1usize.checked_add(self.expr_nodes(value)?)?,
2088            SymExprKind::BinOp(_, left, right) => {
2089                1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2090            }
2091            SymExprKind::TernOp(_, left, right, modulus) => 1usize
2092                .checked_add(self.expr_nodes(left)?)?
2093                .checked_add(self.expr_nodes(right)?)?
2094                .checked_add(self.expr_nodes(modulus)?)?,
2095            SymExprKind::Ite(condition, then_expr, else_expr) => 1usize
2096                .checked_add(self.bool_nodes(condition)?)?
2097                .checked_add(self.expr_nodes(then_expr)?)?
2098                .checked_add(self.expr_nodes(else_expr)?)?,
2099        };
2100        if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2101            return None;
2102        }
2103        self.expr_nodes.insert(expr.clone(), nodes);
2104        Some(nodes)
2105    }
2106
2107    fn bool_nodes(&mut self, expr: &SymBoolExpr) -> Option<usize> {
2108        if let Some(nodes) = self.bool_nodes.get(expr) {
2109            return Some(*nodes);
2110        }
2111        if self.remaining_unique_nodes == 0 {
2112            return None;
2113        }
2114        self.remaining_unique_nodes -= 1;
2115
2116        let nodes = match expr.kind() {
2117            SymBoolExprKind::Const(_) => 1,
2118            SymBoolExprKind::Not(value) => 1usize.checked_add(self.bool_nodes(value)?)?,
2119            SymBoolExprKind::And(values) => {
2120                let mut nodes = 1usize;
2121                for value in values.iter() {
2122                    nodes = nodes.checked_add(self.bool_nodes(value)?)?;
2123                }
2124                nodes
2125            }
2126            SymBoolExprKind::Cmp(_, left, right) => {
2127                1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2128            }
2129        };
2130        if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2131            return None;
2132        }
2133        self.bool_nodes.insert(expr.clone(), nodes);
2134        Some(nodes)
2135    }
2136}
2137
2138fn write_smt_wide_modular_arithmetic(
2139    cx: &SymCx,
2140    out: &mut String,
2141    op: &'static str,
2142    left: &SymExpr,
2143    right: &SymExpr,
2144    modulus: &SymExpr,
2145) {
2146    // if modulus == 0:
2147    //   0
2148    // else:
2149    //   low_256((zext(left) op zext(right)) urem zext(modulus))
2150    out.push_str("(ite (= ");
2151    modulus.write_smt(cx, out);
2152    out.push_str(" (_ bv0 256)) (_ bv0 256) ((_ extract 255 0) (bvurem (");
2153    out.push_str(op);
2154    out.push_str(" ((_ zero_extend 256) ");
2155    left.write_smt(cx, out);
2156    out.push_str(") ((_ zero_extend 256) ");
2157    right.write_smt(cx, out);
2158    out.push_str(")) ((_ zero_extend 256) ");
2159    modulus.write_smt(cx, out);
2160    out.push_str("))))");
2161}
2162
2163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2164pub(crate) enum SymTernOp {
2165    AddMod,
2166    MulMod,
2167}
2168
2169impl SymTernOp {
2170    pub(crate) const fn smt(self) -> &'static str {
2171        match self {
2172            Self::AddMod => "bvadd",
2173            Self::MulMod => "bvmul",
2174        }
2175    }
2176
2177    pub(crate) fn eval(self, left: U256, right: U256, modulus: U256) -> U256 {
2178        if modulus.is_zero() {
2179            return U256::ZERO;
2180        }
2181        match self {
2182            Self::AddMod => left.add_mod(right, modulus),
2183            Self::MulMod => left.mul_mod(right, modulus),
2184        }
2185    }
2186}
2187
2188#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2189pub(crate) enum SymBinOp {
2190    Add,
2191    Sub,
2192    Mul,
2193    UDiv,
2194    URem,
2195    SDiv,
2196    SRem,
2197    And,
2198    Or,
2199    Xor,
2200    Shl,
2201    Shr,
2202    Sar,
2203}
2204
2205impl SymBinOp {
2206    pub(crate) const fn smt(self) -> &'static str {
2207        match self {
2208            Self::Add => "bvadd",
2209            Self::Sub => "bvsub",
2210            Self::Mul => "bvmul",
2211            Self::UDiv => "bvudiv",
2212            Self::URem => "bvurem",
2213            Self::SDiv => "bvsdiv",
2214            Self::SRem => "bvsrem",
2215            Self::And => "bvand",
2216            Self::Or => "bvor",
2217            Self::Xor => "bvxor",
2218            Self::Shl => "bvshl",
2219            Self::Shr => "bvlshr",
2220            Self::Sar => "bvashr",
2221        }
2222    }
2223
2224    pub(crate) fn eval(self, left: U256, right: U256) -> U256 {
2225        match self {
2226            Self::Add => left.wrapping_add(right),
2227            Self::Sub => left.wrapping_sub(right),
2228            Self::Mul => left.wrapping_mul(right),
2229            Self::UDiv => {
2230                if right.is_zero() {
2231                    U256::ZERO
2232                } else {
2233                    left / right
2234                }
2235            }
2236            Self::URem => {
2237                if right.is_zero() {
2238                    U256::ZERO
2239                } else {
2240                    left % right
2241                }
2242            }
2243            Self::SDiv => i256_div(left, right),
2244            Self::SRem => i256_mod(left, right),
2245            Self::And => left & right,
2246            Self::Or => left | right,
2247            Self::Xor => left ^ right,
2248            Self::Shl => {
2249                if right >= U256::from(256) {
2250                    U256::ZERO
2251                } else {
2252                    left << usize::try_from(right).expect("checked word shift")
2253                }
2254            }
2255            Self::Shr => {
2256                if right >= U256::from(256) {
2257                    U256::ZERO
2258                } else {
2259                    left >> usize::try_from(right).expect("checked word shift")
2260                }
2261            }
2262            Self::Sar => {
2263                if right >= U256::from(256) {
2264                    left.arithmetic_shr(256)
2265                } else {
2266                    left.arithmetic_shr(usize::try_from(right).expect("checked word shift"))
2267                }
2268            }
2269        }
2270    }
2271}
2272
2273pub(crate) fn keccak_word(cx: &mut SymCx, bytes: Vec<SymExpr>) -> SymExpr {
2274    let len = bytes.len();
2275    let len = SymExpr::constant(cx, U256::from(len));
2276    keccak_word_with_len(cx, bytes, len)
2277}
2278
2279pub(crate) fn keccak_word_with_len(cx: &mut SymCx, bytes: Vec<SymExpr>, len: SymExpr) -> SymExpr {
2280    if let Some(len) = len.as_const()
2281        && let Ok(len) = usize::try_from(len)
2282        && len <= bytes.len()
2283        && let Ok(concrete) = concrete_expr_bytes(&bytes[..len], "symbolic keccak input")
2284    {
2285        let hash = Into::<U256>::into(keccak256(concrete));
2286        if len == 64 {
2287            cx.record_concrete_keccak_preimage(hash, bytes[..len].to_vec().into());
2288        }
2289        return SymExpr::constant(cx, hash);
2290    }
2291
2292    let exprs = bytes;
2293    let identity = ExpressionDigests::identity(std::iter::once(&len).chain(&exprs));
2294    let name = stable_symbol(cx, "keccak", identity.as_slice());
2295    SymExpr::keccak_symbol(cx, name, len, exprs)
2296}
2297
2298pub(crate) fn symbolic_hash_word_with_len(
2299    cx: &mut SymCx,
2300    algorithm: &'static str,
2301    bytes: Vec<SymExpr>,
2302    len: SymExpr,
2303) -> SymExpr {
2304    let exprs = bytes;
2305    let identity = ExpressionDigests::identity(std::iter::once(&len).chain(&exprs));
2306    let name = stable_symbol(cx, algorithm, identity.as_slice());
2307    let mut identity = Vec::with_capacity(exprs.len() + 1);
2308    identity.push(len);
2309    identity.extend(exprs);
2310    SymExpr::hash_symbol(cx, name, algorithm, identity)
2311}
2312
2313pub(crate) fn create2_address_word(
2314    cx: &mut SymCx,
2315    state: &mut PathState,
2316    creator: Address,
2317    salt: SymExpr,
2318    initcode: &SymCode,
2319) -> Result<(SymExpr, Address), SymbolicError> {
2320    match (salt.as_const(), initcode.concrete_bytes(cx, "symbolic CREATE2 initcode")) {
2321        (Some(salt), Ok(initcode)) => {
2322            let address = creator.create2_from_code(salt.to_be_bytes::<32>(), &initcode);
2323            Ok((SymExpr::constant(cx, address_word(address)), address))
2324        }
2325        (None, Ok(initcode)) => {
2326            let initcode_hash = keccak256(&initcode);
2327            let word = symbolic_create2_address_word(
2328                cx,
2329                state,
2330                format!("address:{creator:?}"),
2331                salt,
2332                format!("hash:{initcode_hash:?}"),
2333            );
2334            let address = state.world.symbolic_address_slot(word.clone());
2335            Ok((word, address))
2336        }
2337        (_, Err(SymbolicError::Unsupported("symbolic CREATE2 initcode"))) => {
2338            let initcode_bytes = initcode.read_byte_exprs(cx, 0, initcode.len());
2339            let word = symbolic_create2_address_word(
2340                cx,
2341                state,
2342                format!("address:{creator:?}"),
2343                salt,
2344                format!("initcode:{:?}", ExpressionDigests::identity(&initcode_bytes)),
2345            );
2346            let address = state.world.symbolic_address_slot(word.clone());
2347            Ok((word, address))
2348        }
2349        (_, Err(err)) => Err(err),
2350    }
2351}
2352
2353pub(crate) fn compute_create2_address_word(
2354    cx: &mut SymCx,
2355    state: &mut PathState,
2356    deployer: SymExpr,
2357    salt: SymExpr,
2358    init_code_hash: SymExpr,
2359) -> Result<SymExpr, SymbolicError> {
2360    let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2361    let salt_concrete = state.constrained_word(cx, &salt);
2362    let init_code_hash_concrete = state.constrained_word(cx, &init_code_hash);
2363
2364    if let (Some(deployer), Some(salt), Some(init_code_hash)) =
2365        (deployer_concrete, salt_concrete, init_code_hash_concrete)
2366    {
2367        let init_code_hash = B256::from(init_code_hash);
2368        let address = deployer.create2(B256::from(salt), init_code_hash);
2369        return Ok(SymExpr::constant(cx, address_word(address)));
2370    }
2371
2372    let deployer_identity = deployer_identity(deployer_concrete, &deployer);
2373    let init_code_hash_identity = init_code_hash_concrete
2374        .map(|init_code_hash| {
2375            let init_code_hash = B256::from(init_code_hash);
2376            format!("hash:{init_code_hash:?}")
2377        })
2378        .unwrap_or_else(|| {
2379            format!("hash_expr:{:?}", ExpressionDigests::identity([&init_code_hash]))
2380        });
2381
2382    Ok(symbolic_create2_address_word(cx, state, deployer_identity, salt, init_code_hash_identity))
2383}
2384
2385pub(crate) fn compute_create_address_word(
2386    cx: &mut SymCx,
2387    state: &mut PathState,
2388    deployer: SymExpr,
2389    nonce: SymExpr,
2390) -> Result<SymExpr, SymbolicError> {
2391    let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2392    let nonce_concrete = state.constrained_word(cx, &nonce);
2393
2394    if let (Some(deployer), Some(nonce)) = (deployer_concrete, nonce_concrete) {
2395        let Ok(nonce) = u64::try_from(nonce) else {
2396            return Err(SymbolicError::Unsupported("symbolic vm.computeCreateAddress nonce"));
2397        };
2398        return Ok(SymExpr::constant(cx, address_word(deployer.create(nonce))));
2399    }
2400
2401    let deployer_identity = deployer_identity(deployer_concrete, &deployer);
2402    Ok(symbolic_create_address_word(cx, state, deployer_identity, nonce))
2403}
2404
2405pub(crate) fn symbolic_create_address_word(
2406    cx: &mut SymCx,
2407    state: &mut PathState,
2408    creator_identity: String,
2409    nonce: SymExpr,
2410) -> SymExpr {
2411    let nonce = ExpressionDigests::identity([&nonce]);
2412    let name =
2413        stable_symbol(cx, "create_address", format!("{creator_identity}:{nonce:?}").as_bytes());
2414    let word = SymExpr::get_var(cx, name);
2415    state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2416    word
2417}
2418
2419pub(crate) fn symbolic_create2_address_word(
2420    cx: &mut SymCx,
2421    state: &mut PathState,
2422    creator_identity: String,
2423    salt: SymExpr,
2424    initcode_identity: String,
2425) -> SymExpr {
2426    let name = stable_symbol(
2427        cx,
2428        "create2_address",
2429        format!(
2430            "{creator_identity}:{:?}:{initcode_identity}",
2431            ExpressionDigests::identity([&salt])
2432        )
2433        .as_bytes(),
2434    );
2435    let word = SymExpr::get_var(cx, name);
2436    state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2437    word
2438}
2439
2440/// Identifies a deployer in a stable address symbol name.
2441///
2442/// The prefixes keep a concrete address apart from the digest of a symbolic deployer.
2443fn deployer_identity(concrete: Option<Address>, deployer: &SymExpr) -> String {
2444    concrete
2445        .map(|deployer| format!("address:{deployer:?}"))
2446        .unwrap_or_else(|| format!("address_expr:{:?}", ExpressionDigests::identity([deployer])))
2447}
2448
2449#[cfg(test)]
2450mod tests {
2451    use super::*;
2452
2453    // Each level reuses the previous node twice, so a walk without memoization visits 2^64 nodes.
2454    #[test]
2455    fn collect_vars_visits_shared_nodes_once() {
2456        let mut cx = SymCx::new();
2457        let x = SymExpr::var(&mut cx, "x");
2458        let y = SymExpr::var(&mut cx, "y");
2459        let mut expr = x.clone();
2460        for _ in 0..64 {
2461            let product = SymExpr::from_kind(
2462                &mut cx,
2463                SymExprKind::BinOp(SymBinOp::Mul, expr.clone(), y.clone()),
2464            );
2465            expr = SymExpr::from_kind(&mut cx, SymExprKind::BinOp(SymBinOp::Add, expr, product));
2466        }
2467        let condition = SymBoolExpr::eq_word_const(&mut cx, &expr, U256::from(7));
2468
2469        let mut vars = SymbolicVars::default();
2470        expr.collect_eval_vars(&mut vars);
2471        condition.collect_vars(&mut vars);
2472        assert_eq!(
2473            vars.iter().copied().collect::<Vec<_>>(),
2474            [x.kind().get_var().unwrap(), y.kind().get_var().unwrap(),]
2475        );
2476
2477        let mut visited = HashSet::<SymExpr>::default();
2478        let result = condition.visit_exprs::<()>(&mut |node| {
2479            assert!(visited.insert(node.clone()), "shared node visited twice");
2480            ControlFlow::Continue(())
2481        });
2482        assert!(result.is_continue());
2483        let mut visits = 0;
2484        let result = expr.visit(&mut |_| {
2485            visits += 1;
2486            if visits == 2 { ControlFlow::Break(42) } else { ControlFlow::Continue(()) }
2487        });
2488        assert_eq!(result, ControlFlow::Break(42));
2489        assert_eq!(visits, 2);
2490    }
2491}