Skip to main content

foundry_evm_symbolic/runtime/
state.rs

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