Skip to main content

foundry_evm_symbolic/runtime/
state.rs

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