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 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 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 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 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 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 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}