Skip to main content

foundry_evm_symbolic/runtime/
state.rs

1use super::*;
2
3#[derive(Clone, Debug)]
4pub(crate) struct PathState {
5    pub(crate) depth: usize,
6    pub(crate) call_depth: usize,
7    pub(crate) origin: Address,
8    pub(crate) origin_word: SymWord,
9    pub(crate) gas_price: SymWord,
10    pub(crate) ffi_enabled: bool,
11    pub(crate) block: SymbolicBlock,
12    pub(crate) frame: CallFrame,
13    pub(crate) world: SymbolicWorld,
14    pub(crate) prank: SymbolicPrank,
15    pub(crate) constraints: Vec<BoolExpr>,
16    pub(crate) next_symbol: usize,
17    pub(crate) recorded_logs: Option<Vec<SymbolicLog>>,
18    pub(crate) access_record: Option<AccessRecord>,
19    pub(crate) root_calldata: Option<SymbolicCalldata>,
20    pub(crate) loop_jumps: BTreeMap<usize, u32>,
21    pub(crate) expected_revert: Option<ExpectedRevert>,
22    pub(crate) assume_no_revert_next_call: Option<AssumeNoRevert>,
23    pub(crate) expected_emit: Option<ExpectedEmit>,
24    pub(crate) expected_calls: Vec<ExpectedCall>,
25    pub(crate) expected_creates: Vec<ExpectedCreate>,
26    pub(crate) call_mocks: Vec<CallMock>,
27    pub(crate) function_mocks: Vec<FunctionMock>,
28    pub(crate) persistent_accounts: BTreeSet<Address>,
29    pub(crate) wallets: BTreeSet<Address>,
30    pub(crate) labels: BTreeMap<Address, String>,
31}
32
33impl PathState {
34    /// Constructs a new instance.
35    pub(crate) fn new(
36        address: Address,
37        caller: Address,
38        callvalue: U256,
39        calldata: SymbolicCalldata,
40        ffi_enabled: bool,
41    ) -> Self {
42        let constraints = calldata.constraints.clone();
43        let call_data = calldata.call_data();
44        Self {
45            depth: 0,
46            call_depth: 0,
47            origin: caller,
48            origin_word: SymWord::Concrete(address_word(caller)),
49            gas_price: SymWord::zero(),
50            ffi_enabled,
51            block: SymbolicBlock::default(),
52            frame: CallFrame::new(
53                address,
54                address,
55                address,
56                caller,
57                SymWord::Concrete(callvalue),
58                false,
59                call_data,
60            ),
61            world: SymbolicWorld::default(),
62            prank: SymbolicPrank::default(),
63            constraints,
64            next_symbol: 0,
65            recorded_logs: None,
66            access_record: None,
67            root_calldata: Some(calldata),
68            loop_jumps: BTreeMap::new(),
69            expected_revert: None,
70            assume_no_revert_next_call: None,
71            expected_emit: None,
72            expected_calls: Vec::new(),
73            expected_creates: Vec::new(),
74            call_mocks: Vec::new(),
75            function_mocks: Vec::new(),
76            persistent_accounts: BTreeSet::new(),
77            wallets: BTreeSet::new(),
78            labels: BTreeMap::new(),
79        }
80    }
81
82    /// Implements the `empty` symbolic state helper.
83    pub(crate) fn empty(address: Address, caller: Address, ffi_enabled: bool) -> Self {
84        Self {
85            depth: 0,
86            call_depth: 0,
87            origin: caller,
88            origin_word: SymWord::Concrete(address_word(caller)),
89            gas_price: SymWord::zero(),
90            ffi_enabled,
91            block: SymbolicBlock::default(),
92            frame: CallFrame::new(
93                address,
94                address,
95                address,
96                caller,
97                SymWord::zero(),
98                false,
99                SymCalldata::new(Vec::new()),
100            ),
101            world: SymbolicWorld::default(),
102            prank: SymbolicPrank::default(),
103            constraints: Vec::new(),
104            next_symbol: 0,
105            recorded_logs: None,
106            access_record: None,
107            root_calldata: None,
108            loop_jumps: BTreeMap::new(),
109            expected_revert: None,
110            assume_no_revert_next_call: None,
111            expected_emit: None,
112            expected_calls: Vec::new(),
113            expected_creates: Vec::new(),
114            call_mocks: Vec::new(),
115            function_mocks: Vec::new(),
116            persistent_accounts: BTreeSet::new(),
117            wallets: BTreeSet::new(),
118            labels: BTreeMap::new(),
119        }
120    }
121
122    /// Applies the `apply_executor_env` symbolic state helper.
123    pub(crate) fn apply_executor_env<FEN: FoundryEvmNetwork>(&mut self, executor: &Executor<FEN>) {
124        self.block = SymbolicBlock::from_executor(executor);
125        let gas_price = executor
126            .inspector()
127            .cheatcodes
128            .as_ref()
129            .and_then(|cheats| cheats.gas_price)
130            .unwrap_or_else(|| executor.tx_env().gas_price());
131        self.gas_price = SymWord::Concrete(U256::from(gas_price));
132    }
133
134    /// Implements the `child` symbolic state helper.
135    pub(crate) fn child(&self, frame: CallFrame) -> Self {
136        let mut child = self.clone();
137        child.call_depth += 1;
138        child.frame = frame;
139        child.loop_jumps = BTreeMap::new();
140        child
141    }
142
143    /// Implements the `constrained_usize` symbolic state helper.
144    pub(crate) fn constrained_usize(&self, word: &SymWord) -> Option<usize> {
145        let value = self.constrained_word(word)?;
146        (value <= U256::from(usize::MAX)).then(|| value.to::<usize>())
147    }
148
149    /// Implements the `upper_bound_usize` symbolic state helper.
150    pub(crate) fn upper_bound_usize(&self, word: &SymWord) -> Option<usize> {
151        self.constrained_usize(word).or_else(|| match word {
152            SymWord::Concrete(value) => u256_to_usize(*value),
153            SymWord::Expr(expr) => self.expr_upper_bound_usize(expr),
154        })
155    }
156
157    /// Implements the `constrained_word` symbolic state helper.
158    pub(crate) fn constrained_word(&self, word: &SymWord) -> Option<U256> {
159        let value = match word {
160            SymWord::Concrete(value) => *value,
161            SymWord::Expr(expr) => self
162                .constraints
163                .iter()
164                .find_map(|constraint| {
165                    bool_forces_expr_const_with_context(constraint, expr, &self.constraints)
166                })
167                .or_else(|| self.constrained_expr_value(expr))?,
168        };
169        Some(value)
170    }
171
172    /// Implements the `constrained_expr_value` symbolic state helper.
173    pub(crate) fn constrained_expr_value(&self, expr: &Expr) -> Option<U256> {
174        if let Some(value) = expr_const_value(expr) {
175            return Some(value);
176        }
177        if let Some(value) = expr_known_word(expr) {
178            return Some(value);
179        }
180
181        let mut vars = BTreeSet::new();
182        collect_eval_vars(expr, &mut vars);
183        let mut model = BTreeMap::new();
184        for var in vars {
185            let var_expr = Expr::Var(var.clone());
186            let value = self.constraints.iter().find_map(|constraint| {
187                bool_forces_expr_const_with_context(constraint, &var_expr, &self.constraints)
188            })?;
189            model.insert(var, value);
190        }
191
192        eval_expr(expr, &model).ok()
193    }
194
195    /// Returns the `expr_upper_bound_usize` symbolic state helper result.
196    pub(crate) fn expr_upper_bound_usize(&self, expr: &Expr) -> Option<usize> {
197        if let Some(value) = expr_const_value(expr) {
198            return u256_to_usize(value);
199        }
200        if let Some(value) = expr_known_word(expr) {
201            return u256_to_usize(value);
202        }
203
204        let constraint_bound = self.constraint_upper_bound_usize(expr);
205        let structural_bound = match expr {
206            Expr::Const(value) => u256_to_usize(*value),
207            Expr::Var(_) | Expr::GasLeft(_) | Expr::Keccak { .. } | Expr::Hash { .. } => None,
208            Expr::Not(_) => None,
209            Expr::Ite(_, left, right) => {
210                Some(self.expr_upper_bound_usize(left)?.max(self.expr_upper_bound_usize(right)?))
211            }
212            Expr::Op(op, left, right) => match op {
213                ExprOp::Add => self
214                    .expr_upper_bound_usize(left)?
215                    .checked_add(self.expr_upper_bound_usize(right)?),
216                ExprOp::Mul => self
217                    .expr_upper_bound_usize(left)?
218                    .checked_mul(self.expr_upper_bound_usize(right)?),
219                ExprOp::UDiv => {
220                    let left = self.expr_upper_bound_usize(left)?;
221                    match expr_const_value(right)? {
222                        divisor if divisor.is_zero() => Some(0),
223                        divisor => Some(left / u256_to_usize(divisor)?),
224                    }
225                }
226                ExprOp::URem => match expr_const_value(right) {
227                    Some(divisor) if divisor.is_zero() => Some(0),
228                    Some(divisor) => u256_to_usize(divisor - U256::from(1)),
229                    None => self.expr_upper_bound_usize(left),
230                },
231                ExprOp::And => expr_const_value(right)
232                    .and_then(u256_to_usize)
233                    .or_else(|| expr_const_value(left).and_then(u256_to_usize))
234                    .map(|mask| {
235                        self.expr_upper_bound_usize(left)
236                            .or_else(|| self.expr_upper_bound_usize(right))
237                            .map_or(mask, |bound| bound.min(mask))
238                    }),
239                ExprOp::Shr => {
240                    let left = self.expr_upper_bound_usize(left)?;
241                    let shift = u256_to_usize(expr_const_value(right)?)?;
242                    Some(if shift >= usize::BITS as usize { 0 } else { left >> shift })
243                }
244                ExprOp::Sub
245                | ExprOp::SDiv
246                | ExprOp::SRem
247                | ExprOp::Or
248                | ExprOp::Xor
249                | ExprOp::Shl
250                | ExprOp::Sar => None,
251            },
252        };
253
254        match (constraint_bound, structural_bound) {
255            (Some(left), Some(right)) => Some(left.min(right)),
256            (Some(bound), None) | (None, Some(bound)) => Some(bound),
257            (None, None) => None,
258        }
259    }
260
261    /// Implements the `constraint_upper_bound_usize` symbolic state helper.
262    pub(crate) fn constraint_upper_bound_usize(&self, expr: &Expr) -> Option<usize> {
263        let mut bound: Option<usize> = None;
264        for constraint in &self.constraints {
265            if let Some(candidate) = bool_upper_bound_usize(constraint, expr) {
266                bound = Some(bound.map_or(candidate, |bound| bound.min(candidate)));
267            }
268        }
269        bound
270    }
271
272    /// Implements the `expect_constrained_usize` symbolic state helper.
273    pub(crate) fn expect_constrained_usize(
274        &self,
275        word: SymWord,
276        reason: &'static str,
277    ) -> Result<usize, SymbolicError> {
278        self.constrained_usize(&word).ok_or(SymbolicError::Unsupported(reason))
279    }
280
281    /// Implements the `expect_constrained_word` symbolic state helper.
282    pub(crate) fn expect_constrained_word(
283        &self,
284        word: SymWord,
285        reason: &'static str,
286    ) -> Result<U256, SymbolicError> {
287        self.constrained_word(&word).ok_or(SymbolicError::Unsupported(reason))
288    }
289
290    /// Implements the `bin_word` symbolic state helper.
291    pub(crate) fn bin_word(
292        &mut self,
293        concrete: impl FnOnce(U256, U256) -> U256,
294        op: ExprOp,
295    ) -> Result<StepOutcome, SymbolicError> {
296        let a = self.stack.pop()?;
297        let b = self.stack.pop()?;
298        self.stack.push(match (a, b) {
299            (SymWord::Concrete(a), SymWord::Concrete(b)) => SymWord::Concrete(concrete(a, b)),
300            (a, b) => SymWord::Expr(Expr::op(op, a.into_expr(), b.into_expr())),
301        })?;
302        Ok(StepOutcome::Continue)
303    }
304
305    /// Implements the `bin_word_div_zero_guard` symbolic state helper.
306    pub(crate) fn bin_word_div_zero_guard(
307        &mut self,
308        concrete: impl FnOnce(U256, U256) -> U256,
309        op: ExprOp,
310    ) -> Result<StepOutcome, SymbolicError> {
311        let a = self.stack.pop()?;
312        let b = self.stack.pop()?;
313        self.stack.push(match (a, b) {
314            (SymWord::Concrete(a), SymWord::Concrete(b)) => SymWord::Concrete(concrete(a, b)),
315            (a, b) => {
316                let a = a.into_expr();
317                let b = b.into_expr();
318                SymWord::Expr(Expr::Ite(
319                    Box::new(BoolExpr::eq(b.clone(), Expr::Const(U256::ZERO))),
320                    Box::new(Expr::Const(U256::ZERO)),
321                    Box::new(Expr::op(op, a, b)),
322                ))
323            }
324        })?;
325        Ok(StepOutcome::Continue)
326    }
327
328    /// Implements the `cmp_word` symbolic state helper.
329    pub(crate) fn cmp_word(
330        &mut self,
331        concrete: impl FnOnce(U256, U256) -> bool,
332        op: BoolExprOp,
333    ) -> Result<StepOutcome, SymbolicError> {
334        let a = self.stack.pop()?;
335        let b = self.stack.pop()?;
336        self.stack.push(match (a, b) {
337            (SymWord::Concrete(a), SymWord::Concrete(b)) => {
338                SymWord::Concrete(U256::from(concrete(a, b)))
339            }
340            (a, b) => SymWord::from_bool(BoolExpr::cmp(op, a.into_expr(), b.into_expr())),
341        })?;
342        Ok(StepOutcome::Continue)
343    }
344
345    /// Computes the `shift_word` symbolic state helper result.
346    pub(crate) fn shift_word(&mut self, kind: ShiftKind) -> Result<StepOutcome, SymbolicError> {
347        let shift = self.stack.pop()?;
348        let value = self.stack.pop()?;
349        let result = match (value, shift) {
350            (SymWord::Concrete(value), SymWord::Concrete(shift)) => {
351                let result = if shift >= U256::from(256) {
352                    if matches!(kind, ShiftKind::Sar) && ((value >> 255) == U256::from(1)) {
353                        U256::MAX
354                    } else {
355                        U256::ZERO
356                    }
357                } else {
358                    let shift = shift.to::<usize>();
359                    match kind {
360                        ShiftKind::Shl => value << shift,
361                        ShiftKind::Shr => value >> shift,
362                        ShiftKind::Sar => sar(value, shift),
363                    }
364                };
365                SymWord::Concrete(result)
366            }
367            (value, shift) => {
368                let expr = match kind {
369                    ShiftKind::Shl => Expr::op(ExprOp::Shl, value.into_expr(), shift.into_expr()),
370                    ShiftKind::Shr => Expr::op(ExprOp::Shr, value.into_expr(), shift.into_expr()),
371                    ShiftKind::Sar => Expr::op(ExprOp::Sar, value.into_expr(), shift.into_expr()),
372                };
373                expr_known_word(&expr).map(SymWord::Concrete).unwrap_or(SymWord::Expr(expr))
374            }
375        };
376        self.stack.push(result)?;
377        Ok(StepOutcome::Continue)
378    }
379
380    /// Computes the `exp_word` symbolic state helper result.
381    pub(crate) fn exp_word(&mut self) -> Result<StepOutcome, SymbolicError> {
382        let base = self.stack.pop()?;
383        let exponent = self.stack.pop()?;
384        let result = if let Some(exponent) = self.constrained_word(&exponent) {
385            match base {
386                SymWord::Concrete(base) => SymWord::Concrete(pow_mod(base, exponent)),
387                base if exponent <= U256::from(SYMBOLIC_EXP_CONCRETE_EXPONENT_LIMIT) => {
388                    SymWord::Expr(exp_expr_for_concrete_exponent(
389                        base.into_expr(),
390                        exponent.to::<usize>(),
391                    ))
392                }
393                _ => return Err(SymbolicError::Unsupported("symbolic EXP base")),
394            }
395        } else {
396            let exponent_limit = if matches!(base, SymWord::Concrete(_)) {
397                CONCRETE_BASE_SYMBOLIC_EXPONENT_LIMIT
398            } else {
399                SYMBOLIC_EXP_CONCRETE_EXPONENT_LIMIT
400            };
401            let max_exponent = self
402                .upper_bound_usize(&exponent)
403                .filter(|exponent| *exponent <= exponent_limit as usize)
404                .ok_or(SymbolicError::Unsupported("symbolic EXP exponent"))?;
405            let exponent = exponent.into_expr();
406            let base = base.into_expr();
407            let mut expr = Expr::Const(U256::ZERO);
408            for candidate in (0..=max_exponent).rev() {
409                expr = Expr::Ite(
410                    Box::new(BoolExpr::eq(exponent.clone(), Expr::Const(U256::from(candidate)))),
411                    Box::new(exp_expr_for_concrete_exponent(base.clone(), candidate)),
412                    Box::new(expr),
413                );
414            }
415            SymWord::Expr(expr)
416        };
417        self.stack.push(result)?;
418        Ok(StepOutcome::Continue)
419    }
420
421    /// Implements the `balance` symbolic state helper.
422    pub(crate) fn balance<FEN: FoundryEvmNetwork>(
423        &self,
424        executor: &Executor<FEN>,
425        address: Address,
426    ) -> SymWord {
427        self.world.balance_word_for_address(executor, address)
428    }
429
430    /// Implements the `balance_word` symbolic state helper.
431    pub(crate) fn balance_word<FEN: FoundryEvmNetwork>(
432        &mut self,
433        executor: &Executor<FEN>,
434        word: SymWord,
435    ) -> Result<SymWord, SymbolicError> {
436        self.world.balance_word(executor, word)
437    }
438
439    /// Implements the `extcode_size_word` symbolic state helper.
440    pub(crate) fn extcode_size_word<FEN: FoundryEvmNetwork>(
441        &mut self,
442        executor: &Executor<FEN>,
443        word: SymWord,
444    ) -> Result<SymWord, SymbolicError> {
445        self.world.extcode_size_word(executor, word)
446    }
447
448    /// Implements the `extcode_hash_word` symbolic state helper.
449    pub(crate) fn extcode_hash_word<FEN: FoundryEvmNetwork>(
450        &mut self,
451        executor: &Executor<FEN>,
452        word: SymWord,
453    ) -> Result<SymWord, SymbolicError> {
454        self.world.extcode_hash_word(executor, word)
455    }
456
457    /// Implements the `extcode_bytes_word` symbolic state helper.
458    pub(crate) fn extcode_bytes_word<FEN: FoundryEvmNetwork>(
459        &mut self,
460        executor: &Executor<FEN>,
461        word: SymWord,
462        offset: SymWord,
463        size: usize,
464    ) -> Result<Vec<SymWord>, SymbolicError> {
465        self.world.extcode_bytes_word(executor, word, offset, size)
466    }
467
468    /// Implements the `pop_address_or_symbolic_slot` symbolic state helper.
469    pub(crate) fn pop_address_or_symbolic_slot(&mut self) -> Result<Address, SymbolicError> {
470        let word = self.stack.pop()?;
471        Ok(self.address_or_symbolic_slot(word))
472    }
473
474    /// Returns the `address_or_symbolic_slot` symbolic state helper result.
475    pub(crate) fn address_or_symbolic_slot(&mut self, word: SymWord) -> Address {
476        if let Some(value) = self.constrained_word(&word) {
477            return word_to_address(value);
478        }
479        self.world.resolve_address(&word).unwrap_or_else(|| self.world.symbolic_address_slot(word))
480    }
481
482    /// Implements the `fresh_word` symbolic state helper.
483    pub(crate) fn fresh_word(&mut self, prefix: &'static str) -> SymWord {
484        let id = self.next_symbol;
485        self.next_symbol += 1;
486        SymWord::Expr(Expr::Var(format!("{prefix}_{id}")))
487    }
488
489    /// Implements the `fresh_gasleft` symbolic state helper.
490    pub(crate) const fn fresh_gasleft(&mut self) -> SymWord {
491        let id = self.next_symbol;
492        self.next_symbol += 1;
493        SymWord::Expr(Expr::GasLeft(id))
494    }
495
496    /// Implements the `fresh_bounded_uint` symbolic state helper.
497    pub(crate) fn fresh_bounded_uint(&mut self, bits: U256) -> SymWord {
498        let value = self.fresh_word("symbolic");
499        if bits < U256::from(256) {
500            let upper =
501                if bits.is_zero() { U256::ZERO } else { U256::from(1) << bits.to::<usize>() };
502            self.constraints.push(BoolExpr::cmp(
503                BoolExprOp::Ult,
504                value.clone().into_expr(),
505                Expr::Const(upper),
506            ));
507        }
508        value
509    }
510
511    /// Implements the `fresh_bounded_int` symbolic state helper.
512    pub(crate) fn fresh_bounded_int(&mut self, bits: U256) -> SymWord {
513        let value = self.fresh_word("symbolic");
514        if bits.is_zero() {
515            self.constraints.push(BoolExpr::eq(value.clone().into_expr(), Expr::Const(U256::ZERO)));
516        } else if bits < U256::from(256) {
517            let magnitude = U256::from(1) << (bits.to::<usize>() - 1);
518            self.constraints.push(BoolExpr::or(vec![
519                BoolExpr::cmp(BoolExprOp::Ult, value.clone().into_expr(), Expr::Const(magnitude)),
520                BoolExpr::cmp(
521                    BoolExprOp::Uge,
522                    value.clone().into_expr(),
523                    Expr::Const(U256::ZERO.wrapping_sub(magnitude)),
524                ),
525            ]));
526        }
527        value
528    }
529
530    /// Implements the `prank_for_next_call` symbolic state helper.
531    pub(crate) fn prank_for_next_call(&mut self) -> (Address, SymWord, Option<(Address, SymWord)>) {
532        if let Some((caller, caller_word)) = self.prank.next_caller.take() {
533            (caller, caller_word, self.prank.next_origin.take())
534        } else {
535            match self.prank.persistent_caller.clone() {
536                Some((caller, caller_word)) => {
537                    (caller, caller_word, self.prank.persistent_origin.clone())
538                }
539                None => {
540                    (self.address, self.address_word.clone(), self.prank.persistent_origin.clone())
541                }
542            }
543        }
544    }
545
546    /// Returns the `read_callers_words` symbolic state helper result.
547    pub(crate) fn read_callers_words(&self) -> Vec<SymWord> {
548        let (mode, caller, origin) = if let Some((_, caller_word)) = self.prank.next_caller.as_ref()
549        {
550            (
551                U256::from(3),
552                caller_word.clone(),
553                self.prank
554                    .next_origin
555                    .as_ref()
556                    .map(|(_, origin_word)| origin_word.clone())
557                    .unwrap_or_else(|| self.origin_word.clone()),
558            )
559        } else if let Some((_, caller_word)) = self.prank.persistent_caller.as_ref() {
560            (
561                U256::from(4),
562                caller_word.clone(),
563                self.prank
564                    .persistent_origin
565                    .as_ref()
566                    .map(|(_, origin_word)| origin_word.clone())
567                    .unwrap_or_else(|| self.origin_word.clone()),
568            )
569        } else {
570            (U256::ZERO, self.caller_word.clone(), self.origin_word.clone())
571        };
572        vec![SymWord::Concrete(mode), caller, origin]
573    }
574
575    /// Applies the `record_log` symbolic state helper.
576    pub(crate) fn record_log(&mut self, log: SymbolicLog) {
577        if let Some(logs) = &mut self.recorded_logs {
578            logs.push(log);
579        }
580    }
581
582    /// Applies the `record_sload` symbolic state helper.
583    pub(crate) fn record_sload(&mut self, address: Address, slot: SymWord) {
584        if let Some(record) = &mut self.access_record {
585            record.read(address, slot);
586        }
587    }
588
589    /// Applies the `record_sstore` symbolic state helper.
590    pub(crate) fn record_sstore(&mut self, address: Address, slot: SymWord) {
591        if let Some(record) = &mut self.access_record {
592            record.write(address, slot);
593        }
594    }
595
596    /// Returns whether `expectations_satisfied` holds.
597    pub(crate) fn expectations_satisfied(&self) -> bool {
598        self.expected_revert.is_none()
599            && self.expected_emit.as_ref().is_none_or(ExpectedEmit::is_satisfied)
600            && self.expected_calls.iter().all(ExpectedCall::is_satisfied)
601            && self.expected_creates.is_empty()
602    }
603}
604
605#[derive(Clone, Debug, PartialEq, Eq)]
606pub(crate) struct SymbolicLog {
607    pub(crate) topics: Vec<SymWord>,
608    pub(crate) data_len: SymWord,
609    pub(crate) data: Vec<SymWord>,
610    pub(crate) emitter: Address,
611}
612
613#[derive(Clone, Debug, Default, PartialEq, Eq)]
614pub(crate) struct AccessRecord {
615    pub(crate) reads: BTreeMap<Address, Vec<SymWord>>,
616    pub(crate) writes: BTreeMap<Address, Vec<SymWord>>,
617}
618
619impl AccessRecord {
620    /// Implements the `read` symbolic state helper.
621    pub(crate) fn read(&mut self, address: Address, slot: SymWord) {
622        push_unique_slot(self.reads.entry(address).or_default(), slot);
623    }
624
625    /// Implements the `write` symbolic state helper.
626    pub(crate) fn write(&mut self, address: Address, slot: SymWord) {
627        push_unique_slot(self.writes.entry(address).or_default(), slot);
628    }
629}
630
631/// Applies the `push_unique_slot` symbolic state helper.
632pub(crate) fn push_unique_slot(slots: &mut Vec<SymWord>, slot: SymWord) {
633    if !slots.iter().any(|existing| existing == &slot) {
634        slots.push(slot);
635    }
636}
637
638/// Implements the `adjust_expected_call_gas_for_value` symbolic state helper.
639pub(crate) fn adjust_expected_call_gas_for_value(
640    value: Option<U256>,
641    gas: Option<u64>,
642    min_gas: Option<u64>,
643) -> (Option<u64>, Option<u64>) {
644    if value.is_some_and(|value| !value.is_zero()) {
645        (
646            gas.map(|gas| gas.saturating_add(CALL_VALUE_STIPEND)),
647            min_gas.map(|gas| gas.saturating_add(CALL_VALUE_STIPEND)),
648        )
649    } else {
650        (gas, min_gas)
651    }
652}
653
654#[derive(Clone, Debug, PartialEq, Eq)]
655pub(crate) struct ExpectedRevert {
656    pub(crate) data: ExpectedRevertData,
657    pub(crate) reverter: Option<SymWord>,
658    pub(crate) remaining: u64,
659}
660
661impl ExpectedRevert {
662    /// Implements the `consume_one` symbolic state helper.
663    pub(crate) const fn consume_one(&mut self) -> bool {
664        self.remaining = self.remaining.saturating_sub(1);
665        self.remaining == 0
666    }
667}
668
669#[derive(Clone, Debug, PartialEq, Eq)]
670pub(crate) enum ExpectedRevertData {
671    Any,
672    Prefix(Vec<SymWord>),
673    Exact(Vec<SymWord>),
674}
675
676#[derive(Clone, Debug, PartialEq, Eq)]
677pub(crate) enum AssumeNoRevert {
678    Any,
679    Filtered(Vec<ExpectedRevert>),
680}
681
682#[derive(Clone, Debug, PartialEq, Eq)]
683pub(crate) struct ExpectedCall {
684    pub(crate) callee: SymWord,
685    pub(crate) value: Option<U256>,
686    pub(crate) gas: Option<u64>,
687    pub(crate) min_gas: Option<u64>,
688    pub(crate) data: Vec<SymWord>,
689    pub(crate) expected: u64,
690    pub(crate) observed: u64,
691    pub(crate) exact: bool,
692}
693
694#[derive(Clone, Debug, PartialEq, Eq)]
695pub(crate) struct ExpectedCreate {
696    pub(crate) bytecode: Vec<u8>,
697    pub(crate) deployer: SymWord,
698    pub(crate) kind: CreateKind,
699}
700
701impl ExpectedCall {
702    /// Implements the `static_parts_match` symbolic state helper.
703    pub(crate) fn static_parts_match(
704        &self,
705        value: Option<U256>,
706        gas: &SymWord,
707    ) -> Result<bool, SymbolicError> {
708        Ok(self.value.is_none_or(|expected| value.is_some_and(|value| expected == value))
709            && self.gas_matches(gas, value)?)
710    }
711
712    /// Returns whether `gas_matches` holds.
713    pub(crate) fn gas_matches(
714        &self,
715        gas: &SymWord,
716        value: Option<U256>,
717    ) -> Result<bool, SymbolicError> {
718        if self.gas.is_none() && self.min_gas.is_none() {
719            return Ok(true);
720        }
721        let mut gas = gas.clone().into_concrete("symbolic expected call gas")?;
722        if value.is_some_and(|value| !value.is_zero()) {
723            gas = gas.saturating_add(U256::from(CALL_VALUE_STIPEND));
724        }
725        Ok(self.gas.is_none_or(|expected| gas == U256::from(expected))
726            && self.min_gas.is_none_or(|expected| gas >= U256::from(expected)))
727    }
728
729    /// Applies the `observe` symbolic state helper.
730    pub(crate) const fn observe(&mut self) -> bool {
731        if self.exact && self.observed >= self.expected {
732            return false;
733        }
734        self.observed = self.observed.saturating_add(1);
735        true
736    }
737
738    /// Returns whether `is_satisfied` holds.
739    pub(crate) const fn is_satisfied(&self) -> bool {
740        if self.exact { self.observed == self.expected } else { self.observed >= self.expected }
741    }
742}
743
744#[derive(Clone, Debug)]
745pub(crate) struct CallMock {
746    pub(crate) callee: SymWord,
747    pub(crate) value: Option<U256>,
748    pub(crate) data: Vec<SymWord>,
749    pub(crate) returns: Vec<SymReturnData>,
750    pub(crate) reverts: bool,
751    pub(crate) calls: usize,
752}
753
754impl CallMock {
755    /// Implements the `static_parts_match` symbolic state helper.
756    pub(crate) fn static_parts_match(&self, value: Option<U256>) -> bool {
757        self.value.is_none_or(|expected| value.is_some_and(|value| expected == value))
758    }
759
760    /// Implements the `next_outcome` symbolic state helper.
761    pub(crate) fn next_outcome(&mut self) -> CallMockOutcome {
762        let idx = self.calls.min(self.returns.len().saturating_sub(1));
763        self.calls = self.calls.saturating_add(1);
764        CallMockOutcome {
765            return_data: self.returns.get(idx).cloned().unwrap_or_default(),
766            reverts: self.reverts,
767        }
768    }
769}
770
771#[derive(Clone, Debug)]
772pub(crate) struct CallMockOutcome {
773    pub(crate) return_data: SymReturnData,
774    pub(crate) reverts: bool,
775}
776
777#[derive(Clone, Debug, PartialEq, Eq)]
778pub(crate) struct FunctionMock {
779    pub(crate) callee: SymWord,
780    pub(crate) target: Address,
781    pub(crate) data: Vec<SymWord>,
782}
783
784#[derive(Clone, Debug, PartialEq, Eq)]
785pub(crate) struct ExpectedEmit {
786    pub(crate) checks: ExpectedEmitChecks,
787    pub(crate) emitter: Option<SymWord>,
788    pub(crate) remaining: u64,
789    pub(crate) template: Option<SymbolicLog>,
790}
791
792impl ExpectedEmit {
793    /// Returns whether `is_satisfied` holds.
794    pub(crate) const fn is_satisfied(&self) -> bool {
795        self.template.is_none() && self.remaining == 0
796    }
797
798    /// Implements the `consume_one` symbolic state helper.
799    pub(crate) fn consume_one(&mut self) -> bool {
800        self.remaining = self.remaining.saturating_sub(1);
801        if self.remaining == 0 {
802            self.template = None;
803            true
804        } else {
805            false
806        }
807    }
808}
809
810#[derive(Clone, Copy, Debug, PartialEq, Eq)]
811pub(crate) struct ExpectedEmitChecks {
812    pub(crate) topics: [bool; 4],
813    pub(crate) data: bool,
814}
815
816impl ExpectedEmitChecks {
817    /// Implements the `default_non_anonymous` symbolic state helper.
818    pub(crate) const fn default_non_anonymous() -> Self {
819        Self { topics: [true, true, true, true], data: true }
820    }
821
822    /// Implements the `default_anonymous` symbolic state helper.
823    pub(crate) const fn default_anonymous() -> Self {
824        Self { topics: [true, true, true, true], data: true }
825    }
826
827    /// Converts values for the `from_non_anonymous_args` symbolic state helper.
828    pub(crate) fn from_non_anonymous_args(
829        memory: &SymMemory,
830        args_offset: usize,
831    ) -> Result<Self, SymbolicError> {
832        Ok(Self {
833            topics: [
834                true,
835                read_abi_bool_arg(memory, args_offset, 0, "symbolic vm.expectEmit")?,
836                read_abi_bool_arg(memory, args_offset, 1, "symbolic vm.expectEmit")?,
837                read_abi_bool_arg(memory, args_offset, 2, "symbolic vm.expectEmit")?,
838            ],
839            data: read_abi_bool_arg(memory, args_offset, 3, "symbolic vm.expectEmit")?,
840        })
841    }
842
843    /// Converts values for the `from_anonymous_args` symbolic state helper.
844    pub(crate) fn from_anonymous_args(
845        memory: &SymMemory,
846        args_offset: usize,
847    ) -> Result<Self, SymbolicError> {
848        Ok(Self {
849            topics: [
850                read_abi_bool_arg(memory, args_offset, 0, "symbolic vm.expectEmitAnonymous")?,
851                read_abi_bool_arg(memory, args_offset, 1, "symbolic vm.expectEmitAnonymous")?,
852                read_abi_bool_arg(memory, args_offset, 2, "symbolic vm.expectEmitAnonymous")?,
853                read_abi_bool_arg(memory, args_offset, 3, "symbolic vm.expectEmitAnonymous")?,
854            ],
855            data: read_abi_bool_arg(memory, args_offset, 4, "symbolic vm.expectEmitAnonymous")?,
856        })
857    }
858}
859
860impl Deref for PathState {
861    type Target = CallFrame;
862
863    /// Implements the `deref` symbolic state helper.
864    fn deref(&self) -> &Self::Target {
865        &self.frame
866    }
867}
868
869impl DerefMut for PathState {
870    /// Implements the `deref_mut` symbolic state helper.
871    fn deref_mut(&mut self) -> &mut Self::Target {
872        &mut self.frame
873    }
874}
875
876#[derive(Clone, Debug)]
877pub(crate) struct CallFrame {
878    pub(crate) pc: usize,
879    pub(crate) address: Address,
880    pub(crate) address_word: SymWord,
881    #[allow(dead_code)]
882    pub(crate) code_address: Address,
883    pub(crate) storage_address: Address,
884    pub(crate) caller: Address,
885    pub(crate) caller_word: SymWord,
886    pub(crate) callvalue: SymWord,
887    pub(crate) is_static: bool,
888    pub(crate) calldata: SymCalldata,
889    pub(crate) stack: SymStack,
890    pub(crate) memory: SymMemory,
891    pub(crate) return_data: SymReturnData,
892}
893
894impl CallFrame {
895    /// Constructs a new instance.
896    pub(crate) fn new(
897        address: Address,
898        code_address: Address,
899        storage_address: Address,
900        caller: Address,
901        callvalue: SymWord,
902        is_static: bool,
903        calldata: SymCalldata,
904    ) -> Self {
905        Self {
906            pc: 0,
907            address,
908            address_word: SymWord::Concrete(address_word(address)),
909            code_address,
910            storage_address,
911            caller,
912            caller_word: SymWord::Concrete(address_word(caller)),
913            callvalue,
914            is_static,
915            calldata,
916            stack: SymStack::default(),
917            memory: SymMemory::default(),
918            return_data: SymReturnData::default(),
919        }
920    }
921}
922
923#[derive(Clone, Debug)]
924pub(crate) struct ExternalCallOutcome {
925    pub(crate) status: TopLevelCallStatus,
926    pub(crate) return_data: SymReturnData,
927    pub(crate) state: PathState,
928}
929
930#[derive(Clone, Debug)]
931pub(crate) struct SequencePath {
932    pub(crate) state: PathState,
933    pub(crate) steps: Vec<SequenceStepTemplate>,
934}
935
936#[derive(Clone, Debug)]
937pub(crate) struct SequenceStepTemplate {
938    pub(crate) sender: Address,
939    pub(crate) address: Address,
940    pub(crate) contract_name: Option<String>,
941    pub(crate) function: Function,
942    pub(crate) calldata: SymbolicCalldata,
943}
944
945#[derive(Clone, Debug)]
946pub(crate) struct InvariantCheckOutcome {
947    pub(crate) failed: bool,
948    pub(crate) state: PathState,
949}
950
951#[derive(Clone, Copy, Debug, PartialEq, Eq)]
952pub(crate) enum TopLevelCallStatus {
953    Success,
954    Revert,
955    Failure,
956}
957
958#[derive(Clone, Debug)]
959pub(crate) struct TopLevelCallOutcome {
960    pub(crate) status: TopLevelCallStatus,
961    pub(crate) return_data: SymReturnData,
962    pub(crate) state: PathState,
963}
964
965#[derive(Clone, Debug, Default, PartialEq, Eq)]
966pub(crate) struct SymbolicPrank {
967    pub(crate) next_caller: Option<(Address, SymWord)>,
968    pub(crate) next_origin: Option<(Address, SymWord)>,
969    pub(crate) persistent_caller: Option<(Address, SymWord)>,
970    pub(crate) persistent_origin: Option<(Address, SymWord)>,
971}
972
973#[derive(Clone, Debug, PartialEq, Eq)]
974pub(crate) struct StorageWrite {
975    pub(crate) address: Address,
976    pub(crate) key: SymWord,
977    pub(crate) value: SymWord,
978}
979
980impl StorageWrite {
981    /// Constructs a new instance.
982    pub(crate) const fn new(address: Address, key: SymWord, value: SymWord) -> Self {
983        Self { address, key, value }
984    }
985}
986
987#[derive(Clone, Debug, Default)]
988pub(crate) struct SymbolicWorldSnapshot {
989    pub(crate) storage: Vec<StorageWrite>,
990    pub(crate) transient_storage: Vec<StorageWrite>,
991    pub(crate) balances: BTreeMap<Address, SymWord>,
992    pub(crate) code_cache: BTreeMap<Address, SymCode>,
993    pub(crate) nonces: BTreeMap<Address, u64>,
994    pub(crate) existing_accounts: BTreeSet<Address>,
995    pub(crate) destroyed_accounts: BTreeSet<Address>,
996    pub(crate) arbitrary_storage_accounts: BTreeSet<Address>,
997    pub(crate) arbitrary_storage_all: bool,
998    pub(crate) zero_init_symbolic_storage: bool,
999    pub(crate) symbolic_address_aliases: BTreeMap<SymWord, Address>,
1000}
1001
1002impl From<&SymbolicWorld> for SymbolicWorldSnapshot {
1003    /// Implements the `from` symbolic state helper.
1004    fn from(world: &SymbolicWorld) -> Self {
1005        Self {
1006            storage: world.storage.clone(),
1007            transient_storage: world.transient_storage.clone(),
1008            balances: world.balances.clone(),
1009            code_cache: world.code_cache.clone(),
1010            nonces: world.nonces.clone(),
1011            existing_accounts: world.existing_accounts.clone(),
1012            destroyed_accounts: world.destroyed_accounts.clone(),
1013            arbitrary_storage_accounts: world.arbitrary_storage_accounts.clone(),
1014            arbitrary_storage_all: world.arbitrary_storage_all,
1015            zero_init_symbolic_storage: world.zero_init_symbolic_storage,
1016            symbolic_address_aliases: world.symbolic_address_aliases.clone(),
1017        }
1018    }
1019}
1020
1021#[derive(Clone, Debug, Default)]
1022pub(crate) struct SymbolicWorld {
1023    pub(crate) storage: Vec<StorageWrite>,
1024    pub(crate) transient_storage: Vec<StorageWrite>,
1025    pub(crate) balances: BTreeMap<Address, SymWord>,
1026    pub(crate) code_cache: BTreeMap<Address, SymCode>,
1027    pub(crate) nonces: BTreeMap<Address, u64>,
1028    pub(crate) existing_accounts: BTreeSet<Address>,
1029    pub(crate) destroyed_accounts: BTreeSet<Address>,
1030    pub(crate) arbitrary_storage_accounts: BTreeSet<Address>,
1031    pub(crate) arbitrary_storage_all: bool,
1032    pub(crate) zero_init_symbolic_storage: bool,
1033    pub(crate) symbolic_address_aliases: BTreeMap<SymWord, Address>,
1034    pub(crate) snapshots: BTreeMap<U256, SymbolicWorldSnapshot>,
1035    pub(crate) next_snapshot_id: u64,
1036}
1037
1038impl SymbolicWorld {
1039    /// Applies the `set_storage_layout` symbolic state helper.
1040    pub(crate) const fn set_storage_layout(&mut self, layout: SymbolicStorageLayout) {
1041        self.arbitrary_storage_all = matches!(layout, SymbolicStorageLayout::Generic);
1042        self.zero_init_symbolic_storage = matches!(layout, SymbolicStorageLayout::ZeroInit);
1043    }
1044
1045    /// Implements the `sload` symbolic state helper.
1046    pub(crate) fn sload<FEN: FoundryEvmNetwork>(
1047        &self,
1048        executor: &Executor<FEN>,
1049        address: Address,
1050        key: SymWord,
1051        concrete_key: Option<U256>,
1052    ) -> Result<SymWord, SymbolicError> {
1053        let base = self.storage_base(executor, address, &key, concrete_key)?;
1054        let read_key = concrete_key.map(SymWord::Concrete).unwrap_or(key);
1055        Ok(read_storage_writes(&self.storage, address, read_key, base))
1056    }
1057
1058    /// Implements the `sstore` symbolic state helper.
1059    pub(crate) fn sstore(&mut self, address: Address, key: SymWord, value: SymWord) {
1060        self.storage.push(StorageWrite::new(address, key, value));
1061    }
1062
1063    /// Implements the `tload` symbolic state helper.
1064    pub(crate) fn tload(&self, address: Address, key: SymWord) -> SymWord {
1065        read_storage_writes(&self.transient_storage, address, key, SymWord::zero())
1066    }
1067
1068    /// Implements the `tstore` symbolic state helper.
1069    pub(crate) fn tstore(&mut self, address: Address, key: SymWord, value: SymWord) {
1070        self.transient_storage.push(StorageWrite::new(address, key, value));
1071    }
1072
1073    /// Clears transaction-scoped transient storage at a top-level call boundary.
1074    pub(crate) fn clear_transient_storage(&mut self) {
1075        self.transient_storage.clear();
1076    }
1077
1078    /// Applies the `enable_arbitrary_storage` symbolic state helper.
1079    pub(crate) fn enable_arbitrary_storage(&mut self, address: Address) {
1080        self.arbitrary_storage_accounts.insert(address);
1081    }
1082
1083    /// Implements the `resolve_address` symbolic state helper.
1084    pub(crate) fn resolve_address(&self, word: &SymWord) -> Option<Address> {
1085        match word {
1086            SymWord::Concrete(value) => Some(word_to_address(*value)),
1087            SymWord::Expr(_) => self.symbolic_address_aliases.get(word).copied().or_else(|| {
1088                self.symbolic_address_aliases.iter().find_map(|(alias, address)| {
1089                    symbolic_address_equivalent(word, alias).then_some(*address)
1090                })
1091            }),
1092        }
1093    }
1094
1095    /// Returns the `symbolic_address_slot` symbolic state helper result.
1096    pub(crate) fn symbolic_address_slot(&mut self, word: SymWord) -> Address {
1097        if let Some(address) = self.resolve_address(&word) {
1098            return address;
1099        }
1100        let address = representative_symbolic_address(&word);
1101        self.symbolic_address_aliases.insert(word, address);
1102        address
1103    }
1104
1105    /// Returns the `symbolic_word_for_address` symbolic state helper result.
1106    pub(crate) fn symbolic_word_for_address(&self, address: Address) -> Option<SymWord> {
1107        self.symbolic_address_aliases
1108            .iter()
1109            .find_map(|(word, slot)| (*slot == address).then(|| word.clone()))
1110    }
1111
1112    /// Implements the `snapshot_state` symbolic state helper.
1113    pub(crate) fn snapshot_state(&mut self) -> U256 {
1114        let id = U256::from(self.next_snapshot_id);
1115        self.next_snapshot_id = self.next_snapshot_id.saturating_add(1);
1116        self.snapshots.insert(id, SymbolicWorldSnapshot::from(&*self));
1117        id
1118    }
1119
1120    /// Applies the `restore_snapshot` symbolic state helper.
1121    pub(crate) fn restore_snapshot(&mut self, id: U256) -> bool {
1122        let Some(snapshot) = self.snapshots.get(&id).cloned() else {
1123            return false;
1124        };
1125        self.storage = snapshot.storage;
1126        self.transient_storage = snapshot.transient_storage;
1127        self.balances = snapshot.balances;
1128        self.code_cache = snapshot.code_cache;
1129        self.nonces = snapshot.nonces;
1130        self.existing_accounts = snapshot.existing_accounts;
1131        self.destroyed_accounts = snapshot.destroyed_accounts;
1132        self.arbitrary_storage_accounts = snapshot.arbitrary_storage_accounts;
1133        self.arbitrary_storage_all = snapshot.arbitrary_storage_all;
1134        self.zero_init_symbolic_storage = snapshot.zero_init_symbolic_storage;
1135        self.symbolic_address_aliases = snapshot.symbolic_address_aliases;
1136        true
1137    }
1138
1139    /// Applies the `delete_snapshot` symbolic state helper.
1140    pub(crate) fn delete_snapshot(&mut self, id: U256) -> bool {
1141        self.snapshots.remove(&id).is_some()
1142    }
1143
1144    /// Applies the `delete_snapshots` symbolic state helper.
1145    pub(crate) fn delete_snapshots(&mut self) {
1146        self.snapshots.clear();
1147    }
1148
1149    /// Implements the `storage_base` symbolic state helper.
1150    pub(crate) fn storage_base<FEN: FoundryEvmNetwork>(
1151        &self,
1152        executor: &Executor<FEN>,
1153        address: Address,
1154        key: &SymWord,
1155        concrete_key: Option<U256>,
1156    ) -> Result<SymWord, SymbolicError> {
1157        if self.arbitrary_storage_all || self.arbitrary_storage_accounts.contains(&address) {
1158            return Ok(SymWord::Expr(Expr::Var(stable_symbol(
1159                "storage",
1160                format!("{address:?}:{key:?}"),
1161            ))));
1162        }
1163        if let Some(key) = concrete_key {
1164            return executor
1165                .backend()
1166                .storage_ref(address, key)
1167                .map(SymWord::Concrete)
1168                .map_err(|err| SymbolicError::Backend(err.to_string()));
1169        }
1170        match key {
1171            SymWord::Concrete(key) => executor
1172                .backend()
1173                .storage_ref(address, *key)
1174                .map(SymWord::Concrete)
1175                .map_err(|err| SymbolicError::Backend(err.to_string())),
1176            SymWord::Expr(_) if self.zero_init_symbolic_storage => Ok(SymWord::zero()),
1177            SymWord::Expr(_) => Ok(SymWord::Expr(Expr::Var(stable_symbol(
1178                "storage",
1179                format!("{address:?}:{key:?}"),
1180            )))),
1181        }
1182    }
1183
1184    /// Implements the `backend_balance` symbolic state helper.
1185    pub(crate) fn backend_balance<FEN: FoundryEvmNetwork>(
1186        &self,
1187        executor: &Executor<FEN>,
1188        address: Address,
1189    ) -> U256 {
1190        executor
1191            .backend()
1192            .basic_ref(address)
1193            .ok()
1194            .flatten()
1195            .map(|account| account.balance)
1196            .unwrap_or_default()
1197    }
1198
1199    /// Implements the `balance_word_for_address` symbolic state helper.
1200    pub(crate) fn balance_word_for_address<FEN: FoundryEvmNetwork>(
1201        &self,
1202        executor: &Executor<FEN>,
1203        address: Address,
1204    ) -> SymWord {
1205        if self.destroyed_accounts.contains(&address) {
1206            return SymWord::zero();
1207        }
1208        self.balances
1209            .get(&address)
1210            .cloned()
1211            .unwrap_or_else(|| SymWord::Concrete(self.backend_balance(executor, address)))
1212    }
1213
1214    /// Implements the `balance_word` symbolic state helper.
1215    pub(crate) fn balance_word<FEN: FoundryEvmNetwork>(
1216        &mut self,
1217        executor: &Executor<FEN>,
1218        word: SymWord,
1219    ) -> Result<SymWord, SymbolicError> {
1220        if let Some(address) = self.resolve_address(&word) {
1221            return Ok(self.balance_word_for_address(executor, address));
1222        }
1223
1224        let expr = word.into_expr();
1225        let representative = representative_symbolic_address(&SymWord::Expr(expr.clone()));
1226        let mut result = self.balance_word_for_address(executor, representative).into_expr();
1227        for (address, balance) in self.balances.iter().rev() {
1228            if self.destroyed_accounts.contains(address) {
1229                continue;
1230            }
1231            result = Expr::Ite(
1232                Box::new(BoolExpr::eq(expr.clone(), Expr::Const(address_word(*address)))),
1233                Box::new(balance.clone().into_expr()),
1234                Box::new(result),
1235            );
1236        }
1237
1238        Ok(SymWord::Expr(result))
1239    }
1240
1241    /// Applies the `set_balance_word` symbolic state helper.
1242    pub(crate) fn set_balance_word(&mut self, address: Address, value: SymWord) {
1243        self.balances.insert(address, value.clone());
1244        if !matches!(value, SymWord::Concrete(value) if value.is_zero()) {
1245            self.existing_accounts.insert(address);
1246            self.destroyed_accounts.remove(&address);
1247        }
1248    }
1249
1250    /// Implements the `transfer` symbolic state helper.
1251    pub(crate) fn transfer<FEN: FoundryEvmNetwork>(
1252        &mut self,
1253        executor: &Executor<FEN>,
1254        from: Address,
1255        to: Address,
1256        value: SymWord,
1257    ) {
1258        if matches!(value, SymWord::Concrete(value) if value.is_zero()) {
1259            return;
1260        }
1261        let from_balance = self.balance_word_for_address(executor, from);
1262        let to_balance = self.balance_word_for_address(executor, to);
1263        self.set_balance_word(from, sym_sub(from_balance, value.clone()));
1264        self.set_balance_word(to, sym_add(to_balance, value));
1265    }
1266
1267    /// Implements the `nonce` symbolic state helper.
1268    pub(crate) fn nonce<FEN: FoundryEvmNetwork>(
1269        &self,
1270        executor: &Executor<FEN>,
1271        address: Address,
1272    ) -> Result<u64, SymbolicError> {
1273        if self.destroyed_accounts.contains(&address) {
1274            return Ok(self.nonces.get(&address).copied().unwrap_or_default());
1275        }
1276        if let Some(nonce) = self.nonces.get(&address) {
1277            return Ok(*nonce);
1278        }
1279        executor
1280            .backend()
1281            .basic_ref(address)
1282            .map_err(|err| SymbolicError::Backend(err.to_string()))
1283            .map(|account| account.map(|account| account.nonce).unwrap_or_default())
1284    }
1285
1286    /// Applies the `set_nonce` symbolic state helper.
1287    pub(crate) fn set_nonce(&mut self, address: Address, nonce: u64) {
1288        self.nonces.insert(address, nonce);
1289        if nonce != 0 {
1290            self.existing_accounts.insert(address);
1291            self.destroyed_accounts.remove(&address);
1292        }
1293    }
1294
1295    /// Implements the `increment_nonce` symbolic state helper.
1296    pub(crate) fn increment_nonce<FEN: FoundryEvmNetwork>(
1297        &mut self,
1298        executor: &Executor<FEN>,
1299        address: Address,
1300    ) -> Result<(), SymbolicError> {
1301        let nonce = self.nonce(executor, address)?;
1302        self.set_nonce(address, nonce.saturating_add(1));
1303        Ok(())
1304    }
1305
1306    /// Returns whether `has_code_or_nonce` holds.
1307    pub(crate) fn has_code_or_nonce<FEN: FoundryEvmNetwork>(
1308        &mut self,
1309        executor: &Executor<FEN>,
1310        address: Address,
1311    ) -> Result<bool, SymbolicError> {
1312        if self.destroyed_accounts.contains(&address) {
1313            return Ok(false);
1314        }
1315        Ok(!self.extcode(executor, address)?.is_empty() || self.nonce(executor, address)? != 0)
1316    }
1317
1318    /// Applies the `install_code` symbolic state helper.
1319    pub(crate) fn install_code(&mut self, address: Address, code: SymCode) {
1320        self.code_cache.insert(address, code);
1321        self.existing_accounts.insert(address);
1322        self.destroyed_accounts.remove(&address);
1323    }
1324
1325    /// Implements the `selfdestruct` symbolic state helper.
1326    pub(crate) fn selfdestruct<FEN: FoundryEvmNetwork>(
1327        &mut self,
1328        executor: &Executor<FEN>,
1329        address: Address,
1330        beneficiary: Address,
1331    ) -> Result<(), SymbolicError> {
1332        let balance = self.balance_word_for_address(executor, address);
1333        if beneficiary != address && !matches!(balance, SymWord::Concrete(value) if value.is_zero())
1334        {
1335            let beneficiary_balance = self.balance_word_for_address(executor, beneficiary);
1336            self.set_balance_word(beneficiary, sym_add(beneficiary_balance, balance));
1337        }
1338        self.balances.insert(address, SymWord::zero());
1339        self.code_cache.insert(address, SymCode::default());
1340        if !self.nonces.contains_key(&address) {
1341            let nonce = self.nonce(executor, address)?;
1342            self.nonces.insert(address, nonce);
1343        }
1344        self.storage.retain(|write| write.address != address);
1345        self.transient_storage.retain(|write| write.address != address);
1346        self.existing_accounts.remove(&address);
1347        self.destroyed_accounts.insert(address);
1348        Ok(())
1349    }
1350
1351    /// Implements the `account_exists` symbolic state helper.
1352    pub(crate) fn account_exists<FEN: FoundryEvmNetwork>(
1353        &mut self,
1354        executor: &Executor<FEN>,
1355        address: Address,
1356    ) -> Result<bool, SymbolicError> {
1357        if is_known_cheatcode(address) || is_supported_precompile(address) {
1358            return Ok(true);
1359        }
1360        if self.destroyed_accounts.contains(&address) {
1361            return Ok(false);
1362        }
1363        if self.existing_accounts.contains(&address) {
1364            return Ok(true);
1365        }
1366        if self
1367            .balances
1368            .get(&address)
1369            .is_some_and(|balance| !matches!(balance, SymWord::Concrete(value) if value.is_zero()))
1370            || self.nonces.get(&address).is_some_and(|nonce| *nonce != 0)
1371            || self.code_cache.get(&address).is_some_and(|code| !code.is_empty())
1372        {
1373            self.existing_accounts.insert(address);
1374            return Ok(true);
1375        }
1376
1377        let Some(account) = executor
1378            .backend()
1379            .basic_ref(address)
1380            .map_err(|err| SymbolicError::Backend(err.to_string()))?
1381        else {
1382            return Ok(false);
1383        };
1384
1385        if account.nonce != 0 || !account.balance.is_zero() {
1386            self.existing_accounts.insert(address);
1387            return Ok(true);
1388        }
1389
1390        let code = account.code.map(|code| code.original_bytes().to_vec()).unwrap_or_default();
1391        if !code.is_empty() {
1392            self.code_cache.insert(address, SymCode::concrete(code));
1393            self.existing_accounts.insert(address);
1394            return Ok(true);
1395        }
1396
1397        Ok(false)
1398    }
1399
1400    /// Implements the `extcode` symbolic state helper.
1401    pub(crate) fn extcode<FEN: FoundryEvmNetwork>(
1402        &mut self,
1403        executor: &Executor<FEN>,
1404        address: Address,
1405    ) -> Result<SymCode, SymbolicError> {
1406        if is_known_cheatcode(address) {
1407            return Ok(SymCode::concrete(vec![0]));
1408        }
1409        if is_supported_precompile(address) {
1410            return Ok(SymCode::default());
1411        }
1412        if self.destroyed_accounts.contains(&address) {
1413            return Ok(SymCode::default());
1414        }
1415        if let Some(code) = self.code_cache.get(&address) {
1416            return Ok(code.clone());
1417        }
1418        let account = executor
1419            .backend()
1420            .basic_ref(address)
1421            .map_err(|err| SymbolicError::Backend(err.to_string()))?;
1422        let code = account
1423            .as_ref()
1424            .and_then(|account| account.code.as_ref().map(|code| code.original_bytes().to_vec()))
1425            .unwrap_or_default();
1426        if let Some(account) = account
1427            && (account.nonce != 0 || !account.balance.is_zero() || !code.is_empty())
1428        {
1429            self.existing_accounts.insert(address);
1430        }
1431        let code = SymCode::concrete(code);
1432        self.code_cache.insert(address, code.clone());
1433        Ok(code)
1434    }
1435
1436    /// Implements the `extcode_hash_for_address` symbolic state helper.
1437    pub(crate) fn extcode_hash_for_address<FEN: FoundryEvmNetwork>(
1438        &mut self,
1439        executor: &Executor<FEN>,
1440        address: Address,
1441    ) -> Result<SymWord, SymbolicError> {
1442        if self.account_exists(executor, address)? {
1443            let code = self.extcode(executor, address)?;
1444            Ok(keccak_word(code.read_bytes(0, code.len())))
1445        } else {
1446            Ok(SymWord::zero())
1447        }
1448    }
1449
1450    /// Implements the `extcode_size_word` symbolic state helper.
1451    pub(crate) fn extcode_size_word<FEN: FoundryEvmNetwork>(
1452        &mut self,
1453        executor: &Executor<FEN>,
1454        word: SymWord,
1455    ) -> Result<SymWord, SymbolicError> {
1456        if let Some(address) = self.resolve_address(&word) {
1457            return Ok(SymWord::Concrete(U256::from(self.extcode(executor, address)?.len())));
1458        }
1459
1460        let expr = word.into_expr();
1461        let representative = representative_symbolic_address(&SymWord::Expr(expr.clone()));
1462        let mut result = Expr::Const(U256::from(self.extcode(executor, representative)?.len()));
1463        for (address, code) in self.code_cache.iter().rev() {
1464            if self.destroyed_accounts.contains(address) {
1465                continue;
1466            }
1467            result = Expr::Ite(
1468                Box::new(BoolExpr::eq(expr.clone(), Expr::Const(address_word(*address)))),
1469                Box::new(Expr::Const(U256::from(code.len()))),
1470                Box::new(result),
1471            );
1472        }
1473
1474        Ok(SymWord::Expr(result))
1475    }
1476
1477    /// Implements the `extcode_hash_word` symbolic state helper.
1478    pub(crate) fn extcode_hash_word<FEN: FoundryEvmNetwork>(
1479        &mut self,
1480        executor: &Executor<FEN>,
1481        word: SymWord,
1482    ) -> Result<SymWord, SymbolicError> {
1483        if let Some(address) = self.resolve_address(&word) {
1484            return self.extcode_hash_for_address(executor, address);
1485        }
1486
1487        let expr = word.into_expr();
1488        let representative = representative_symbolic_address(&SymWord::Expr(expr.clone()));
1489        let mut result = self.extcode_hash_for_address(executor, representative)?.into_expr();
1490        let cached_codes: Vec<_> =
1491            self.code_cache.iter().map(|(address, code)| (*address, code.clone())).collect();
1492        for (address, code) in cached_codes.into_iter().rev() {
1493            let hash = if self.destroyed_accounts.contains(&address) {
1494                SymWord::zero()
1495            } else {
1496                keccak_word(code.read_bytes(0, code.len()))
1497            };
1498            result = Expr::Ite(
1499                Box::new(BoolExpr::eq(expr.clone(), Expr::Const(address_word(address)))),
1500                Box::new(hash.into_expr()),
1501                Box::new(result),
1502            );
1503        }
1504
1505        Ok(SymWord::Expr(result))
1506    }
1507
1508    /// Implements the `extcode_bytes_word` symbolic state helper.
1509    pub(crate) fn extcode_bytes_word<FEN: FoundryEvmNetwork>(
1510        &mut self,
1511        executor: &Executor<FEN>,
1512        word: SymWord,
1513        offset: SymWord,
1514        size: usize,
1515    ) -> Result<Vec<SymWord>, SymbolicError> {
1516        if let Some(address) = self.resolve_address(&word) {
1517            return Ok(self.extcode(executor, address)?.read_bytes_offset(offset, size));
1518        }
1519
1520        let expr = word.into_expr();
1521        let representative = representative_symbolic_address(&SymWord::Expr(expr.clone()));
1522        let mut result =
1523            self.extcode(executor, representative)?.read_bytes_offset(offset.clone(), size);
1524        let cached_codes: Vec<_> =
1525            self.code_cache.iter().map(|(address, code)| (*address, code.clone())).collect();
1526        for (address, code) in cached_codes.into_iter().rev() {
1527            let bytes = if self.destroyed_accounts.contains(&address) {
1528                vec![SymWord::zero(); size]
1529            } else {
1530                code.read_bytes_offset(offset.clone(), size)
1531            };
1532            for (idx, byte) in bytes.into_iter().enumerate() {
1533                result[idx] = SymWord::Expr(Expr::Ite(
1534                    Box::new(BoolExpr::eq(expr.clone(), Expr::Const(address_word(address)))),
1535                    Box::new(byte.into_expr()),
1536                    Box::new(result[idx].clone().into_expr()),
1537                ));
1538            }
1539        }
1540
1541        Ok(result)
1542    }
1543
1544    /// Returns the `symbolic_call_targets` symbolic state helper result.
1545    pub(crate) fn symbolic_call_targets<FEN: FoundryEvmNetwork>(
1546        &mut self,
1547        executor: &Executor<FEN>,
1548    ) -> Result<Vec<Address>, SymbolicError> {
1549        let mut addresses = BTreeSet::new();
1550        addresses.extend(self.code_cache.keys().copied());
1551        addresses.extend(self.existing_accounts.iter().copied());
1552        addresses.extend(executor.backend().mem_db().cache.accounts.keys().copied());
1553        if let Some(db) = executor.backend().active_fork_db() {
1554            addresses.extend(db.cache.accounts.keys().copied());
1555        }
1556
1557        let mut targets = Vec::new();
1558        for address in addresses {
1559            if is_known_cheatcode(address) || is_supported_precompile(address) {
1560                continue;
1561            }
1562            if !self.extcode(executor, address)?.is_empty() {
1563                targets.push(address);
1564            }
1565        }
1566        Ok(targets)
1567    }
1568}
1569
1570#[derive(Clone, Debug)]
1571pub(crate) struct SymbolicBlock {
1572    pub(crate) chain_id: SymWord,
1573    pub(crate) coinbase: Address,
1574    pub(crate) timestamp: SymWord,
1575    pub(crate) number: SymWord,
1576    pub(crate) difficulty: SymWord,
1577    pub(crate) gaslimit: SymWord,
1578    pub(crate) basefee: SymWord,
1579    pub(crate) blob_basefee: SymWord,
1580    pub(crate) block_hashes: BTreeMap<U256, SymWord>,
1581    pub(crate) blob_hashes: Vec<B256>,
1582}
1583
1584impl Default for SymbolicBlock {
1585    /// Implements the `default` symbolic state helper.
1586    fn default() -> Self {
1587        Self {
1588            chain_id: SymWord::Concrete(U256::from(1)),
1589            coinbase: Address::ZERO,
1590            timestamp: SymWord::zero(),
1591            number: SymWord::zero(),
1592            difficulty: SymWord::zero(),
1593            gaslimit: SymWord::zero(),
1594            basefee: SymWord::zero(),
1595            blob_basefee: SymWord::zero(),
1596            block_hashes: BTreeMap::new(),
1597            blob_hashes: Vec::new(),
1598        }
1599    }
1600}
1601
1602/// Collects the symbolic variables needed to concretely evaluate an expression.
1603fn collect_eval_vars(expr: &Expr, vars: &mut BTreeSet<String>) {
1604    match expr {
1605        Expr::Const(_) => {}
1606        Expr::GasLeft(_) => {}
1607        Expr::Var(var) | Expr::Hash { name: var, .. } => {
1608            vars.insert(var.clone());
1609        }
1610        Expr::Keccak { len, bytes, .. } => {
1611            collect_eval_vars(len, vars);
1612            for byte in bytes {
1613                collect_eval_vars(byte, vars);
1614            }
1615        }
1616        Expr::Not(value) => collect_eval_vars(value, vars),
1617        Expr::Op(_, left, right) => {
1618            collect_eval_vars(left, vars);
1619            collect_eval_vars(right, vars);
1620        }
1621        Expr::Ite(condition, left, right) => {
1622            collect_eval_bool_vars(condition, vars);
1623            collect_eval_vars(left, vars);
1624            collect_eval_vars(right, vars);
1625        }
1626    }
1627}
1628
1629/// Collects the symbolic variables needed to concretely evaluate a boolean expression.
1630fn collect_eval_bool_vars(expr: &BoolExpr, vars: &mut BTreeSet<String>) {
1631    match expr {
1632        BoolExpr::Const(_) => {}
1633        BoolExpr::Not(value) => collect_eval_bool_vars(value, vars),
1634        BoolExpr::And(values) => {
1635            for value in values {
1636                collect_eval_bool_vars(value, vars);
1637            }
1638        }
1639        BoolExpr::Eq(left, right) | BoolExpr::Cmp(_, left, right) => {
1640            collect_eval_vars(left, vars);
1641            collect_eval_vars(right, vars);
1642        }
1643    }
1644}
1645
1646impl SymbolicBlock {
1647    /// Converts values for the `from_executor` symbolic state helper.
1648    pub(crate) fn from_executor<FEN: FoundryEvmNetwork>(executor: &Executor<FEN>) -> Self {
1649        let evm_env = executor.evm_env();
1650        let block = executor
1651            .inspector()
1652            .cheatcodes
1653            .as_ref()
1654            .and_then(|cheats| cheats.block.as_ref())
1655            .unwrap_or(&evm_env.block_env);
1656        let difficulty = block
1657            .prevrandao()
1658            .map(|hash| U256::from_be_bytes(hash.0))
1659            .unwrap_or_else(|| block.difficulty());
1660
1661        Self {
1662            chain_id: SymWord::Concrete(U256::from(evm_env.cfg_env.chain_id)),
1663            coinbase: block.beneficiary(),
1664            timestamp: SymWord::Concrete(block.timestamp()),
1665            number: SymWord::Concrete(block.number()),
1666            difficulty: SymWord::Concrete(difficulty),
1667            gaslimit: SymWord::Concrete(U256::from(block.gas_limit())),
1668            basefee: SymWord::Concrete(U256::from(block.basefee())),
1669            blob_basefee: SymWord::Concrete(U256::from(block.blob_gasprice().unwrap_or_default())),
1670            block_hashes: BTreeMap::new(),
1671            blob_hashes: executor.tx_env().blob_versioned_hashes().to_vec(),
1672        }
1673    }
1674
1675    /// Applies the `set_block_hash` symbolic state helper.
1676    pub(crate) fn set_block_hash(
1677        &mut self,
1678        block_number: U256,
1679        block_hash: SymWord,
1680    ) -> Result<(), SymbolicError> {
1681        let current =
1682            self.number.clone().into_concrete("symbolic vm.setBlockhash current number")?;
1683        if block_number < current && current - block_number <= U256::from(256) {
1684            self.block_hashes.insert(block_number, block_hash);
1685        }
1686        Ok(())
1687    }
1688
1689    /// Implements the `block_hash` symbolic state helper.
1690    pub(crate) fn block_hash<FEN: FoundryEvmNetwork>(
1691        &self,
1692        executor: &Executor<FEN>,
1693        block_number: U256,
1694    ) -> Result<SymWord, SymbolicError> {
1695        let current = self.number.clone().into_concrete("symbolic BLOCKHASH current number")?;
1696        if block_number >= current || current - block_number > U256::from(256) {
1697            return Ok(SymWord::zero());
1698        }
1699        if let Some(hash) = self.block_hashes.get(&block_number) {
1700            return Ok(hash.clone());
1701        }
1702        let Ok(block_number) = u64::try_from(block_number) else {
1703            return Ok(SymWord::zero());
1704        };
1705        let hash = executor
1706            .backend()
1707            .block_hash_ref(block_number)
1708            .map_err(|err| SymbolicError::Backend(err.to_string()))?;
1709        Ok(SymWord::Concrete(U256::from_be_slice(hash.as_slice())))
1710    }
1711
1712    /// Implements the `block_hash_word` symbolic state helper.
1713    pub(crate) fn block_hash_word<FEN: FoundryEvmNetwork>(
1714        &self,
1715        executor: &Executor<FEN>,
1716        block_number: SymWord,
1717    ) -> Result<SymWord, SymbolicError> {
1718        let block_number = match block_number {
1719            SymWord::Concrete(block_number) => {
1720                return self.block_hash(executor, block_number);
1721            }
1722            SymWord::Expr(block_number) => block_number,
1723        };
1724
1725        let current = self.number.clone().into_concrete("symbolic BLOCKHASH current number")?;
1726        if current.is_zero() {
1727            return Ok(SymWord::zero());
1728        }
1729
1730        let mut result = Expr::Const(U256::ZERO);
1731        let max_distance = current.min(U256::from(256)).to::<usize>();
1732        for distance in (1..=max_distance).rev() {
1733            let candidate = current - U256::from(distance);
1734            let hash = self.block_hash(executor, candidate)?;
1735            if matches!(&hash, SymWord::Concrete(hash) if hash.is_zero()) {
1736                continue;
1737            }
1738            result = Expr::Ite(
1739                Box::new(BoolExpr::eq(block_number.clone(), Expr::Const(candidate))),
1740                Box::new(hash.into_expr()),
1741                Box::new(result),
1742            );
1743        }
1744
1745        Ok(SymWord::Expr(result))
1746    }
1747
1748    /// Applies the `set_blob_hashes` symbolic state helper.
1749    pub(crate) fn set_blob_hashes(&mut self, blob_hashes: Vec<B256>) {
1750        self.blob_hashes = blob_hashes;
1751    }
1752
1753    /// Implements the `blob_hash` symbolic state helper.
1754    pub(crate) fn blob_hash(&self, index: usize) -> B256 {
1755        self.blob_hashes.get(index).copied().unwrap_or_default()
1756    }
1757}