Skip to main content

foundry_evm_symbolic/runtime/expr/
expr.rs

1use super::{hashcons::HashConsed, *};
2
3pub(crate) fn keccak_word(cx: &mut SymCx, bytes: Vec<SymExpr>) -> SymExpr {
4    let len = bytes.len();
5    let len = SymExpr::constant(cx, U256::from(len));
6    keccak_word_with_len(cx, bytes, len)
7}
8
9pub(crate) fn keccak_word_with_len(cx: &mut SymCx, bytes: Vec<SymExpr>, len: SymExpr) -> SymExpr {
10    if let Some(len) = len.as_const()
11        && let Ok(len) = usize::try_from(len)
12        && len <= bytes.len()
13        && let Ok(concrete) = concrete_expr_bytes(&bytes[..len], "symbolic keccak input")
14    {
15        let hash = U256::from_be_bytes(keccak256(concrete).0);
16        if len == 64 {
17            cx.record_concrete_keccak_preimage(hash, bytes[..len].to_vec().into());
18        }
19        return SymExpr::constant(cx, hash);
20    }
21
22    let exprs = bytes;
23    let name = stable_symbol(cx, "keccak", format!("{len:?}:{exprs:?}").as_bytes());
24    SymExpr::keccak_symbol(cx, name, len, exprs)
25}
26
27pub(crate) fn symbolic_hash_word_with_len(
28    cx: &mut SymCx,
29    algorithm: &'static str,
30    bytes: Vec<SymExpr>,
31    len: SymExpr,
32) -> SymExpr {
33    let exprs = bytes;
34    let name = stable_symbol(cx, algorithm, format!("{len:?}:{exprs:?}").as_bytes());
35    let mut identity = Vec::with_capacity(exprs.len() + 1);
36    identity.push(len);
37    identity.extend(exprs);
38    SymExpr::hash_symbol(cx, name, algorithm, identity)
39}
40
41pub(crate) fn create2_address_word(
42    cx: &mut SymCx,
43    state: &mut PathState,
44    creator: Address,
45    salt: SymExpr,
46    initcode: &SymCode,
47) -> Result<(SymExpr, Address), SymbolicError> {
48    match (salt.as_const(), initcode.concrete_bytes(cx, "symbolic CREATE2 initcode")) {
49        (Some(salt), Ok(initcode)) => {
50            let address = creator.create2_from_code(salt.to_be_bytes::<32>(), &initcode);
51            Ok((SymExpr::constant(cx, address_word(address)), address))
52        }
53        (None, Ok(initcode)) => {
54            let initcode_hash = keccak256(&initcode);
55            let word = symbolic_create2_address_word(
56                cx,
57                state,
58                format!("{creator:?}"),
59                salt,
60                format!("{initcode_hash:?}"),
61            );
62            let address = state.world.symbolic_address_slot(word.clone());
63            Ok((word, address))
64        }
65        (_, Err(SymbolicError::Unsupported("symbolic CREATE2 initcode"))) => {
66            let initcode_bytes = initcode.read_byte_exprs(cx, 0, initcode.len());
67            let word = symbolic_create2_address_word(
68                cx,
69                state,
70                format!("{creator:?}"),
71                salt,
72                format!("{initcode_bytes:?}"),
73            );
74            let address = state.world.symbolic_address_slot(word.clone());
75            Ok((word, address))
76        }
77        (_, Err(err)) => Err(err),
78    }
79}
80
81pub(crate) fn compute_create2_address_word(
82    cx: &mut SymCx,
83    state: &mut PathState,
84    deployer: SymExpr,
85    salt: SymExpr,
86    init_code_hash: SymExpr,
87) -> Result<SymExpr, SymbolicError> {
88    let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
89    let salt_concrete = state.constrained_word(cx, &salt);
90    let init_code_hash_concrete = state.constrained_word(cx, &init_code_hash);
91
92    if let (Some(deployer), Some(salt), Some(init_code_hash)) =
93        (deployer_concrete, salt_concrete, init_code_hash_concrete)
94    {
95        let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
96        let address = deployer.create2(B256::from(salt.to_be_bytes::<32>()), init_code_hash);
97        return Ok(SymExpr::constant(cx, address_word(address)));
98    }
99
100    let deployer_identity = deployer_concrete
101        .map(|deployer| format!("{deployer:?}"))
102        .unwrap_or_else(|| format!("{deployer:?}"));
103    let init_code_hash_identity = init_code_hash_concrete
104        .map(|init_code_hash| {
105            let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
106            format!("{init_code_hash:?}")
107        })
108        .unwrap_or_else(|| format!("{init_code_hash:?}"));
109
110    Ok(symbolic_create2_address_word(cx, state, deployer_identity, salt, init_code_hash_identity))
111}
112
113pub(crate) fn compute_create_address_word(
114    cx: &mut SymCx,
115    state: &mut PathState,
116    deployer: SymExpr,
117    nonce: SymExpr,
118) -> Result<SymExpr, SymbolicError> {
119    let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
120    let nonce_concrete = state.constrained_word(cx, &nonce);
121
122    if let (Some(deployer), Some(nonce)) = (deployer_concrete, nonce_concrete) {
123        let Ok(nonce) = u64::try_from(nonce) else {
124            return Err(SymbolicError::Unsupported("symbolic vm.computeCreateAddress nonce"));
125        };
126        return Ok(SymExpr::constant(cx, address_word(deployer.create(nonce))));
127    }
128
129    let deployer_identity = deployer_concrete
130        .map(|deployer| format!("{deployer:?}"))
131        .unwrap_or_else(|| format!("{deployer:?}"));
132    Ok(symbolic_create_address_word(cx, state, deployer_identity, nonce))
133}
134
135pub(crate) fn symbolic_create_address_word(
136    cx: &mut SymCx,
137    state: &mut PathState,
138    creator_identity: String,
139    nonce: SymExpr,
140) -> SymExpr {
141    let name =
142        stable_symbol(cx, "create_address", format!("{creator_identity}:{nonce:?}").as_bytes());
143    let word = SymExpr::get_var(cx, name);
144    state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
145    word
146}
147
148pub(crate) fn symbolic_create2_address_word(
149    cx: &mut SymCx,
150    state: &mut PathState,
151    creator_identity: String,
152    salt: SymExpr,
153    initcode_identity: String,
154) -> SymExpr {
155    let name = stable_symbol(
156        cx,
157        "create2_address",
158        format!("{creator_identity}:{salt:?}:{initcode_identity}").as_bytes(),
159    );
160    let word = SymExpr::get_var(cx, name);
161    state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
162    word
163}
164
165impl SymExpr {
166    pub(crate) fn select_storage_write(
167        self,
168        cx: &mut SymCx,
169        write_key: Self,
170        write_value: Self,
171        base: Self,
172    ) -> Self {
173        if write_value == base {
174            return base;
175        }
176        let condition = self.storage_key_eq(cx, &write_key);
177        match condition.as_const() {
178            Some(true) => write_value,
179            Some(false) => base,
180            None => Self::ite(cx, condition, write_value, base),
181        }
182    }
183
184    pub(crate) fn storage_key_eq(&self, cx: &mut SymCx, write_key: &Self) -> SymBoolExpr {
185        if let (Some(read_root), Some(write_root)) =
186            (self.storage_mapping_root_slot(cx), write_key.storage_mapping_root_slot(cx))
187            && read_root != write_root
188        {
189            return SymBoolExpr::constant(cx, false);
190        }
191        match (self.storage_layout_key(cx), write_key.storage_layout_key(cx)) {
192            (Some((read_base, read_offset)), Some((write_base, write_offset))) => {
193                let read_base = read_base
194                    .storage_base_eq(cx, &write_base)
195                    .unwrap_or_else(|| SymBoolExpr::eq(cx, read_base, write_base));
196                let read_offset = SymBoolExpr::eq(cx, read_offset, write_offset);
197                SymBoolExpr::and(cx, vec![read_base, read_offset])
198            }
199            (Some(_), None) if write_key.as_const().is_some() => SymBoolExpr::constant(cx, false),
200            (None, Some(_)) if self.as_const().is_some() => SymBoolExpr::constant(cx, false),
201            _ => SymBoolExpr::eq(cx, self.clone(), write_key.clone()),
202        }
203    }
204
205    fn storage_base_eq(&self, cx: &mut SymCx, other: &Self) -> Option<SymBoolExpr> {
206        let read = self.storage_mapping_key(cx)?;
207        let write = other.storage_mapping_key(cx)?;
208
209        let key_eq = storage_mapping_key_eq(cx, &read, &write);
210        let slot_eq = read
211            .slot
212            .storage_base_eq(cx, &write.slot)
213            .unwrap_or_else(|| SymBoolExpr::eq(cx, read.slot, write.slot));
214        Some(SymBoolExpr::and(cx, vec![key_eq, slot_eq]))
215    }
216
217    fn storage_mapping_key(&self, cx: &mut SymCx) -> Option<StorageMappingKey> {
218        let bytes = self.storage_mapping_key_bytes(cx)?;
219        let key_bytes = &bytes[..32];
220        let preserve_key_bytes =
221            (!storage_mapping_key_bytes_form_compact_word(key_bytes)).then(|| key_bytes.to_vec());
222        let key = Self::from_bytes(cx, key_bytes.iter().cloned());
223        let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
224        Some(StorageMappingKey { key, key_bytes: preserve_key_bytes, slot })
225    }
226
227    fn storage_mapping_root_slot(&self, cx: &mut SymCx) -> Option<U256> {
228        let bytes = self.storage_mapping_key_bytes(cx)?;
229        let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
230        match slot.kind() {
231            SymExprKind::Const(value) if cx.concrete_keccak_preimage(*value).is_some() => {
232                slot.storage_mapping_root_slot(cx)
233            }
234            SymExprKind::Const(slot) => Some(*slot),
235            SymExprKind::Keccak { .. } => slot.storage_mapping_root_slot(cx),
236            _ => None,
237        }
238    }
239
240    fn storage_mapping_key_bytes(&self, cx: &SymCx) -> Option<Arc<[Self]>> {
241        match self.kind() {
242            SymExprKind::Keccak { len, bytes, .. }
243                if len.as_const() == Some(U256::from(64)) && bytes.len() >= 64 =>
244            {
245                Some(bytes.clone())
246            }
247            SymExprKind::Const(hash) => cx.concrete_keccak_preimage(*hash),
248            _ => None,
249        }
250    }
251
252    fn storage_layout_key(&self, cx: &mut SymCx) -> Option<(Self, Self)> {
253        match self.kind() {
254            SymExprKind::Keccak { .. } => Some((self.clone(), Self::zero(cx))),
255            SymExprKind::Const(hash) if cx.concrete_keccak_preimage(*hash).is_some() => {
256                Some((self.clone(), Self::zero(cx)))
257            }
258            SymExprKind::BinOp(SymBinOp::Add, left, right) => {
259                if let Some((base, offset)) = left.storage_layout_key(cx)
260                    && !right.contains_keccak()
261                {
262                    let offset = Self::binop(cx, SymBinOp::Add, offset, right.clone());
263                    return Some((base, offset));
264                }
265                if let Some((base, offset)) = right.storage_layout_key(cx)
266                    && !left.contains_keccak()
267                {
268                    let offset = Self::binop(cx, SymBinOp::Add, offset, left.clone());
269                    return Some((base, offset));
270                }
271                None
272            }
273            _ => None,
274        }
275    }
276}
277
278struct StorageMappingKey {
279    key: SymExpr,
280    key_bytes: Option<Vec<SymExpr>>,
281    slot: SymExpr,
282}
283
284fn storage_mapping_key_eq(
285    cx: &mut SymCx,
286    read: &StorageMappingKey,
287    write: &StorageMappingKey,
288) -> SymBoolExpr {
289    if read.key_bytes.is_some() || write.key_bytes.is_some() {
290        let read_owned;
291        let read_bytes = if let Some(bytes) = read.key_bytes.as_deref() {
292            bytes
293        } else {
294            read_owned = read.key.clone().into_byte_exprs(cx);
295            &read_owned
296        };
297        let write_owned;
298        let write_bytes = if let Some(bytes) = write.key_bytes.as_deref() {
299            bytes
300        } else {
301            write_owned = write.key.clone().into_byte_exprs(cx);
302            &write_owned
303        };
304        let byte_equalities = read_bytes
305            .iter()
306            .zip(write_bytes)
307            .map(|(read, write)| {
308                let read = read.byte_term(cx, 31).unwrap_or_else(|| read.clone().low_byte(cx));
309                let write = write.byte_term(cx, 31).unwrap_or_else(|| write.clone().low_byte(cx));
310                SymBoolExpr::eq(cx, read, write)
311            })
312            .collect();
313        SymBoolExpr::and(cx, byte_equalities)
314    } else {
315        SymBoolExpr::eq(cx, read.key.clone(), write.key.clone())
316    }
317}
318
319fn storage_mapping_key_bytes_form_compact_word(bytes: &[SymExpr]) -> bool {
320    bytes.iter().all(|byte| byte.as_const().is_some()) || word_from_extracted_bytes(bytes).is_some()
321}
322
323fn masked_expr_matches(candidate: &SymExprKind, target: &SymExpr) -> Option<U256> {
324    match candidate {
325        SymExprKind::BinOp(SymBinOp::And, left, right) if left == target => right.eval(),
326        SymExprKind::BinOp(SymBinOp::And, left, right) if right == target => left.eval(),
327        _ => None,
328    }
329}
330
331fn context_forces_masked_expr(context: &[SymBoolExpr], target: &SymExpr, mask: U256) -> bool {
332    context.iter().any(|condition| match condition.kind() {
333        SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
334            (left == target && masked_expr_matches(right.kind(), target) == Some(mask))
335                || (right == target && masked_expr_matches(left.kind(), target) == Some(mask))
336        }
337        SymBoolExprKind::And(values) => context_forces_masked_expr(values, target, mask),
338        _ => false,
339    })
340}
341
342pub(crate) fn concrete_expr_bytes(
343    bytes: &[SymExpr],
344    reason: &'static str,
345) -> Result<Vec<u8>, SymbolicError> {
346    bytes
347        .iter()
348        .map(|byte| match byte.as_const() {
349            Some(value) => Ok(value.to::<u8>()),
350            None => Err(SymbolicError::Unsupported(reason)),
351        })
352        .collect()
353}
354
355pub(crate) fn mask_low_bits(mask: U256) -> Option<usize> {
356    let bits = mask.bit_len();
357    (mask == mask_bits(U256::MAX, bits)).then_some(bits)
358}
359
360fn power_of_two_shift(value: U256) -> Option<usize> {
361    if value <= U256::ONE || !value.is_power_of_two() {
362        return None;
363    }
364    Some(value.bit_len() - 1)
365}
366
367pub(in crate::runtime::expr) fn low_masked_source(expr: &SymExpr, bits: usize) -> Option<&SymExpr> {
368    match expr.kind() {
369        // `a & low_mask => a`.
370        SymExprKind::BinOp(SymBinOp::And, left, right)
371            if right.as_const().and_then(mask_low_bits) == Some(bits) =>
372        {
373            Some(left)
374        }
375        _ => None,
376    }
377}
378
379pub(in crate::runtime::expr) fn low_masked_source_any(expr: &SymExpr) -> Option<&SymExpr> {
380    match expr.kind() {
381        // `a & low_mask => a`.
382        SymExprKind::BinOp(SymBinOp::And, left, right)
383            if right.as_const().and_then(mask_low_bits).is_some() =>
384        {
385            Some(left)
386        }
387        _ => None,
388    }
389}
390
391fn word_from_extracted_bytes(bytes: &[SymExpr]) -> Option<SymExpr> {
392    if bytes.len() < 32 {
393        return None;
394    }
395
396    let source = bytes
397        .iter()
398        .take(32)
399        .enumerate()
400        .find_map(|(idx, byte)| byte.extracted_byte_source(idx))?;
401
402    for (idx, byte) in bytes.iter().take(32).enumerate() {
403        if let Some(byte_source) = byte.extracted_byte_source(idx) {
404            if byte_source != source {
405                return None;
406            }
407            continue;
408        }
409
410        let byte = byte.as_const()?;
411        if source.known_byte(idx) != Some(byte.to::<u8>()) {
412            return None;
413        }
414    }
415    Some(source)
416}
417
418#[derive(Clone, PartialEq, Eq, Hash)]
419pub(crate) struct SymExpr {
420    pub(in crate::runtime::expr) kind: HashConsed<SymExprKind>,
421}
422
423impl fmt::Debug for SymExpr {
424    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425        self.kind().fmt(f)
426    }
427}
428
429#[derive(Clone, Debug, PartialEq, Eq, Hash)]
430pub(in crate::runtime) enum SymExprKind {
431    Const(U256),
432    Var(Symbol),
433    GasLeft(Symbol),
434    Keccak { name: Symbol, len: SymExpr, bytes: Arc<[SymExpr]> },
435    Hash { name: Symbol, algorithm: &'static str, bytes: Arc<[SymExpr]> },
436    Not(SymExpr),
437    BinOp(SymBinOp, SymExpr, SymExpr),
438    TernOp(SymTernOp, SymExpr, SymExpr, SymExpr),
439    Ite(SymBoolExpr, SymExpr, SymExpr),
440}
441
442impl SymExprKind {
443    pub(in crate::runtime) const fn get_var(&self) -> Option<Symbol> {
444        match self {
445            Self::Var(symbol)
446            | Self::GasLeft(symbol)
447            | Self::Keccak { name: symbol, .. }
448            | Self::Hash { name: symbol, .. } => Some(*symbol),
449            _ => None,
450        }
451    }
452
453    pub(in crate::runtime) const fn get_eval_var(&self) -> Option<Symbol> {
454        match self {
455            Self::Var(symbol) | Self::GasLeft(symbol) | Self::Hash { name: symbol, .. } => {
456                Some(*symbol)
457            }
458            _ => None,
459        }
460    }
461}
462
463impl SymExpr {
464    pub(in crate::runtime) fn kind(&self) -> &SymExprKind {
465        self.kind.value()
466    }
467
468    #[cfg(test)]
469    pub(crate) fn get_var_name<'a>(&self, cx: &'a SymCx) -> Option<&'a str> {
470        self.kind().get_var().map(|symbol| cx.symbol_name(symbol))
471    }
472
473    #[cfg(test)]
474    pub(crate) fn is_keccak(&self) -> bool {
475        matches!(self.kind(), SymExprKind::Keccak { .. })
476    }
477
478    #[cfg(test)]
479    pub(crate) fn keccak_len_and_byte_count(&self) -> Option<(&Self, usize)> {
480        match self.kind() {
481            SymExprKind::Keccak { len, bytes, .. } => Some((len, bytes.len())),
482            _ => None,
483        }
484    }
485
486    #[cfg(test)]
487    pub(crate) fn hash_algorithm(&self) -> Option<&'static str> {
488        match self.kind() {
489            SymExprKind::Hash { algorithm, .. } => Some(algorithm),
490            _ => None,
491        }
492    }
493
494    pub(in crate::runtime) fn into_kind(self) -> SymExprKind {
495        self.kind.into_value()
496    }
497
498    pub(in crate::runtime) fn from_kind(cx: &mut SymCx, kind: SymExprKind) -> Self {
499        cx.mk_expr_kind(kind)
500    }
501
502    pub(crate) fn zero(cx: &mut SymCx) -> Self {
503        Self::constant(cx, U256::ZERO)
504    }
505
506    pub(crate) fn one(cx: &mut SymCx) -> Self {
507        Self::constant(cx, U256::ONE)
508    }
509
510    pub(crate) fn constant(cx: &mut SymCx, value: U256) -> Self {
511        if value.is_zero() {
512            return cx.cached_zero();
513        }
514        if value == U256::ONE {
515            return cx.cached_one();
516        }
517        Self::from_kind(cx, SymExprKind::Const(value))
518    }
519
520    pub(crate) fn var(cx: &mut SymCx, name: &str) -> Self {
521        let symbol = cx.intern(name);
522        Self::get_var(cx, symbol)
523    }
524
525    pub(crate) fn get_var(cx: &mut SymCx, symbol: Symbol) -> Self {
526        Self::from_kind(cx, SymExprKind::Var(symbol))
527    }
528
529    pub(crate) fn gas_left(cx: &mut SymCx, id: usize) -> Self {
530        let symbol = cx.intern(&format!("gasleft_{id}"));
531        Self::from_kind(cx, SymExprKind::GasLeft(symbol))
532    }
533
534    pub(crate) fn not(cx: &mut SymCx, value: Self) -> Self {
535        match value.kind() {
536            SymExprKind::Const(value) => Self::constant(cx, !*value),
537            SymExprKind::Not(value) => value.clone(),
538            _ => Self::from_kind(cx, SymExprKind::Not(value)),
539        }
540    }
541
542    pub(crate) fn binop(cx: &mut SymCx, binop: SymBinOp, left: Self, right: Self) -> Self {
543        match binop {
544            SymBinOp::Add => match (left.kind(), right.kind()) {
545                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
546                    // `const + const => const`.
547                    Self::constant(cx, binop.eval(*left_value, *right_value))
548                }
549                // `0 + a => a`.
550                (SymExprKind::Const(value), _) if value.is_zero() => right,
551                // `a + 0 => a`.
552                (_, SymExprKind::Const(value)) if value.is_zero() => left,
553                _ => Self::commutative_binop(cx, binop, left, right),
554            },
555            SymBinOp::Sub => match (left.kind(), right.kind()) {
556                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
557                    // `const - const => const`.
558                    Self::constant(cx, binop.eval(*left_value, *right_value))
559                }
560                // `a - 0 => a`.
561                (_, SymExprKind::Const(value)) if value.is_zero() => left,
562                // `a - a => 0`.
563                _ if left == right => Self::zero(cx),
564                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
565            },
566            SymBinOp::Mul => match (left.kind(), right.kind()) {
567                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
568                    // `const * const => const`.
569                    Self::constant(cx, binop.eval(*left_value, *right_value))
570                }
571                // `0 * a => 0`.
572                (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
573                    if value.is_zero() =>
574                {
575                    Self::zero(cx)
576                }
577                // `1 * a => a`.
578                (SymExprKind::Const(value), _) if *value == U256::ONE => right,
579                // `a * 1 => a`.
580                (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
581                // `2**n * a => a << n`.
582                (SymExprKind::Const(value), _) if let Some(shift) = power_of_two_shift(*value) => {
583                    let shift = Self::constant(cx, U256::from(shift));
584                    Self::binop(cx, SymBinOp::Shl, right, shift)
585                }
586                // `a * 2**n => a << n`.
587                (_, SymExprKind::Const(value)) if let Some(shift) = power_of_two_shift(*value) => {
588                    let shift = Self::constant(cx, U256::from(shift));
589                    Self::binop(cx, SymBinOp::Shl, left, shift)
590                }
591                _ => Self::commutative_binop(cx, binop, left, right),
592            },
593            SymBinOp::UDiv | SymBinOp::SDiv => match (left.kind(), right.kind()) {
594                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
595                    // `const / const => const`.
596                    Self::constant(cx, binop.eval(*left_value, *right_value))
597                }
598                // `a / 0 => 0`.
599                (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
600                // `a / 1 => a`.
601                (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
602                // `(a - (a & (2**n - 1))) u/ 2**n => a >> n`.
603                (
604                    SymExprKind::BinOp(SymBinOp::Sub, value, low_bits),
605                    SymExprKind::Const(divisor),
606                ) if binop == SymBinOp::UDiv
607                    && let Some(shift) = power_of_two_shift(*divisor)
608                    && low_masked_source(low_bits, shift) == Some(value) =>
609                {
610                    let shift = Self::constant(cx, U256::from(shift));
611                    Self::binop(cx, SymBinOp::Shr, value.clone(), shift)
612                }
613                // `a u/ 2**n => a >> n`.
614                (_, SymExprKind::Const(divisor))
615                    if binop == SymBinOp::UDiv
616                        && let Some(shift) = power_of_two_shift(*divisor) =>
617                {
618                    let shift = Self::constant(cx, U256::from(shift));
619                    Self::binop(cx, SymBinOp::Shr, left, shift)
620                }
621                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
622            },
623            SymBinOp::URem | SymBinOp::SRem => match (left.kind(), right.kind()) {
624                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
625                    // `const % const => const`.
626                    Self::constant(cx, binop.eval(*left_value, *right_value))
627                }
628                // `a % 0 => 0`.
629                (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
630                // `a % 1 => 0`.
631                (_, SymExprKind::Const(value)) if *value == U256::ONE => Self::zero(cx),
632                // `a u% 2**n => a & (2**n - 1)`.
633                (_, SymExprKind::Const(divisor))
634                    if binop == SymBinOp::URem
635                        && let Some(bits) = power_of_two_shift(*divisor) =>
636                {
637                    Self::and_const(cx, left, mask_bits(U256::MAX, bits))
638                }
639                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
640            },
641            SymBinOp::And => match (left.kind(), right.kind()) {
642                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
643                    // `const & const => const`.
644                    Self::constant(cx, binop.eval(*left_value, *right_value))
645                }
646                // `0 & a => 0`.
647                (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
648                    if value.is_zero() =>
649                {
650                    Self::zero(cx)
651                }
652                // `MAX & a => a`.
653                (SymExprKind::Const(value), _) if *value == U256::MAX => right,
654                // `a & MAX => a`.
655                (_, SymExprKind::Const(value)) if *value == U256::MAX => left,
656                // `a & a => a`.
657                _ if left == right => left,
658                (SymExprKind::Const(mask), _) => Self::and_const(cx, right, *mask),
659                (_, SymExprKind::Const(mask)) => Self::and_const(cx, left, *mask),
660                _ => Self::commutative_binop(cx, binop, left, right),
661            },
662            SymBinOp::Or => match (left.kind(), right.kind()) {
663                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
664                    // `const | const => const`.
665                    Self::constant(cx, binop.eval(*left_value, *right_value))
666                }
667                // `0 | a => a`.
668                (SymExprKind::Const(value), _) if value.is_zero() => right,
669                // `a | 0 => a`.
670                (_, SymExprKind::Const(value)) if value.is_zero() => left,
671                // `a | a => a`.
672                _ if left == right => left,
673                _ => Self::or(cx, left, right),
674            },
675            SymBinOp::Xor => match (left.kind(), right.kind()) {
676                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
677                    // `const ^ const => const`.
678                    Self::constant(cx, binop.eval(*left_value, *right_value))
679                }
680                // `0 ^ a => a`.
681                (SymExprKind::Const(value), _) if value.is_zero() => right,
682                // `a ^ 0 => a`.
683                (_, SymExprKind::Const(value)) if value.is_zero() => left,
684                // `a ^ a => 0`.
685                _ if left == right => Self::zero(cx),
686                _ => Self::commutative_binop(cx, binop, left, right),
687            },
688            SymBinOp::Shl => match (left.kind(), right.kind()) {
689                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
690                    // `const << const => const`.
691                    Self::constant(cx, binop.eval(*left_value, *right_value))
692                }
693                // `a << 0 => a`.
694                (_, SymExprKind::Const(value)) if value.is_zero() => left,
695                // `0 << a => 0`.
696                (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
697                // `a << 256 => 0`.
698                (_, SymExprKind::Const(value)) if *value >= U256::from(256) => Self::zero(cx),
699                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
700            },
701            SymBinOp::Shr => match (left.kind(), right.kind()) {
702                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
703                    // `const >> const => const`.
704                    Self::constant(cx, binop.eval(*left_value, *right_value))
705                }
706                // `a >> 0 => a`.
707                (_, SymExprKind::Const(value)) if value.is_zero() => left,
708                // `0 >> a => 0`.
709                (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
710                (_, SymExprKind::Const(value)) => Self::shr_const(cx, left, *value),
711                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
712            },
713            SymBinOp::Sar => match (left.kind(), right.kind()) {
714                (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
715                    // `const >>s const => const`.
716                    Self::constant(cx, binop.eval(*left_value, *right_value))
717                }
718                // `a >>s 0 => a`.
719                (_, SymExprKind::Const(value)) if value.is_zero() => left,
720                _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
721            },
722        }
723    }
724
725    pub(crate) fn ternop(
726        cx: &mut SymCx,
727        ternop: SymTernOp,
728        left: Self,
729        right: Self,
730        modulus: Self,
731    ) -> Self {
732        match (left.kind(), right.kind(), modulus.kind()) {
733            (_, _, SymExprKind::Const(modulus)) if modulus.is_zero() || *modulus == U256::ONE => {
734                // `addmod/mulmod(a, b, 0) => 0`.
735                Self::zero(cx)
736            }
737            (SymExprKind::Const(left), SymExprKind::Const(right), SymExprKind::Const(modulus)) => {
738                // `addmod/mulmod(const, const, const) => const`.
739                Self::constant(cx, ternop.eval(*left, *right, *modulus))
740            }
741            // `addmod/mulmod(a, b, 2**n) => op(a, b) & (2**n - 1)`.
742            (_, _, SymExprKind::Const(modulus))
743                if let Some(bits) = power_of_two_shift(*modulus) =>
744            {
745                let binop = match ternop {
746                    SymTernOp::AddMod => SymBinOp::Add,
747                    SymTernOp::MulMod => SymBinOp::Mul,
748                };
749                let value = Self::binop(cx, binop, left, right);
750                Self::and_const(cx, value, mask_bits(U256::MAX, bits))
751            }
752            _ => {
753                // `addmod/mulmod(a, b, m) => addmod/mulmod(ordered(a, b), m)`.
754                let (left, right) = Self::ordered_commutative_operands(left, right);
755                Self::from_kind(cx, SymExprKind::TernOp(ternop, left, right, modulus))
756            }
757        }
758    }
759
760    pub(crate) fn ite(
761        cx: &mut SymCx,
762        condition: SymBoolExpr,
763        then_expr: Self,
764        else_expr: Self,
765    ) -> Self {
766        match condition.as_const() {
767            // `ite(true, a, b) => a`.
768            Some(true) => then_expr,
769            // `ite(false, a, b) => b`.
770            Some(false) => else_expr,
771            // `ite(c, a, a) => a`.
772            None if then_expr == else_expr => then_expr,
773            // `ite(a == 0, 0, a / a) => a != 0`.
774            None if then_expr.as_const().is_some_and(|value| value.is_zero())
775                && Self::self_div_expr_matches_zero_check(&condition, &else_expr) =>
776            {
777                let condition = condition.not(cx);
778                Self::bool_word(cx, condition)
779            }
780            // `ite(c, 1, bool_word(c)) => bool_word(c)`.
781            None if then_expr.as_const() == Some(U256::ONE)
782                && else_expr.bool_word_condition().as_ref() == Some(&condition) =>
783            {
784                else_expr
785            }
786            // `ite(c, bool_word(c), 0) => bool_word(c)`.
787            None if else_expr.as_const().is_some_and(|value| value.is_zero())
788                && then_expr.bool_word_condition().as_ref() == Some(&condition) =>
789            {
790                then_expr
791            }
792            None => Self::from_kind(cx, SymExprKind::Ite(condition, then_expr, else_expr)),
793        }
794    }
795
796    pub(crate) fn bool_word(cx: &mut SymCx, value: SymBoolExpr) -> Self {
797        let one = Self::one(cx);
798        let zero = Self::zero(cx);
799        Self::ite(cx, value, one, zero)
800    }
801
802    fn self_div_expr_matches_zero_check(cond: &SymBoolExpr, expr: &Self) -> bool {
803        let Some(zero_operand) = cond.zero_check_operand() else { return false };
804        let Some((numerator, denominator)) = expr.udiv_operands() else { return false };
805        numerator == zero_operand && denominator == zero_operand
806    }
807
808    pub(crate) fn keccak_symbol(cx: &mut SymCx, name: Symbol, len: Self, bytes: Vec<Self>) -> Self {
809        Self::from_kind(cx, SymExprKind::Keccak { name, len, bytes: bytes.into() })
810    }
811
812    pub(crate) fn hash_symbol(
813        cx: &mut SymCx,
814        name: Symbol,
815        algorithm: &'static str,
816        bytes: Vec<Self>,
817    ) -> Self {
818        Self::from_kind(cx, SymExprKind::Hash { name, algorithm, bytes: bytes.into() })
819    }
820
821    fn or(cx: &mut SymCx, left: Self, right: Self) -> Self {
822        if let Some(rebuilt) = Self::rebuild_from_or_terms(&left, &right) {
823            // `byte_parts(a) | byte_parts(a) => a`.
824            return rebuilt;
825        }
826        Self::commutative_binop(cx, SymBinOp::Or, left, right)
827    }
828
829    fn commutative_binop(cx: &mut SymCx, op: SymBinOp, left: Self, right: Self) -> Self {
830        // `a + b => b + a`.
831        let (left, right) = Self::ordered_commutative_operands(left, right);
832        Self::from_kind(cx, SymExprKind::BinOp(op, left, right))
833    }
834
835    pub(in crate::runtime::expr) fn ordered_commutative_operands(
836        left: Self,
837        right: Self,
838    ) -> (Self, Self) {
839        match left.complexity().cmp(&right.complexity()) {
840            // Put less complex operands, like constants, on the RHS.
841            std::cmp::Ordering::Less => (right, left),
842            std::cmp::Ordering::Greater => (left, right),
843            std::cmp::Ordering::Equal if right.kind.stable_hash_cmp(&left.kind).is_lt() => {
844                (right, left)
845            }
846            std::cmp::Ordering::Equal => (left, right),
847        }
848    }
849
850    fn complexity(&self) -> usize {
851        match self.kind() {
852            SymExprKind::Const(_) => 0,
853            SymExprKind::Not(_) => 1,
854            SymExprKind::BinOp(..) => 2,
855            SymExprKind::TernOp(..) => 3,
856            _ => 4,
857        }
858    }
859
860    fn and_const(cx: &mut SymCx, expr: Self, mask: U256) -> Self {
861        if mask.is_zero() {
862            // `a & 0 => 0`.
863            return Self::zero(cx);
864        }
865        if mask == U256::MAX {
866            // `a & MAX => a`.
867            return expr;
868        }
869
870        match expr.kind() {
871            // `const & mask => const`.
872            SymExprKind::Const(value) => Self::constant(cx, *value & mask),
873            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
874                // `(a | b) & mask => (a & mask) | (b & mask)`.
875                let left = Self::and_const(cx, left.clone(), mask);
876                let right = Self::and_const(cx, right.clone(), mask);
877                Self::binop(cx, SymBinOp::Or, left, right)
878            }
879            SymExprKind::BinOp(SymBinOp::Shl, _, shift)
880                if mask_low_bits(mask).is_some_and(|bits| {
881                    shift
882                        .as_const()
883                        .and_then(|shift| usize::try_from(shift).ok())
884                        .is_some_and(|shift| bits <= shift)
885                }) =>
886            {
887                // `(a << n) & low_mask(n) => 0`.
888                Self::zero(cx)
889            }
890            SymExprKind::BinOp(SymBinOp::And, left, right) => {
891                if right.as_const() == Some(mask) {
892                    // `(a & mask) & mask => a & mask`.
893                    Self::and_const(cx, left.clone(), mask)
894                } else if left == right {
895                    // `(a & a) & mask => a & mask`.
896                    Self::and_const(cx, left.clone(), mask)
897                } else {
898                    let mask = Self::constant(cx, mask);
899                    Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
900                }
901            }
902            _ => {
903                let mask = Self::constant(cx, mask);
904                Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
905            }
906        }
907    }
908
909    fn shr_const(cx: &mut SymCx, expr: Self, shift: U256) -> Self {
910        if shift.is_zero() {
911            // `a >> 0 => a`.
912            return expr;
913        }
914        if shift >= U256::from(256) {
915            // `a >> 256 => 0`.
916            return Self::zero(cx);
917        }
918
919        let shift = usize::try_from(shift).expect("shift is less than 256");
920        if expr.unsigned_bits() <= shift {
921            // `small(a) >> bits(a) => 0`.
922            return Self::zero(cx);
923        }
924
925        if let SymExprKind::BinOp(SymBinOp::Shl, inner, left_shift) = expr.kind()
926            && left_shift.as_const() == Some(U256::from(shift))
927            && inner.unsigned_bits() <= 256 - shift
928        {
929            // `(a << n) >> n => a`.
930            return inner.clone();
931        }
932
933        if let SymExprKind::BinOp(SymBinOp::Or, left, right) = expr.kind() {
934            // Distribute only when it collapses part of the OR; expanding broad
935            // bit-smearing chains eagerly makes SMT CSE much larger.
936            let left = Self::shr_const(cx, left.clone(), U256::from(shift));
937            let right = Self::shr_const(cx, right.clone(), U256::from(shift));
938            if left.as_const().is_some_and(|value| value.is_zero()) {
939                return right;
940            }
941            if right.as_const().is_some_and(|value| value.is_zero()) {
942                return left;
943            }
944        }
945
946        let shift = Self::constant(cx, U256::from(shift));
947        Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::Shr, expr, shift))
948    }
949
950    fn rebuild_from_or_terms(left: &Self, right: &Self) -> Option<Self> {
951        let mut terms = Vec::new();
952        left.push_or_terms(&mut terms);
953        right.push_or_terms(&mut terms);
954        Self::rebuild_from_extracted_byte_terms(&terms)
955            .or_else(|| Self::rebuild_from_shifted_word_fragments(&terms))
956    }
957
958    pub(in crate::runtime) fn push_or_terms<'a>(&'a self, terms: &mut Vec<&'a Self>) {
959        match self.kind() {
960            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
961                left.push_or_terms(terms);
962                right.push_or_terms(terms);
963            }
964            _ => terms.push(self),
965        }
966    }
967
968    fn rebuild_from_extracted_byte_terms(terms: &[&Self]) -> Option<Self> {
969        if terms.len() <= 1 {
970            return None;
971        }
972
973        let mut source = None;
974        let mut seen = [false; 32];
975        for term in terms {
976            if term.as_const().is_some_and(|value| value.is_zero()) {
977                continue;
978            }
979            let (term_source, index) = term.extracted_shifted_byte_term()?;
980            match &source {
981                Some(source) if source != &term_source => return None,
982                Some(_) => {}
983                None => source = Some(term_source),
984            }
985            seen[index] = true;
986        }
987
988        let source = source?;
989        for (index, seen) in seen.into_iter().enumerate() {
990            if !seen && source.known_byte(index) != Some(0) {
991                return None;
992            }
993        }
994        Some(source)
995    }
996
997    fn extracted_shifted_byte_term(&self) -> Option<(Self, usize)> {
998        match self.kind() {
999            SymExprKind::BinOp(SymBinOp::Shl, byte, shift) => {
1000                let shift = shift.as_const()?;
1001                let Ok(shift) = usize::try_from(shift) else { return None };
1002                if shift % 8 != 0 || shift > 248 {
1003                    return None;
1004                }
1005                let index = 31 - shift / 8;
1006                let source = byte.extracted_unshifted_byte_source(index)?;
1007                Some((source, index))
1008            }
1009            _ => self.extracted_unshifted_byte_source(31).map(|source| (source, 31)),
1010        }
1011    }
1012
1013    fn extracted_unshifted_byte_source(&self, index: usize) -> Option<Self> {
1014        let expr = self.strip_low_byte_mask();
1015        if index == 31 {
1016            return Some(expr.clone());
1017        }
1018        let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1019        let shift = shift.as_const()?;
1020        (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1021    }
1022
1023    fn rebuild_from_shifted_word_fragments(terms: &[&Self]) -> Option<Self> {
1024        if terms.len() != 2 {
1025            return None;
1026        }
1027
1028        let left_low = terms[0].low_word_fragment();
1029        let right_low = terms[1].low_word_fragment();
1030        let left_high = terms[0].shifted_high_word_fragment();
1031        let right_high = terms[1].shifted_high_word_fragment();
1032        match (left_low, right_low, left_high, right_high) {
1033            (Some((low_source, low_bits)), None, None, Some((high_source, high_bits)))
1034            | (None, Some((low_source, low_bits)), Some((high_source, high_bits)), None)
1035                if low_source == high_source && low_bits == high_bits =>
1036            {
1037                Some(low_source)
1038            }
1039            _ => None,
1040        }
1041    }
1042
1043    fn low_word_fragment(&self) -> Option<(Self, usize)> {
1044        let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1045        let mask = right.as_const()?;
1046        mask_low_bits(mask).map(|bits| (left.clone(), bits))
1047    }
1048
1049    fn shifted_high_word_fragment(&self) -> Option<(Self, usize)> {
1050        let SymExprKind::BinOp(SymBinOp::Shl, value, shift) = self.kind() else { return None };
1051        let bits = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1052        if bits == 0 || bits >= 256 {
1053            return None;
1054        }
1055
1056        let (source, source_shift, width) = value.shifted_low_fragment_source()?;
1057        (source_shift == bits && width == 256 - bits).then_some((source, bits))
1058    }
1059
1060    fn shifted_low_fragment_source(&self) -> Option<(Self, usize, usize)> {
1061        let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1062        let mask = right.as_const()?;
1063        Self::shifted_low_fragment_source_with_mask(left, mask)
1064    }
1065
1066    fn shifted_low_fragment_source_with_mask(
1067        value: &Self,
1068        mask: U256,
1069    ) -> Option<(Self, usize, usize)> {
1070        let width = mask_low_bits(mask)?;
1071        match value.kind() {
1072            SymExprKind::BinOp(SymBinOp::Shr, source, shift) => {
1073                let shift = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1074                Some((source.clone(), shift, width))
1075            }
1076            _ => Some((value.clone(), 0, width)),
1077        }
1078    }
1079
1080    pub(crate) fn low_byte(self, cx: &mut SymCx) -> Self {
1081        if let Some(word) = self.as_const() {
1082            return Self::constant(cx, U256::from(word.to::<u8>()));
1083        }
1084        let mask = Self::constant(cx, U256::from(0xff));
1085        Self::binop(cx, SymBinOp::And, self, mask)
1086    }
1087
1088    pub(crate) fn into_byte_exprs(self, cx: &mut SymCx) -> Vec<Self> {
1089        SymBytes::word(cx, self).materialize(cx)
1090    }
1091
1092    pub(crate) fn into_bytes(self, cx: &mut SymCx) -> SymBytes {
1093        SymBytes::word(cx, self)
1094    }
1095
1096    pub(crate) fn from_bytes(cx: &mut SymCx, bytes: impl IntoIterator<Item = Self>) -> Self {
1097        let bytes = bytes.into_iter().collect::<Vec<_>>();
1098        if let Ok(concrete) = concrete_expr_bytes(&bytes, "symbolic word bytes") {
1099            let mut word = [0u8; 32];
1100            for (idx, byte) in concrete.into_iter().take(32).enumerate() {
1101                word[idx] = byte;
1102            }
1103            return Self::constant(cx, U256::from_be_bytes(word));
1104        }
1105
1106        if let Some(expr) = word_from_extracted_bytes(&bytes) {
1107            return expr;
1108        }
1109
1110        let mut expr = Self::zero(cx);
1111        for (idx, byte) in bytes.into_iter().take(32).enumerate() {
1112            let shift = (31 - idx) * 8;
1113            let byte = byte.low_byte(cx);
1114            let byte = if shift == 0 {
1115                byte
1116            } else {
1117                let shift = Self::constant(cx, U256::from(shift));
1118                Self::binop(cx, SymBinOp::Shl, byte, shift)
1119            };
1120            expr = Self::binop(cx, SymBinOp::Or, expr, byte);
1121        }
1122        expr
1123    }
1124
1125    pub(crate) fn as_const(&self) -> Option<U256> {
1126        match self.kind() {
1127            SymExprKind::Const(value) => Some(*value),
1128            _ => None,
1129        }
1130    }
1131
1132    pub(crate) fn eval(&self) -> Option<U256> {
1133        self.eval_model_if_complete(&NoopModel).ok().flatten()
1134    }
1135
1136    pub(crate) fn eval_model<M: SymbolicModelLookup + ?Sized>(
1137        &self,
1138        model: &M,
1139    ) -> Result<U256, SymbolicError> {
1140        let kind = self.kind();
1141        if let Some(var) = kind.get_eval_var() {
1142            return Ok(model.value(var).unwrap_or_default());
1143        }
1144        Ok(match kind {
1145            SymExprKind::Const(value) => *value,
1146            SymExprKind::Var(_) | SymExprKind::GasLeft(_) | SymExprKind::Hash { .. } => {
1147                unreachable!("symbolic eval leaf handled above")
1148            }
1149            SymExprKind::Keccak { len, bytes, .. } => {
1150                let len = len.eval_model(model)?;
1151                let Ok(len) = usize::try_from(len) else {
1152                    return Err(SymbolicError::Solver(
1153                        "solver model uses an invalid keccak length".to_string(),
1154                    ));
1155                };
1156                if len > bytes.len() {
1157                    return Err(SymbolicError::Solver(
1158                        "solver model uses an invalid keccak length".to_string(),
1159                    ));
1160                }
1161
1162                let mut input = Vec::with_capacity(len);
1163                for byte in bytes.iter().take(len) {
1164                    input.push((byte.eval_model(model)? & U256::from(0xff)).to::<u8>());
1165                }
1166
1167                U256::from_be_bytes(keccak256(input).0)
1168            }
1169            SymExprKind::Not(value) => !value.eval_model(model)?,
1170            SymExprKind::BinOp(op, left, right) => {
1171                op.eval(left.eval_model(model)?, right.eval_model(model)?)
1172            }
1173            SymExprKind::TernOp(op, left, right, modulus) => op.eval(
1174                left.eval_model(model)?,
1175                right.eval_model(model)?,
1176                modulus.eval_model(model)?,
1177            ),
1178            SymExprKind::Ite(cond, then_expr, else_expr) => {
1179                if cond.eval_model(model)? {
1180                    then_expr.eval_model(model)?
1181                } else {
1182                    else_expr.eval_model(model)?
1183                }
1184            }
1185        })
1186    }
1187
1188    pub(crate) fn eval_model_if_complete<M: SymbolicModelLookup + ?Sized>(
1189        &self,
1190        model: &M,
1191    ) -> Result<Option<U256>, SymbolicError> {
1192        let mut vars = SymbolicVars::default();
1193        self.collect_eval_vars(&mut vars);
1194        if vars.iter().copied().all(|var| model.contains_name(var)) {
1195            self.eval_model(model).map(Some)
1196        } else {
1197            Ok(None)
1198        }
1199    }
1200
1201    pub(crate) fn assign_model_value(&self, model: &mut SymbolicModel, value: U256) -> bool {
1202        match self.kind() {
1203            SymExprKind::Const(existing) => *existing == value,
1204            SymExprKind::Var(var) => {
1205                if let Some(existing) = model.get(var) {
1206                    *existing == value
1207                } else {
1208                    model.insert(*var, value);
1209                    true
1210                }
1211            }
1212            SymExprKind::GasLeft(symbol) => {
1213                if let Some(existing) = model.get(symbol) {
1214                    *existing == value
1215                } else {
1216                    model.insert(*symbol, value);
1217                    true
1218                }
1219            }
1220            _ => false,
1221        }
1222    }
1223
1224    pub(crate) fn bool_word_condition(&self) -> Option<SymBoolExpr> {
1225        let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() else {
1226            return None;
1227        };
1228        Self::bool_word_condition_from_parts(condition, then_expr, else_expr)
1229    }
1230
1231    fn bool_word_condition_from_parts(
1232        condition: &SymBoolExpr,
1233        then_expr: &Self,
1234        else_expr: &Self,
1235    ) -> Option<SymBoolExpr> {
1236        match (then_expr.as_const(), else_expr.as_const()) {
1237            (Some(then_value), Some(else_value))
1238                if then_value == U256::ONE && else_value.is_zero() =>
1239            {
1240                Some(condition.clone())
1241            }
1242            (Some(then_value), Some(else_value))
1243                if then_value.is_zero() && else_value == U256::ONE =>
1244            {
1245                None
1246            }
1247            _ => None,
1248        }
1249    }
1250
1251    pub(crate) fn truth(&self) -> Option<bool> {
1252        self.as_const().map(|value| !value.is_zero())
1253    }
1254
1255    pub(crate) fn into_zero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1256        match self.kind() {
1257            SymExprKind::Const(value) => SymBoolExpr::constant(cx, value.is_zero()),
1258            SymExprKind::Ite(condition, then_expr, else_expr) => {
1259                match Self::bool_word_condition_from_parts(condition, then_expr, else_expr) {
1260                    Some(condition) => SymBoolExpr::not_bool(cx, condition),
1261                    None => {
1262                        let zero = Self::zero(cx);
1263                        SymBoolExpr::eq(cx, self, zero)
1264                    }
1265                }
1266            }
1267            _ => {
1268                let zero = Self::zero(cx);
1269                SymBoolExpr::eq(cx, self, zero)
1270            }
1271        }
1272    }
1273
1274    pub(crate) fn nonzero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1275        let zero = self.into_zero_bool(cx);
1276        SymBoolExpr::not_bool(cx, zero)
1277    }
1278
1279    pub(crate) fn as_const_or(&self, reason: &'static str) -> Result<U256, SymbolicError> {
1280        self.as_const().ok_or(SymbolicError::Unsupported(reason))
1281    }
1282
1283    pub(crate) fn as_usize_or(&self, reason: &'static str) -> Result<usize, SymbolicError> {
1284        let value = self.as_const_or(reason)?;
1285        usize::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
1286    }
1287
1288    pub(crate) fn contains_keccak(&self) -> bool {
1289        self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Keccak { .. }))
1290    }
1291
1292    pub(crate) fn contains_gasleft(&self) -> bool {
1293        self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::GasLeft(_)))
1294    }
1295
1296    pub(crate) fn contains_udiv(&self) -> bool {
1297        self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::BinOp(SymBinOp::UDiv, _, _)))
1298    }
1299
1300    pub(crate) fn contains_ite(&self) -> bool {
1301        self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Ite(_, _, _)))
1302    }
1303
1304    pub(in crate::runtime) fn udiv_operands(&self) -> Option<(&Self, &Self)> {
1305        match self.kind() {
1306            SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
1307                Some((numerator, denominator))
1308            }
1309            _ => None,
1310        }
1311    }
1312
1313    pub(crate) fn collect_eval_vars(&self, vars: &mut SymbolicVars) {
1314        let _ = self.visit(&mut |expr| {
1315            if let Some(var) = expr.kind().get_eval_var() {
1316                vars.insert(var);
1317            }
1318            ControlFlow::<()>::Continue(())
1319        });
1320    }
1321
1322    pub(crate) fn known_byte(&self, index: usize) -> Option<u8> {
1323        debug_assert!(index < 32);
1324        match self.kind() {
1325            SymExprKind::Const(value) => Some(value.to_be_bytes::<32>()[index]),
1326            SymExprKind::Var(_)
1327            | SymExprKind::GasLeft(_)
1328            | SymExprKind::Keccak { .. }
1329            | SymExprKind::Hash { .. } => None,
1330            SymExprKind::Not(value) => value.known_byte(index).map(|byte| !byte),
1331            SymExprKind::Ite(_, then_expr, else_expr) => {
1332                let then_byte = then_expr.known_byte(index)?;
1333                let else_byte = else_expr.known_byte(index)?;
1334                (then_byte == else_byte).then_some(then_byte)
1335            }
1336            SymExprKind::BinOp(op, left, right) => match op {
1337                SymBinOp::And => match (left.known_byte(index), right.known_byte(index)) {
1338                    (Some(left), Some(right)) => Some(left & right),
1339                    (Some(0), _) | (_, Some(0)) => Some(0),
1340                    _ => None,
1341                },
1342                SymBinOp::Or => Some(left.known_byte(index)? | right.known_byte(index)?),
1343                SymBinOp::Xor => Some(left.known_byte(index)? ^ right.known_byte(index)?),
1344                SymBinOp::Shl => {
1345                    let shift = right.as_const()?;
1346                    if shift >= U256::from(256) {
1347                        return Some(0);
1348                    }
1349                    let shift = usize::try_from(shift).expect("checked byte shift");
1350                    if shift % 8 != 0 {
1351                        return None;
1352                    }
1353                    let source_index = index + shift / 8;
1354                    if source_index >= 32 { Some(0) } else { left.known_byte(source_index) }
1355                }
1356                SymBinOp::Shr => {
1357                    let shift = right.as_const()?;
1358                    if shift >= U256::from(256) {
1359                        return Some(0);
1360                    }
1361                    let shift = usize::try_from(shift).expect("checked byte shift");
1362                    if shift % 8 != 0 {
1363                        return None;
1364                    }
1365                    let byte_shift = shift / 8;
1366                    if index < byte_shift { Some(0) } else { left.known_byte(index - byte_shift) }
1367                }
1368                SymBinOp::Add
1369                | SymBinOp::Sub
1370                | SymBinOp::Mul
1371                | SymBinOp::UDiv
1372                | SymBinOp::URem
1373                | SymBinOp::SDiv
1374                | SymBinOp::SRem
1375                | SymBinOp::Sar => None,
1376            },
1377            SymExprKind::TernOp(_, _, _, _) => None,
1378        }
1379    }
1380
1381    pub(crate) fn known_word(&self) -> Option<U256> {
1382        let mut word = [0u8; 32];
1383        for (idx, byte) in word.iter_mut().enumerate() {
1384            *byte = self.known_byte(idx)?;
1385        }
1386        Some(U256::from_be_bytes(word))
1387    }
1388
1389    pub(crate) fn unsigned_bits(&self) -> usize {
1390        match self.kind() {
1391            SymExprKind::Const(value) => value.bit_len().max(1),
1392            SymExprKind::BinOp(SymBinOp::And, left, right) => {
1393                if let Some(mask) = right.as_const() {
1394                    left.unsigned_bits().min(mask.bit_len())
1395                } else {
1396                    256
1397                }
1398            }
1399            SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1400                left.unsigned_bits().max(right.unsigned_bits()).saturating_add(1).min(256)
1401            }
1402            SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1403                left.unsigned_bits().saturating_add(right.unsigned_bits()).min(256)
1404            }
1405            SymExprKind::BinOp(SymBinOp::Shl, left, right) => {
1406                if let Some(shift) = right.as_const().and_then(|shift| usize::try_from(shift).ok())
1407                {
1408                    left.unsigned_bits().saturating_add(shift).min(256)
1409                } else {
1410                    256
1411                }
1412            }
1413            SymExprKind::BinOp(SymBinOp::Shr, left, right) => {
1414                if let Some(shift) = right.as_const().and_then(|shift| usize::try_from(shift).ok())
1415                {
1416                    left.unsigned_bits().saturating_sub(shift).max(1)
1417                } else {
1418                    256
1419                }
1420            }
1421            SymExprKind::BinOp(SymBinOp::UDiv, left, _) => left.unsigned_bits(),
1422            SymExprKind::TernOp(_, _, _, modulus) => modulus.unsigned_bits(),
1423            SymExprKind::Ite(_, left, right) => left.unsigned_bits().max(right.unsigned_bits()),
1424            _ => 256,
1425        }
1426    }
1427
1428    pub(crate) fn extracted_byte(&self, cx: &mut SymCx, index: usize) -> Self {
1429        debug_assert!(index < 32);
1430        let shift = Self::constant(cx, U256::from((31 - index) * 8));
1431        let shifted = Self::binop(cx, SymBinOp::Shr, self.clone(), shift);
1432        let mask = Self::constant(cx, U256::from(0xff));
1433        Self::binop(cx, SymBinOp::And, shifted, mask)
1434    }
1435
1436    pub(crate) fn extracted_byte_source(&self, index: usize) -> Option<Self> {
1437        let expr = self.strip_low_byte_mask();
1438        if index == 31 {
1439            return Some(expr.clone());
1440        }
1441        let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1442        let shift = shift.as_const()?;
1443        (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1444    }
1445
1446    pub(crate) fn strip_low_byte_mask(&self) -> &Self {
1447        match self.kind() {
1448            SymExprKind::BinOp(SymBinOp::And, left, right)
1449                if right.as_const() == Some(U256::from(0xff)) =>
1450            {
1451                left.strip_low_byte_mask()
1452            }
1453            _ => self,
1454        }
1455    }
1456
1457    pub(crate) fn byte_term(&self, cx: &mut SymCx, index: usize) -> Option<Self> {
1458        debug_assert!(index < 32);
1459
1460        match self.kind() {
1461            SymExprKind::Const(value) => {
1462                Some(Self::constant(cx, U256::from(value.to_be_bytes::<32>()[index])))
1463            }
1464            SymExprKind::Var(_)
1465            | SymExprKind::GasLeft(_)
1466            | SymExprKind::Keccak { .. }
1467            | SymExprKind::Hash { .. } => Some(self.extracted_byte(cx, index)),
1468            SymExprKind::Not(value) => {
1469                let value = value.byte_term(cx, index)?;
1470                Some(Self::not(cx, value))
1471            }
1472            SymExprKind::Ite(cond, then_expr, else_expr) => {
1473                let then_expr = then_expr.byte_term(cx, index)?;
1474                let else_expr = else_expr.byte_term(cx, index)?;
1475                Some(Self::ite(cx, cond.clone(), then_expr, else_expr))
1476            }
1477            SymExprKind::BinOp(op, left, right) => match op {
1478                SymBinOp::And => Self::binary_byte_term(
1479                    cx,
1480                    left,
1481                    right,
1482                    index,
1483                    SymBinOp::And,
1484                    |byte| byte == 0xff,
1485                    |byte| byte == 0,
1486                ),
1487                SymBinOp::Or => Self::binary_byte_term(
1488                    cx,
1489                    left,
1490                    right,
1491                    index,
1492                    SymBinOp::Or,
1493                    |byte| byte == 0,
1494                    |_| false,
1495                ),
1496                SymBinOp::Xor => Self::binary_byte_term(
1497                    cx,
1498                    left,
1499                    right,
1500                    index,
1501                    SymBinOp::Xor,
1502                    |byte| byte == 0,
1503                    |_| false,
1504                ),
1505                SymBinOp::Shl => {
1506                    let shift = right.eval()?;
1507                    if shift >= U256::from(256) {
1508                        return Some(Self::zero(cx));
1509                    }
1510                    let shift = usize::try_from(shift).expect("checked byte shift");
1511                    if shift % 8 != 0 {
1512                        return None;
1513                    }
1514                    let source_index = index + shift / 8;
1515                    if source_index >= 32 {
1516                        Some(Self::zero(cx))
1517                    } else {
1518                        left.byte_term(cx, source_index)
1519                    }
1520                }
1521                SymBinOp::Shr => {
1522                    let shift = right.eval()?;
1523                    if shift >= U256::from(256) {
1524                        return Some(Self::zero(cx));
1525                    }
1526                    let shift = usize::try_from(shift).expect("checked byte shift");
1527                    if shift % 8 != 0 {
1528                        return None;
1529                    }
1530                    let byte_shift = shift / 8;
1531                    if index < byte_shift {
1532                        Some(Self::zero(cx))
1533                    } else {
1534                        left.byte_term(cx, index - byte_shift)
1535                    }
1536                }
1537                SymBinOp::Add
1538                | SymBinOp::Sub
1539                | SymBinOp::Mul
1540                | SymBinOp::UDiv
1541                | SymBinOp::URem
1542                | SymBinOp::SDiv
1543                | SymBinOp::SRem
1544                | SymBinOp::Sar => None,
1545            },
1546            SymExprKind::TernOp(_, _, _, _) => None,
1547        }
1548    }
1549
1550    fn binary_byte_term(
1551        cx: &mut SymCx,
1552        left: &Self,
1553        right: &Self,
1554        index: usize,
1555        op: SymBinOp,
1556        identity: impl Fn(u8) -> bool,
1557        absorbing: impl Fn(u8) -> bool,
1558    ) -> Option<Self> {
1559        let left = left.byte_term(cx, index)?;
1560        let right = right.byte_term(cx, index)?;
1561        match (left.byte_const(), right.byte_const()) {
1562            (Some(left), _) if absorbing(left) => Some(Self::constant(cx, U256::from(left))),
1563            (_, Some(right)) if absorbing(right) => Some(Self::constant(cx, U256::from(right))),
1564            (Some(left), _) if identity(left) => Some(right),
1565            (_, Some(right)) if identity(right) => Some(left),
1566            _ => Some(Self::binop(cx, op, left, right)),
1567        }
1568    }
1569
1570    pub(crate) fn byte_const(&self) -> Option<u8> {
1571        self.as_const().map(|value| value.to::<u8>())
1572    }
1573
1574    pub(crate) fn equality_forces_const(
1575        &self,
1576        value: U256,
1577        expr: &Self,
1578        context: &[SymBoolExpr],
1579    ) -> Option<U256> {
1580        if self == expr {
1581            return Some(value);
1582        }
1583        self.equality_forces_const_inner(value, expr, context)
1584    }
1585
1586    fn equality_forces_const_inner(
1587        &self,
1588        value: U256,
1589        expr: &Self,
1590        context: &[SymBoolExpr],
1591    ) -> Option<U256> {
1592        let mask = masked_expr_matches(self.kind(), expr)?;
1593        if value & !mask != U256::ZERO || !context_forces_masked_expr(context, expr, mask) {
1594            return None;
1595        }
1596        Some(value)
1597    }
1598
1599    pub(crate) fn nonzero_forces_const(
1600        &self,
1601        target: &Self,
1602        context: &[SymBoolExpr],
1603    ) -> Option<U256> {
1604        match self.kind() {
1605            SymExprKind::Const(_)
1606            | SymExprKind::Var(_)
1607            | SymExprKind::GasLeft(_)
1608            | SymExprKind::Keccak { .. }
1609            | SymExprKind::Hash { .. }
1610            | SymExprKind::Not(_) => None,
1611            SymExprKind::Ite(cond, then_expr, else_expr) => {
1612                if then_expr.eval().is_some_and(|value| !value.is_zero())
1613                    && else_expr.eval().is_some_and(|value| value.is_zero())
1614                {
1615                    cond.forces_expr_const_with_context(target, context)
1616                } else {
1617                    None
1618                }
1619            }
1620            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1621                if left.eval().is_some_and(|value| value.is_zero()) {
1622                    return right.nonzero_forces_const(target, context);
1623                }
1624                if right.eval().is_some_and(|value| value.is_zero()) {
1625                    return left.nonzero_forces_const(target, context);
1626                }
1627                None
1628            }
1629            SymExprKind::BinOp(SymBinOp::And, left, right) => {
1630                if left.eval().is_some_and(|value| !value.is_zero()) {
1631                    return right.nonzero_forces_const(target, context);
1632                }
1633                if right.eval().is_some_and(|value| !value.is_zero()) {
1634                    return left.nonzero_forces_const(target, context);
1635                }
1636                None
1637            }
1638            SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, value, shift)
1639                if shift.eval().is_some_and(|shift| shift.is_zero()) =>
1640            {
1641                value.nonzero_forces_const(target, context)
1642            }
1643            SymExprKind::TernOp(_, _, _, _) => None,
1644            SymExprKind::BinOp(_, _, _) => None,
1645        }
1646    }
1647
1648    pub(crate) fn is_raw_gasleft(&self) -> bool {
1649        matches!(self.kind(), SymExprKind::GasLeft(_))
1650    }
1651
1652    pub(crate) fn add_const(cx: &mut SymCx, expr: Self, value: U256) -> Self {
1653        if value.is_zero() {
1654            return expr;
1655        }
1656        match expr.kind() {
1657            SymExprKind::Const(expr) => Self::constant(cx, expr.wrapping_add(value)),
1658            _ => {
1659                let value = Self::constant(cx, value);
1660                Self::binop(cx, SymBinOp::Add, expr, value)
1661            }
1662        }
1663    }
1664
1665    /// Visits this expression and all child expressions.
1666    pub(crate) fn visit<B>(
1667        &self,
1668        visitor: &mut impl FnMut(&Self) -> ControlFlow<B>,
1669    ) -> ControlFlow<B> {
1670        visitor(self)?;
1671        match self.kind() {
1672            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => {}
1673            SymExprKind::Keccak { len, bytes, .. } => {
1674                len.visit(visitor)?;
1675                for byte in bytes.iter() {
1676                    byte.visit(visitor)?;
1677                }
1678            }
1679            SymExprKind::Hash { bytes, .. } => {
1680                for byte in bytes.iter() {
1681                    byte.visit(visitor)?;
1682                }
1683            }
1684            SymExprKind::Not(value) => value.visit(visitor)?,
1685            SymExprKind::BinOp(_, left, right) => {
1686                left.visit(visitor)?;
1687                right.visit(visitor)?;
1688            }
1689            SymExprKind::TernOp(_, left, right, modulus) => {
1690                left.visit(visitor)?;
1691                right.visit(visitor)?;
1692                modulus.visit(visitor)?;
1693            }
1694            SymExprKind::Ite(cond, left, right) => {
1695                cond.visit_exprs(visitor)?;
1696                left.visit(visitor)?;
1697                right.visit(visitor)?;
1698            }
1699        }
1700        ControlFlow::Continue(())
1701    }
1702
1703    pub(crate) fn visit_bool(&self, mut visitor: impl FnMut(&Self) -> bool) -> bool {
1704        self.visit(&mut |expr| {
1705            if visitor(expr) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
1706        })
1707        .is_break()
1708    }
1709
1710    pub(crate) fn fold(
1711        self,
1712        cx: &mut SymCx,
1713        folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1714    ) -> Self {
1715        if matches!(
1716            self.kind(),
1717            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_)
1718        ) {
1719            return folder(cx, self);
1720        }
1721
1722        let expr = match self.into_kind() {
1723            SymExprKind::Keccak { name, len, bytes } => {
1724                let len = len.fold(cx, folder);
1725                let bytes = bytes.iter().cloned().map(|byte| byte.fold(cx, folder)).collect();
1726                Self::keccak_symbol(cx, name, len, bytes)
1727            }
1728            SymExprKind::Hash { name, algorithm, bytes } => {
1729                let bytes = bytes.iter().cloned().map(|byte| byte.fold(cx, folder)).collect();
1730                Self::hash_symbol(cx, name, algorithm, bytes)
1731            }
1732            SymExprKind::Not(value) => {
1733                let value = value.fold(cx, folder);
1734                Self::not(cx, value)
1735            }
1736            SymExprKind::BinOp(op, left, right) => {
1737                let left = left.fold(cx, folder);
1738                let right = right.fold(cx, folder);
1739                Self::binop(cx, op, left, right)
1740            }
1741            SymExprKind::TernOp(op, left, right, modulus) => {
1742                let left = left.fold(cx, folder);
1743                let right = right.fold(cx, folder);
1744                let modulus = modulus.fold(cx, folder);
1745                Self::ternop(cx, op, left, right, modulus)
1746            }
1747            SymExprKind::Ite(condition, then_expr, else_expr) => {
1748                let condition = condition.fold_exprs(cx, folder);
1749                let then_expr = then_expr.fold(cx, folder);
1750                let else_expr = else_expr.fold(cx, folder);
1751                Self::ite(cx, condition, then_expr, else_expr)
1752            }
1753            SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => {
1754                unreachable!("leaf expression returned before folding children")
1755            }
1756        };
1757        folder(cx, expr)
1758    }
1759
1760    #[cfg(test)]
1761    pub(crate) fn smt(&self, cx: &SymCx) -> String {
1762        let mut smt = String::new();
1763        self.write_smt(cx, &mut smt);
1764        smt
1765    }
1766
1767    pub(in crate::runtime::expr) fn write_smt(&self, cx: &SymCx, out: &mut String) {
1768        match self.kind() {
1769            SymExprKind::Const(value) => {
1770                let _ = write!(out, "(_ bv{value} 256)");
1771            }
1772            SymExprKind::Var(symbol)
1773            | SymExprKind::GasLeft(symbol)
1774            | SymExprKind::Keccak { name: symbol, .. }
1775            | SymExprKind::Hash { name: symbol, .. } => out.push_str(cx.symbol_name(*symbol)),
1776            SymExprKind::Not(value) => {
1777                out.push_str("(bvnot ");
1778                value.write_smt(cx, out);
1779                out.push(')');
1780            }
1781            SymExprKind::BinOp(op, left, right) => {
1782                let _ = write!(out, "({} ", op.smt());
1783                left.write_smt(cx, out);
1784                out.push(' ');
1785                right.write_smt(cx, out);
1786                out.push(')');
1787            }
1788            SymExprKind::TernOp(op, left, right, modulus) => {
1789                write_smt_wide_modular_arithmetic(cx, out, op.smt(), left, right, modulus);
1790            }
1791            SymExprKind::Ite(cond, left, right) => {
1792                out.push_str("(ite ");
1793                cond.write_smt(cx, out);
1794                out.push(' ');
1795                left.write_smt(cx, out);
1796                out.push(' ');
1797                right.write_smt(cx, out);
1798                out.push(')');
1799            }
1800        }
1801    }
1802}
1803
1804fn write_smt_wide_modular_arithmetic(
1805    cx: &SymCx,
1806    out: &mut String,
1807    op: &'static str,
1808    left: &SymExpr,
1809    right: &SymExpr,
1810    modulus: &SymExpr,
1811) {
1812    // if modulus == 0:
1813    //   0
1814    // else:
1815    //   low_256((zext(left) op zext(right)) urem zext(modulus))
1816    out.push_str("(ite (= ");
1817    modulus.write_smt(cx, out);
1818    out.push_str(" (_ bv0 256)) (_ bv0 256) ((_ extract 255 0) (bvurem (");
1819    out.push_str(op);
1820    out.push_str(" ((_ zero_extend 256) ");
1821    left.write_smt(cx, out);
1822    out.push_str(") ((_ zero_extend 256) ");
1823    right.write_smt(cx, out);
1824    out.push_str(")) ((_ zero_extend 256) ");
1825    modulus.write_smt(cx, out);
1826    out.push_str("))))");
1827}
1828
1829#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1830pub(crate) enum SymTernOp {
1831    AddMod,
1832    MulMod,
1833}
1834
1835impl SymTernOp {
1836    pub(crate) const fn smt(self) -> &'static str {
1837        match self {
1838            Self::AddMod => "bvadd",
1839            Self::MulMod => "bvmul",
1840        }
1841    }
1842
1843    pub(crate) fn eval(self, left: U256, right: U256, modulus: U256) -> U256 {
1844        if modulus.is_zero() {
1845            return U256::ZERO;
1846        }
1847        match self {
1848            Self::AddMod => left.add_mod(right, modulus),
1849            Self::MulMod => left.mul_mod(right, modulus),
1850        }
1851    }
1852}
1853
1854#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
1855pub(crate) enum SymBinOp {
1856    Add,
1857    Sub,
1858    Mul,
1859    UDiv,
1860    URem,
1861    SDiv,
1862    SRem,
1863    And,
1864    Or,
1865    Xor,
1866    Shl,
1867    Shr,
1868    Sar,
1869}
1870
1871impl SymBinOp {
1872    pub(crate) const fn smt(self) -> &'static str {
1873        match self {
1874            Self::Add => "bvadd",
1875            Self::Sub => "bvsub",
1876            Self::Mul => "bvmul",
1877            Self::UDiv => "bvudiv",
1878            Self::URem => "bvurem",
1879            Self::SDiv => "bvsdiv",
1880            Self::SRem => "bvsrem",
1881            Self::And => "bvand",
1882            Self::Or => "bvor",
1883            Self::Xor => "bvxor",
1884            Self::Shl => "bvshl",
1885            Self::Shr => "bvlshr",
1886            Self::Sar => "bvashr",
1887        }
1888    }
1889
1890    pub(crate) fn eval(self, left: U256, right: U256) -> U256 {
1891        match self {
1892            Self::Add => left.wrapping_add(right),
1893            Self::Sub => left.wrapping_sub(right),
1894            Self::Mul => left.wrapping_mul(right),
1895            Self::UDiv => {
1896                if right.is_zero() {
1897                    U256::ZERO
1898                } else {
1899                    left / right
1900                }
1901            }
1902            Self::URem => {
1903                if right.is_zero() {
1904                    U256::ZERO
1905                } else {
1906                    left % right
1907                }
1908            }
1909            Self::SDiv => sdiv(left, right),
1910            Self::SRem => smod(left, right),
1911            Self::And => left & right,
1912            Self::Or => left | right,
1913            Self::Xor => left ^ right,
1914            Self::Shl => {
1915                if right >= U256::from(256) {
1916                    U256::ZERO
1917                } else {
1918                    left << usize::try_from(right).expect("checked word shift")
1919                }
1920            }
1921            Self::Shr => {
1922                if right >= U256::from(256) {
1923                    U256::ZERO
1924                } else {
1925                    left >> usize::try_from(right).expect("checked word shift")
1926                }
1927            }
1928            Self::Sar => {
1929                if right >= U256::from(256) {
1930                    sar(left, 256)
1931                } else {
1932                    sar(left, usize::try_from(right).expect("checked word shift"))
1933                }
1934            }
1935        }
1936    }
1937}