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            if let Some(var) = expr.kind().get_eval_var() {
1462                vars.insert(var);
1463            }
1464            ControlFlow::<()>::Continue(())
1465        });
1466    }
1467
1468    pub(crate) fn known_byte(&self, index: usize) -> Option<u8> {
1469        debug_assert!(index < 32);
1470        match self.kind() {
1471            SymExprKind::Const(value) => Some(value.to_be_bytes::<32>()[index]),
1472            SymExprKind::Var(_)
1473            | SymExprKind::GasLeft(_)
1474            | SymExprKind::Keccak { .. }
1475            | SymExprKind::Hash { .. } => None,
1476            SymExprKind::Not(value) => value.known_byte(index).map(|byte| !byte),
1477            SymExprKind::Ite(_, then_expr, else_expr) => {
1478                let then_byte = then_expr.known_byte(index)?;
1479                let else_byte = else_expr.known_byte(index)?;
1480                (then_byte == else_byte).then_some(then_byte)
1481            }
1482            SymExprKind::BinOp(op, left, right) => match op {
1483                SymBinOp::And => match (left.known_byte(index), right.known_byte(index)) {
1484                    (Some(left), Some(right)) => Some(left & right),
1485                    (Some(0), _) | (_, Some(0)) => Some(0),
1486                    _ => None,
1487                },
1488                SymBinOp::Or => Some(left.known_byte(index)? | right.known_byte(index)?),
1489                SymBinOp::Xor => Some(left.known_byte(index)? ^ right.known_byte(index)?),
1490                SymBinOp::Shl => {
1491                    let shift = right.as_const()?;
1492                    if shift >= U256::from(256) {
1493                        return Some(0);
1494                    }
1495                    let shift = usize::try_from(shift).expect("checked byte shift");
1496                    if shift % 8 != 0 {
1497                        return None;
1498                    }
1499                    let source_index = index + shift / 8;
1500                    if source_index >= 32 { Some(0) } else { left.known_byte(source_index) }
1501                }
1502                SymBinOp::Shr => {
1503                    let shift = right.as_const()?;
1504                    if shift >= U256::from(256) {
1505                        return Some(0);
1506                    }
1507                    let shift = usize::try_from(shift).expect("checked byte shift");
1508                    if shift % 8 != 0 {
1509                        return None;
1510                    }
1511                    let byte_shift = shift / 8;
1512                    if index < byte_shift { Some(0) } else { left.known_byte(index - byte_shift) }
1513                }
1514                SymBinOp::Add
1515                | SymBinOp::Sub
1516                | SymBinOp::Mul
1517                | SymBinOp::UDiv
1518                | SymBinOp::URem
1519                | SymBinOp::SDiv
1520                | SymBinOp::SRem
1521                | SymBinOp::Sar => None,
1522            },
1523            SymExprKind::TernOp(_, _, _, _) => None,
1524        }
1525    }
1526
1527    pub(crate) fn known_word(&self) -> Option<U256> {
1528        let mut word = [0u8; 32];
1529        for (idx, byte) in word.iter_mut().enumerate() {
1530            *byte = self.known_byte(idx)?;
1531        }
1532        Some(U256::from_be_bytes(word))
1533    }
1534
1535    pub(crate) fn unsigned_bits(&self) -> usize {
1536        let mut bit_widths = HashMap::default();
1537        let mut remaining = usize::MAX;
1538        self.unsigned_bits_cached(&mut bit_widths, &mut remaining).unwrap_or(256)
1539    }
1540
1541    fn unsigned_bits_cached(
1542        &self,
1543        bit_widths: &mut HashMap<Self, usize>,
1544        remaining: &mut usize,
1545    ) -> Option<usize> {
1546        if let Some(bits) = bit_widths.get(self) {
1547            return Some(*bits);
1548        }
1549
1550        let mut pending = vec![(self.clone(), false)];
1551        while let Some((expr, children_visited)) = pending.pop() {
1552            if bit_widths.contains_key(&expr) {
1553                continue;
1554            }
1555            if !children_visited {
1556                if *remaining == 0 {
1557                    return None;
1558                }
1559                *remaining -= 1;
1560                pending.push((expr.clone(), true));
1561                match expr.kind() {
1562                    SymExprKind::BinOp(SymBinOp::And, left, right)
1563                        if right.as_const().is_some() =>
1564                    {
1565                        pending.push((left.clone(), false));
1566                    }
1567                    SymExprKind::BinOp(SymBinOp::Add | SymBinOp::Mul, left, right)
1568                    | SymExprKind::Ite(_, left, right) => {
1569                        pending.push((right.clone(), false));
1570                        pending.push((left.clone(), false));
1571                    }
1572                    SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, left, right)
1573                        if right
1574                            .as_const()
1575                            .and_then(|shift| usize::try_from(shift).ok())
1576                            .is_some() =>
1577                    {
1578                        pending.push((left.clone(), false));
1579                    }
1580                    SymExprKind::BinOp(SymBinOp::UDiv, left, _) => {
1581                        pending.push((left.clone(), false));
1582                    }
1583                    SymExprKind::TernOp(_, _, _, modulus) => {
1584                        pending.push((modulus.clone(), false));
1585                    }
1586                    _ => {}
1587                }
1588                continue;
1589            }
1590
1591            let bits = match expr.kind() {
1592                SymExprKind::Const(value) => value.bit_len().max(1),
1593                SymExprKind::BinOp(SymBinOp::And, left, right) => {
1594                    if let Some(mask) = right.as_const() {
1595                        bit_widths[left].min(mask.bit_len())
1596                    } else {
1597                        256
1598                    }
1599                }
1600                SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1601                    bit_widths[left].max(bit_widths[right]).saturating_add(1).min(256)
1602                }
1603                SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1604                    bit_widths[left].saturating_add(bit_widths[right]).min(256)
1605                }
1606                SymExprKind::BinOp(SymBinOp::Shl, left, right) => right
1607                    .as_const()
1608                    .and_then(|shift| usize::try_from(shift).ok())
1609                    .map_or(256, |shift| bit_widths[left].saturating_add(shift).min(256)),
1610                SymExprKind::BinOp(SymBinOp::Shr, left, right) => right
1611                    .as_const()
1612                    .and_then(|shift| usize::try_from(shift).ok())
1613                    .map_or(256, |shift| bit_widths[left].saturating_sub(shift).max(1)),
1614                SymExprKind::BinOp(SymBinOp::UDiv, left, _) => bit_widths[left],
1615                SymExprKind::TernOp(_, _, _, modulus) => bit_widths[modulus],
1616                SymExprKind::Ite(_, left, right) => bit_widths[left].max(bit_widths[right]),
1617                _ => 256,
1618            };
1619            bit_widths.insert(expr, bits);
1620        }
1621        bit_widths.get(self).copied()
1622    }
1623
1624    pub(crate) fn extracted_byte(&self, cx: &mut SymCx, index: usize) -> Self {
1625        debug_assert!(index < 32);
1626        let shift = Self::constant(cx, U256::from((31 - index) * 8));
1627        let shifted = Self::binop(cx, SymBinOp::Shr, self.clone(), shift);
1628        let mask = Self::constant(cx, U256::from(0xff));
1629        Self::binop(cx, SymBinOp::And, shifted, mask)
1630    }
1631
1632    pub(crate) fn extracted_byte_source(&self, index: usize) -> Option<Self> {
1633        let expr = self.strip_low_byte_mask();
1634        if index == 31 {
1635            return Some(expr.clone());
1636        }
1637        let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1638        let shift = shift.as_const()?;
1639        (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1640    }
1641
1642    pub(crate) fn strip_low_byte_mask(&self) -> &Self {
1643        match self.kind() {
1644            SymExprKind::BinOp(SymBinOp::And, left, right)
1645                if right.as_const() == Some(U256::from(0xff)) =>
1646            {
1647                left.strip_low_byte_mask()
1648            }
1649            _ => self,
1650        }
1651    }
1652
1653    pub(crate) fn byte_term(&self, cx: &mut SymCx, index: usize) -> Option<Self> {
1654        debug_assert!(index < 32);
1655
1656        match self.kind() {
1657            SymExprKind::Const(value) => {
1658                Some(Self::constant(cx, U256::from(value.to_be_bytes::<32>()[index])))
1659            }
1660            SymExprKind::Var(_)
1661            | SymExprKind::GasLeft(_)
1662            | SymExprKind::Keccak { .. }
1663            | SymExprKind::Hash { .. } => Some(self.extracted_byte(cx, index)),
1664            SymExprKind::Not(value) => {
1665                let value = value.byte_term(cx, index)?;
1666                Some(Self::not(cx, value))
1667            }
1668            SymExprKind::Ite(cond, then_expr, else_expr) => {
1669                let then_expr = then_expr.byte_term(cx, index)?;
1670                let else_expr = else_expr.byte_term(cx, index)?;
1671                Some(Self::ite(cx, cond.clone(), then_expr, else_expr))
1672            }
1673            SymExprKind::BinOp(op, left, right) => match op {
1674                SymBinOp::And => Self::binary_byte_term(
1675                    cx,
1676                    left,
1677                    right,
1678                    index,
1679                    SymBinOp::And,
1680                    |byte| byte == 0xff,
1681                    |byte| byte == 0,
1682                ),
1683                SymBinOp::Or => Self::binary_byte_term(
1684                    cx,
1685                    left,
1686                    right,
1687                    index,
1688                    SymBinOp::Or,
1689                    |byte| byte == 0,
1690                    |_| false,
1691                ),
1692                SymBinOp::Xor => Self::binary_byte_term(
1693                    cx,
1694                    left,
1695                    right,
1696                    index,
1697                    SymBinOp::Xor,
1698                    |byte| byte == 0,
1699                    |_| false,
1700                ),
1701                SymBinOp::Shl => {
1702                    let shift = right.eval()?;
1703                    if shift >= U256::from(256) {
1704                        return Some(Self::zero(cx));
1705                    }
1706                    let shift = usize::try_from(shift).expect("checked byte shift");
1707                    if shift % 8 != 0 {
1708                        return None;
1709                    }
1710                    let source_index = index + shift / 8;
1711                    if source_index >= 32 {
1712                        Some(Self::zero(cx))
1713                    } else {
1714                        left.byte_term(cx, source_index)
1715                    }
1716                }
1717                SymBinOp::Shr => {
1718                    let shift = right.eval()?;
1719                    if shift >= U256::from(256) {
1720                        return Some(Self::zero(cx));
1721                    }
1722                    let shift = usize::try_from(shift).expect("checked byte shift");
1723                    if shift % 8 != 0 {
1724                        return None;
1725                    }
1726                    let byte_shift = shift / 8;
1727                    if index < byte_shift {
1728                        Some(Self::zero(cx))
1729                    } else {
1730                        left.byte_term(cx, index - byte_shift)
1731                    }
1732                }
1733                SymBinOp::Add
1734                | SymBinOp::Sub
1735                | SymBinOp::Mul
1736                | SymBinOp::UDiv
1737                | SymBinOp::URem
1738                | SymBinOp::SDiv
1739                | SymBinOp::SRem
1740                | SymBinOp::Sar => None,
1741            },
1742            SymExprKind::TernOp(_, _, _, _) => None,
1743        }
1744    }
1745
1746    fn binary_byte_term(
1747        cx: &mut SymCx,
1748        left: &Self,
1749        right: &Self,
1750        index: usize,
1751        op: SymBinOp,
1752        identity: impl Fn(u8) -> bool,
1753        absorbing: impl Fn(u8) -> bool,
1754    ) -> Option<Self> {
1755        let left = left.byte_term(cx, index)?;
1756        let right = right.byte_term(cx, index)?;
1757        match (
1758            left.as_const().map(|value| value.to::<u8>()),
1759            right.as_const().map(|value| value.to::<u8>()),
1760        ) {
1761            (Some(left), _) if absorbing(left) => Some(Self::constant(cx, U256::from(left))),
1762            (_, Some(right)) if absorbing(right) => Some(Self::constant(cx, U256::from(right))),
1763            (Some(left), _) if identity(left) => Some(right),
1764            (_, Some(right)) if identity(right) => Some(left),
1765            _ => Some(Self::binop(cx, op, left, right)),
1766        }
1767    }
1768
1769    pub(crate) fn equality_forces_const(
1770        &self,
1771        value: U256,
1772        expr: &Self,
1773        context: &[SymBoolExpr],
1774    ) -> Option<U256> {
1775        if self == expr {
1776            return Some(value);
1777        }
1778        self.equality_forces_const_inner(value, expr, context)
1779    }
1780
1781    fn equality_forces_const_inner(
1782        &self,
1783        value: U256,
1784        expr: &Self,
1785        context: &[SymBoolExpr],
1786    ) -> Option<U256> {
1787        let mask = masked_expr_matches(self.kind(), expr)?;
1788        if !(value & !mask).is_zero() || !context_forces_masked_expr(context, expr, mask) {
1789            return None;
1790        }
1791        Some(value)
1792    }
1793
1794    pub(crate) fn nonzero_forces_const(
1795        &self,
1796        target: &Self,
1797        context: &[SymBoolExpr],
1798    ) -> Option<U256> {
1799        match self.kind() {
1800            SymExprKind::Const(_)
1801            | SymExprKind::Var(_)
1802            | SymExprKind::GasLeft(_)
1803            | SymExprKind::Keccak { .. }
1804            | SymExprKind::Hash { .. }
1805            | SymExprKind::Not(_) => None,
1806            SymExprKind::Ite(cond, then_expr, else_expr) => {
1807                if then_expr.eval().is_some_and(|value| !value.is_zero())
1808                    && else_expr.eval().is_some_and(|value| value.is_zero())
1809                {
1810                    cond.forces_expr_const_with_context(target, context)
1811                } else {
1812                    None
1813                }
1814            }
1815            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1816                if left.eval().is_some_and(|value| value.is_zero()) {
1817                    return right.nonzero_forces_const(target, context);
1818                }
1819                if right.eval().is_some_and(|value| value.is_zero()) {
1820                    return left.nonzero_forces_const(target, context);
1821                }
1822                None
1823            }
1824            SymExprKind::BinOp(SymBinOp::And, left, right) => {
1825                if left.eval().is_some_and(|value| !value.is_zero()) {
1826                    return right.nonzero_forces_const(target, context);
1827                }
1828                if right.eval().is_some_and(|value| !value.is_zero()) {
1829                    return left.nonzero_forces_const(target, context);
1830                }
1831                None
1832            }
1833            SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, value, shift)
1834                if shift.eval().is_some_and(|shift| shift.is_zero()) =>
1835            {
1836                value.nonzero_forces_const(target, context)
1837            }
1838            SymExprKind::TernOp(_, _, _, _) => None,
1839            SymExprKind::BinOp(_, _, _) => None,
1840        }
1841    }
1842
1843    pub(crate) fn is_raw_gasleft(&self) -> bool {
1844        matches!(self.kind(), SymExprKind::GasLeft(_))
1845    }
1846
1847    pub(crate) fn add_const(cx: &mut SymCx, expr: Self, value: U256) -> Self {
1848        if value.is_zero() {
1849            return expr;
1850        }
1851        match expr.kind() {
1852            SymExprKind::Const(expr) => Self::constant(cx, expr.wrapping_add(value)),
1853            _ => {
1854                let value = Self::constant(cx, value);
1855                Self::binop(cx, SymBinOp::Add, expr, value)
1856            }
1857        }
1858    }
1859
1860    /// Returns a constant `self - other` modulo the EVM word size when it follows from the
1861    /// expression structure on every branch.
1862    pub(crate) fn constant_difference(&self, other: &Self) -> Option<U256> {
1863        let mut differences = HashMap::default();
1864        let mut remaining = MAX_CONSTANT_DIFFERENCE_VISITS;
1865        self.constant_difference_cached(other, &mut differences, &mut remaining)
1866    }
1867
1868    fn constant_difference_cached(
1869        &self,
1870        other: &Self,
1871        differences: &mut HashMap<(Self, Self), Option<U256>>,
1872        remaining: &mut usize,
1873    ) -> Option<U256> {
1874        if self == other {
1875            return Some(U256::ZERO);
1876        }
1877        let key = (self.clone(), other.clone());
1878        if let Some(difference) = differences.get(&key) {
1879            return *difference;
1880        }
1881        *remaining = remaining.checked_sub(1)?;
1882
1883        let difference = match (self.kind(), other.kind()) {
1884            (SymExprKind::Const(left), SymExprKind::Const(right)) => {
1885                Some(left.wrapping_sub(*right))
1886            }
1887            (
1888                SymExprKind::Ite(left_condition, left_then, left_else),
1889                SymExprKind::Ite(right_condition, right_then, right_else),
1890            ) if left_condition == right_condition => {
1891                let then_difference =
1892                    left_then.constant_difference_cached(right_then, differences, remaining)?;
1893                let else_difference =
1894                    left_else.constant_difference_cached(right_else, differences, remaining)?;
1895                (then_difference == else_difference).then_some(then_difference)
1896            }
1897            (SymExprKind::BinOp(SymBinOp::Add, value, constant), _)
1898                if let Some(constant) = constant.as_const() =>
1899            {
1900                value
1901                    .constant_difference_cached(other, differences, remaining)
1902                    .map(|difference| difference.wrapping_add(constant))
1903            }
1904            (SymExprKind::BinOp(SymBinOp::Sub, value, constant), _)
1905                if let Some(constant) = constant.as_const() =>
1906            {
1907                value
1908                    .constant_difference_cached(other, differences, remaining)
1909                    .map(|difference| difference.wrapping_sub(constant))
1910            }
1911            (_, SymExprKind::BinOp(SymBinOp::Add, value, constant))
1912                if let Some(constant) = constant.as_const() =>
1913            {
1914                self.constant_difference_cached(value, differences, remaining)
1915                    .map(|difference| difference.wrapping_sub(constant))
1916            }
1917            (_, SymExprKind::BinOp(SymBinOp::Sub, value, constant))
1918                if let Some(constant) = constant.as_const() =>
1919            {
1920                self.constant_difference_cached(value, differences, remaining)
1921                    .map(|difference| difference.wrapping_add(constant))
1922            }
1923            _ => None,
1924        };
1925        differences.insert(key, difference);
1926        difference
1927    }
1928
1929    /// Visits this expression and all child expressions.
1930    pub(crate) fn visit<B>(
1931        &self,
1932        visitor: &mut impl FnMut(&Self) -> ControlFlow<B>,
1933    ) -> ControlFlow<B> {
1934        visitor(self)?;
1935        match self.kind() {
1936            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => {}
1937            SymExprKind::Keccak { len, bytes, .. } => {
1938                len.visit(visitor)?;
1939                for byte in bytes.iter() {
1940                    byte.visit(visitor)?;
1941                }
1942            }
1943            SymExprKind::Hash { bytes, .. } => {
1944                for byte in bytes.iter() {
1945                    byte.visit(visitor)?;
1946                }
1947            }
1948            SymExprKind::Not(value) => value.visit(visitor)?,
1949            SymExprKind::BinOp(_, left, right) => {
1950                left.visit(visitor)?;
1951                right.visit(visitor)?;
1952            }
1953            SymExprKind::TernOp(_, left, right, modulus) => {
1954                left.visit(visitor)?;
1955                right.visit(visitor)?;
1956                modulus.visit(visitor)?;
1957            }
1958            SymExprKind::Ite(cond, left, right) => {
1959                cond.visit_exprs(visitor)?;
1960                left.visit(visitor)?;
1961                right.visit(visitor)?;
1962            }
1963        }
1964        ControlFlow::Continue(())
1965    }
1966
1967    /// Returns whether any node satisfies `visitor`, visiting each distinct node once.
1968    pub(crate) fn visit_bool(&self, visitor: impl FnMut(&Self) -> bool) -> bool {
1969        visit_unique(Vec::new(), vec![self.clone()], visitor)
1970    }
1971
1972    /// Rewrites each distinct word or nested Boolean node once in bottom-up order.
1973    ///
1974    /// The folder must return the same result for every occurrence of one hash-consed node.
1975    pub(crate) fn fold(
1976        &self,
1977        cx: &mut SymCx,
1978        folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1979    ) -> Self {
1980        let mut folded = ExpressionFoldCache::default();
1981        self.fold_cached(cx, folder, &mut folded)
1982    }
1983
1984    pub(in crate::runtime::expr) fn fold_cached<'a>(
1985        &'a self,
1986        cx: &mut SymCx,
1987        folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1988        folded: &mut ExpressionFoldCache<'a>,
1989    ) -> Self {
1990        if let Some(expr) = folded.words.get(self) {
1991            return expr.clone();
1992        }
1993
1994        let expr = match self.kind() {
1995            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => self.clone(),
1996            SymExprKind::Keccak { name, len, bytes } => {
1997                let len = len.fold_cached(cx, folder, folded);
1998                let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
1999                Self::keccak_symbol(cx, *name, len, bytes)
2000            }
2001            SymExprKind::Hash { name, algorithm, bytes } => {
2002                let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
2003                Self::hash_symbol(cx, *name, algorithm, bytes)
2004            }
2005            SymExprKind::Not(value) => {
2006                let value = value.fold_cached(cx, folder, folded);
2007                Self::not(cx, value)
2008            }
2009            SymExprKind::BinOp(op, left, right) => {
2010                let left = left.fold_cached(cx, folder, folded);
2011                let right = right.fold_cached(cx, folder, folded);
2012                Self::binop(cx, *op, left, right)
2013            }
2014            SymExprKind::TernOp(op, left, right, modulus) => {
2015                let left = left.fold_cached(cx, folder, folded);
2016                let right = right.fold_cached(cx, folder, folded);
2017                let modulus = modulus.fold_cached(cx, folder, folded);
2018                Self::ternop(cx, *op, left, right, modulus)
2019            }
2020            SymExprKind::Ite(condition, then_expr, else_expr) => {
2021                let condition = condition.fold_exprs_cached(cx, folder, folded);
2022                let then_expr = then_expr.fold_cached(cx, folder, folded);
2023                let else_expr = else_expr.fold_cached(cx, folder, folded);
2024                Self::ite(cx, condition, then_expr, else_expr)
2025            }
2026        };
2027        let expr = folder(cx, expr);
2028        folded.words.insert(self, expr.clone());
2029        expr
2030    }
2031
2032    pub(in crate::runtime::expr) fn write_smt(&self, cx: &SymCx, out: &mut String) {
2033        match self.kind() {
2034            SymExprKind::Const(value) => {
2035                let _ = write!(out, "(_ bv{value} 256)");
2036            }
2037            SymExprKind::Var(symbol)
2038            | SymExprKind::GasLeft(symbol)
2039            | SymExprKind::Keccak { name: symbol, .. }
2040            | SymExprKind::Hash { name: symbol, .. } => out.push_str(cx.symbol_name(*symbol)),
2041            SymExprKind::Not(value) => {
2042                out.push_str("(bvnot ");
2043                value.write_smt(cx, out);
2044                out.push(')');
2045            }
2046            SymExprKind::BinOp(op, left, right) => {
2047                let _ = write!(out, "({} ", op.smt());
2048                left.write_smt(cx, out);
2049                out.push(' ');
2050                right.write_smt(cx, out);
2051                out.push(')');
2052            }
2053            SymExprKind::TernOp(op, left, right, modulus) => {
2054                write_smt_wide_modular_arithmetic(cx, out, op.smt(), left, right, modulus);
2055            }
2056            SymExprKind::Ite(cond, left, right) => {
2057                out.push_str("(ite ");
2058                cond.write_smt(cx, out);
2059                out.push(' ');
2060                left.write_smt(cx, out);
2061                out.push(' ');
2062                right.write_smt(cx, out);
2063                out.push(')');
2064            }
2065        }
2066    }
2067}
2068
2069// Branchless expression rewrites are optional. Bound both the distinct DAG nodes inspected while
2070// deciding whether to rewrite and the unfolded size of the expression they could produce.
2071const MAX_BRANCHLESS_REWRITE_NODES: usize = 256;
2072const MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES: usize = 8 * 1024;
2073
2074struct UnfoldedNodeCounter {
2075    expr_nodes: HashMap<SymExpr, usize>,
2076    bool_nodes: HashMap<SymBoolExpr, usize>,
2077    remaining_unique_nodes: usize,
2078}
2079
2080impl UnfoldedNodeCounter {
2081    fn new() -> Self {
2082        Self {
2083            expr_nodes: HashMap::default(),
2084            bool_nodes: HashMap::default(),
2085            remaining_unique_nodes: MAX_BRANCHLESS_REWRITE_NODES,
2086        }
2087    }
2088
2089    /// Counts unfolded occurrences while memoizing the cost of each distinct expression node.
2090    /// Reusing a cached cost still adds every occurrence, which exposes exponential shared DAGs.
2091    fn expr_nodes(&mut self, expr: &SymExpr) -> Option<usize> {
2092        if let Some(nodes) = self.expr_nodes.get(expr) {
2093            return Some(*nodes);
2094        }
2095        if self.remaining_unique_nodes == 0 {
2096            return None;
2097        }
2098        self.remaining_unique_nodes -= 1;
2099
2100        let nodes = match expr.kind() {
2101            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => 1,
2102            SymExprKind::Keccak { len, bytes, .. } => {
2103                let mut nodes = 1usize.checked_add(self.expr_nodes(len)?)?;
2104                for byte in bytes.iter() {
2105                    nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2106                }
2107                nodes
2108            }
2109            SymExprKind::Hash { bytes, .. } => {
2110                let mut nodes = 1usize;
2111                for byte in bytes.iter() {
2112                    nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2113                }
2114                nodes
2115            }
2116            SymExprKind::Not(value) => 1usize.checked_add(self.expr_nodes(value)?)?,
2117            SymExprKind::BinOp(_, left, right) => {
2118                1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2119            }
2120            SymExprKind::TernOp(_, left, right, modulus) => 1usize
2121                .checked_add(self.expr_nodes(left)?)?
2122                .checked_add(self.expr_nodes(right)?)?
2123                .checked_add(self.expr_nodes(modulus)?)?,
2124            SymExprKind::Ite(condition, then_expr, else_expr) => 1usize
2125                .checked_add(self.bool_nodes(condition)?)?
2126                .checked_add(self.expr_nodes(then_expr)?)?
2127                .checked_add(self.expr_nodes(else_expr)?)?,
2128        };
2129        if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2130            return None;
2131        }
2132        self.expr_nodes.insert(expr.clone(), nodes);
2133        Some(nodes)
2134    }
2135
2136    fn bool_nodes(&mut self, expr: &SymBoolExpr) -> Option<usize> {
2137        if let Some(nodes) = self.bool_nodes.get(expr) {
2138            return Some(*nodes);
2139        }
2140        if self.remaining_unique_nodes == 0 {
2141            return None;
2142        }
2143        self.remaining_unique_nodes -= 1;
2144
2145        let nodes = match expr.kind() {
2146            SymBoolExprKind::Const(_) => 1,
2147            SymBoolExprKind::Not(value) => 1usize.checked_add(self.bool_nodes(value)?)?,
2148            SymBoolExprKind::And(values) => {
2149                let mut nodes = 1usize;
2150                for value in values.iter() {
2151                    nodes = nodes.checked_add(self.bool_nodes(value)?)?;
2152                }
2153                nodes
2154            }
2155            SymBoolExprKind::Cmp(_, left, right) => {
2156                1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2157            }
2158        };
2159        if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2160            return None;
2161        }
2162        self.bool_nodes.insert(expr.clone(), nodes);
2163        Some(nodes)
2164    }
2165}
2166
2167fn write_smt_wide_modular_arithmetic(
2168    cx: &SymCx,
2169    out: &mut String,
2170    op: &'static str,
2171    left: &SymExpr,
2172    right: &SymExpr,
2173    modulus: &SymExpr,
2174) {
2175    // if modulus == 0:
2176    //   0
2177    // else:
2178    //   low_256((zext(left) op zext(right)) urem zext(modulus))
2179    out.push_str("(ite (= ");
2180    modulus.write_smt(cx, out);
2181    out.push_str(" (_ bv0 256)) (_ bv0 256) ((_ extract 255 0) (bvurem (");
2182    out.push_str(op);
2183    out.push_str(" ((_ zero_extend 256) ");
2184    left.write_smt(cx, out);
2185    out.push_str(") ((_ zero_extend 256) ");
2186    right.write_smt(cx, out);
2187    out.push_str(")) ((_ zero_extend 256) ");
2188    modulus.write_smt(cx, out);
2189    out.push_str("))))");
2190}
2191
2192#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2193pub(crate) enum SymTernOp {
2194    AddMod,
2195    MulMod,
2196}
2197
2198impl SymTernOp {
2199    pub(crate) const fn smt(self) -> &'static str {
2200        match self {
2201            Self::AddMod => "bvadd",
2202            Self::MulMod => "bvmul",
2203        }
2204    }
2205
2206    pub(crate) fn eval(self, left: U256, right: U256, modulus: U256) -> U256 {
2207        if modulus.is_zero() {
2208            return U256::ZERO;
2209        }
2210        match self {
2211            Self::AddMod => left.add_mod(right, modulus),
2212            Self::MulMod => left.mul_mod(right, modulus),
2213        }
2214    }
2215}
2216
2217#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2218pub(crate) enum SymBinOp {
2219    Add,
2220    Sub,
2221    Mul,
2222    UDiv,
2223    URem,
2224    SDiv,
2225    SRem,
2226    And,
2227    Or,
2228    Xor,
2229    Shl,
2230    Shr,
2231    Sar,
2232}
2233
2234impl SymBinOp {
2235    pub(crate) const fn smt(self) -> &'static str {
2236        match self {
2237            Self::Add => "bvadd",
2238            Self::Sub => "bvsub",
2239            Self::Mul => "bvmul",
2240            Self::UDiv => "bvudiv",
2241            Self::URem => "bvurem",
2242            Self::SDiv => "bvsdiv",
2243            Self::SRem => "bvsrem",
2244            Self::And => "bvand",
2245            Self::Or => "bvor",
2246            Self::Xor => "bvxor",
2247            Self::Shl => "bvshl",
2248            Self::Shr => "bvlshr",
2249            Self::Sar => "bvashr",
2250        }
2251    }
2252
2253    pub(crate) fn eval(self, left: U256, right: U256) -> U256 {
2254        match self {
2255            Self::Add => left.wrapping_add(right),
2256            Self::Sub => left.wrapping_sub(right),
2257            Self::Mul => left.wrapping_mul(right),
2258            Self::UDiv => {
2259                if right.is_zero() {
2260                    U256::ZERO
2261                } else {
2262                    left / right
2263                }
2264            }
2265            Self::URem => {
2266                if right.is_zero() {
2267                    U256::ZERO
2268                } else {
2269                    left % right
2270                }
2271            }
2272            Self::SDiv => i256_div(left, right),
2273            Self::SRem => i256_mod(left, right),
2274            Self::And => left & right,
2275            Self::Or => left | right,
2276            Self::Xor => left ^ right,
2277            Self::Shl => {
2278                if right >= U256::from(256) {
2279                    U256::ZERO
2280                } else {
2281                    left << usize::try_from(right).expect("checked word shift")
2282                }
2283            }
2284            Self::Shr => {
2285                if right >= U256::from(256) {
2286                    U256::ZERO
2287                } else {
2288                    left >> usize::try_from(right).expect("checked word shift")
2289                }
2290            }
2291            Self::Sar => {
2292                if right >= U256::from(256) {
2293                    left.arithmetic_shr(256)
2294                } else {
2295                    left.arithmetic_shr(usize::try_from(right).expect("checked word shift"))
2296                }
2297            }
2298        }
2299    }
2300}
2301
2302pub(crate) fn keccak_word(cx: &mut SymCx, bytes: Vec<SymExpr>) -> SymExpr {
2303    let len = bytes.len();
2304    let len = SymExpr::constant(cx, U256::from(len));
2305    keccak_word_with_len(cx, bytes, len)
2306}
2307
2308pub(crate) fn keccak_word_with_len(cx: &mut SymCx, bytes: Vec<SymExpr>, len: SymExpr) -> SymExpr {
2309    if let Some(len) = len.as_const()
2310        && let Ok(len) = usize::try_from(len)
2311        && len <= bytes.len()
2312        && let Ok(concrete) = concrete_expr_bytes(&bytes[..len], "symbolic keccak input")
2313    {
2314        let hash = Into::<U256>::into(keccak256(concrete));
2315        if len == 64 {
2316            cx.record_concrete_keccak_preimage(hash, bytes[..len].to_vec().into());
2317        }
2318        return SymExpr::constant(cx, hash);
2319    }
2320
2321    let exprs = bytes;
2322    let identity = ExpressionDigests::identity(std::iter::once(&len).chain(&exprs));
2323    let name = stable_symbol(cx, "keccak", identity.as_slice());
2324    SymExpr::keccak_symbol(cx, name, len, exprs)
2325}
2326
2327pub(crate) fn symbolic_hash_word_with_len(
2328    cx: &mut SymCx,
2329    algorithm: &'static str,
2330    bytes: Vec<SymExpr>,
2331    len: SymExpr,
2332) -> SymExpr {
2333    let exprs = bytes;
2334    let identity = ExpressionDigests::identity(std::iter::once(&len).chain(&exprs));
2335    let name = stable_symbol(cx, algorithm, identity.as_slice());
2336    let mut identity = Vec::with_capacity(exprs.len() + 1);
2337    identity.push(len);
2338    identity.extend(exprs);
2339    SymExpr::hash_symbol(cx, name, algorithm, identity)
2340}
2341
2342pub(crate) fn create2_address_word(
2343    cx: &mut SymCx,
2344    state: &mut PathState,
2345    creator: Address,
2346    salt: SymExpr,
2347    initcode: &SymCode,
2348) -> Result<(SymExpr, Address), SymbolicError> {
2349    match (salt.as_const(), initcode.concrete_bytes(cx, "symbolic CREATE2 initcode")) {
2350        (Some(salt), Ok(initcode)) => {
2351            let address = creator.create2_from_code(salt.to_be_bytes::<32>(), &initcode);
2352            Ok((SymExpr::constant(cx, address_word(address)), address))
2353        }
2354        (None, Ok(initcode)) => {
2355            let initcode_hash = keccak256(&initcode);
2356            let word = symbolic_create2_address_word(
2357                cx,
2358                state,
2359                format!("address:{creator:?}"),
2360                salt,
2361                format!("hash:{initcode_hash:?}"),
2362            );
2363            let address = state.world.symbolic_address_slot(word.clone());
2364            Ok((word, address))
2365        }
2366        (_, Err(SymbolicError::Unsupported("symbolic CREATE2 initcode"))) => {
2367            let initcode_bytes = initcode.read_byte_exprs(cx, 0, initcode.len());
2368            let word = symbolic_create2_address_word(
2369                cx,
2370                state,
2371                format!("address:{creator:?}"),
2372                salt,
2373                format!("initcode:{:?}", ExpressionDigests::identity(&initcode_bytes)),
2374            );
2375            let address = state.world.symbolic_address_slot(word.clone());
2376            Ok((word, address))
2377        }
2378        (_, Err(err)) => Err(err),
2379    }
2380}
2381
2382pub(crate) fn compute_create2_address_word(
2383    cx: &mut SymCx,
2384    state: &mut PathState,
2385    deployer: SymExpr,
2386    salt: SymExpr,
2387    init_code_hash: SymExpr,
2388) -> Result<SymExpr, SymbolicError> {
2389    let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2390    let salt_concrete = state.constrained_word(cx, &salt);
2391    let init_code_hash_concrete = state.constrained_word(cx, &init_code_hash);
2392
2393    if let (Some(deployer), Some(salt), Some(init_code_hash)) =
2394        (deployer_concrete, salt_concrete, init_code_hash_concrete)
2395    {
2396        let init_code_hash = B256::from(init_code_hash);
2397        let address = deployer.create2(B256::from(salt), init_code_hash);
2398        return Ok(SymExpr::constant(cx, address_word(address)));
2399    }
2400
2401    let deployer_identity = deployer_identity(deployer_concrete, &deployer);
2402    let init_code_hash_identity = init_code_hash_concrete
2403        .map(|init_code_hash| {
2404            let init_code_hash = B256::from(init_code_hash);
2405            format!("hash:{init_code_hash:?}")
2406        })
2407        .unwrap_or_else(|| {
2408            format!("hash_expr:{:?}", ExpressionDigests::identity([&init_code_hash]))
2409        });
2410
2411    Ok(symbolic_create2_address_word(cx, state, deployer_identity, salt, init_code_hash_identity))
2412}
2413
2414pub(crate) fn compute_create_address_word(
2415    cx: &mut SymCx,
2416    state: &mut PathState,
2417    deployer: SymExpr,
2418    nonce: SymExpr,
2419) -> Result<SymExpr, SymbolicError> {
2420    let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2421    let nonce_concrete = state.constrained_word(cx, &nonce);
2422
2423    if let (Some(deployer), Some(nonce)) = (deployer_concrete, nonce_concrete) {
2424        let Ok(nonce) = u64::try_from(nonce) else {
2425            return Err(SymbolicError::Unsupported("symbolic vm.computeCreateAddress nonce"));
2426        };
2427        return Ok(SymExpr::constant(cx, address_word(deployer.create(nonce))));
2428    }
2429
2430    let deployer_identity = deployer_identity(deployer_concrete, &deployer);
2431    Ok(symbolic_create_address_word(cx, state, deployer_identity, nonce))
2432}
2433
2434pub(crate) fn symbolic_create_address_word(
2435    cx: &mut SymCx,
2436    state: &mut PathState,
2437    creator_identity: String,
2438    nonce: SymExpr,
2439) -> SymExpr {
2440    let nonce = ExpressionDigests::identity([&nonce]);
2441    let name =
2442        stable_symbol(cx, "create_address", format!("{creator_identity}:{nonce:?}").as_bytes());
2443    let word = SymExpr::get_var(cx, name);
2444    state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2445    word
2446}
2447
2448pub(crate) fn symbolic_create2_address_word(
2449    cx: &mut SymCx,
2450    state: &mut PathState,
2451    creator_identity: String,
2452    salt: SymExpr,
2453    initcode_identity: String,
2454) -> SymExpr {
2455    let name = stable_symbol(
2456        cx,
2457        "create2_address",
2458        format!(
2459            "{creator_identity}:{:?}:{initcode_identity}",
2460            ExpressionDigests::identity([&salt])
2461        )
2462        .as_bytes(),
2463    );
2464    let word = SymExpr::get_var(cx, name);
2465    state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2466    word
2467}
2468
2469/// Identifies a deployer in a stable address symbol name.
2470///
2471/// The prefixes keep a concrete address apart from the digest of a symbolic deployer.
2472fn deployer_identity(concrete: Option<Address>, deployer: &SymExpr) -> String {
2473    concrete
2474        .map(|deployer| format!("address:{deployer:?}"))
2475        .unwrap_or_else(|| format!("address_expr:{:?}", ExpressionDigests::identity([deployer])))
2476}