Skip to main content

foundry_evm_symbolic/runtime/
evm.rs

1use super::*;
2use alloy_sol_types::{
3    Panic, PanicKind, Revert, SolError,
4    abi::{AbiDecoderConfig, decode_with_config, token::PackedSeqToken},
5};
6use foundry_evm::core::decode::ASSERTION_FAILED_PREFIX;
7
8pub(crate) fn exp_expr_for_concrete_exponent(
9    cx: &mut SymCx,
10    base: SymExpr,
11    exponent: usize,
12) -> SymExpr {
13    if exponent == 0 {
14        return SymExpr::one(cx);
15    }
16    if let Some(base) = base.as_const() {
17        return SymExpr::constant(cx, base.wrapping_pow(U256::from(exponent)));
18    }
19
20    let mut expr = base.clone();
21    for _ in 1..exponent {
22        expr = SymExpr::binop(cx, SymBinOp::Mul, expr, base.clone());
23    }
24    expr
25}
26
27pub(crate) fn signextend(byte_index: U256, value: U256) -> U256 {
28    if byte_index >= U256::from(32) {
29        return value;
30    }
31    let bit_index = usize::try_from(byte_index).expect("checked byte index") * 8 + 7;
32    let sign_bit = U256::ONE << bit_index;
33    let mask = sign_bit - U256::ONE;
34    if (value & sign_bit).is_zero() { value & mask } else { value | !mask }
35}
36
37pub(crate) fn signextend_word(cx: &mut SymCx, byte_index: U256, value: SymExpr) -> SymExpr {
38    if byte_index >= U256::from(32) {
39        return value;
40    }
41    if let Some(value) = value.as_const() {
42        return SymExpr::constant(cx, signextend(byte_index, value));
43    }
44    let bit_index = usize::try_from(byte_index).expect("checked byte index") * 8 + 7;
45    let sign_bit = U256::ONE << bit_index;
46    let mask_value = sign_bit - U256::ONE;
47    let sign_bit = SymExpr::constant(cx, sign_bit);
48    let masked_sign = SymExpr::binop(cx, SymBinOp::And, value.clone(), sign_bit);
49    let zero = SymExpr::zero(cx);
50    let condition = SymBoolExpr::eq(cx, masked_sign, zero);
51    let inverse_mask = SymExpr::constant(cx, !mask_value);
52    let mask = SymExpr::constant(cx, mask_value);
53    let masked = SymExpr::binop(cx, SymBinOp::And, value.clone(), mask);
54    let extended = SymExpr::binop(cx, SymBinOp::Or, value, inverse_mask);
55    SymExpr::ite(cx, condition, masked, extended)
56}
57
58pub(crate) fn signextend_word_dynamic(
59    cx: &mut SymCx,
60    byte_index: SymExpr,
61    value: SymExpr,
62) -> SymExpr {
63    if let Some(byte_index) = byte_index.as_const() {
64        return signextend_word(cx, byte_index, value);
65    }
66
67    let mut result = value.clone();
68    for idx in (0..31).rev() {
69        let idx_expr = SymExpr::constant(cx, U256::from(idx));
70        let condition = SymBoolExpr::eq(cx, byte_index.clone(), idx_expr);
71        let value = signextend_word(cx, U256::from(idx), value.clone());
72        result = SymExpr::ite(cx, condition, value, result);
73    }
74    result
75}
76
77pub(crate) fn byte_word(cx: &mut SymCx, index: U256, word: SymExpr) -> SymExpr {
78    if index >= U256::from(32) {
79        return SymExpr::zero(cx);
80    }
81    let index = usize::try_from(index).expect("checked byte index");
82    if let Some(word) = word.as_const() {
83        SymExpr::constant(cx, U256::from(word.to_be_bytes::<32>()[index]))
84    } else {
85        byte_expr(cx, index, &word)
86    }
87}
88
89pub(crate) fn byte_word_dynamic(cx: &mut SymCx, index: SymExpr, word: SymExpr) -> SymExpr {
90    if let Some(index) = index.as_const() {
91        return byte_word(cx, index, word);
92    }
93
94    let mut result = SymExpr::zero(cx);
95    if let Some(word) = word.as_const() {
96        let bytes = word.to_be_bytes::<32>();
97        for idx in (0..32).rev() {
98            let idx_expr = SymExpr::constant(cx, U256::from(idx));
99            let condition = SymBoolExpr::eq(cx, index.clone(), idx_expr);
100            let byte = SymExpr::constant(cx, U256::from(bytes[idx]));
101            result = SymExpr::ite(cx, condition, byte, result);
102        }
103    } else {
104        for idx in (0..32).rev() {
105            let idx_expr = SymExpr::constant(cx, U256::from(idx));
106            let condition = SymBoolExpr::eq(cx, index.clone(), idx_expr);
107            let byte = byte_expr(cx, idx, &word);
108            result = SymExpr::ite(cx, condition, byte, result);
109        }
110    }
111    result
112}
113
114/// Returns the byte extraction expression for a symbolic word.
115pub(crate) fn byte_expr(cx: &mut SymCx, index: usize, expr: &SymExpr) -> SymExpr {
116    debug_assert!(index < 32);
117    if let Some(byte) = expr.known_byte(index) {
118        return SymExpr::constant(cx, U256::from(byte));
119    }
120    expr.extracted_byte(cx, index)
121}
122
123pub(crate) fn shift_left(cx: &mut SymCx, value: SymExpr, bits: usize) -> SymExpr {
124    if let Some(value) = value.as_const() {
125        SymExpr::constant(cx, value << bits)
126    } else {
127        let bits = SymExpr::constant(cx, U256::from(bits));
128        SymExpr::binop(cx, SymBinOp::Shl, value, bits)
129    }
130}
131
132pub(crate) fn is_assertion_revert(data: &[u8]) -> bool {
133    Panic::abi_decode(data).is_ok_and(|panic| panic.kind() == Some(PanicKind::Assert))
134        || is_revert_assertion_failure(data)
135}
136
137pub(crate) fn is_revert_assertion_failure(data: &[u8]) -> bool {
138    if data.len() < ERROR_DATA_MIN_LEN {
139        return false;
140    }
141    let Some(data) = data.strip_prefix(&Revert::SELECTOR) else { return false };
142    // Decode a borrowed token to preserve strict UTF-8 checks without allocating a String.
143    let config = AbiDecoderConfig::new().memory_limit(data.len());
144    decode_with_config::<PackedSeqToken<'_>>(data, config)
145        .ok()
146        .and_then(|message| std::str::from_utf8(message.0).ok())
147        .is_some_and(|message| message.contains(ASSERTION_FAILED_PREFIX))
148}