Skip to main content

foundry_evm_symbolic/runtime/
expressions.rs

1use super::*;
2
3/// Computes the `keccak_word` symbolic expression helper result.
4pub(crate) fn keccak_word(bytes: Vec<SymWord>) -> SymWord {
5    keccak_word_with_len(bytes.clone(), SymWord::Concrete(U256::from(bytes.len())))
6}
7
8/// Computes the `keccak_word_with_len` symbolic expression helper result.
9pub(crate) fn keccak_word_with_len(bytes: Vec<SymWord>, len: SymWord) -> SymWord {
10    if bytes.iter().all(|byte| matches!(byte, SymWord::Concrete(_)))
11        && let SymWord::Concrete(len) = len
12        && len <= U256::from(bytes.len())
13    {
14        let len = len.to::<usize>();
15        let bytes = bytes
16            .into_iter()
17            .take(len)
18            .map(|byte| {
19                let SymWord::Concrete(byte) = byte else { unreachable!() };
20                byte.to::<u8>()
21            })
22            .collect::<Vec<_>>();
23        return SymWord::Concrete(U256::from_be_bytes(keccak256(bytes).0));
24    }
25
26    let len = len.into_expr();
27    let exprs = bytes.into_iter().map(SymWord::into_expr).collect::<Vec<_>>();
28    SymWord::Expr(Expr::Keccak {
29        name: stable_symbol("keccak", format!("{len:?}:{exprs:?}")),
30        len: Box::new(len),
31        bytes: exprs,
32    })
33}
34
35/// Returns the `symbolic_hash_word_with_len` symbolic expression helper result.
36pub(crate) fn symbolic_hash_word_with_len(
37    algorithm: &'static str,
38    bytes: Vec<SymWord>,
39    len: SymWord,
40) -> SymWord {
41    let len = len.into_expr();
42    let exprs = bytes.into_iter().map(SymWord::into_expr).collect::<Vec<_>>();
43    let identity = std::iter::once(len.clone()).chain(exprs.clone()).collect::<Vec<_>>();
44    SymWord::Expr(Expr::Hash {
45        name: stable_symbol(algorithm, format!("{len:?}:{exprs:?}")),
46        algorithm,
47        bytes: identity,
48    })
49}
50
51/// Implements the `create2_address_word` symbolic expression helper.
52pub(crate) fn create2_address_word(
53    state: &mut PathState,
54    creator: Address,
55    salt: SymWord,
56    initcode: &SymCode,
57) -> Result<(SymWord, Address), SymbolicError> {
58    match (salt, initcode.concrete_bytes("symbolic CREATE2 initcode")) {
59        (SymWord::Concrete(salt), Ok(initcode)) => {
60            let address = creator.create2_from_code(salt.to_be_bytes::<32>(), &initcode);
61            Ok((SymWord::Concrete(address_word(address)), address))
62        }
63        (salt, Ok(initcode)) => {
64            let initcode_hash = keccak256(&initcode);
65            let word = symbolic_create2_address_word(
66                state,
67                format!("{creator:?}"),
68                salt.into_expr(),
69                format!("{initcode_hash:?}"),
70            );
71            let address = state.world.symbolic_address_slot(word.clone());
72            Ok((word, address))
73        }
74        (salt, Err(SymbolicError::Unsupported("symbolic CREATE2 initcode"))) => {
75            let initcode_bytes =
76                initcode.bytes.iter().cloned().map(SymWord::into_expr).collect::<Vec<_>>();
77            let word = symbolic_create2_address_word(
78                state,
79                format!("{creator:?}"),
80                salt.into_expr(),
81                format!("{initcode_bytes:?}"),
82            );
83            let address = state.world.symbolic_address_slot(word.clone());
84            Ok((word, address))
85        }
86        (_, Err(err)) => Err(err),
87    }
88}
89
90/// Computes the `compute_create2_address_word` symbolic expression helper result.
91pub(crate) fn compute_create2_address_word(
92    state: &mut PathState,
93    deployer: SymWord,
94    salt: SymWord,
95    init_code_hash: SymWord,
96) -> Result<SymWord, SymbolicError> {
97    let deployer_concrete = state.constrained_word(&deployer).map(word_to_address);
98    let salt_concrete = state.constrained_word(&salt);
99    let init_code_hash_concrete = state.constrained_word(&init_code_hash);
100
101    if let (Some(deployer), Some(salt), Some(init_code_hash)) =
102        (deployer_concrete, salt_concrete, init_code_hash_concrete)
103    {
104        let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
105        let address = deployer.create2(B256::from(salt.to_be_bytes::<32>()), init_code_hash);
106        return Ok(SymWord::Concrete(address_word(address)));
107    }
108
109    let deployer_identity = deployer_concrete
110        .map(|deployer| format!("{deployer:?}"))
111        .unwrap_or_else(|| format!("{:?}", deployer.into_expr()));
112    let init_code_hash_identity = init_code_hash_concrete
113        .map(|init_code_hash| {
114            let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
115            format!("{init_code_hash:?}")
116        })
117        .unwrap_or_else(|| format!("{:?}", init_code_hash.into_expr()));
118
119    Ok(symbolic_create2_address_word(
120        state,
121        deployer_identity,
122        salt.into_expr(),
123        init_code_hash_identity,
124    ))
125}
126
127/// Computes the `compute_create_address_word` symbolic expression helper result.
128pub(crate) fn compute_create_address_word(
129    state: &mut PathState,
130    deployer: SymWord,
131    nonce: SymWord,
132) -> Result<SymWord, SymbolicError> {
133    let deployer_concrete = state.constrained_word(&deployer).map(word_to_address);
134    let nonce_concrete = state.constrained_word(&nonce);
135
136    if let (Some(deployer), Some(nonce)) = (deployer_concrete, nonce_concrete) {
137        if nonce > U256::from(u64::MAX) {
138            return Err(SymbolicError::Unsupported("symbolic vm.computeCreateAddress nonce"));
139        }
140        return Ok(SymWord::Concrete(address_word(deployer.create(nonce.to()))));
141    }
142
143    let deployer_identity = deployer_concrete
144        .map(|deployer| format!("{deployer:?}"))
145        .unwrap_or_else(|| format!("{:?}", deployer.into_expr()));
146    Ok(symbolic_create_address_word(state, deployer_identity, nonce.into_expr()))
147}
148
149/// Returns the `symbolic_create_address_word` symbolic expression helper result.
150pub(crate) fn symbolic_create_address_word(
151    state: &mut PathState,
152    creator_identity: String,
153    nonce: Expr,
154) -> SymWord {
155    let word = SymWord::Expr(Expr::Var(stable_symbol(
156        "create_address",
157        format!("{creator_identity}:{nonce:?}"),
158    )));
159    state.constraints.push(BoolExpr::cmp(
160        BoolExprOp::Ult,
161        word.clone().into_expr(),
162        Expr::Const(U256::from(1) << 160),
163    ));
164    word
165}
166
167/// Returns the `symbolic_create2_address_word` symbolic expression helper result.
168pub(crate) fn symbolic_create2_address_word(
169    state: &mut PathState,
170    creator_identity: String,
171    salt: Expr,
172    initcode_identity: String,
173) -> SymWord {
174    let word = SymWord::Expr(Expr::Var(stable_symbol(
175        "create2_address",
176        format!("{creator_identity}:{salt:?}:{initcode_identity}"),
177    )));
178    state.constraints.push(BoolExpr::cmp(
179        BoolExprOp::Ult,
180        word.clone().into_expr(),
181        Expr::Const(U256::from(1) << 160),
182    ));
183    word
184}
185
186/// Returns the `read_storage_writes` symbolic expression helper result.
187pub(crate) fn read_storage_writes(
188    writes: &[StorageWrite],
189    address: Address,
190    key: SymWord,
191    base: SymWord,
192) -> SymWord {
193    let mut value = base;
194    for write in writes.iter().filter(|write| write.address == address) {
195        value = storage_select(key.clone(), write.key.clone(), write.value.clone(), value);
196    }
197    value
198}
199
200/// Implements the `storage_select` symbolic expression helper.
201pub(crate) fn storage_select(
202    read_key: SymWord,
203    write_key: SymWord,
204    write_value: SymWord,
205    base: SymWord,
206) -> SymWord {
207    if write_value == base {
208        return base;
209    }
210    let condition = storage_key_eq(read_key, write_key);
211    match condition {
212        BoolExpr::Const(true) => write_value,
213        BoolExpr::Const(false) => base,
214        condition => SymWord::Expr(Expr::Ite(
215            Box::new(condition),
216            Box::new(write_value.into_expr()),
217            Box::new(base.into_expr()),
218        )),
219    }
220}
221
222/// Implements the `storage_key_eq` symbolic expression helper.
223pub(crate) fn storage_key_eq(read_key: SymWord, write_key: SymWord) -> BoolExpr {
224    let read_key = read_key.into_expr();
225    let write_key = write_key.into_expr();
226    if let (Some(read_root), Some(write_root)) =
227        (storage_mapping_root_slot(&read_key), storage_mapping_root_slot(&write_key))
228        && read_root != write_root
229    {
230        return BoolExpr::Const(false);
231    }
232    match (storage_layout_key(&read_key), storage_layout_key(&write_key)) {
233        (Some((read_base, read_offset)), Some((write_base, write_offset))) => BoolExpr::and(vec![
234            BoolExpr::eq(read_base, write_base),
235            BoolExpr::eq(read_offset, write_offset),
236        ]),
237        (Some(_), None) if matches!(write_key, Expr::Const(_)) => BoolExpr::Const(false),
238        (None, Some(_)) if matches!(read_key, Expr::Const(_)) => BoolExpr::Const(false),
239        _ => BoolExpr::eq(read_key, write_key),
240    }
241}
242
243/// Returns the root Solidity storage slot for a mapping-style keccak key.
244pub(crate) fn storage_mapping_root_slot(key: &Expr) -> Option<U256> {
245    let Expr::Keccak { len, bytes, .. } = key else { return None };
246    if !matches!(len.as_ref(), Expr::Const(value) if *value == U256::from(64)) || bytes.len() < 64 {
247        return None;
248    }
249
250    let slot = word_from_bytes(bytes[32..64].iter().cloned().map(|expr| match expr {
251        Expr::Const(value) => SymWord::Concrete(value),
252        expr => SymWord::Expr(expr),
253    }))
254    .into_expr();
255    match slot {
256        Expr::Const(slot) => Some(slot),
257        Expr::Keccak { .. } => storage_mapping_root_slot(&slot),
258        _ => None,
259    }
260}
261
262/// Implements the `storage_layout_key` symbolic expression helper.
263pub(crate) fn storage_layout_key(key: &Expr) -> Option<(Expr, Expr)> {
264    match key {
265        Expr::Keccak { .. } => Some((key.clone(), Expr::Const(U256::ZERO))),
266        Expr::Op(ExprOp::Add, left, right) => {
267            if let Some((base, offset)) = storage_layout_key(left)
268                && !expr_contains_keccak(right)
269            {
270                return Some((base, expr_add(offset, (**right).clone())));
271            }
272            if let Some((base, offset)) = storage_layout_key(right)
273                && !expr_contains_keccak(left)
274            {
275                return Some((base, expr_add(offset, (**left).clone())));
276            }
277            None
278        }
279        _ => None,
280    }
281}
282
283/// Returns the `expr_add` symbolic expression helper result.
284pub(crate) fn expr_add(left: Expr, right: Expr) -> Expr {
285    match (left, right) {
286        (Expr::Const(left), Expr::Const(right)) => Expr::Const(left.wrapping_add(right)),
287        (Expr::Const(value), expr) | (expr, Expr::Const(value)) if value.is_zero() => expr,
288        (left, right) => Expr::op(ExprOp::Add, left, right),
289    }
290}
291
292/// Implements the `sym_add` symbolic expression helper.
293pub(crate) fn sym_add(left: SymWord, right: SymWord) -> SymWord {
294    match (left, right) {
295        (SymWord::Concrete(left), SymWord::Concrete(right)) => {
296            SymWord::Concrete(left.wrapping_add(right))
297        }
298        (left, right) => SymWord::Expr(expr_add(left.into_expr(), right.into_expr())),
299    }
300}
301
302/// Implements the `sym_sub` symbolic expression helper.
303pub(crate) fn sym_sub(left: SymWord, right: SymWord) -> SymWord {
304    match (left, right) {
305        (SymWord::Concrete(left), SymWord::Concrete(right)) => {
306            SymWord::Concrete(left.wrapping_sub(right))
307        }
308        (left, right) => SymWord::Expr(Expr::op(ExprOp::Sub, left.into_expr(), right.into_expr())),
309    }
310}
311
312/// Returns the `expr_contains_keccak` symbolic expression helper result.
313pub(crate) fn expr_contains_keccak(expr: &Expr) -> bool {
314    match expr {
315        Expr::Keccak { .. } => true,
316        Expr::Const(_) | Expr::Var(_) | Expr::GasLeft(_) | Expr::Hash { .. } => false,
317        Expr::Not(value) => expr_contains_keccak(value),
318        Expr::Op(_, left, right) => expr_contains_keccak(left) || expr_contains_keccak(right),
319        Expr::Ite(cond, left, right) => {
320            bool_contains_keccak(cond) || expr_contains_keccak(left) || expr_contains_keccak(right)
321        }
322    }
323}
324
325/// Returns whether a word expression depends on the opaque `GAS` / `gasleft()` value.
326pub(crate) fn expr_contains_gasleft(expr: &Expr) -> bool {
327    match expr {
328        Expr::Const(_) => false,
329        Expr::Var(_) => false,
330        Expr::GasLeft(_) => true,
331        Expr::Keccak { len, bytes, .. } => {
332            expr_contains_gasleft(len) || bytes.iter().any(expr_contains_gasleft)
333        }
334        Expr::Hash { bytes, .. } => bytes.iter().any(expr_contains_gasleft),
335        Expr::Not(value) => expr_contains_gasleft(value),
336        Expr::Op(_, left, right) => expr_contains_gasleft(left) || expr_contains_gasleft(right),
337        Expr::Ite(cond, left, right) => {
338            bool_contains_gasleft(cond)
339                || expr_contains_gasleft(left)
340                || expr_contains_gasleft(right)
341        }
342    }
343}
344
345/// Returns the `bool_forces_expr_const_with_context` symbolic expression helper result.
346pub(crate) fn bool_forces_expr_const_with_context(
347    condition: &BoolExpr,
348    expr: &Expr,
349    context: &[BoolExpr],
350) -> Option<U256> {
351    match condition {
352        BoolExpr::Eq(left, Expr::Const(value)) => {
353            expr_equality_forces_const(left, *value, expr, context)
354        }
355        BoolExpr::Eq(Expr::Const(value), right) => {
356            expr_equality_forces_const(right, *value, expr, context)
357        }
358        BoolExpr::Not(value) => match value.as_ref() {
359            BoolExpr::Eq(left, Expr::Const(value)) if value.is_zero() => {
360                expr_nonzero_forces_const(left, expr, context)
361            }
362            BoolExpr::Eq(Expr::Const(value), right) if value.is_zero() => {
363                expr_nonzero_forces_const(right, expr, context)
364            }
365            BoolExpr::Not(value) => bool_forces_expr_const_with_context(value, expr, context),
366            _ => None,
367        },
368        BoolExpr::And(values) => values
369            .iter()
370            .find_map(|value| bool_forces_expr_const_with_context(value, expr, context)),
371        _ => None,
372    }
373}
374
375/// Returns the `expr_equality_forces_const` symbolic expression helper result.
376pub(crate) fn expr_equality_forces_const(
377    candidate: &Expr,
378    value: U256,
379    expr: &Expr,
380    context: &[BoolExpr],
381) -> Option<U256> {
382    if candidate == expr {
383        return Some(value);
384    }
385    let mask = masked_expr_matches(candidate, expr)?;
386    if value & !mask != U256::ZERO || !context_forces_masked_expr(context, expr, mask) {
387        return None;
388    }
389    Some(value)
390}
391
392/// Returns the `expr_nonzero_forces_const` symbolic expression helper result.
393pub(crate) fn expr_nonzero_forces_const(
394    expr: &Expr,
395    target: &Expr,
396    context: &[BoolExpr],
397) -> Option<U256> {
398    match expr {
399        Expr::Const(_)
400        | Expr::Var(_)
401        | Expr::GasLeft(_)
402        | Expr::Keccak { .. }
403        | Expr::Hash { .. }
404        | Expr::Not(_) => None,
405        Expr::Ite(cond, then_expr, else_expr) => {
406            if expr_const_value(then_expr).is_some_and(|value| !value.is_zero())
407                && expr_const_value(else_expr).is_some_and(|value| value.is_zero())
408            {
409                bool_forces_expr_const_with_context(cond, target, context)
410            } else {
411                None
412            }
413        }
414        Expr::Op(ExprOp::Or, left, right) => {
415            if expr_const_value(left).is_some_and(|value| value.is_zero()) {
416                return expr_nonzero_forces_const(right, target, context);
417            }
418            if expr_const_value(right).is_some_and(|value| value.is_zero()) {
419                return expr_nonzero_forces_const(left, target, context);
420            }
421            None
422        }
423        Expr::Op(ExprOp::And, left, right) => {
424            if expr_const_value(left).is_some_and(|value| !value.is_zero()) {
425                return expr_nonzero_forces_const(right, target, context);
426            }
427            if expr_const_value(right).is_some_and(|value| !value.is_zero()) {
428                return expr_nonzero_forces_const(left, target, context);
429            }
430            None
431        }
432        Expr::Op(ExprOp::Shl | ExprOp::Shr, value, shift)
433            if expr_const_value(shift).is_some_and(|shift| shift.is_zero()) =>
434        {
435            expr_nonzero_forces_const(value, target, context)
436        }
437        Expr::Op(_, _, _) => None,
438    }
439}
440
441/// Returns whether `masked_expr_matches` holds.
442pub(crate) fn masked_expr_matches(candidate: &Expr, target: &Expr) -> Option<U256> {
443    match candidate {
444        Expr::Op(ExprOp::And, left, right) if left.as_ref() == target => expr_const_value(right),
445        Expr::Op(ExprOp::And, left, right) if right.as_ref() == target => expr_const_value(left),
446        _ => None,
447    }
448}
449
450/// Implements the `context_forces_masked_expr` symbolic expression helper.
451pub(crate) fn context_forces_masked_expr(context: &[BoolExpr], target: &Expr, mask: U256) -> bool {
452    context.iter().any(|condition| match condition {
453        BoolExpr::Eq(left, right) => {
454            (left == target && masked_expr_matches(right, target) == Some(mask))
455                || (right == target && masked_expr_matches(left, target) == Some(mask))
456        }
457        BoolExpr::And(values) => context_forces_masked_expr(values, target, mask),
458        _ => false,
459    })
460}
461
462/// Returns the `expr_const_value` symbolic expression helper result.
463pub(crate) fn expr_const_value(expr: &Expr) -> Option<U256> {
464    match expr {
465        Expr::Const(value) => Some(*value),
466        Expr::Var(_) | Expr::GasLeft(_) | Expr::Keccak { .. } | Expr::Hash { .. } => None,
467        Expr::Not(value) => Some(!expr_const_value(value)?),
468        Expr::Op(op, left, right) => {
469            Some(eval_expr_op(*op, expr_const_value(left)?, expr_const_value(right)?))
470        }
471        Expr::Ite(cond, then_expr, else_expr) => {
472            if bool_const_value(cond)? {
473                expr_const_value(then_expr)
474            } else {
475                expr_const_value(else_expr)
476            }
477        }
478    }
479}
480
481/// Returns the `bool_const_value` symbolic expression helper result.
482pub(crate) fn bool_const_value(expr: &BoolExpr) -> Option<bool> {
483    match expr {
484        BoolExpr::Const(value) => Some(*value),
485        BoolExpr::Not(value) => Some(!bool_const_value(value)?),
486        BoolExpr::And(values) => {
487            let mut all_true = true;
488            for value in values {
489                all_true &= bool_const_value(value)?;
490            }
491            Some(all_true)
492        }
493        BoolExpr::Eq(left, right) => Some(expr_const_value(left)? == expr_const_value(right)?),
494        BoolExpr::Cmp(op, left, right) => {
495            let left = expr_const_value(left)?;
496            let right = expr_const_value(right)?;
497            Some(match op {
498                BoolExprOp::Ult => left < right,
499                BoolExprOp::Ugt => left > right,
500                BoolExprOp::Ule => left <= right,
501                BoolExprOp::Uge => left >= right,
502                BoolExprOp::Slt => slt(left, right),
503                BoolExprOp::Sgt => slt(right, left),
504            })
505        }
506    }
507}
508
509/// Returns the `bool_contains_keccak` symbolic expression helper result.
510pub(crate) fn bool_contains_keccak(expr: &BoolExpr) -> bool {
511    match expr {
512        BoolExpr::Const(_) => false,
513        BoolExpr::Not(value) => bool_contains_keccak(value),
514        BoolExpr::And(values) => values.iter().any(bool_contains_keccak),
515        BoolExpr::Eq(left, right) | BoolExpr::Cmp(_, left, right) => {
516            expr_contains_keccak(left) || expr_contains_keccak(right)
517        }
518    }
519}
520
521/// Returns whether a boolean expression depends on the opaque `GAS` / `gasleft()` value.
522pub(crate) fn bool_contains_gasleft(expr: &BoolExpr) -> bool {
523    match expr {
524        BoolExpr::Const(_) => false,
525        BoolExpr::Not(value) => bool_contains_gasleft(value),
526        BoolExpr::And(values) => values.iter().any(bool_contains_gasleft),
527        BoolExpr::Eq(left, right) | BoolExpr::Cmp(_, left, right) => {
528            expr_contains_gasleft(left) || expr_contains_gasleft(right)
529        }
530    }
531}
532
533/// Returns the `word_bytes` symbolic expression helper result.
534pub(crate) fn word_bytes(word: SymWord) -> Vec<SymWord> {
535    match word {
536        SymWord::Concrete(word) => word
537            .to_be_bytes::<32>()
538            .into_iter()
539            .map(|byte| SymWord::Concrete(U256::from(byte)))
540            .collect(),
541        word => (0..32).map(|idx| byte_word(U256::from(idx), word.clone())).collect(),
542    }
543}
544
545/// Returns the `word_from_bytes` symbolic expression helper result.
546pub(crate) fn word_from_bytes(bytes: impl IntoIterator<Item = SymWord>) -> SymWord {
547    let bytes = bytes.into_iter().collect::<Vec<_>>();
548    if bytes.iter().all(|byte| matches!(byte, SymWord::Concrete(_))) {
549        let mut word = [0u8; 32];
550        for (idx, byte) in bytes.into_iter().take(32).enumerate() {
551            let SymWord::Concrete(byte) = byte else { unreachable!() };
552            word[idx] = byte.to::<u8>();
553        }
554        return SymWord::Concrete(U256::from_be_bytes(word));
555    }
556
557    if let Some(expr) = word_from_extracted_bytes(&bytes) {
558        return SymWord::Expr(expr);
559    }
560
561    let mut expr = Expr::Const(U256::ZERO);
562    for (idx, byte) in bytes.into_iter().take(32).enumerate() {
563        let shift = (31 - idx) * 8;
564        let byte = low_byte(byte).into_expr();
565        let byte = if shift == 0 {
566            byte
567        } else {
568            Expr::op(ExprOp::Shl, byte, Expr::Const(U256::from(shift)))
569        };
570        expr = Expr::op(ExprOp::Or, expr, byte);
571    }
572    SymWord::Expr(expr)
573}
574
575/// Returns the `word_from_extracted_bytes` symbolic expression helper result.
576pub(crate) fn word_from_extracted_bytes(bytes: &[SymWord]) -> Option<Expr> {
577    if bytes.len() < 32 {
578        return None;
579    }
580
581    let source = bytes
582        .iter()
583        .take(32)
584        .enumerate()
585        .find_map(|(idx, byte)| extracted_byte_source(byte, idx))?;
586
587    for (idx, byte) in bytes.iter().take(32).enumerate() {
588        if let Some(byte_source) = extracted_byte_source(byte, idx) {
589            if byte_source != source {
590                return None;
591            }
592            continue;
593        }
594
595        let SymWord::Concrete(byte) = byte else { return None };
596        if expr_known_byte(&source, idx) != Some(byte.to::<u8>()) {
597            return None;
598        }
599    }
600    Some(source)
601}
602
603/// Implements the `extracted_byte_source` symbolic expression helper.
604pub(crate) fn extracted_byte_source(byte: &SymWord, index: usize) -> Option<Expr> {
605    let SymWord::Expr(expr) = byte else { return None };
606    let expr = strip_low_byte_mask(expr)?;
607    if index == 31 {
608        return Some(expr.clone());
609    }
610    let Expr::Op(ExprOp::Shr, source, shift) = expr else { return None };
611    let Expr::Const(shift) = shift.as_ref() else { return None };
612    (*shift == U256::from((31 - index) * 8)).then(|| *source.clone())
613}
614
615/// Implements the `strip_low_byte_mask` symbolic expression helper.
616pub(crate) fn strip_low_byte_mask(expr: &Expr) -> Option<&Expr> {
617    match expr {
618        Expr::Op(ExprOp::And, left, right) if matches!(right.as_ref(), Expr::Const(mask) if *mask == U256::from(0xff)) => {
619            Some(strip_low_byte_mask(left).unwrap_or(left))
620        }
621        Expr::Op(ExprOp::And, left, right) if matches!(left.as_ref(), Expr::Const(mask) if *mask == U256::from(0xff)) => {
622            Some(strip_low_byte_mask(right).unwrap_or(right))
623        }
624        _ => Some(expr),
625    }
626}
627
628/// Returns the `low_byte` symbolic expression helper result.
629pub(crate) fn low_byte(word: SymWord) -> SymWord {
630    match word {
631        SymWord::Concrete(word) => SymWord::Concrete(U256::from(word.to::<u8>())),
632        word => {
633            SymWord::Expr(Expr::op(ExprOp::And, word.into_expr(), Expr::Const(U256::from(0xff))))
634        }
635    }
636}
637
638/// Returns the `model_word` symbolic expression helper result.
639pub(crate) fn model_word(
640    word: &SymWord,
641    model: &BTreeMap<String, U256>,
642) -> Result<U256, SymbolicError> {
643    eval_expr(&word.clone().into_expr(), model)
644}
645
646/// Returns the `model_bytes` symbolic expression helper result.
647pub(crate) fn model_bytes(
648    bytes: &[SymWord],
649    model: &BTreeMap<String, U256>,
650) -> Result<Vec<u8>, SymbolicError> {
651    bytes.iter().map(|byte| Ok(model_word(byte, model)?.to::<u8>())).collect()
652}
653
654/// Returns the `concrete_bytes` symbolic expression helper result.
655pub(crate) fn concrete_bytes(
656    bytes: &[SymWord],
657    reason: &'static str,
658) -> Result<Vec<u8>, SymbolicError> {
659    bytes
660        .iter()
661        .map(|byte| match byte {
662            SymWord::Concrete(value) => Ok(value.to::<u8>()),
663            SymWord::Expr(_) => Err(SymbolicError::Unsupported(reason)),
664        })
665        .collect()
666}
667
668/// Implements the `calldata_prefix_condition` symbolic expression helper.
669pub(crate) fn calldata_prefix_condition(
670    calldata: &[SymWord],
671    prefix: &[SymWord],
672    _reason: &'static str,
673) -> Result<Option<BoolExpr>, SymbolicError> {
674    if prefix.len() > calldata.len() {
675        return Ok(None);
676    }
677    let mut conditions = Vec::new();
678    for (actual, expected) in calldata.iter().zip(prefix) {
679        if actual == expected {
680            continue;
681        }
682        match (actual, expected) {
683            (SymWord::Concrete(actual), SymWord::Concrete(expected))
684                if actual.to::<u8>() == expected.to::<u8>() => {}
685            (SymWord::Concrete(_), SymWord::Concrete(_)) => return Ok(None),
686            _ => conditions
687                .push(BoolExpr::eq(actual.clone().into_expr(), expected.clone().into_expr())),
688        }
689    }
690    Ok(Some(BoolExpr::and(conditions)))
691}
692
693/// Implements the `function_mock_match_condition` symbolic expression helper.
694pub(crate) fn function_mock_match_condition(
695    mock: &FunctionMock,
696    callee: Address,
697    calldata: &[SymWord],
698    reason: &'static str,
699) -> Result<Option<BoolExpr>, SymbolicError> {
700    let Some(data_condition) = calldata_prefix_condition(calldata, &mock.data, reason)? else {
701        return Ok(None);
702    };
703    Ok(Some(BoolExpr::and(vec![address_match_condition(&mock.callee, callee), data_condition])))
704}
705
706/// Returns the `eval_expr` symbolic expression helper result.
707pub(crate) fn eval_expr(
708    expr: &Expr,
709    model: &BTreeMap<String, U256>,
710) -> Result<U256, SymbolicError> {
711    Ok(match expr {
712        Expr::Const(value) => *value,
713        Expr::Var(var) => model.get(var).copied().unwrap_or_default(),
714        Expr::GasLeft(_) => return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled")),
715        Expr::Keccak { len, bytes, .. } => eval_keccak_expr(len, bytes, model)?,
716        Expr::Hash { name, .. } => model.get(name).copied().unwrap_or_default(),
717        Expr::Not(value) => !eval_expr(value, model)?,
718        Expr::Op(op, left, right) => {
719            let left = eval_expr(left, model)?;
720            let right = eval_expr(right, model)?;
721            eval_expr_op(*op, left, right)
722        }
723        Expr::Ite(cond, then_expr, else_expr) => {
724            if eval_bool_expr(cond, model)? {
725                eval_expr(then_expr, model)?
726            } else {
727                eval_expr(else_expr, model)?
728            }
729        }
730    })
731}
732
733/// Returns the concrete keccak value implied by a solver model.
734pub(crate) fn eval_keccak_expr(
735    len: &Expr,
736    bytes: &[Expr],
737    model: &BTreeMap<String, U256>,
738) -> Result<U256, SymbolicError> {
739    let len = eval_expr(len, model)?;
740    if len > U256::from(bytes.len()) {
741        return Err(SymbolicError::Solver(
742            "solver model uses an invalid keccak length".to_string(),
743        ));
744    }
745
746    let mut input = Vec::with_capacity(len.to::<usize>());
747    for byte in bytes.iter().take(len.to::<usize>()) {
748        input.push((eval_expr(byte, model)? & U256::from(0xff)).to::<u8>());
749    }
750
751    Ok(U256::from_be_bytes(keccak256(input).0))
752}
753
754/// Returns the `eval_expr_op` symbolic expression helper result.
755pub(crate) fn eval_expr_op(op: ExprOp, left: U256, right: U256) -> U256 {
756    match op {
757        ExprOp::Add => left.wrapping_add(right),
758        ExprOp::Sub => left.wrapping_sub(right),
759        ExprOp::Mul => left.wrapping_mul(right),
760        ExprOp::UDiv => {
761            if right.is_zero() {
762                U256::ZERO
763            } else {
764                left / right
765            }
766        }
767        ExprOp::URem => {
768            if right.is_zero() {
769                U256::ZERO
770            } else {
771                left % right
772            }
773        }
774        ExprOp::SDiv => sdiv(left, right),
775        ExprOp::SRem => smod(left, right),
776        ExprOp::And => left & right,
777        ExprOp::Or => left | right,
778        ExprOp::Xor => left ^ right,
779        ExprOp::Shl => {
780            if right >= U256::from(256) {
781                U256::ZERO
782            } else {
783                left << right.to::<usize>()
784            }
785        }
786        ExprOp::Shr => {
787            if right >= U256::from(256) {
788                U256::ZERO
789            } else {
790                left >> right.to::<usize>()
791            }
792        }
793        ExprOp::Sar => {
794            if right >= U256::from(256) {
795                sar(left, 256)
796            } else {
797                sar(left, right.to::<usize>())
798            }
799        }
800    }
801}
802
803/// Returns the `eval_bool_expr` symbolic expression helper result.
804pub(crate) fn eval_bool_expr(
805    expr: &BoolExpr,
806    model: &BTreeMap<String, U256>,
807) -> Result<bool, SymbolicError> {
808    Ok(match expr {
809        BoolExpr::Const(value) => *value,
810        BoolExpr::Not(value) => !eval_bool_expr(value, model)?,
811        BoolExpr::And(values) => {
812            for value in values {
813                if !eval_bool_expr(value, model)? {
814                    return Ok(false);
815                }
816            }
817            true
818        }
819        BoolExpr::Eq(left, right) => eval_expr(left, model)? == eval_expr(right, model)?,
820        BoolExpr::Cmp(op, left, right) => {
821            let left = eval_expr(left, model)?;
822            let right = eval_expr(right, model)?;
823            match op {
824                BoolExprOp::Ult => left < right,
825                BoolExprOp::Ugt => left > right,
826                BoolExprOp::Ule => left <= right,
827                BoolExprOp::Uge => left >= right,
828                BoolExprOp::Slt => slt(left, right),
829                BoolExprOp::Sgt => slt(right, left),
830            }
831        }
832    })
833}
834
835#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
836pub(crate) enum SymWord {
837    Concrete(U256),
838    Expr(Expr),
839}
840
841impl SymWord {
842    /// Implements the `zero` symbolic expression helper.
843    pub(crate) const fn zero() -> Self {
844        Self::Concrete(U256::ZERO)
845    }
846
847    /// Returns whether this word depends on the opaque `GAS` / `gasleft()` value.
848    pub(crate) fn contains_gasleft(&self) -> bool {
849        match self {
850            Self::Concrete(_) => false,
851            Self::Expr(expr) => expr_contains_gasleft(expr),
852        }
853    }
854
855    /// Returns whether this word is exactly the opaque `GAS` / `gasleft()` value.
856    pub(crate) const fn is_raw_gasleft(&self) -> bool {
857        matches!(self, Self::Expr(Expr::GasLeft(_)))
858    }
859
860    /// Implements the `into_expr` symbolic expression helper.
861    pub(crate) fn into_expr(self) -> Expr {
862        match self {
863            Self::Concrete(value) => Expr::Const(value),
864            Self::Expr(expr) => expr,
865        }
866    }
867
868    /// Converts values for the `from_bool` symbolic expression helper.
869    pub(crate) fn from_bool(value: BoolExpr) -> Self {
870        match value {
871            BoolExpr::Const(value) => Self::Concrete(U256::from(value)),
872            value => Self::Expr(Expr::Ite(
873                Box::new(value),
874                Box::new(Expr::Const(U256::from(1))),
875                Box::new(Expr::Const(U256::ZERO)),
876            )),
877        }
878    }
879
880    /// Implements the `truth` symbolic expression helper.
881    pub(crate) fn truth(&self) -> Option<bool> {
882        match self {
883            Self::Concrete(value) => Some(!value.is_zero()),
884            _ => None,
885        }
886    }
887
888    /// Implements the `into_zero_bool` symbolic expression helper.
889    pub(crate) fn into_zero_bool(self) -> BoolExpr {
890        match self {
891            Self::Concrete(value) => BoolExpr::Const(value.is_zero()),
892            Self::Expr(Expr::Ite(cond, then_expr, else_expr))
893                if then_expr.as_ref() == &Expr::Const(U256::from(1))
894                    && else_expr.as_ref() == &Expr::Const(U256::ZERO) =>
895            {
896                cond.not()
897            }
898            Self::Expr(Expr::Ite(cond, then_expr, else_expr))
899                if then_expr.as_ref() == &Expr::Const(U256::ZERO)
900                    && else_expr.as_ref() == &Expr::Const(U256::from(1)) =>
901            {
902                *cond
903            }
904            value => BoolExpr::eq(value.into_expr(), Expr::Const(U256::ZERO)),
905        }
906    }
907
908    /// Implements the `nonzero_bool` symbolic expression helper.
909    pub(crate) fn nonzero_bool(self) -> BoolExpr {
910        self.into_zero_bool().not()
911    }
912
913    /// Implements the `into_concrete` symbolic expression helper.
914    pub(crate) fn into_concrete(self, reason: &'static str) -> Result<U256, SymbolicError> {
915        match self {
916            Self::Concrete(value) => Ok(value),
917            Self::Expr(_) => Err(SymbolicError::Unsupported(reason)),
918        }
919    }
920
921    /// Implements the `into_usize` symbolic expression helper.
922    pub(crate) fn into_usize(self, reason: &'static str) -> Result<usize, SymbolicError> {
923        let value = self.into_concrete(reason)?;
924        if value > U256::from(usize::MAX) {
925            return Err(SymbolicError::Unsupported(reason));
926        }
927        Ok(value.to::<usize>())
928    }
929}
930
931#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
932pub(crate) enum Expr {
933    Const(U256),
934    Var(String),
935    GasLeft(usize),
936    Keccak { name: String, len: Box<Self>, bytes: Vec<Self> },
937    Hash { name: String, algorithm: &'static str, bytes: Vec<Self> },
938    Not(Box<Self>),
939    Op(ExprOp, Box<Self>, Box<Self>),
940    Ite(Box<BoolExpr>, Box<Self>, Box<Self>),
941}
942
943impl Expr {
944    /// Implements the `op` symbolic expression helper.
945    pub(crate) fn op(op: ExprOp, left: Self, right: Self) -> Self {
946        if let (Self::Const(left), Self::Const(right)) = (&left, &right) {
947            return Self::Const(eval_expr_op(op, *left, *right));
948        }
949
950        match (op, left, right) {
951            (ExprOp::Add, Self::Const(value), expr) | (ExprOp::Add, expr, Self::Const(value))
952                if value.is_zero() =>
953            {
954                expr
955            }
956            (ExprOp::Sub, expr, Self::Const(value)) if value.is_zero() => expr,
957            (ExprOp::Sub, left, right) if left == right => Self::Const(U256::ZERO),
958            (ExprOp::Mul, Self::Const(value), _) | (ExprOp::Mul, _, Self::Const(value))
959                if value.is_zero() =>
960            {
961                Self::Const(U256::ZERO)
962            }
963            (ExprOp::Mul, Self::Const(value), expr) | (ExprOp::Mul, expr, Self::Const(value))
964                if value == U256::from(1) =>
965            {
966                expr
967            }
968            (ExprOp::UDiv | ExprOp::URem | ExprOp::SDiv | ExprOp::SRem, _, Self::Const(value))
969                if value.is_zero() =>
970            {
971                Self::Const(U256::ZERO)
972            }
973            (ExprOp::UDiv | ExprOp::SDiv, expr, Self::Const(value)) if value == U256::from(1) => {
974                expr
975            }
976            (ExprOp::URem | ExprOp::SRem, _, Self::Const(value)) if value == U256::from(1) => {
977                Self::Const(U256::ZERO)
978            }
979            (ExprOp::And, Self::Const(value), _) | (ExprOp::And, _, Self::Const(value))
980                if value.is_zero() =>
981            {
982                Self::Const(U256::ZERO)
983            }
984            (ExprOp::And, Self::Const(value), expr) | (ExprOp::And, expr, Self::Const(value))
985                if value == U256::MAX =>
986            {
987                expr
988            }
989            (ExprOp::And, left, right) if left == right => left,
990            (ExprOp::And, Self::Const(mask), expr) | (ExprOp::And, expr, Self::Const(mask)) => {
991                Self::and_const(expr, mask)
992            }
993            (ExprOp::Or | ExprOp::Xor, Self::Const(value), expr)
994            | (ExprOp::Or | ExprOp::Xor, expr, Self::Const(value))
995                if value.is_zero() =>
996            {
997                expr
998            }
999            (ExprOp::Shl | ExprOp::Shr | ExprOp::Sar, expr, Self::Const(value))
1000                if value.is_zero() =>
1001            {
1002                expr
1003            }
1004            (ExprOp::Shl | ExprOp::Shr, Self::Const(value), _) if value.is_zero() => {
1005                Self::Const(U256::ZERO)
1006            }
1007            (op, left, right) => Self::Op(op, Box::new(left), Box::new(right)),
1008        }
1009    }
1010
1011    fn and_const(expr: Self, mask: U256) -> Self {
1012        if mask.is_zero() {
1013            return Self::Const(U256::ZERO);
1014        }
1015        if mask == U256::MAX {
1016            return expr;
1017        }
1018
1019        match expr {
1020            Self::Op(ExprOp::And, left, right) => match (*left, *right) {
1021                (Self::Const(existing), inner) | (inner, Self::Const(existing))
1022                    if existing == mask =>
1023                {
1024                    Self::and_const(inner, mask)
1025                }
1026                (left, right) if left == right => Self::and_const(left, mask),
1027                (left, right) => Self::Op(
1028                    ExprOp::And,
1029                    Box::new(Self::Op(ExprOp::And, Box::new(left), Box::new(right))),
1030                    Box::new(Self::Const(mask)),
1031                ),
1032            },
1033            expr => Self::Op(ExprOp::And, Box::new(expr), Box::new(Self::Const(mask))),
1034        }
1035    }
1036
1037    /// Implements the `collect_vars` symbolic expression helper.
1038    pub(crate) fn collect_vars(&self, vars: &mut BTreeSet<String>) {
1039        match self {
1040            Self::Const(_) => {}
1041            Self::GasLeft(_) => {}
1042            Self::Var(var) => {
1043                vars.insert(var.clone());
1044            }
1045            Self::Keccak { name, .. } | Self::Hash { name, .. } => {
1046                vars.insert(name.clone());
1047            }
1048            Self::Not(value) => value.collect_vars(vars),
1049            Self::Op(_, left, right) => {
1050                left.collect_vars(vars);
1051                right.collect_vars(vars);
1052            }
1053            Self::Ite(cond, left, right) => {
1054                cond.collect_vars(vars);
1055                left.collect_vars(vars);
1056                right.collect_vars(vars);
1057            }
1058        }
1059    }
1060
1061    /// Implements the `smt` symbolic expression helper.
1062    pub(crate) fn smt(&self) -> String {
1063        match self {
1064            Self::Const(value) => format!("(_ bv{value} 256)"),
1065            Self::Var(var) => var.clone(),
1066            Self::GasLeft(id) => format!("gasleft_{id}"),
1067            Self::Keccak { name, .. } | Self::Hash { name, .. } => name.clone(),
1068            Self::Not(value) => format!("(bvnot {})", value.smt()),
1069            Self::Op(op, left, right) => format!("({} {} {})", op.smt(), left.smt(), right.smt()),
1070            Self::Ite(cond, left, right) => {
1071                format!("(ite {} {} {})", cond.smt(), left.smt(), right.smt())
1072            }
1073        }
1074    }
1075}
1076
1077#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1078pub(crate) enum ExprOp {
1079    Add,
1080    Sub,
1081    Mul,
1082    UDiv,
1083    URem,
1084    SDiv,
1085    SRem,
1086    And,
1087    Or,
1088    Xor,
1089    Shl,
1090    Shr,
1091    Sar,
1092}
1093
1094impl ExprOp {
1095    /// Implements the `smt` symbolic expression helper.
1096    pub(crate) const fn smt(self) -> &'static str {
1097        match self {
1098            Self::Add => "bvadd",
1099            Self::Sub => "bvsub",
1100            Self::Mul => "bvmul",
1101            Self::UDiv => "bvudiv",
1102            Self::URem => "bvurem",
1103            Self::SDiv => "bvsdiv",
1104            Self::SRem => "bvsrem",
1105            Self::And => "bvand",
1106            Self::Or => "bvor",
1107            Self::Xor => "bvxor",
1108            Self::Shl => "bvshl",
1109            Self::Shr => "bvlshr",
1110            Self::Sar => "bvashr",
1111        }
1112    }
1113}
1114
1115#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1116pub(crate) enum BoolExpr {
1117    Const(bool),
1118    Not(Box<Self>),
1119    And(Vec<Self>),
1120    Eq(Expr, Expr),
1121    Cmp(BoolExprOp, Expr, Expr),
1122}
1123
1124impl BoolExpr {
1125    /// Implements the `eq` symbolic expression helper.
1126    pub(crate) fn eq(left: Expr, right: Expr) -> Self {
1127        if left == right {
1128            return Self::Const(true);
1129        }
1130        match (&left, &right) {
1131            (Expr::Const(left), Expr::Const(right)) => Self::Const(left == right),
1132            (
1133                Expr::Keccak { len: left_len, bytes: left_bytes, .. },
1134                Expr::Keccak { len: right_len, bytes: right_bytes, .. },
1135            ) if left_bytes.len() == right_bytes.len() => {
1136                let mut conditions = vec![Self::eq((**left_len).clone(), (**right_len).clone())];
1137                conditions.extend(
1138                    left_bytes
1139                        .iter()
1140                        .cloned()
1141                        .zip(right_bytes.iter().cloned())
1142                        .map(|(left, right)| Self::eq(left, right)),
1143                );
1144                Self::and(conditions)
1145            }
1146            (
1147                Expr::Hash { algorithm: left_algorithm, bytes: left_bytes, .. },
1148                Expr::Hash { algorithm: right_algorithm, bytes: right_bytes, .. },
1149            ) if left_algorithm == right_algorithm && left_bytes.len() == right_bytes.len() => {
1150                Self::and(
1151                    left_bytes
1152                        .iter()
1153                        .cloned()
1154                        .zip(right_bytes.iter().cloned())
1155                        .map(|(left, right)| Self::eq(left, right))
1156                        .collect(),
1157                )
1158            }
1159            _ => Self::Eq(left, right),
1160        }
1161    }
1162
1163    /// Implements the `and` symbolic expression helper.
1164    pub(crate) fn and(values: Vec<Self>) -> Self {
1165        let mut out = Vec::new();
1166        for value in values {
1167            match value {
1168                Self::Const(true) => {}
1169                Self::Const(false) => return Self::Const(false),
1170                Self::And(values) => out.extend(values),
1171                value => out.push(value),
1172            }
1173        }
1174        if out.is_empty() {
1175            Self::Const(true)
1176        } else if out.len() == 1 {
1177            out.pop().expect("single item exists")
1178        } else {
1179            Self::And(out)
1180        }
1181    }
1182
1183    /// Implements the `or` symbolic expression helper.
1184    pub(crate) fn or(values: Vec<Self>) -> Self {
1185        let mut out = Vec::new();
1186        for value in values {
1187            match value {
1188                Self::Const(false) => {}
1189                Self::Const(true) => return Self::Const(true),
1190                value => out.push(value),
1191            }
1192        }
1193        if out.is_empty() {
1194            Self::Const(false)
1195        } else if out.len() == 1 {
1196            out.pop().expect("single item exists")
1197        } else {
1198            Self::and(out.into_iter().map(Self::not).collect()).not()
1199        }
1200    }
1201
1202    /// Implements the `cmp` symbolic expression helper.
1203    pub(crate) fn cmp(op: BoolExprOp, left: Expr, right: Expr) -> Self {
1204        if left == right {
1205            return Self::Const(matches!(op, BoolExprOp::Ule | BoolExprOp::Uge));
1206        }
1207        if let (Expr::Const(left), Expr::Const(right)) = (&left, &right) {
1208            return Self::Const(match op {
1209                BoolExprOp::Ult => left < right,
1210                BoolExprOp::Ugt => left > right,
1211                BoolExprOp::Ule => left <= right,
1212                BoolExprOp::Uge => left >= right,
1213                BoolExprOp::Slt => slt(*left, *right),
1214                BoolExprOp::Sgt => slt(*right, *left),
1215            });
1216        }
1217        match (op, &left, &right) {
1218            (BoolExprOp::Ugt, Expr::Const(value), _) if value.is_zero() => {
1219                return Self::Const(false);
1220            }
1221            (BoolExprOp::Ule, Expr::Const(value), _) if value.is_zero() => {
1222                return Self::Const(true);
1223            }
1224            (BoolExprOp::Ult, _, Expr::Const(value)) if value.is_zero() => {
1225                return Self::Const(false);
1226            }
1227            (BoolExprOp::Uge, _, Expr::Const(value)) if value.is_zero() => {
1228                return Self::Const(true);
1229            }
1230            (BoolExprOp::Ult, Expr::Const(value), _) if *value == U256::MAX => {
1231                return Self::Const(false);
1232            }
1233            (BoolExprOp::Uge, Expr::Const(value), _) if *value == U256::MAX => {
1234                return Self::Const(true);
1235            }
1236            (BoolExprOp::Ugt, _, Expr::Const(value)) if *value == U256::MAX => {
1237                return Self::Const(false);
1238            }
1239            (BoolExprOp::Ule, _, Expr::Const(value)) if *value == U256::MAX => {
1240                return Self::Const(true);
1241            }
1242            _ => {}
1243        }
1244        Self::Cmp(op, left, right)
1245    }
1246
1247    /// Implements the `not` symbolic expression helper.
1248    pub(crate) fn not(self) -> Self {
1249        match self {
1250            Self::Const(value) => Self::Const(!value),
1251            Self::Not(value) => *value,
1252            Self::And(values) => Self::Not(Box::new(Self::And(values))),
1253            value => Self::Not(Box::new(value)),
1254        }
1255    }
1256
1257    /// Implements the `collect_vars` symbolic expression helper.
1258    pub(crate) fn collect_vars(&self, vars: &mut BTreeSet<String>) {
1259        match self {
1260            Self::Const(_) => {}
1261            Self::Not(value) => value.collect_vars(vars),
1262            Self::And(values) => {
1263                for value in values {
1264                    value.collect_vars(vars);
1265                }
1266            }
1267            Self::Eq(left, right) | Self::Cmp(_, left, right) => {
1268                left.collect_vars(vars);
1269                right.collect_vars(vars);
1270            }
1271        }
1272    }
1273
1274    /// Implements the `smt` symbolic expression helper.
1275    pub(crate) fn smt(&self) -> String {
1276        match self {
1277            Self::Const(value) => value.to_string(),
1278            Self::Not(value) => format!("(not {})", value.smt()),
1279            Self::And(values) => {
1280                format!("(and {})", values.iter().map(Self::smt).collect::<Vec<_>>().join(" "))
1281            }
1282            Self::Eq(left, right) => format!("(= {} {})", left.smt(), right.smt()),
1283            Self::Cmp(op, left, right) => format!("({} {} {})", op.smt(), left.smt(), right.smt()),
1284        }
1285    }
1286}
1287
1288#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1289pub(crate) enum BoolExprOp {
1290    Ult,
1291    Ugt,
1292    Ule,
1293    Uge,
1294    Slt,
1295    Sgt,
1296}
1297
1298impl BoolExprOp {
1299    /// Implements the `smt` symbolic expression helper.
1300    pub(crate) const fn smt(self) -> &'static str {
1301        match self {
1302            Self::Ult => "bvult",
1303            Self::Ugt => "bvugt",
1304            Self::Ule => "bvule",
1305            Self::Uge => "bvuge",
1306            Self::Slt => "bvslt",
1307            Self::Sgt => "bvsgt",
1308        }
1309    }
1310}
1311
1312/// Returns the `u256_to_usize` symbolic expression helper result.
1313pub(crate) fn u256_to_usize(value: U256) -> Option<usize> {
1314    if value > U256::from(usize::MAX) { None } else { Some(value.to::<usize>()) }
1315}
1316
1317/// Returns the `bool_upper_bound_usize` symbolic expression helper result.
1318pub(crate) fn bool_upper_bound_usize(condition: &BoolExpr, expr: &Expr) -> Option<usize> {
1319    match condition {
1320        BoolExpr::Const(_) | BoolExpr::Not(_) => None,
1321        BoolExpr::And(values) => {
1322            let mut bound: Option<usize> = None;
1323            for value in values {
1324                if let Some(candidate) = bool_upper_bound_usize(value, expr) {
1325                    bound = Some(bound.map_or(candidate, |bound| bound.min(candidate)));
1326                }
1327            }
1328            bound
1329        }
1330        BoolExpr::Eq(left, right) => match (left == expr, right == expr) {
1331            (true, _) => expr_const_value(right).and_then(u256_to_usize),
1332            (_, true) => expr_const_value(left).and_then(u256_to_usize),
1333            _ => None,
1334        },
1335        BoolExpr::Cmp(op, left, right) => {
1336            if left == expr {
1337                match op {
1338                    BoolExprOp::Ult => expr_const_value(right)
1339                        .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::from(1)))
1340                        .and_then(u256_to_usize),
1341                    BoolExprOp::Ule => expr_const_value(right).and_then(u256_to_usize),
1342                    _ => None,
1343                }
1344            } else if right == expr {
1345                match op {
1346                    BoolExprOp::Ugt => expr_const_value(left)
1347                        .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::from(1)))
1348                        .and_then(u256_to_usize),
1349                    BoolExprOp::Uge => expr_const_value(left).and_then(u256_to_usize),
1350                    _ => None,
1351                }
1352            } else {
1353                None
1354            }
1355        }
1356    }
1357}