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