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