Skip to main content

foundry_evm_symbolic/runtime/
state.rs

1use super::*;
2use foundry_evm::revm::context_interface::cfg::gas::CALL_STIPEND;
3
4const MAX_BOUND_ANALYSIS_VISITS: usize = 256;
5
6#[derive(Clone, Debug)]
7pub(crate) struct PathState {
8    pub(crate) depth: usize,
9    pub(crate) call_depth: usize,
10    pub(crate) origin: Address,
11    pub(crate) origin_word: SymExpr,
12    pub(crate) gas_price: SymExpr,
13    pub(crate) ffi_enabled: bool,
14    pub(crate) block: SymbolicBlock,
15    pub(crate) frame: CallFrame,
16    pub(crate) world: SymbolicWorld,
17    pub(crate) prank: SymbolicPrank,
18    pub(crate) constraints: Vec<SymBoolExpr>,
19    pub(crate) next_symbol: usize,
20    pub(crate) recorded_logs: Option<Vec<SymbolicLog>>,
21    pub(crate) access_record: Option<AccessRecord>,
22    pub(crate) root_calldata: Option<SymbolicCalldata>,
23    /// Predicate context follows child calls but is not committed back to handler state.
24    pub(crate) invariant_predicate: bool,
25    corpus_seed_models: Vec<Arc<SymbolicModel>>,
26    branch_target: Option<SymbolicBranchTarget>,
27    branch_target_reached: bool,
28    needs_feasibility_check: bool,
29    pub(crate) loop_jumps: HashMap<usize, u32>,
30    pub(crate) expected_revert: Option<ExpectedRevert>,
31    pub(crate) assume_no_revert_next_call: Option<AssumeNoRevert>,
32    pub(crate) expected_emit: Option<ExpectedEmit>,
33    pub(crate) expected_calls: Vec<ExpectedCall>,
34    pub(crate) expected_creates: Vec<ExpectedCreate>,
35    pub(crate) call_mocks: Vec<CallMock>,
36    pub(crate) function_mocks: Vec<FunctionMock>,
37    pub(crate) persistent_accounts: HashSet<Address>,
38    pub(crate) wallets: IndexSet<Address>,
39    pub(crate) labels: HashMap<Address, String>,
40    pub(crate) storage_load_hooks: HashMap<Address, SymbolicStorageHook>,
41    pub(crate) storage_store_hooks: HashMap<Address, SymbolicStorageHook>,
42    pub(crate) mapping_storage_store_hooks: HashMap<(Address, U256), SymbolicStorageHook>,
43    pub(crate) mapping_hook_keccak_preimages: HashMap<(Address, SymExpr), Arc<[SymExpr]>>,
44    pub(crate) storage_hook_active: bool,
45    pub(crate) pending_storage_hook_revert: bool,
46}
47
48impl PathState {
49    pub(crate) fn new(
50        cx: &mut SymCx,
51        address: Address,
52        caller: Address,
53        callvalue: U256,
54        calldata: SymbolicCalldata,
55        ffi_enabled: bool,
56    ) -> Self {
57        let mut state = Self::empty(cx, address, caller, ffi_enabled);
58        let callvalue = SymExpr::constant(cx, callvalue);
59        let call_data = calldata.call_data(cx);
60        state.frame = CallFrame::new(cx, address, address, caller, callvalue, false, call_data);
61        state.constraints = calldata.constraints().to_vec();
62        state.root_calldata = Some(calldata);
63        state
64    }
65
66    pub(crate) fn empty(
67        cx: &mut SymCx,
68        address: Address,
69        caller: Address,
70        ffi_enabled: bool,
71    ) -> Self {
72        let origin_word = SymExpr::constant(cx, address_word(caller));
73        let gas_price = SymExpr::zero(cx);
74        let block = SymbolicBlock::new(cx);
75        let callvalue = SymExpr::zero(cx);
76        let calldata = SymBytes::empty(cx);
77        let calldata = SymCalldata::from_bytes(cx, calldata);
78        let frame = CallFrame::new(cx, address, address, caller, callvalue, false, calldata);
79        Self {
80            depth: 0,
81            call_depth: 0,
82            origin: caller,
83            origin_word,
84            gas_price,
85            ffi_enabled,
86            block,
87            frame,
88            world: SymbolicWorld::default(),
89            prank: SymbolicPrank::default(),
90            constraints: Vec::new(),
91            next_symbol: 0,
92            recorded_logs: None,
93            access_record: None,
94            root_calldata: None,
95            invariant_predicate: false,
96            corpus_seed_models: Vec::new(),
97            branch_target: None,
98            branch_target_reached: false,
99            needs_feasibility_check: false,
100            loop_jumps: HashMap::default(),
101            expected_revert: None,
102            assume_no_revert_next_call: None,
103            expected_emit: None,
104            expected_calls: Vec::new(),
105            expected_creates: Vec::new(),
106            call_mocks: Vec::new(),
107            function_mocks: Vec::new(),
108            persistent_accounts: HashSet::default(),
109            wallets: IndexSet::default(),
110            labels: HashMap::default(),
111            storage_load_hooks: HashMap::default(),
112            storage_store_hooks: HashMap::default(),
113            mapping_storage_store_hooks: HashMap::default(),
114            mapping_hook_keccak_preimages: HashMap::default(),
115            storage_hook_active: false,
116            pending_storage_hook_revert: false,
117        }
118    }
119
120    pub(crate) fn apply_executor_env<FEN: FoundryEvmNetwork>(
121        &mut self,
122        cx: &mut SymCx,
123        executor: &Executor<FEN>,
124    ) {
125        self.block = SymbolicBlock::from_executor(cx, executor);
126        let gas_price = executor
127            .inspector()
128            .cheatcodes
129            .as_ref()
130            .and_then(|cheats| cheats.gas_price)
131            .unwrap_or_else(|| executor.tx_env().gas_price());
132        self.gas_price = SymExpr::constant(cx, U256::from(gas_price));
133        if let Some(cheats) = executor.inspector().cheatcodes.as_ref() {
134            for (target, overwrite) in cheats.arbitrary_storage_target_overwrite_modes() {
135                self.world.enable_arbitrary_storage(target, overwrite);
136            }
137            for (target, source) in cheats.arbitrary_storage_copied_target_sources() {
138                Arc::make_mut(&mut self.world.state)
139                    .arbitrary_storage_copies
140                    .insert(target, source);
141            }
142            self.storage_load_hooks.extend(cheats.storage_load_hooks().map(|(target, hook)| {
143                (
144                    target,
145                    SymbolicStorageHook {
146                        callback_target: hook.callback_target,
147                        callback_selector: hook.callback_selector,
148                    },
149                )
150            }));
151            self.storage_store_hooks.extend(cheats.storage_store_hooks().map(|(target, hook)| {
152                (
153                    target,
154                    SymbolicStorageHook {
155                        callback_target: hook.callback_target,
156                        callback_selector: hook.callback_selector,
157                    },
158                )
159            }));
160            self.mapping_storage_store_hooks.extend(cheats.mapping_storage_store_hooks().map(
161                |(target, root, hook)| {
162                    (
163                        (target, root.into()),
164                        SymbolicStorageHook {
165                            callback_target: hook.callback_target,
166                            callback_selector: hook.callback_selector,
167                        },
168                    )
169                },
170            ));
171        }
172    }
173
174    pub(crate) fn child(&self, frame: CallFrame) -> Self {
175        let mut child = self.clone();
176        child.call_depth += 1;
177        child.frame = frame;
178        // A prank changes the call being entered; calls made by the callee use normal EVM caller
179        // semantics unless the callee sets its own prank.
180        child.prank = SymbolicPrank::default();
181        child.loop_jumps.clear();
182        child.expected_revert = None;
183        child.assume_no_revert_next_call = None;
184        child
185    }
186
187    pub(crate) fn storage_hook_child(&self, frame: CallFrame) -> Self {
188        let mut child = self.child(frame);
189        child.storage_hook_active = true;
190        child.recorded_logs = None;
191        child.access_record = None;
192        child.expected_emit = None;
193        child.expected_calls.clear();
194        child.expected_creates.clear();
195        child.call_mocks.clear();
196        child.function_mocks.clear();
197        child.set_branch_target(None);
198        child
199    }
200
201    pub(crate) fn copy_call_output_offset(
202        &mut self,
203        cx: &mut SymCx,
204        dest: SymExpr,
205        size: &BoundedCopySize,
206    ) -> Result<(), SymbolicError> {
207        let CallFrame { memory, return_data, .. } = &mut self.frame;
208        memory.copy_call_output_offset(cx, dest, size, return_data)
209    }
210
211    pub(crate) fn constrained_usize(&self, cx: &mut SymCx, expr: &SymExpr) -> Option<usize> {
212        self.constrained_usize_checked(cx, expr).and_then(Result::ok)
213    }
214
215    pub(crate) fn constrained_usize_checked(
216        &self,
217        cx: &mut SymCx,
218        expr: &SymExpr,
219    ) -> Option<Result<usize, U256>> {
220        self.constrained_word(cx, expr).map(|value| usize::try_from(value).map_err(|_| value))
221    }
222
223    pub(crate) fn upper_bound_usize(&self, _cx: &mut SymCx, expr: &SymExpr) -> Option<usize> {
224        let mut bounds = HashMap::default();
225        let mut ordering = HashMap::default();
226        let mut remaining = MAX_BOUND_ANALYSIS_VISITS;
227        self.expr_upper_bound_usize_cached(expr, &mut bounds, &mut ordering, &mut remaining)
228    }
229
230    /// Returns a conservative lower bound for values representable as host memory offsets.
231    pub(crate) fn lower_bound_usize(&self, expr: &SymExpr) -> usize {
232        let mut lower_bounds = HashMap::default();
233        let mut upper_bounds = HashMap::default();
234        let mut ordering = HashMap::default();
235        let mut remaining = MAX_BOUND_ANALYSIS_VISITS;
236        self.expr_lower_bound_usize(
237            expr,
238            &mut lower_bounds,
239            &mut upper_bounds,
240            &mut ordering,
241            &mut remaining,
242        )
243    }
244
245    pub(crate) fn constrained_word(&self, cx: &mut SymCx, expr: &SymExpr) -> Option<U256> {
246        expr.as_const().or_else(|| {
247            self.constraints
248                .iter()
249                .find_map(|constraint| {
250                    constraint.forces_expr_const_with_context(expr, &self.constraints)
251                })
252                .or_else(|| self.constrained_expr_value(cx, expr))
253        })
254    }
255
256    pub(crate) fn constrained_expr_value(&self, cx: &mut SymCx, expr: &SymExpr) -> Option<U256> {
257        if let Some(value) = expr.eval() {
258            return Some(value);
259        }
260        if let Some(value) = expr.known_word() {
261            return Some(value);
262        }
263
264        let mut vars = SymbolicVars::default();
265        expr.collect_eval_vars(&mut vars);
266        let mut model = SymbolicModel::default();
267        for var in vars {
268            let var_expr = SymExpr::get_var(cx, var);
269            let value = self.constraints.iter().find_map(|constraint| {
270                constraint.forces_expr_const_with_context(&var_expr, &self.constraints)
271            })?;
272            model.insert(var, value);
273        }
274
275        expr.eval_model(&model).ok()
276    }
277
278    pub(crate) fn split_corpus_seed_models(
279        &self,
280        condition: &SymBoolExpr,
281    ) -> (Vec<Arc<SymbolicModel>>, Vec<Arc<SymbolicModel>>) {
282        let mut true_models = Vec::new();
283        let mut false_models = Vec::new();
284        for model in &self.corpus_seed_models {
285            match condition.eval_model_if_complete(model.as_ref()) {
286                Ok(Some(true)) => true_models.push(Arc::clone(model)),
287                Ok(Some(false)) => false_models.push(Arc::clone(model)),
288                Ok(None) | Err(_) => {
289                    true_models.push(Arc::clone(model));
290                    false_models.push(Arc::clone(model));
291                }
292            }
293        }
294        (true_models, false_models)
295    }
296
297    pub(crate) fn set_corpus_seed_models(&mut self, models: Vec<Arc<SymbolicModel>>) {
298        self.corpus_seed_models = models;
299    }
300
301    pub(crate) const fn corpus_seed_model_count(&self) -> usize {
302        self.corpus_seed_models.len()
303    }
304
305    pub(crate) const fn set_branch_target(&mut self, target: Option<SymbolicBranchTarget>) {
306        self.branch_target = target;
307        self.branch_target_reached = false;
308    }
309
310    pub(crate) const fn branch_target(&self) -> Option<SymbolicBranchTarget> {
311        self.branch_target
312    }
313
314    pub(crate) const fn mark_branch_target_reached(&mut self) {
315        self.branch_target_reached = true;
316    }
317
318    pub(crate) fn inherit_branch_target_progress(&mut self, child: &Self) {
319        if self.branch_target == child.branch_target && child.branch_target_reached {
320            self.mark_branch_target_reached();
321        }
322    }
323
324    pub(crate) fn take_noncommitting_check_state(&mut self, check: &mut Self) {
325        self.constraints = std::mem::take(&mut check.constraints);
326        self.next_symbol = self.next_symbol.max(check.next_symbol);
327        self.world.merge_replay_metadata_from(&check.world);
328        self.storage_load_hooks = std::mem::take(&mut check.storage_load_hooks);
329        self.storage_store_hooks = std::mem::take(&mut check.storage_store_hooks);
330        self.mapping_storage_store_hooks = std::mem::take(&mut check.mapping_storage_store_hooks);
331    }
332
333    pub(crate) fn take_call_outcome_state(&mut self, child: &mut Self) {
334        self.constraints = std::mem::take(&mut child.constraints);
335        self.next_symbol = child.next_symbol;
336        self.inherit_branch_target_progress(child);
337        self.storage_load_hooks = std::mem::take(&mut child.storage_load_hooks);
338        self.storage_store_hooks = std::mem::take(&mut child.storage_store_hooks);
339        self.mapping_storage_store_hooks = std::mem::take(&mut child.mapping_storage_store_hooks);
340        self.mapping_hook_keccak_preimages =
341            std::mem::take(&mut child.mapping_hook_keccak_preimages);
342        self.recorded_logs = child.recorded_logs.take();
343        self.access_record = child.access_record.take();
344        // Inspector state survives child reverts and exceptional halts.
345        self.block = child.block.clone();
346        self.expected_calls = std::mem::take(&mut child.expected_calls);
347        self.call_mocks = std::mem::take(&mut child.call_mocks);
348        self.function_mocks = std::mem::take(&mut child.function_mocks);
349    }
350
351    pub(crate) fn take_reverted_top_level_effects(&mut self, mut reverted: Self) {
352        self.take_noncommitting_check_state(&mut reverted);
353        self.block = reverted.block;
354        self.recorded_logs = reverted.recorded_logs;
355        self.access_record = reverted.access_record;
356        self.expected_revert = reverted.expected_revert;
357        self.assume_no_revert_next_call = reverted.assume_no_revert_next_call;
358        self.expected_emit = reverted.expected_emit;
359        self.expected_calls = reverted.expected_calls;
360        self.expected_creates = reverted.expected_creates;
361        self.call_mocks = reverted.call_mocks;
362        self.function_mocks = reverted.function_mocks;
363    }
364
365    /// Returns `true` if the path can be materialized into a replayable corpus seed.
366    ///
367    /// Gas-dependent constraints are never modeled, so a seed for such a path would carry a
368    /// fabricated `gasleft()` value; skip the seed rather than failing the whole run.
369    pub(crate) fn can_materialize_seed(&self) -> bool {
370        !self.constraints.iter().any(SymBoolExpr::contains_gasleft)
371    }
372
373    pub(crate) const fn satisfies_branch_target(&self) -> bool {
374        self.branch_target.is_none() || self.branch_target_reached
375    }
376
377    pub(crate) const fn defer_feasibility_check(&mut self) {
378        self.needs_feasibility_check = true;
379    }
380
381    pub(crate) const fn take_deferred_feasibility_check(&mut self) -> bool {
382        let needs_check = self.needs_feasibility_check;
383        self.needs_feasibility_check = false;
384        needs_check
385    }
386
387    fn expr_upper_bound_usize_cached(
388        &self,
389        expr: &SymExpr,
390        bounds: &mut HashMap<SymExpr, Option<usize>>,
391        ordering: &mut HashMap<(SymExpr, SymExpr), bool>,
392        remaining: &mut usize,
393    ) -> Option<usize> {
394        if let Some(bound) = bounds.get(expr) {
395            return *bound;
396        }
397        if let Some(value) = expr.as_const() {
398            return usize::try_from(value).ok();
399        }
400
401        let constraint_bound = self.constraint_upper_bound_usize(expr);
402        let structural_bound = remaining.checked_sub(1).and_then(|next| {
403            *remaining = next;
404            match expr.kind() {
405                SymExprKind::Const(value) => usize::try_from(*value).ok(),
406                SymExprKind::Var(_)
407                | SymExprKind::GasLeft(_)
408                | SymExprKind::Keccak { .. }
409                | SymExprKind::Hash { .. } => None,
410                SymExprKind::Not(_) => None,
411                SymExprKind::TernOp(_, _, _, modulus) => match modulus.as_const() {
412                    Some(modulus) if modulus.is_zero() => Some(0),
413                    Some(modulus) => usize::try_from(modulus - U256::ONE).ok(),
414                    None => self
415                        .expr_upper_bound_usize_cached(modulus, bounds, ordering, remaining)
416                        .and_then(|bound| bound.checked_sub(1)),
417                },
418                SymExprKind::Ite(condition, left, right) => {
419                    let left_bound =
420                        self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining);
421                    let right_bound =
422                        self.expr_upper_bound_usize_cached(right, bounds, ordering, remaining);
423                    match (left_bound, right_bound) {
424                        (Some(left_bound), Some(right_bound)) => Some(left_bound.max(right_bound)),
425                        (None, Some(right_bound)) => condition
426                            .implies_unsigned_less_or_equal(true, left, right, remaining)
427                            .then_some(right_bound),
428                        (Some(left_bound), None) => condition
429                            .implies_unsigned_less_or_equal(false, right, left, remaining)
430                            .then_some(left_bound),
431                        (None, None) => None,
432                    }
433                }
434                SymExprKind::BinOp(op, left, right) => match op {
435                    SymBinOp::Add => self
436                        .expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?
437                        .checked_add(
438                            self.expr_upper_bound_usize_cached(right, bounds, ordering, remaining)?,
439                        ),
440                    SymBinOp::Mul => self
441                        .expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?
442                        .checked_mul(
443                            self.expr_upper_bound_usize_cached(right, bounds, ordering, remaining)?,
444                        ),
445                    SymBinOp::UDiv => {
446                        let left =
447                            self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
448                        match right.as_const()? {
449                            divisor if divisor.is_zero() => Some(0),
450                            divisor => Some(left / usize::try_from(divisor).ok()?),
451                        }
452                    }
453                    SymBinOp::URem => match right.as_const() {
454                        Some(divisor) if divisor.is_zero() => Some(0),
455                        Some(divisor) => usize::try_from(divisor - U256::ONE).ok(),
456                        None => {
457                            self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)
458                        }
459                    },
460                    SymBinOp::And => right
461                        .as_const()
462                        .and_then(|value| usize::try_from(value).ok())
463                        .or_else(|| left.as_const().and_then(|value| usize::try_from(value).ok()))
464                        .map(|mask| {
465                            self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)
466                                .or_else(|| {
467                                    self.expr_upper_bound_usize_cached(
468                                        right, bounds, ordering, remaining,
469                                    )
470                                })
471                                .map_or(mask, |bound| bound.min(mask))
472                        }),
473                    SymBinOp::Shr => {
474                        let left =
475                            self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
476                        let shift = usize::try_from(right.as_const()?).ok()?;
477                        Some(if shift >= usize::BITS as usize { 0 } else { left >> shift })
478                    }
479                    SymBinOp::Sub => {
480                        if let Some(difference) = left
481                            .constant_difference(right)
482                            .and_then(|difference| usize::try_from(difference).ok())
483                        {
484                            return Some(difference);
485                        }
486                        let left_bound =
487                            self.expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
488                        self.expressions_are_unsigned_ordered(right, left, ordering, remaining)
489                            .then_some(left_bound)
490                    }
491                    SymBinOp::Shl => match right.as_const() {
492                        Some(shift) if shift >= U256::from(256) => Some(0),
493                        Some(shift) => usize::try_from(shift).ok().and_then(|shift| {
494                            let bound = self
495                                .expr_upper_bound_usize_cached(left, bounds, ordering, remaining)?;
496                            if bound == 0 {
497                                return Some(0);
498                            }
499                            let factor = 1usize.checked_shl(u32::try_from(shift).ok()?)?;
500                            bound.checked_mul(factor)
501                        }),
502                        None => None,
503                    },
504                    SymBinOp::SDiv
505                    | SymBinOp::SRem
506                    | SymBinOp::Or
507                    | SymBinOp::Xor
508                    | SymBinOp::Sar => None,
509                },
510            }
511        });
512
513        let bound = match (constraint_bound, structural_bound) {
514            (Some(left), Some(right)) => Some(left.min(right)),
515            (Some(bound), None) | (None, Some(bound)) => Some(bound),
516            (None, None) => None,
517        };
518        bounds.insert(expr.clone(), bound);
519        bound
520    }
521
522    fn expressions_are_unsigned_ordered(
523        &self,
524        left: &SymExpr,
525        right: &SymExpr,
526        ordering: &mut HashMap<(SymExpr, SymExpr), bool>,
527        remaining: &mut usize,
528    ) -> bool {
529        if left == right {
530            return true;
531        }
532        let key = (left.clone(), right.clone());
533        if let Some(ordered) = ordering.get(&key) {
534            return *ordered;
535        }
536        let Some(next) = remaining.checked_sub(1) else { return false };
537        *remaining = next;
538
539        let ordered = if let (Some(left), Some(right)) = (left.as_const(), right.as_const()) {
540            left <= right
541        } else if let SymExprKind::Ite(condition, then_value, else_value) = left.kind() {
542            let then_ordered = condition
543                .implies_unsigned_less_or_equal(true, then_value, right, remaining)
544                || self.expressions_are_unsigned_ordered(then_value, right, ordering, remaining);
545            let else_ordered = condition
546                .implies_unsigned_less_or_equal(false, else_value, right, remaining)
547                || self.expressions_are_unsigned_ordered(else_value, right, ordering, remaining);
548            then_ordered && else_ordered
549        } else if let SymExprKind::Ite(condition, then_value, else_value) = right.kind() {
550            let then_ordered = condition
551                .implies_unsigned_less_or_equal(true, left, then_value, remaining)
552                || self.expressions_are_unsigned_ordered(left, then_value, ordering, remaining);
553            let else_ordered = condition
554                .implies_unsigned_less_or_equal(false, left, else_value, remaining)
555                || self.expressions_are_unsigned_ordered(left, else_value, ordering, remaining);
556            then_ordered && else_ordered
557        } else {
558            false
559        };
560        ordering.insert(key, ordered);
561        ordered
562    }
563
564    fn expr_lower_bound_usize(
565        &self,
566        expr: &SymExpr,
567        lower_bounds: &mut HashMap<SymExpr, usize>,
568        upper_bounds: &mut HashMap<SymExpr, Option<usize>>,
569        ordering: &mut HashMap<(SymExpr, SymExpr), bool>,
570        remaining: &mut usize,
571    ) -> usize {
572        if let Some(bound) = lower_bounds.get(expr) {
573            return *bound;
574        }
575        if let Some(value) = expr.as_const().and_then(|value| usize::try_from(value).ok()) {
576            return value;
577        }
578        let Some(next) = remaining.checked_sub(1) else {
579            return 0;
580        };
581        *remaining = next;
582
583        let bound = match expr.kind() {
584            SymExprKind::Const(value) => usize::try_from(*value).unwrap_or_default(),
585            SymExprKind::Ite(_, left, right) => {
586                let left = self.expr_lower_bound_usize(
587                    left,
588                    lower_bounds,
589                    upper_bounds,
590                    ordering,
591                    remaining,
592                );
593                let right = self.expr_lower_bound_usize(
594                    right,
595                    lower_bounds,
596                    upper_bounds,
597                    ordering,
598                    remaining,
599                );
600                left.min(right)
601            }
602            SymExprKind::BinOp(SymBinOp::Add, left, right) => {
603                let no_wrap = self
604                    .expr_upper_bound_usize_cached(left, upper_bounds, ordering, remaining)
605                    .and_then(|left| {
606                        self.expr_upper_bound_usize_cached(right, upper_bounds, ordering, remaining)
607                            .and_then(|right| left.checked_add(right))
608                    })
609                    .is_some();
610                if no_wrap {
611                    let left = self.expr_lower_bound_usize(
612                        left,
613                        lower_bounds,
614                        upper_bounds,
615                        ordering,
616                        remaining,
617                    );
618                    let right = self.expr_lower_bound_usize(
619                        right,
620                        lower_bounds,
621                        upper_bounds,
622                        ordering,
623                        remaining,
624                    );
625                    left.checked_add(right).unwrap_or_default()
626                } else {
627                    0
628                }
629            }
630            SymExprKind::BinOp(SymBinOp::Sub, left, right) => left
631                .constant_difference(right)
632                .and_then(|difference| usize::try_from(difference).ok())
633                .unwrap_or_default(),
634            SymExprKind::BinOp(SymBinOp::Or, left, right) => {
635                let left = self.expr_lower_bound_usize(
636                    left,
637                    lower_bounds,
638                    upper_bounds,
639                    ordering,
640                    remaining,
641                );
642                let right = self.expr_lower_bound_usize(
643                    right,
644                    lower_bounds,
645                    upper_bounds,
646                    ordering,
647                    remaining,
648                );
649                left.max(right)
650            }
651            SymExprKind::Var(_)
652            | SymExprKind::GasLeft(_)
653            | SymExprKind::Keccak { .. }
654            | SymExprKind::Hash { .. }
655            | SymExprKind::Not(_)
656            | SymExprKind::BinOp(_, _, _)
657            | SymExprKind::TernOp(_, _, _, _) => 0,
658        };
659        lower_bounds.insert(expr.clone(), bound);
660        bound
661    }
662
663    pub(crate) fn constraint_upper_bound_usize(&self, expr: &SymExpr) -> Option<usize> {
664        let mut bound: Option<usize> = None;
665        for constraint in &self.constraints {
666            if let Some(candidate) = constraint.upper_bound_usize(expr) {
667                bound = Some(bound.map_or(candidate, |bound| bound.min(candidate)));
668            }
669        }
670        bound
671    }
672
673    pub(crate) fn expect_constrained_usize(
674        &self,
675        cx: &mut SymCx,
676        expr: SymExpr,
677        reason: &'static str,
678    ) -> Result<usize, SymbolicError> {
679        self.constrained_usize(cx, &expr).ok_or(SymbolicError::Unsupported(reason))
680    }
681
682    pub(crate) fn expect_constrained_word(
683        &self,
684        cx: &mut SymCx,
685        expr: SymExpr,
686        reason: &'static str,
687    ) -> Result<U256, SymbolicError> {
688        self.constrained_word(cx, &expr).ok_or(SymbolicError::Unsupported(reason))
689    }
690
691    pub(crate) fn bin_word(&mut self, cx: &mut SymCx, op: SymBinOp) -> Result<(), SymbolicError> {
692        let a = self.stack.pop()?;
693        let b = self.stack.pop()?;
694        self.stack.push(SymExpr::binop(cx, op, a, b))
695    }
696
697    pub(crate) fn bin_word_div_zero_guard(
698        &mut self,
699        cx: &mut SymCx,
700        op: SymBinOp,
701    ) -> Result<(), SymbolicError> {
702        let a = self.stack.pop()?;
703        let b = self.stack.pop()?;
704        let zero = SymExpr::zero(cx);
705        let condition = SymBoolExpr::eq(cx, b.clone(), zero.clone());
706        let expr = SymExpr::binop(cx, op, a, b);
707        self.stack.push(SymExpr::ite(cx, condition, zero, expr))
708    }
709
710    pub(crate) fn cmp_word_condition(
711        &mut self,
712        cx: &mut SymCx,
713        op: SymCmpOp,
714    ) -> Result<SymBoolExpr, SymbolicError> {
715        let a = self.stack.pop()?;
716        let b = self.stack.pop()?;
717        Ok(SymBoolExpr::cmp(cx, op, a, b))
718    }
719
720    pub(crate) fn shift_word(&mut self, cx: &mut SymCx, op: SymBinOp) -> Result<(), SymbolicError> {
721        let shift = self.stack.pop()?;
722        let value = self.stack.pop()?;
723        let expr = SymExpr::binop(cx, op, value, shift);
724        let result = expr.known_word().map(|word| SymExpr::constant(cx, word)).unwrap_or(expr);
725        self.stack.push(result)
726    }
727
728    pub(crate) fn exp_word(&mut self, cx: &mut SymCx) -> Result<(), SymbolicError> {
729        let base = self.stack.pop()?;
730        let exponent = self.stack.pop()?;
731        let result = if let Some(exponent) = self.constrained_word(cx, &exponent) {
732            if let Some(base_value) = base.as_const() {
733                SymExpr::constant(cx, base_value.wrapping_pow(exponent))
734            } else if exponent <= U256::from(SYMBOLIC_EXP_CONCRETE_EXPONENT_LIMIT) {
735                exp_expr_for_concrete_exponent(
736                    cx,
737                    base,
738                    usize::try_from(exponent).expect("checked symbolic exponent"),
739                )
740            } else {
741                return Err(SymbolicError::Unsupported("symbolic EXP base"));
742            }
743        } else {
744            let exponent_limit = if base.as_const().is_some() {
745                CONCRETE_BASE_SYMBOLIC_EXPONENT_LIMIT
746            } else {
747                SYMBOLIC_EXP_CONCRETE_EXPONENT_LIMIT
748            };
749            let max_exponent = self
750                .upper_bound_usize(cx, &exponent)
751                .filter(|exponent| *exponent <= exponent_limit as usize)
752                .ok_or(SymbolicError::Unsupported("symbolic EXP exponent"))?;
753            let mut expr = SymExpr::zero(cx);
754            for candidate in (0..=max_exponent).rev() {
755                let candidate_expr = SymExpr::constant(cx, U256::from(candidate));
756                let condition = SymBoolExpr::eq(cx, exponent.clone(), candidate_expr);
757                let value = exp_expr_for_concrete_exponent(cx, base.clone(), candidate);
758                expr = SymExpr::ite(cx, condition, value, expr);
759            }
760            expr
761        };
762        self.stack.push(result)
763    }
764
765    pub(crate) fn balance<FEN: FoundryEvmNetwork>(
766        &self,
767        cx: &mut SymCx,
768        executor: &Executor<FEN>,
769        address: Address,
770    ) -> SymExpr {
771        self.world.balance_word_for_address(cx, executor, address)
772    }
773
774    pub(crate) fn balance_word<FEN: FoundryEvmNetwork>(
775        &mut self,
776        cx: &mut SymCx,
777        executor: &Executor<FEN>,
778        address_expr: SymExpr,
779    ) -> Result<SymExpr, SymbolicError> {
780        self.world.balance_word(cx, executor, address_expr)
781    }
782
783    pub(crate) fn extcode_size_word<FEN: FoundryEvmNetwork>(
784        &mut self,
785        cx: &mut SymCx,
786        executor: &Executor<FEN>,
787        address_expr: SymExpr,
788    ) -> Result<SymExpr, SymbolicError> {
789        self.world.extcode_size_word(cx, executor, address_expr)
790    }
791
792    pub(crate) fn extcode_hash_word<FEN: FoundryEvmNetwork>(
793        &mut self,
794        cx: &mut SymCx,
795        executor: &Executor<FEN>,
796        address_expr: SymExpr,
797    ) -> Result<SymExpr, SymbolicError> {
798        self.world.extcode_hash_word(cx, executor, address_expr)
799    }
800
801    pub(crate) fn extcode_bytes_word<FEN: FoundryEvmNetwork>(
802        &mut self,
803        cx: &mut SymCx,
804        executor: &Executor<FEN>,
805        address_expr: SymExpr,
806        offset: SymExpr,
807        size: usize,
808    ) -> Result<SymBytes, SymbolicError> {
809        self.world.extcode_bytes_word(cx, executor, address_expr, offset, size)
810    }
811
812    pub(crate) fn pop_address_word_or_symbolic_slot(
813        &mut self,
814        cx: &mut SymCx,
815    ) -> Result<(SymExpr, Address), SymbolicError> {
816        let expr = self.stack.pop()?;
817        let address = self.address_or_symbolic_slot(cx, expr.clone());
818        Ok((expr, address))
819    }
820
821    pub(crate) fn address_or_symbolic_slot(&mut self, cx: &mut SymCx, expr: SymExpr) -> Address {
822        if let Some(value) = self.constrained_word(cx, &expr) {
823            return word_to_address(value);
824        }
825        self.world.resolve_address(&expr).unwrap_or_else(|| self.world.symbolic_address_slot(expr))
826    }
827
828    pub(crate) fn fresh_word(&mut self, cx: &mut SymCx, prefix: &'static str) -> SymExpr {
829        let id = self.next_symbol;
830        self.next_symbol += 1;
831        SymExpr::var(cx, &format!("{prefix}_{id}"))
832    }
833
834    pub(crate) fn fresh_gasleft(&mut self, cx: &mut SymCx) -> SymExpr {
835        let id = self.next_symbol;
836        self.next_symbol += 1;
837        SymExpr::gas_left(cx, id)
838    }
839
840    pub(crate) fn fresh_bounded_uint(&mut self, cx: &mut SymCx, bits: U256) -> SymExpr {
841        let value = self.fresh_word(cx, "symbolic");
842        if bits < U256::from(256) {
843            let upper = if bits.is_zero() {
844                U256::ZERO
845            } else {
846                U256::ONE << usize::try_from(bits).expect("checked bit width")
847            };
848            self.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &value, upper));
849        }
850        value
851    }
852
853    pub(crate) fn fresh_bytes(&mut self, cx: &mut SymCx, len: usize) -> Vec<SymExpr> {
854        (0..len).map(|_| self.fresh_bounded_uint(cx, U256::from(8))).collect()
855    }
856
857    pub(crate) fn fresh_printable_ascii_bytes(
858        &mut self,
859        cx: &mut SymCx,
860        len: usize,
861    ) -> Vec<SymExpr> {
862        (0..len)
863            .map(|_| {
864                let byte = self.fresh_bounded_uint(cx, U256::from(8));
865                self.constraints.push(SymBoolExpr::cmp_word_const(
866                    cx,
867                    SymCmpOp::Uge,
868                    &byte,
869                    U256::from(0x20),
870                ));
871                self.constraints.push(SymBoolExpr::cmp_word_const(
872                    cx,
873                    SymCmpOp::Ule,
874                    &byte,
875                    U256::from(0x7e),
876                ));
877                byte
878            })
879            .collect()
880    }
881
882    pub(crate) fn fresh_bounded_int(&mut self, cx: &mut SymCx, bits: U256) -> SymExpr {
883        let value = self.fresh_word(cx, "symbolic");
884        if bits.is_zero() {
885            self.constraints.push(SymBoolExpr::eq_word_const(cx, &value, U256::ZERO));
886        } else if bits < U256::from(256) {
887            let magnitude = U256::ONE << (usize::try_from(bits).expect("checked bit width") - 1);
888            let lt = SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &value, magnitude);
889            let ge = SymBoolExpr::cmp_word_const(
890                cx,
891                SymCmpOp::Uge,
892                &value,
893                U256::ZERO.wrapping_sub(magnitude),
894            );
895            let condition = SymBoolExpr::or(cx, vec![lt, ge]);
896            self.constraints.push(condition);
897        }
898        value
899    }
900
901    pub(crate) fn prank_for_next_call(&mut self) -> (Address, SymExpr, Option<(Address, SymExpr)>) {
902        if let Some((caller, caller_word)) = self.prank.next_caller.take() {
903            (caller, caller_word, self.prank.next_origin.take())
904        } else {
905            match self.prank.persistent_caller.clone() {
906                Some((caller, caller_word)) => {
907                    (caller, caller_word, self.prank.persistent_origin.clone())
908                }
909                None => {
910                    (self.address, self.address_word.clone(), self.prank.persistent_origin.clone())
911                }
912            }
913        }
914    }
915
916    pub(crate) fn read_callers_words(&self, cx: &mut SymCx) -> Vec<SymExpr> {
917        let (mode, caller, origin) = if let Some((_, caller_word)) = self.prank.next_caller.as_ref()
918        {
919            (
920                U256::from(3),
921                caller_word.clone(),
922                self.prank
923                    .next_origin
924                    .as_ref()
925                    .map(|(_, origin_word)| origin_word.clone())
926                    .unwrap_or_else(|| self.origin_word.clone()),
927            )
928        } else if let Some((_, caller_word)) = self.prank.persistent_caller.as_ref() {
929            (
930                U256::from(4),
931                caller_word.clone(),
932                self.prank
933                    .persistent_origin
934                    .as_ref()
935                    .map(|(_, origin_word)| origin_word.clone())
936                    .unwrap_or_else(|| self.origin_word.clone()),
937            )
938        } else {
939            (U256::ZERO, self.caller_word.clone(), self.origin_word.clone())
940        };
941        vec![SymExpr::constant(cx, mode), caller, origin]
942    }
943
944    pub(crate) fn record_log(&mut self, log: SymbolicLog) {
945        if let Some(logs) = &mut self.recorded_logs {
946            logs.push(log);
947        }
948    }
949
950    pub(crate) fn record_sload(&mut self, address: Address, slot: SymExpr) {
951        if let Some(record) = &mut self.access_record {
952            record.read(address, slot);
953        }
954    }
955
956    pub(crate) fn record_sstore(&mut self, address: Address, slot: SymExpr) {
957        if let Some(record) = &mut self.access_record {
958            record.write(address, slot);
959        }
960    }
961
962    pub(crate) fn expectations_satisfied(&self) -> bool {
963        self.expected_revert.is_none()
964            && self.expected_emit.as_ref().is_none_or(ExpectedEmit::is_satisfied)
965            && self.expected_calls.iter().all(ExpectedCall::is_satisfied)
966            && self.expected_creates.is_empty()
967    }
968}
969
970#[derive(Clone, Copy, Debug, PartialEq, Eq)]
971pub(crate) struct SymbolicStorageHook {
972    pub(crate) callback_target: Address,
973    pub(crate) callback_selector: [u8; 4],
974}
975
976#[derive(Clone, Debug, PartialEq, Eq)]
977pub(crate) struct SymbolicLog {
978    topics: Arc<[SymExpr]>,
979    data_len: SymExpr,
980    data: SymBytes,
981    emitter: Address,
982}
983
984impl SymbolicLog {
985    pub(crate) fn new(
986        topics: Vec<SymExpr>,
987        data_len: SymExpr,
988        data: SymBytes,
989        emitter: Address,
990    ) -> Self {
991        Self { topics: topics.into(), data_len, data, emitter }
992    }
993
994    pub(crate) fn into_parts(self) -> (Arc<[SymExpr]>, SymExpr, SymBytes, Address) {
995        (self.topics, self.data_len, self.data, self.emitter)
996    }
997}
998
999#[derive(Clone, Debug, Default, PartialEq, Eq)]
1000pub(crate) struct AccessRecord {
1001    reads: HashMap<Address, Vec<SymExpr>>,
1002    writes: HashMap<Address, Vec<SymExpr>>,
1003}
1004
1005impl AccessRecord {
1006    pub(crate) fn read(&mut self, address: Address, slot: SymExpr) {
1007        Self::push_unique_slot(self.reads.entry(address).or_default(), slot);
1008    }
1009
1010    pub(crate) fn write(&mut self, address: Address, slot: SymExpr) {
1011        self.read(address, slot.clone());
1012        Self::push_unique_slot(self.writes.entry(address).or_default(), slot);
1013    }
1014
1015    pub(crate) fn addresses(&self) -> Vec<Address> {
1016        let mut addresses = HashSet::<Address>::default();
1017        addresses.extend(self.reads.keys().copied());
1018        addresses.extend(self.writes.keys().copied());
1019        let mut addresses = addresses.into_iter().collect::<Vec<_>>();
1020        addresses.sort_unstable();
1021        addresses
1022    }
1023
1024    pub(crate) fn read_slots(&self, address: Address) -> Vec<SymExpr> {
1025        self.reads.get(&address).cloned().unwrap_or_default()
1026    }
1027
1028    pub(crate) fn write_slots(&self, address: Address) -> Vec<SymExpr> {
1029        self.writes.get(&address).cloned().unwrap_or_default()
1030    }
1031
1032    fn push_unique_slot(slots: &mut Vec<SymExpr>, slot: SymExpr) {
1033        if !slots.iter().any(|existing| existing == &slot) {
1034            slots.push(slot);
1035        }
1036    }
1037}
1038
1039#[derive(Clone, Debug, PartialEq, Eq)]
1040pub(crate) struct ExpectedRevert {
1041    data: ExpectedRevertData,
1042    reverter: Option<SymExpr>,
1043    remaining: u64,
1044}
1045
1046impl ExpectedRevert {
1047    pub(crate) fn new(data: ExpectedRevertData, reverter: Option<SymExpr>, remaining: u64) -> Self {
1048        Self { data, reverter, remaining: remaining.max(1) }
1049    }
1050
1051    pub(crate) const fn consume_one(&mut self) -> bool {
1052        self.remaining = self.remaining.saturating_sub(1);
1053        self.remaining == 0
1054    }
1055
1056    pub(crate) fn match_condition(
1057        &self,
1058        cx: &mut SymCx,
1059        reverter: Address,
1060        return_data: &SymReturnData,
1061    ) -> Option<SymBoolExpr> {
1062        let mut conditions = Vec::new();
1063        if let Some(expected_reverter) = &self.reverter {
1064            conditions.push(expected_reverter.address_match_condition(cx, reverter));
1065        }
1066        match &self.data {
1067            ExpectedRevertData::Any => {}
1068            ExpectedRevertData::Prefix(prefix) => {
1069                if return_data.len() < prefix.len() {
1070                    return None;
1071                }
1072                let prefix_len = SymExpr::constant(cx, U256::from(prefix.len()));
1073                conditions.push(SymBoolExpr::cmp_word_expr(
1074                    cx,
1075                    SymCmpOp::Uge,
1076                    &return_data.len_word,
1077                    prefix_len,
1078                ));
1079                conditions.extend((0..prefix.len()).map(|offset| {
1080                    let expected = prefix.byte(cx, offset);
1081                    let actual = return_data.byte(cx, offset);
1082                    SymBoolExpr::eq(cx, actual, expected)
1083                }));
1084            }
1085            ExpectedRevertData::Exact(data) => {
1086                if return_data.len() < data.len() {
1087                    return None;
1088                }
1089                let len = SymExpr::constant(cx, U256::from(data.len()));
1090                conditions.push(SymBoolExpr::eq(cx, return_data.len_word.clone(), len));
1091                conditions.extend((0..data.len()).map(|offset| {
1092                    let expected = data.byte(cx, offset);
1093                    let actual = return_data.byte(cx, offset);
1094                    SymBoolExpr::eq(cx, actual, expected)
1095                }));
1096            }
1097        }
1098        Some(SymBoolExpr::and(cx, conditions))
1099    }
1100}
1101
1102#[derive(Clone, Debug, PartialEq, Eq)]
1103pub(crate) enum ExpectedRevertData {
1104    Any,
1105    Prefix(SymBytes),
1106    Exact(SymBytes),
1107}
1108
1109#[derive(Clone, Debug, PartialEq, Eq)]
1110pub(crate) enum AssumeNoRevert {
1111    Any,
1112    Filtered(Vec<ExpectedRevert>),
1113}
1114
1115#[derive(Clone, Debug, PartialEq, Eq)]
1116pub(crate) struct ExpectedCall {
1117    callee: SymExpr,
1118    value: Option<U256>,
1119    gas: Option<u64>,
1120    min_gas: Option<u64>,
1121    data: SymBytes,
1122    expected: u64,
1123    observed: u64,
1124    exact: bool,
1125}
1126
1127#[derive(Clone, Debug, PartialEq, Eq)]
1128pub(crate) struct ExpectedCreate {
1129    bytecode: Vec<u8>,
1130    deployer: SymExpr,
1131    kind: CreateKind,
1132}
1133
1134impl ExpectedCreate {
1135    pub(crate) const fn new(bytecode: Vec<u8>, deployer: SymExpr, kind: CreateKind) -> Self {
1136        Self { bytecode, deployer, kind }
1137    }
1138
1139    pub(crate) fn match_condition(
1140        &self,
1141        cx: &mut SymCx,
1142        deployer: Address,
1143        kind: CreateKind,
1144        bytecode: &[u8],
1145    ) -> Option<SymBoolExpr> {
1146        (self.kind == kind && self.bytecode == bytecode)
1147            .then(|| self.deployer.address_match_condition(cx, deployer))
1148    }
1149}
1150
1151impl ExpectedCall {
1152    pub(crate) fn new(
1153        callee: SymExpr,
1154        value: Option<U256>,
1155        gas: Option<u64>,
1156        min_gas: Option<u64>,
1157        data: SymBytes,
1158        count: Option<u64>,
1159    ) -> Self {
1160        let (gas, min_gas) = if value.is_some_and(|value| !value.is_zero()) {
1161            (
1162                gas.map(|gas| gas.saturating_add(CALL_STIPEND)),
1163                min_gas.map(|gas| gas.saturating_add(CALL_STIPEND)),
1164            )
1165        } else {
1166            (gas, min_gas)
1167        };
1168        Self {
1169            callee,
1170            value,
1171            gas,
1172            min_gas,
1173            data,
1174            expected: count.unwrap_or(1),
1175            observed: 0,
1176            exact: count.is_some(),
1177        }
1178    }
1179
1180    pub(crate) const fn value(&self) -> Option<U256> {
1181        self.value
1182    }
1183
1184    pub(crate) fn match_condition(
1185        &self,
1186        cx: &mut SymCx,
1187        callee: Address,
1188        value: Option<U256>,
1189        gas: &SymExpr,
1190        calldata: &SymBytes,
1191    ) -> Result<Option<SymBoolExpr>, SymbolicError> {
1192        if !self.value.is_none_or(|expected| value.is_some_and(|value| expected == value))
1193            || !self.gas_matches(gas, value)?
1194        {
1195            return Ok(None);
1196        }
1197        let Some(data_condition) = calldata.prefix_condition(cx, &self.data) else {
1198            return Ok(None);
1199        };
1200        let callee_condition = self.callee.address_match_condition(cx, callee);
1201        Ok(Some(SymBoolExpr::and(cx, vec![callee_condition, data_condition])))
1202    }
1203
1204    fn gas_matches(&self, gas: &SymExpr, value: Option<U256>) -> Result<bool, SymbolicError> {
1205        if self.gas.is_none() && self.min_gas.is_none() {
1206            return Ok(true);
1207        }
1208        let mut gas = gas.as_const_or("symbolic expected call gas")?;
1209        if value.is_some_and(|value| !value.is_zero()) {
1210            gas = gas.saturating_add(U256::from(CALL_STIPEND));
1211        }
1212        Ok(self.gas.is_none_or(|expected| gas == U256::from(expected))
1213            && self.min_gas.is_none_or(|expected| gas >= U256::from(expected)))
1214    }
1215
1216    pub(crate) const fn observe(&mut self) -> bool {
1217        if self.exact && self.observed >= self.expected {
1218            return false;
1219        }
1220        self.observed = self.observed.saturating_add(1);
1221        true
1222    }
1223
1224    pub(crate) const fn is_satisfied(&self) -> bool {
1225        if self.exact { self.observed == self.expected } else { self.observed >= self.expected }
1226    }
1227}
1228
1229/// Registers an expected call using the concrete cheatcode's keyed-additive semantics.
1230pub(crate) fn register_expected_call(
1231    expected_calls: &mut Vec<ExpectedCall>,
1232    cx: &mut SymCx,
1233    expected: ExpectedCall,
1234) -> Result<(), &'static str> {
1235    if let Some(existing) = expected_calls
1236        .iter_mut()
1237        .find(|call| call.callee == expected.callee && call.data.same_bytes(cx, &expected.data))
1238    {
1239        if expected.exact {
1240            return Err("counted expected calls can only bet set once");
1241        }
1242        if existing.exact {
1243            return Err("cannot overwrite a counted expectCall with a non-counted expectCall");
1244        }
1245        existing.expected += 1;
1246    } else {
1247        expected_calls.push(expected);
1248    }
1249    Ok(())
1250}
1251
1252#[derive(Clone, Debug)]
1253pub(crate) struct CallMock {
1254    pub(crate) callee: SymExpr,
1255    value: Option<U256>,
1256    pub(crate) data: SymBytes,
1257    returns: Vec<SymReturnData>,
1258    reverts: bool,
1259    calls: usize,
1260}
1261
1262impl CallMock {
1263    pub(crate) const fn new(
1264        callee: SymExpr,
1265        value: Option<U256>,
1266        data: SymBytes,
1267        returns: Vec<SymReturnData>,
1268        reverts: bool,
1269    ) -> Self {
1270        Self { callee, value, data, returns, reverts, calls: 0 }
1271    }
1272
1273    pub(crate) const fn value(&self) -> Option<U256> {
1274        self.value
1275    }
1276
1277    pub(crate) fn specificity(&self) -> (usize, bool) {
1278        (self.data.len(), self.value.is_some())
1279    }
1280
1281    pub(crate) fn match_condition(
1282        &self,
1283        cx: &mut SymCx,
1284        callee: Address,
1285        value: Option<U256>,
1286        calldata: &SymBytes,
1287    ) -> Option<SymBoolExpr> {
1288        if !self.value.is_none_or(|expected| value.is_some_and(|value| expected == value)) {
1289            return None;
1290        }
1291        let data_condition = calldata.prefix_condition(cx, &self.data)?;
1292        let callee_condition = self.callee.address_match_condition(cx, callee);
1293        Some(SymBoolExpr::and(cx, vec![callee_condition, data_condition]))
1294    }
1295
1296    pub(crate) fn next_outcome(&mut self, cx: &mut SymCx) -> CallMockOutcome {
1297        let idx = self.calls.min(self.returns.len().saturating_sub(1));
1298        self.calls = self.calls.saturating_add(1);
1299        CallMockOutcome {
1300            return_data: self.returns.get(idx).cloned().unwrap_or_else(|| SymReturnData::empty(cx)),
1301            reverts: self.reverts,
1302        }
1303    }
1304}
1305
1306#[derive(Clone, Debug)]
1307pub(crate) struct CallMockOutcome {
1308    return_data: SymReturnData,
1309    reverts: bool,
1310}
1311
1312impl CallMockOutcome {
1313    pub(crate) fn into_parts(self) -> (SymReturnData, bool) {
1314        (self.return_data, self.reverts)
1315    }
1316}
1317
1318#[derive(Clone, Debug, PartialEq, Eq)]
1319pub(crate) struct FunctionMock {
1320    callee: SymExpr,
1321    target: Address,
1322    data: SymBytes,
1323}
1324
1325impl FunctionMock {
1326    pub(crate) const fn new(callee: SymExpr, target: Address, data: SymBytes) -> Self {
1327        Self { callee, target, data }
1328    }
1329
1330    pub(crate) fn matches_definition(
1331        &self,
1332        cx: &mut SymCx,
1333        callee: &SymExpr,
1334        data: &SymBytes,
1335    ) -> bool {
1336        self.callee == *callee && self.data.same_bytes(cx, data)
1337    }
1338
1339    pub(crate) const fn set_target(&mut self, target: Address) {
1340        self.target = target;
1341    }
1342
1343    pub(crate) fn calldata_len(&self) -> usize {
1344        self.data.len()
1345    }
1346
1347    pub(crate) const fn target(&self) -> Address {
1348        self.target
1349    }
1350
1351    pub(crate) fn match_condition(
1352        &self,
1353        cx: &mut SymCx,
1354        callee: Address,
1355        calldata: &SymBytes,
1356    ) -> Option<SymBoolExpr> {
1357        let data_condition = calldata.prefix_condition(cx, &self.data)?;
1358        let callee_condition = self.callee.address_match_condition(cx, callee);
1359        Some(SymBoolExpr::and(cx, vec![callee_condition, data_condition]))
1360    }
1361}
1362
1363#[derive(Clone, Debug, PartialEq, Eq)]
1364pub(crate) struct ExpectedEmit {
1365    checks: ExpectedEmitChecks,
1366    emitter: Option<SymExpr>,
1367    remaining: u64,
1368    template: Option<SymbolicLog>,
1369}
1370
1371impl ExpectedEmit {
1372    pub(crate) fn new(
1373        checks: ExpectedEmitChecks,
1374        emitter: Option<SymExpr>,
1375        remaining: u64,
1376    ) -> Self {
1377        Self { checks, emitter, remaining: remaining.max(1), template: None }
1378    }
1379
1380    pub(crate) const fn is_satisfied(&self) -> bool {
1381        self.template.is_none() && self.remaining == 0
1382    }
1383
1384    pub(crate) const fn template(&self) -> Option<&SymbolicLog> {
1385        self.template.as_ref()
1386    }
1387
1388    pub(crate) fn set_template(&mut self, log: SymbolicLog) {
1389        self.template = Some(log);
1390    }
1391
1392    pub(crate) fn consume_one(&mut self) -> bool {
1393        self.remaining = self.remaining.saturating_sub(1);
1394        if self.remaining == 0 {
1395            self.template = None;
1396            true
1397        } else {
1398            false
1399        }
1400    }
1401
1402    pub(crate) fn match_condition(
1403        &self,
1404        cx: &mut SymCx,
1405        template: &SymbolicLog,
1406        actual: &SymbolicLog,
1407    ) -> Option<SymBoolExpr> {
1408        let mut conditions = Vec::new();
1409        if let Some(expected_emitter) = &self.emitter {
1410            conditions.push(expected_emitter.address_match_condition(cx, actual.emitter));
1411        }
1412        for (idx, &check_topic) in self.checks.topics.iter().enumerate() {
1413            if !check_topic {
1414                continue;
1415            }
1416            match (template.topics.get(idx), actual.topics.get(idx)) {
1417                (Some(left), Some(right)) => {
1418                    conditions.push(SymBoolExpr::eq(cx, left.clone(), right.clone()));
1419                }
1420                (None, None) => {}
1421                _ => return None,
1422            }
1423        }
1424
1425        if self.checks.data {
1426            conditions.push(SymBoolExpr::eq(
1427                cx,
1428                template.data_len.clone(),
1429                actual.data_len.clone(),
1430            ));
1431            if template.data.len() != actual.data.len() {
1432                return None;
1433            }
1434            conditions.extend((0..template.data.len()).map(|idx| {
1435                let template = template.data.byte(cx, idx);
1436                let actual = actual.data.byte(cx, idx);
1437                SymBoolExpr::eq(cx, template, actual)
1438            }));
1439        }
1440
1441        Some(SymBoolExpr::and(cx, conditions))
1442    }
1443}
1444
1445#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1446pub(crate) struct ExpectedEmitChecks {
1447    topics: [bool; 4],
1448    data: bool,
1449}
1450
1451impl Default for ExpectedEmitChecks {
1452    fn default() -> Self {
1453        Self { topics: [true; 4], data: true }
1454    }
1455}
1456
1457impl ExpectedEmitChecks {
1458    pub(crate) fn from_non_anonymous_args(
1459        cx: &mut SymCx,
1460        memory: &SymMemory,
1461        args_offset: usize,
1462    ) -> Result<Self, SymbolicError> {
1463        Ok(Self {
1464            topics: [
1465                true,
1466                read_abi_bool_arg(cx, memory, args_offset, 0, "symbolic vm.expectEmit")?,
1467                read_abi_bool_arg(cx, memory, args_offset, 1, "symbolic vm.expectEmit")?,
1468                read_abi_bool_arg(cx, memory, args_offset, 2, "symbolic vm.expectEmit")?,
1469            ],
1470            data: read_abi_bool_arg(cx, memory, args_offset, 3, "symbolic vm.expectEmit")?,
1471        })
1472    }
1473
1474    pub(crate) fn from_anonymous_args(
1475        cx: &mut SymCx,
1476        memory: &SymMemory,
1477        args_offset: usize,
1478    ) -> Result<Self, SymbolicError> {
1479        Ok(Self {
1480            topics: [
1481                read_abi_bool_arg(cx, memory, args_offset, 0, "symbolic vm.expectEmitAnonymous")?,
1482                read_abi_bool_arg(cx, memory, args_offset, 1, "symbolic vm.expectEmitAnonymous")?,
1483                read_abi_bool_arg(cx, memory, args_offset, 2, "symbolic vm.expectEmitAnonymous")?,
1484                read_abi_bool_arg(cx, memory, args_offset, 3, "symbolic vm.expectEmitAnonymous")?,
1485            ],
1486            data: read_abi_bool_arg(cx, memory, args_offset, 4, "symbolic vm.expectEmitAnonymous")?,
1487        })
1488    }
1489}
1490
1491impl Deref for PathState {
1492    type Target = CallFrame;
1493
1494    fn deref(&self) -> &Self::Target {
1495        &self.frame
1496    }
1497}
1498
1499impl DerefMut for PathState {
1500    fn deref_mut(&mut self) -> &mut Self::Target {
1501        &mut self.frame
1502    }
1503}
1504
1505#[derive(Clone, Debug)]
1506pub(crate) struct CallFrame {
1507    pub(crate) pc: usize,
1508    pub(crate) address: Address,
1509    pub(crate) address_word: SymExpr,
1510    pub(crate) storage_address: Address,
1511    pub(crate) caller: Address,
1512    pub(crate) caller_word: SymExpr,
1513    pub(crate) callvalue: SymExpr,
1514    pub(crate) is_static: bool,
1515    pub(crate) calldata: SymCalldata,
1516    pub(crate) stack: SymStack,
1517    pub(crate) memory: SymMemory,
1518    pub(crate) return_data: SymReturnData,
1519}
1520
1521impl CallFrame {
1522    pub(crate) fn new(
1523        cx: &mut SymCx,
1524        address: Address,
1525        storage_address: Address,
1526        caller: Address,
1527        callvalue: SymExpr,
1528        is_static: bool,
1529        calldata: SymCalldata,
1530    ) -> Self {
1531        Self {
1532            pc: 0,
1533            address,
1534            address_word: SymExpr::constant(cx, address_word(address)),
1535            storage_address,
1536            caller,
1537            caller_word: SymExpr::constant(cx, address_word(caller)),
1538            callvalue,
1539            is_static,
1540            calldata,
1541            stack: SymStack::default(),
1542            memory: SymMemory::default(),
1543            return_data: SymReturnData::empty(cx),
1544        }
1545    }
1546}
1547
1548#[derive(Clone, Debug, Default, PartialEq, Eq)]
1549pub(crate) struct SymbolicPrank {
1550    next_caller: Option<(Address, SymExpr)>,
1551    next_origin: Option<(Address, SymExpr)>,
1552    persistent_caller: Option<(Address, SymExpr)>,
1553    persistent_origin: Option<(Address, SymExpr)>,
1554}
1555
1556impl SymbolicPrank {
1557    pub(crate) fn set_next(
1558        &mut self,
1559        caller: (Address, SymExpr),
1560        origin: Option<(Address, SymExpr)>,
1561    ) {
1562        self.next_caller = Some(caller);
1563        self.next_origin = origin;
1564    }
1565
1566    pub(crate) fn set_persistent(
1567        &mut self,
1568        caller: (Address, SymExpr),
1569        origin: Option<(Address, SymExpr)>,
1570    ) {
1571        self.persistent_caller = Some(caller);
1572        self.persistent_origin = origin;
1573    }
1574
1575    pub(crate) const fn has_active(&self) -> bool {
1576        self.next_caller.is_some()
1577            || self.next_origin.is_some()
1578            || self.persistent_caller.is_some()
1579            || self.persistent_origin.is_some()
1580    }
1581}
1582
1583#[derive(Clone, Debug, PartialEq, Eq)]
1584pub(crate) struct StorageWrite {
1585    address: Address,
1586    key: SymExpr,
1587    value: SymExpr,
1588}
1589
1590impl StorageWrite {
1591    pub(crate) const fn new(address: Address, key: SymExpr, value: SymExpr) -> Self {
1592        Self { address, key, value }
1593    }
1594
1595    pub(crate) fn select_from(
1596        cx: &mut SymCx,
1597        writes: &[Self],
1598        address: Address,
1599        key: SymExpr,
1600        base: SymExpr,
1601    ) -> SymExpr {
1602        let mut value = base;
1603        for write in writes.iter().filter(|write| write.address == address) {
1604            value =
1605                key.clone().select_storage_write(cx, write.key.clone(), write.value.clone(), value);
1606        }
1607        value
1608    }
1609}
1610
1611/// World data shared copy-on-write by branched paths and snapshots.
1612#[derive(Clone, Debug, Default)]
1613pub(crate) struct SymbolicWorldState {
1614    storage: Vec<StorageWrite>,
1615    transient_storage: Vec<StorageWrite>,
1616    created_accounts: HashSet<Address>,
1617    current_transaction_created_accounts: HashSet<Address>,
1618    balances: HashMap<Address, SymExpr>,
1619    code_cache: HashMap<Address, SymCode>,
1620    nonces: HashMap<Address, u64>,
1621    existing_accounts: HashSet<Address>,
1622    destroyed_accounts: HashSet<Address>,
1623    arbitrary_storage_accounts: HashMap<Address, bool>,
1624    arbitrary_storage_copies: HashMap<Address, Address>,
1625    arbitrary_storage_all: bool,
1626    zero_init_symbolic_storage: bool,
1627    symbolic_address_aliases: HashMap<SymExpr, Address>,
1628    replay_storage_slots: HashMap<Symbol, Vec<SymbolicReplayStorageSlot>>,
1629}
1630
1631#[derive(Clone, Debug)]
1632struct SymbolicReplayStorageSlot {
1633    address: Address,
1634    slot: U256,
1635}
1636
1637#[derive(Clone, Debug, Default)]
1638pub(crate) struct SymbolicWorld {
1639    state: Arc<SymbolicWorldState>,
1640    snapshots: HashMap<U256, Arc<SymbolicWorldState>>,
1641    next_snapshot_id: u64,
1642}
1643
1644impl Deref for SymbolicWorld {
1645    type Target = SymbolicWorldState;
1646
1647    fn deref(&self) -> &Self::Target {
1648        &self.state
1649    }
1650}
1651
1652impl SymbolicWorld {
1653    pub(crate) fn is_destroyed(&self, address: Address) -> bool {
1654        self.destroyed_accounts.contains(&address)
1655    }
1656
1657    pub(crate) fn set_storage_layout(&mut self, layout: SymbolicStorageLayout) {
1658        let state = Arc::make_mut(&mut self.state);
1659        state.arbitrary_storage_all = matches!(layout, SymbolicStorageLayout::Generic);
1660        state.zero_init_symbolic_storage = matches!(layout, SymbolicStorageLayout::ZeroInit);
1661    }
1662
1663    pub(crate) fn sload<FEN: FoundryEvmNetwork>(
1664        &mut self,
1665        cx: &mut SymCx,
1666        executor: &Executor<FEN>,
1667        address: Address,
1668        key: SymExpr,
1669        concrete_key: Option<U256>,
1670    ) -> Result<SymExpr, SymbolicError> {
1671        let base = self.storage_base(cx, executor, address, &key, concrete_key)?;
1672        let read_key = concrete_key.map(|key| SymExpr::constant(cx, key)).unwrap_or(key);
1673        Ok(StorageWrite::select_from(cx, &self.storage, address, read_key, base))
1674    }
1675
1676    pub(crate) fn sstore(&mut self, address: Address, key: SymExpr, value: SymExpr) {
1677        Arc::make_mut(&mut self.state).storage.push(StorageWrite::new(address, key, value));
1678    }
1679
1680    pub(crate) fn tload(&self, cx: &mut SymCx, address: Address, key: SymExpr) -> SymExpr {
1681        let base = SymExpr::zero(cx);
1682        StorageWrite::select_from(cx, &self.transient_storage, address, key, base)
1683    }
1684
1685    pub(crate) fn tstore(&mut self, address: Address, key: SymExpr, value: SymExpr) {
1686        Arc::make_mut(&mut self.state)
1687            .transient_storage
1688            .push(StorageWrite::new(address, key, value));
1689    }
1690
1691    /// Clears transaction-scoped state at a top-level call boundary.
1692    pub(crate) fn clear_transaction_scoped_state(&mut self) {
1693        let state = Arc::make_mut(&mut self.state);
1694        state.transient_storage.clear();
1695        state.current_transaction_created_accounts.clear();
1696    }
1697
1698    pub(crate) fn mark_current_transaction_created(&mut self, address: Address) {
1699        let state = Arc::make_mut(&mut self.state);
1700        state.created_accounts.insert(address);
1701        state.current_transaction_created_accounts.insert(address);
1702    }
1703
1704    /// Returns whether `address` was created in the current top-level symbolic transaction.
1705    pub(crate) fn was_created_in_current_transaction(&self, address: Address) -> bool {
1706        self.current_transaction_created_accounts.contains(&address)
1707    }
1708
1709    pub(crate) fn enable_arbitrary_storage(&mut self, address: Address, overwrite: bool) {
1710        Arc::make_mut(&mut self.state).arbitrary_storage_accounts.insert(address, overwrite);
1711    }
1712
1713    pub(crate) fn replay_storage_symbols(&self) -> SymbolicVars {
1714        self.replay_storage_slots.keys().copied().collect()
1715    }
1716
1717    pub(crate) fn replay_storage_assignments(
1718        &self,
1719        model: &SymbolicModel,
1720    ) -> Result<Vec<SymbolicStorageAssignment>, SymbolicError> {
1721        let mut assignments = std::collections::BTreeMap::<(Address, U256), U256>::new();
1722        for (symbol, slots) in &self.replay_storage_slots {
1723            let Some(value) = model.get(symbol).copied() else { continue };
1724            for slot in slots {
1725                match assignments.entry((slot.address, slot.slot)) {
1726                    std::collections::btree_map::Entry::Vacant(entry) => {
1727                        entry.insert(value);
1728                    }
1729                    std::collections::btree_map::Entry::Occupied(entry)
1730                        if *entry.get() == value => {}
1731                    std::collections::btree_map::Entry::Occupied(_) => {
1732                        return Err(SymbolicError::Solver(
1733                            "conflicting symbolic storage replay assignments".to_string(),
1734                        ));
1735                    }
1736                }
1737            }
1738        }
1739        Ok(assignments
1740            .into_iter()
1741            .map(|((address, slot), value)| SymbolicStorageAssignment { address, slot, value })
1742            .collect())
1743    }
1744
1745    pub(crate) fn resolve_address(&self, expr: &SymExpr) -> Option<Address> {
1746        expr.as_const().map(word_to_address).or_else(|| {
1747            self.symbolic_address_aliases.get(expr).copied().or_else(|| {
1748                self.symbolic_address_aliases.iter().find_map(|(alias, address)| {
1749                    expr.symbolic_address_equivalent(alias).then_some(*address)
1750                })
1751            })
1752        })
1753    }
1754
1755    pub(crate) fn symbolic_address_slot(&mut self, expr: SymExpr) -> Address {
1756        if let Some(address) = self.resolve_address(&expr) {
1757            return address;
1758        }
1759        let address = expr.representative_symbolic_address();
1760        Arc::make_mut(&mut self.state).symbolic_address_aliases.insert(expr, address);
1761        address
1762    }
1763
1764    pub(crate) fn symbolic_word_for_address(&self, address: Address) -> Option<SymExpr> {
1765        self.symbolic_address_aliases
1766            .iter()
1767            .find_map(|(word, slot)| (*slot == address).then(|| word.clone()))
1768    }
1769
1770    pub(crate) fn snapshot_state(&mut self) -> U256 {
1771        let id = U256::from(self.next_snapshot_id);
1772        self.next_snapshot_id = self.next_snapshot_id.saturating_add(1);
1773        self.snapshots.insert(id, Arc::clone(&self.state));
1774        id
1775    }
1776
1777    pub(crate) fn restore_snapshot(&mut self, id: U256) -> bool {
1778        let Some(snapshot) = self.snapshots.get(&id) else {
1779            return false;
1780        };
1781        self.state = Arc::clone(snapshot);
1782        true
1783    }
1784
1785    pub(crate) fn delete_snapshot(&mut self, id: U256) -> bool {
1786        self.snapshots.remove(&id).is_some()
1787    }
1788
1789    pub(crate) fn delete_snapshots(&mut self) {
1790        self.snapshots.clear();
1791    }
1792
1793    pub(crate) fn storage_base<FEN: FoundryEvmNetwork>(
1794        &mut self,
1795        cx: &mut SymCx,
1796        executor: &Executor<FEN>,
1797        address: Address,
1798        key: &SymExpr,
1799        concrete_key: Option<U256>,
1800    ) -> Result<SymExpr, SymbolicError> {
1801        if let Some(base) = self.arbitrary_storage_base(cx, executor, address, key, concrete_key)? {
1802            return Ok(base);
1803        }
1804        if self.created_accounts.contains(&address) {
1805            return Ok(SymExpr::zero(cx));
1806        }
1807        if let Some(key) = concrete_key {
1808            return executor
1809                .backend()
1810                .storage_ref(address, key)
1811                .map(|value| SymExpr::constant(cx, value))
1812                .map_err(|err| SymbolicError::Backend(err.to_string()));
1813        }
1814        if let Some(key) = key.as_const() {
1815            executor
1816                .backend()
1817                .storage_ref(address, key)
1818                .map(|value| SymExpr::constant(cx, value))
1819                .map_err(|err| SymbolicError::Backend(err.to_string()))
1820        } else if self.zero_init_symbolic_storage {
1821            Ok(SymExpr::zero(cx))
1822        } else {
1823            let name = symbolic_storage_symbol(cx, address, key);
1824            Ok(SymExpr::get_var(cx, name))
1825        }
1826    }
1827
1828    fn arbitrary_storage_base<FEN: FoundryEvmNetwork>(
1829        &mut self,
1830        cx: &mut SymCx,
1831        executor: &Executor<FEN>,
1832        address: Address,
1833        key: &SymExpr,
1834        concrete_key: Option<U256>,
1835    ) -> Result<Option<SymExpr>, SymbolicError> {
1836        if let Some(slot) = concrete_key.or_else(|| key.as_const())
1837            && !self.arbitrary_storage_all
1838        {
1839            let overwrite_arbitrary_storage =
1840                self.arbitrary_storage_accounts.get(&address).copied();
1841            let has_arbitrary_storage = overwrite_arbitrary_storage.is_some();
1842            let is_copied_storage =
1843                !has_arbitrary_storage && self.arbitrary_storage_copies.contains_key(&address);
1844            let preserve_nonzero_slot =
1845                overwrite_arbitrary_storage == Some(false) || is_copied_storage;
1846            if preserve_nonzero_slot {
1847                let concrete = executor
1848                    .backend()
1849                    .storage_ref(address, slot)
1850                    .map_err(|err| SymbolicError::Backend(err.to_string()))?;
1851                if !concrete.is_zero() {
1852                    return Ok(Some(SymExpr::constant(cx, concrete)));
1853                }
1854            }
1855        }
1856
1857        Ok(self.unchecked_arbitrary_storage_base(cx, address, key, concrete_key))
1858    }
1859
1860    fn unchecked_arbitrary_storage_base(
1861        &mut self,
1862        cx: &mut SymCx,
1863        address: Address,
1864        key: &SymExpr,
1865        concrete_key: Option<U256>,
1866    ) -> Option<SymExpr> {
1867        let overwrite_arbitrary_storage = self.arbitrary_storage_accounts.get(&address).copied();
1868        let has_arbitrary_storage = overwrite_arbitrary_storage.is_some();
1869        let copied_source = (!has_arbitrary_storage)
1870            .then(|| self.arbitrary_storage_copies.get(&address).copied())
1871            .flatten();
1872        let symbol_address = if self.arbitrary_storage_all || has_arbitrary_storage {
1873            address
1874        } else {
1875            copied_source?
1876        };
1877        let symbol = symbolic_storage_symbol(cx, symbol_address, key);
1878        if let Some(slot) = concrete_key.or_else(|| key.as_const()) {
1879            if has_arbitrary_storage {
1880                self.record_replay_storage_slot(symbol, address, slot);
1881            }
1882            if let Some(source) = copied_source {
1883                self.record_replay_storage_slot(symbol, source, slot);
1884                self.record_replay_storage_slot(symbol, address, slot);
1885            }
1886        }
1887        let value = SymExpr::get_var(cx, symbol);
1888        if let Some(source) = copied_source {
1889            self.sstore(source, key.clone(), value.clone());
1890        }
1891        Some(value)
1892    }
1893
1894    fn record_replay_storage_slot(&mut self, symbol: Symbol, address: Address, slot: U256) {
1895        if self.replay_storage_slots.get(&symbol).is_some_and(|slots| {
1896            slots.iter().any(|existing| existing.address == address && existing.slot == slot)
1897        }) {
1898            return;
1899        }
1900        Arc::make_mut(&mut self.state)
1901            .replay_storage_slots
1902            .entry(symbol)
1903            .or_default()
1904            .push(SymbolicReplayStorageSlot { address, slot });
1905    }
1906
1907    fn merge_replay_metadata_from(&mut self, other: &Self) {
1908        for (symbol, slots) in &other.replay_storage_slots {
1909            for slot in slots {
1910                self.record_replay_storage_slot(*symbol, slot.address, slot.slot);
1911            }
1912        }
1913        let state = Arc::make_mut(&mut self.state);
1914        for (expr, address) in &other.symbolic_address_aliases {
1915            state.symbolic_address_aliases.entry(expr.clone()).or_insert(*address);
1916        }
1917    }
1918
1919    pub(crate) fn backend_balance<FEN: FoundryEvmNetwork>(
1920        &self,
1921        executor: &Executor<FEN>,
1922        address: Address,
1923    ) -> U256 {
1924        executor
1925            .backend()
1926            .basic_ref(address)
1927            .ok()
1928            .flatten()
1929            .map(|account| account.balance)
1930            .unwrap_or_default()
1931    }
1932
1933    pub(crate) fn balance_word_for_address<FEN: FoundryEvmNetwork>(
1934        &self,
1935        cx: &mut SymCx,
1936        executor: &Executor<FEN>,
1937        address: Address,
1938    ) -> SymExpr {
1939        if self.destroyed_accounts.contains(&address) {
1940            return SymExpr::zero(cx);
1941        }
1942        self.balances
1943            .get(&address)
1944            .cloned()
1945            .unwrap_or_else(|| SymExpr::constant(cx, self.backend_balance(executor, address)))
1946    }
1947
1948    pub(crate) fn balance_word<FEN: FoundryEvmNetwork>(
1949        &mut self,
1950        cx: &mut SymCx,
1951        executor: &Executor<FEN>,
1952        address_expr: SymExpr,
1953    ) -> Result<SymExpr, SymbolicError> {
1954        if let Some(address) = self.resolve_address(&address_expr) {
1955            return Ok(self.balance_word_for_address(cx, executor, address));
1956        }
1957
1958        let expr = address_expr;
1959        let representative = expr.representative_symbolic_address();
1960        let mut result = self.balance_word_for_address(cx, executor, representative);
1961        for (address, balance) in &self.balances {
1962            if self.destroyed_accounts.contains(address) {
1963                continue;
1964            }
1965            let address = SymExpr::constant(cx, address_word(*address));
1966            let condition = SymBoolExpr::eq(cx, expr.clone(), address);
1967            result = SymExpr::ite(cx, condition, balance.clone(), result);
1968        }
1969
1970        Ok(result)
1971    }
1972
1973    pub(crate) fn set_balance_word(&mut self, address: Address, value: SymExpr) {
1974        let account_exists = !value.as_const().is_some_and(|value| value.is_zero());
1975        let state = Arc::make_mut(&mut self.state);
1976        state.balances.insert(address, value);
1977        if account_exists {
1978            state.existing_accounts.insert(address);
1979            state.destroyed_accounts.remove(&address);
1980        }
1981    }
1982
1983    pub(crate) fn transfer<FEN: FoundryEvmNetwork>(
1984        &mut self,
1985        cx: &mut SymCx,
1986        executor: &Executor<FEN>,
1987        from: Address,
1988        to: Address,
1989        value: SymExpr,
1990    ) {
1991        if from == to || value.as_const().is_some_and(|value| value.is_zero()) {
1992            return;
1993        }
1994        let from_balance = self.balance_word_for_address(cx, executor, from);
1995        let to_balance = self.balance_word_for_address(cx, executor, to);
1996        let from_balance = SymExpr::binop(cx, SymBinOp::Sub, from_balance, value.clone());
1997        let to_balance = SymExpr::binop(cx, SymBinOp::Add, to_balance, value);
1998        self.set_balance_word(from, from_balance);
1999        self.set_balance_word(to, to_balance);
2000    }
2001
2002    pub(crate) fn nonce<FEN: FoundryEvmNetwork>(
2003        &self,
2004        executor: &Executor<FEN>,
2005        address: Address,
2006    ) -> Result<u64, SymbolicError> {
2007        if self.destroyed_accounts.contains(&address) {
2008            return Ok(self.nonces.get(&address).copied().unwrap_or_default());
2009        }
2010        if let Some(nonce) = self.nonces.get(&address) {
2011            return Ok(*nonce);
2012        }
2013        executor
2014            .backend()
2015            .basic_ref(address)
2016            .map_err(|err| SymbolicError::Backend(err.to_string()))
2017            .map(|account| account.map(|account| account.nonce).unwrap_or_default())
2018    }
2019
2020    pub(crate) fn set_nonce(&mut self, address: Address, nonce: u64) {
2021        let state = Arc::make_mut(&mut self.state);
2022        state.nonces.insert(address, nonce);
2023        if nonce != 0 {
2024            state.existing_accounts.insert(address);
2025            state.destroyed_accounts.remove(&address);
2026        }
2027    }
2028
2029    pub(crate) fn increment_nonce<FEN: FoundryEvmNetwork>(
2030        &mut self,
2031        executor: &Executor<FEN>,
2032        address: Address,
2033    ) -> Result<(), SymbolicError> {
2034        let nonce = self.nonce(executor, address)?;
2035        self.set_nonce(address, nonce.saturating_add(1));
2036        Ok(())
2037    }
2038
2039    pub(crate) fn has_code_or_nonce<FEN: FoundryEvmNetwork>(
2040        &mut self,
2041        cx: &mut SymCx,
2042        executor: &Executor<FEN>,
2043        address: Address,
2044    ) -> Result<bool, SymbolicError> {
2045        if self.destroyed_accounts.contains(&address) {
2046            return Ok(false);
2047        }
2048        Ok(!self.extcode(cx, executor, address)?.is_empty() || self.nonce(executor, address)? != 0)
2049    }
2050
2051    pub(crate) fn install_code(&mut self, address: Address, code: SymCode) {
2052        let state = Arc::make_mut(&mut self.state);
2053        state.code_cache.insert(address, code);
2054        state.existing_accounts.insert(address);
2055        state.destroyed_accounts.remove(&address);
2056    }
2057
2058    /// Implements legacy `SELFDESTRUCT` semantics.
2059    pub(crate) fn selfdestruct_legacy<FEN: FoundryEvmNetwork>(
2060        &mut self,
2061        cx: &mut SymCx,
2062        executor: &Executor<FEN>,
2063        address: Address,
2064        beneficiary: Address,
2065    ) -> Result<(), SymbolicError> {
2066        let balance = self.balance_word_for_address(cx, executor, address);
2067        if beneficiary != address && !balance.as_const().is_some_and(|value| value.is_zero()) {
2068            let beneficiary_balance = self.balance_word_for_address(cx, executor, beneficiary);
2069            let beneficiary_balance =
2070                SymExpr::binop(cx, SymBinOp::Add, beneficiary_balance, balance);
2071            self.set_balance_word(beneficiary, beneficiary_balance);
2072        }
2073        let nonce = if self.nonces.contains_key(&address) {
2074            None
2075        } else {
2076            Some(self.nonce(executor, address)?)
2077        };
2078        let zero = SymExpr::zero(cx);
2079        let empty_code = SymCode::empty(cx);
2080        let state = Arc::make_mut(&mut self.state);
2081        state.balances.insert(address, zero);
2082        state.code_cache.insert(address, empty_code);
2083        if let Some(nonce) = nonce {
2084            state.nonces.insert(address, nonce);
2085        }
2086        state.storage.retain(|write| write.address != address);
2087        state.transient_storage.retain(|write| write.address != address);
2088        state.created_accounts.remove(&address);
2089        state.current_transaction_created_accounts.remove(&address);
2090        state.existing_accounts.remove(&address);
2091        state.destroyed_accounts.insert(address);
2092        Ok(())
2093    }
2094
2095    /// Implements Cancun+ `SELFDESTRUCT` semantics for accounts not created in the current tx.
2096    pub(crate) fn selfdestruct_cancun_existing<FEN: FoundryEvmNetwork>(
2097        &mut self,
2098        cx: &mut SymCx,
2099        executor: &Executor<FEN>,
2100        address: Address,
2101        beneficiary: Address,
2102    ) {
2103        let balance = self.balance_word_for_address(cx, executor, address);
2104        if beneficiary != address && !balance.as_const().is_some_and(|value| value.is_zero()) {
2105            let beneficiary_balance = self.balance_word_for_address(cx, executor, beneficiary);
2106            // Symbolic balances are treated as possibly non-zero, matching transfer's
2107            // account-existence approximation.
2108            let beneficiary_balance =
2109                SymExpr::binop(cx, SymBinOp::Add, beneficiary_balance, balance);
2110            self.set_balance_word(beneficiary, beneficiary_balance);
2111            let zero = SymExpr::zero(cx);
2112            Arc::make_mut(&mut self.state).balances.insert(address, zero);
2113        }
2114    }
2115
2116    pub(crate) fn account_exists<FEN: FoundryEvmNetwork>(
2117        &mut self,
2118        cx: &mut SymCx,
2119        executor: &Executor<FEN>,
2120        address: Address,
2121    ) -> Result<bool, SymbolicError> {
2122        if address == CHEATCODE_ADDRESS || address == SYMBOLIC_VM_COMPAT_ADDRESS {
2123            return Ok(true);
2124        }
2125        if self.destroyed_accounts.contains(&address) {
2126            return Ok(false);
2127        }
2128        if self.existing_accounts.contains(&address) {
2129            return Ok(true);
2130        }
2131        if self
2132            .balances
2133            .get(&address)
2134            .is_some_and(|balance| !balance.as_const().is_some_and(|value| value.is_zero()))
2135            || self.nonces.get(&address).is_some_and(|nonce| *nonce != 0)
2136            || self.code_cache.get(&address).is_some_and(|code| !code.is_empty())
2137        {
2138            Arc::make_mut(&mut self.state).existing_accounts.insert(address);
2139            return Ok(true);
2140        }
2141
2142        let Some(account) = executor
2143            .backend()
2144            .basic_ref(address)
2145            .map_err(|err| SymbolicError::Backend(err.to_string()))?
2146        else {
2147            return Ok(false);
2148        };
2149
2150        if account.nonce != 0 || !account.balance.is_zero() {
2151            Arc::make_mut(&mut self.state).existing_accounts.insert(address);
2152            return Ok(true);
2153        }
2154
2155        if let Some(code) = account.code.as_ref()
2156            && !code.is_empty()
2157        {
2158            let code = SymCode::from_bytecode(cx, code);
2159            let state = Arc::make_mut(&mut self.state);
2160            state.code_cache.insert(address, code);
2161            state.existing_accounts.insert(address);
2162            return Ok(true);
2163        }
2164
2165        Ok(false)
2166    }
2167
2168    pub(crate) fn extcode<FEN: FoundryEvmNetwork>(
2169        &mut self,
2170        cx: &mut SymCx,
2171        executor: &Executor<FEN>,
2172        address: Address,
2173    ) -> Result<SymCode, SymbolicError> {
2174        if address == CHEATCODE_ADDRESS || address == SYMBOLIC_VM_COMPAT_ADDRESS {
2175            return Ok(SymCode::concrete(cx, vec![0]));
2176        }
2177        let spec_id: SpecId = executor.spec_id().into();
2178        if precompile_number_for_spec(address, spec_id).is_some() {
2179            return Ok(SymCode::empty(cx));
2180        }
2181        if self.destroyed_accounts.contains(&address) {
2182            return Ok(SymCode::empty(cx));
2183        }
2184        if let Some(code) = self.code_cache.get(&address) {
2185            return Ok(code.clone());
2186        }
2187        let account = executor
2188            .backend()
2189            .basic_ref(address)
2190            .map_err(|err| SymbolicError::Backend(err.to_string()))?;
2191        if let Some(account) = account.as_ref()
2192            && (account.nonce != 0
2193                || !account.balance.is_zero()
2194                || account.code.as_ref().is_some_and(|code| !code.is_empty()))
2195        {
2196            Arc::make_mut(&mut self.state).existing_accounts.insert(address);
2197        }
2198        let bytecode = account.as_ref().and_then(|account| account.code.as_ref());
2199        let code = bytecode
2200            .map(|bytecode| SymCode::from_bytecode(cx, bytecode))
2201            .unwrap_or_else(|| SymCode::empty(cx));
2202        Arc::make_mut(&mut self.state).code_cache.insert(address, code.clone());
2203        Ok(code)
2204    }
2205
2206    pub(crate) fn extcode_hash_for_address<FEN: FoundryEvmNetwork>(
2207        &mut self,
2208        cx: &mut SymCx,
2209        executor: &Executor<FEN>,
2210        address: Address,
2211    ) -> Result<SymExpr, SymbolicError> {
2212        if self.account_exists(cx, executor, address)? {
2213            let code = self.extcode(cx, executor, address)?;
2214            let bytes = code.read_byte_exprs(cx, 0, code.len());
2215            Ok(keccak_word(cx, bytes))
2216        } else {
2217            Ok(SymExpr::zero(cx))
2218        }
2219    }
2220
2221    pub(crate) fn extcode_size_word<FEN: FoundryEvmNetwork>(
2222        &mut self,
2223        cx: &mut SymCx,
2224        executor: &Executor<FEN>,
2225        address_expr: SymExpr,
2226    ) -> Result<SymExpr, SymbolicError> {
2227        if let Some(address) = self.resolve_address(&address_expr) {
2228            let len = self.extcode(cx, executor, address)?.len();
2229            return Ok(SymExpr::constant(cx, U256::from(len)));
2230        }
2231
2232        let expr = address_expr;
2233        let representative = expr.representative_symbolic_address();
2234        let len = self.extcode(cx, executor, representative)?.len();
2235        let mut result = SymExpr::constant(cx, U256::from(len));
2236        for (address, code) in &self.code_cache {
2237            if self.destroyed_accounts.contains(address) {
2238                continue;
2239            }
2240            let address = SymExpr::constant(cx, address_word(*address));
2241            let condition = SymBoolExpr::eq(cx, expr.clone(), address);
2242            let len = SymExpr::constant(cx, U256::from(code.len()));
2243            result = SymExpr::ite(cx, condition, len, result);
2244        }
2245
2246        Ok(result)
2247    }
2248
2249    pub(crate) fn extcode_hash_word<FEN: FoundryEvmNetwork>(
2250        &mut self,
2251        cx: &mut SymCx,
2252        executor: &Executor<FEN>,
2253        address_expr: SymExpr,
2254    ) -> Result<SymExpr, SymbolicError> {
2255        if let Some(address) = self.resolve_address(&address_expr) {
2256            return self.extcode_hash_for_address(cx, executor, address);
2257        }
2258
2259        let expr = address_expr;
2260        let representative = expr.representative_symbolic_address();
2261        let mut result = self.extcode_hash_for_address(cx, executor, representative)?;
2262        let cached_codes = self.code_cache.iter().collect::<Vec<_>>();
2263        for (address, code) in cached_codes.into_iter().rev() {
2264            let hash = if self.destroyed_accounts.contains(address) {
2265                SymExpr::zero(cx)
2266            } else {
2267                let bytes = code.read_byte_exprs(cx, 0, code.len());
2268                keccak_word(cx, bytes)
2269            };
2270            let address = SymExpr::constant(cx, address_word(*address));
2271            let condition = SymBoolExpr::eq(cx, expr.clone(), address);
2272            result = SymExpr::ite(cx, condition, hash, result);
2273        }
2274
2275        Ok(result)
2276    }
2277
2278    pub(crate) fn extcode_bytes_word<FEN: FoundryEvmNetwork>(
2279        &mut self,
2280        cx: &mut SymCx,
2281        executor: &Executor<FEN>,
2282        address_expr: SymExpr,
2283        offset: SymExpr,
2284        size: usize,
2285    ) -> Result<SymBytes, SymbolicError> {
2286        if let Some(address) = self.resolve_address(&address_expr) {
2287            return Ok(self.extcode(cx, executor, address)?.read_bytes_offset(cx, offset, size));
2288        }
2289
2290        let expr = address_expr;
2291        let representative = expr.representative_symbolic_address();
2292        let mut result = self.extcode(cx, executor, representative)?.read_byte_exprs_offset(
2293            cx,
2294            offset.clone(),
2295            size,
2296        );
2297        let cached_codes = self.code_cache.iter().collect::<Vec<_>>();
2298        for (address, code) in cached_codes.into_iter().rev() {
2299            let bytes = if self.destroyed_accounts.contains(address) {
2300                vec![SymExpr::zero(cx); size]
2301            } else {
2302                code.read_byte_exprs_offset(cx, offset.clone(), size)
2303            };
2304            let address = SymExpr::constant(cx, address_word(*address));
2305            let condition = SymBoolExpr::eq(cx, expr.clone(), address);
2306            for (idx, byte) in bytes.into_iter().enumerate() {
2307                result[idx] = SymExpr::ite(cx, condition.clone(), byte, result[idx].clone());
2308            }
2309        }
2310
2311        Ok(SymBytes::exprs(cx, result))
2312    }
2313
2314    pub(crate) fn symbolic_call_targets<FEN: FoundryEvmNetwork>(
2315        &mut self,
2316        cx: &mut SymCx,
2317        executor: &Executor<FEN>,
2318    ) -> Result<Vec<Address>, SymbolicError> {
2319        let mut addresses = HashSet::<Address>::default();
2320        addresses.extend(self.code_cache.keys().copied());
2321        addresses.extend(self.existing_accounts.iter().copied());
2322        addresses.extend(executor.backend().mem_db().cache.accounts.keys().copied());
2323        if let Some(db) = executor.backend().active_fork_db() {
2324            addresses.extend(db.cache.accounts.keys().copied());
2325        }
2326        let mut addresses = addresses.into_iter().collect::<Vec<_>>();
2327        addresses.sort_unstable();
2328
2329        let mut targets = Vec::new();
2330        let spec_id: SpecId = executor.spec_id().into();
2331        for address in addresses {
2332            if address == CHEATCODE_ADDRESS
2333                || address == SYMBOLIC_VM_COMPAT_ADDRESS
2334                || precompile_number_for_spec(address, spec_id).is_some()
2335            {
2336                continue;
2337            }
2338            if !self.extcode(cx, executor, address)?.is_empty() {
2339                targets.push(address);
2340            }
2341        }
2342        Ok(targets)
2343    }
2344}
2345
2346fn symbolic_storage_symbol(cx: &mut SymCx, address: Address, key: &SymExpr) -> Symbol {
2347    stable_symbol(
2348        cx,
2349        "storage",
2350        format!("{address:?}:{:?}", ExpressionDigests::identity([key])).as_bytes(),
2351    )
2352}
2353
2354#[derive(Clone, Debug)]
2355pub(crate) struct SymbolicBlock {
2356    pub(crate) chain_id: SymExpr,
2357    pub(crate) coinbase: Address,
2358    pub(crate) timestamp: SymExpr,
2359    pub(crate) number: SymExpr,
2360    pub(crate) difficulty: SymExpr,
2361    pub(crate) gaslimit: SymExpr,
2362    pub(crate) basefee: SymExpr,
2363    pub(crate) blob_basefee: SymExpr,
2364    pub(crate) block_hashes: HashMap<U256, SymExpr>,
2365    pub(crate) blob_hashes: Vec<B256>,
2366}
2367
2368impl SymbolicBlock {
2369    pub(crate) fn new(cx: &mut SymCx) -> Self {
2370        Self {
2371            chain_id: SymExpr::one(cx),
2372            coinbase: Address::ZERO,
2373            timestamp: SymExpr::zero(cx),
2374            number: SymExpr::zero(cx),
2375            difficulty: SymExpr::zero(cx),
2376            gaslimit: SymExpr::zero(cx),
2377            basefee: SymExpr::zero(cx),
2378            blob_basefee: SymExpr::zero(cx),
2379            block_hashes: HashMap::default(),
2380            blob_hashes: Vec::new(),
2381        }
2382    }
2383
2384    pub(crate) fn from_executor<FEN: FoundryEvmNetwork>(
2385        cx: &mut SymCx,
2386        executor: &Executor<FEN>,
2387    ) -> Self {
2388        let evm_env = executor.evm_env();
2389        let block = executor
2390            .inspector()
2391            .cheatcodes
2392            .as_ref()
2393            .and_then(|cheats| cheats.block.as_ref())
2394            .unwrap_or(evm_env.block_env());
2395        let difficulty = block.prevrandao().map(Into::into).unwrap_or_else(|| block.difficulty());
2396
2397        Self {
2398            chain_id: SymExpr::constant(cx, U256::from(evm_env.cfg_env.chain_id)),
2399            coinbase: block.beneficiary(),
2400            timestamp: SymExpr::constant(cx, block.timestamp()),
2401            number: SymExpr::constant(cx, block.number()),
2402            difficulty: SymExpr::constant(cx, difficulty),
2403            gaslimit: SymExpr::constant(cx, U256::from(block.gas_limit())),
2404            basefee: SymExpr::constant(cx, U256::from(block.basefee())),
2405            blob_basefee: SymExpr::constant(
2406                cx,
2407                U256::from(block.blob_gasprice().unwrap_or_default()),
2408            ),
2409            block_hashes: HashMap::default(),
2410            blob_hashes: executor.tx_env().blob_versioned_hashes().to_vec(),
2411        }
2412    }
2413
2414    pub(crate) fn set_block_hash(
2415        &mut self,
2416        block_number: U256,
2417        block_hash: SymExpr,
2418    ) -> Result<(), SymbolicError> {
2419        let current = self.number.as_const_or("symbolic vm.setBlockhash current number")?;
2420        if block_number < current && current - block_number <= U256::from(256) {
2421            self.block_hashes.insert(block_number, block_hash);
2422        }
2423        Ok(())
2424    }
2425
2426    pub(crate) fn block_hash<FEN: FoundryEvmNetwork>(
2427        &self,
2428        cx: &mut SymCx,
2429        executor: &Executor<FEN>,
2430        block_number: U256,
2431    ) -> Result<SymExpr, SymbolicError> {
2432        let current = self.number.as_const_or("symbolic BLOCKHASH current number")?;
2433        if block_number >= current || current - block_number > U256::from(256) {
2434            return Ok(SymExpr::zero(cx));
2435        }
2436        if let Some(hash) = self.block_hashes.get(&block_number) {
2437            return Ok(hash.clone());
2438        }
2439        let Ok(block_number) = u64::try_from(block_number) else {
2440            return Ok(SymExpr::zero(cx));
2441        };
2442        let hash = executor
2443            .backend()
2444            .block_hash_ref(block_number)
2445            .map_err(|err| SymbolicError::Backend(err.to_string()))?;
2446        Ok(SymExpr::constant(cx, U256::from_be_slice(hash.as_slice())))
2447    }
2448
2449    pub(crate) fn block_hash_word<FEN: FoundryEvmNetwork>(
2450        &self,
2451        cx: &mut SymCx,
2452        executor: &Executor<FEN>,
2453        block_number: SymExpr,
2454    ) -> Result<SymExpr, SymbolicError> {
2455        if let Some(block_number) = block_number.as_const() {
2456            return self.block_hash(cx, executor, block_number);
2457        }
2458        let current = self.number.as_const_or("symbolic BLOCKHASH current number")?;
2459        if current.is_zero() {
2460            return Ok(SymExpr::zero(cx));
2461        }
2462
2463        let mut result = SymExpr::zero(cx);
2464        let max_distance =
2465            usize::try_from(current.min(U256::from(256))).expect("checked blockhash distance");
2466        for distance in (1..=max_distance).rev() {
2467            let candidate = current - U256::from(distance);
2468            let hash = self.block_hash(cx, executor, candidate)?;
2469            if hash.as_const().is_some_and(|hash| hash.is_zero()) {
2470                continue;
2471            }
2472            let candidate = SymExpr::constant(cx, candidate);
2473            let condition = SymBoolExpr::eq(cx, block_number.clone(), candidate);
2474            result = SymExpr::ite(cx, condition, hash, result);
2475        }
2476
2477        Ok(result)
2478    }
2479}