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