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