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