Skip to main content

foundry_evm_symbolic/runtime/
state.rs

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