Skip to main content

foundry_evm_symbolic/executor/
opcodes.rs

1use super::*;
2
3enum MappingStorageProvenance {
4    None,
5    Exact(SymbolicMappingProvenance),
6    Fork { equality: Vec<SymBoolExpr>, inequality: Vec<SymBoolExpr> },
7}
8
9impl SymbolicExecutor {
10    fn classify_mapping_match(
11        &mut self,
12        state: &PathState,
13        matches: SymBoolExpr,
14        provenance: SymbolicMappingProvenance,
15    ) -> Result<Option<MappingStorageProvenance>, SymbolicError> {
16        let does_not_match = matches.clone().not(&mut self.cx);
17        let (inequality, inequality_is_sat) =
18            self.constraints_with_condition(state, does_not_match)?;
19        if !inequality_is_sat {
20            return Ok(Some(MappingStorageProvenance::Exact(provenance)));
21        }
22        let (equality, equality_is_sat) = self.constraints_with_condition(state, matches)?;
23        Ok(equality_is_sat.then_some(MappingStorageProvenance::Fork { equality, inequality }))
24    }
25
26    fn observed_mapping_chain(
27        &mut self,
28        state: &PathState,
29        hash: &SymExpr,
30    ) -> Option<(SymExpr, Vec<SymExpr>)> {
31        let account = state.storage_address;
32        let mut current = hash.clone();
33        let mut keys = Vec::new();
34        let mut visited = Vec::new();
35        loop {
36            if visited.contains(&current) {
37                return None;
38            }
39            visited.push(current.clone());
40            let Some(bytes) =
41                state.mapping_hook_keccak_preimages.get(&(account, current.clone())).cloned()
42            else {
43                keys.reverse();
44                return Some((current, keys));
45            };
46            if bytes.len() != 64 {
47                return None;
48            }
49            keys.push(SymExpr::from_bytes(&mut self.cx, bytes[..32].iter().cloned()));
50            current = SymExpr::from_bytes(&mut self.cx, bytes[32..64].iter().cloned());
51        }
52    }
53
54    fn mapping_storage_provenance(
55        &mut self,
56        state: &PathState,
57        key: &SymExpr,
58    ) -> Result<MappingStorageProvenance, SymbolicError> {
59        let account = state.storage_address;
60        let observed = |hash: &SymExpr| {
61            state.mapping_hook_keccak_preimages.get(&(account, hash.clone())).cloned()
62        };
63        if let Some(provenance) =
64            key.storage_mapping_provenance_observed_with(&mut self.cx, observed)
65        {
66            return Ok(MappingStorageProvenance::Exact(provenance));
67        }
68        let mut hashes = state
69            .mapping_hook_keccak_preimages
70            .keys()
71            .filter(|(address, _)| *address == account)
72            .map(|(_, hash)| hash.clone())
73            .collect::<Vec<_>>();
74        let contains_observed_hash =
75            hashes.iter().any(|hash| key.visit_bool(|candidate| candidate == hash));
76        let key_is_const = key.as_const().is_some();
77        let key_contains_keccak = key.contains_keccak();
78        let key_is_storage_mapping_key = key.storage_mapping_key(&mut self.cx).is_some();
79        let roots = state
80            .mapping_storage_store_hooks
81            .keys()
82            .filter(|(address, _)| *address == account)
83            .map(|(_, root)| *root)
84            .collect::<Vec<_>>();
85        hashes.sort_by_key(|hash| hash != key);
86        for hash in hashes {
87            if contains_observed_hash && !key.visit_bool(|candidate| candidate == &hash) {
88                continue;
89            }
90            let use_legacy_match = contains_observed_hash || !key_contains_keccak;
91            if use_legacy_match
92                && let Some(provenance) =
93                    hash.storage_mapping_provenance_observed_with(&mut self.cx, |candidate| {
94                        state
95                            .mapping_hook_keccak_preimages
96                            .get(&(account, candidate.clone()))
97                            .cloned()
98                    })
99            {
100                if !state.mapping_storage_store_hooks.contains_key(&(account, provenance.root_slot))
101                {
102                    continue;
103                }
104                let equality = SymBoolExpr::eq(&mut self.cx, key.clone(), hash.clone());
105                if key_is_const {
106                    let inequality = equality.not(&mut self.cx);
107                    let (_, inequality_is_sat) =
108                        self.constraints_with_condition(state, inequality)?;
109                    if !inequality_is_sat {
110                        return Ok(MappingStorageProvenance::Exact(provenance));
111                    }
112                    continue;
113                }
114                if let Some(provenance) =
115                    self.classify_mapping_match(state, equality, provenance)?
116                {
117                    return Ok(provenance);
118                }
119                continue;
120            }
121            if (key_is_const || key_contains_keccak) && !key_is_storage_mapping_key {
122                continue;
123            }
124            let Some((root, keys)) = self.observed_mapping_chain(state, &hash) else {
125                continue;
126            };
127            for &root_slot in &roots {
128                let slot_matches = if key_is_const || key.visit_bool(|candidate| candidate == &hash)
129                {
130                    SymBoolExpr::eq(&mut self.cx, key.clone(), hash.clone())
131                } else {
132                    key.storage_key_eq(&mut self.cx, &hash)
133                };
134                let matches = if let Some(constrained_root) =
135                    state.constrained_word(&mut self.cx, &root)
136                {
137                    if constrained_root != root_slot {
138                        continue;
139                    }
140                    slot_matches
141                } else {
142                    let root_slot_expr = SymExpr::constant(&mut self.cx, root_slot);
143                    let root_matches = SymBoolExpr::eq(&mut self.cx, root.clone(), root_slot_expr);
144                    SymBoolExpr::and(&mut self.cx, vec![slot_matches, root_matches])
145                };
146                let provenance = SymbolicMappingProvenance { root_slot, keys: keys.clone() };
147                if let Some(provenance) = self.classify_mapping_match(state, matches, provenance)? {
148                    return Ok(provenance);
149                }
150            }
151        }
152        Ok(MappingStorageProvenance::None)
153    }
154
155    fn storage_hook_calldata(
156        &mut self,
157        selector: [u8; 4],
158        words: impl IntoIterator<Item = SymExpr>,
159    ) -> SymCalldata {
160        let selector = SymBytes::concrete(&mut self.cx, selector.to_vec());
161        let words = words.into_iter().map(|word| word.into_bytes(&mut self.cx)).collect::<Vec<_>>();
162        let bytes = SymBytes::concat(&mut self.cx, std::iter::once(selector).chain(words));
163        SymCalldata::from_bytes(&mut self.cx, bytes)
164    }
165
166    fn mapping_storage_hook_calldata(
167        &mut self,
168        selector: [u8; 4],
169        [account, computed_slot, root_slot, old_value, new_value]: [SymExpr; 5],
170        keys: Vec<SymExpr>,
171    ) -> SymCalldata {
172        let selector = SymBytes::concrete(&mut self.cx, selector.to_vec());
173        let keys_offset = SymExpr::constant(&mut self.cx, U256::from(6 * 32));
174        let mut words = vec![account, computed_slot, root_slot, keys_offset, old_value, new_value];
175        words.push(SymExpr::constant(&mut self.cx, U256::from(keys.len())));
176        words.extend(keys);
177        let words = words.into_iter().map(|word| word.into_bytes(&mut self.cx)).collect::<Vec<_>>();
178        let bytes = SymBytes::concat(&mut self.cx, std::iter::once(selector).chain(words));
179        SymCalldata::from_bytes(&mut self.cx, bytes)
180    }
181
182    fn invoke_storage_hook<FEN: FoundryEvmNetwork>(
183        &mut self,
184        executor: &Executor<FEN>,
185        state: &mut PathState,
186        worklist: &mut VecDeque<PathState>,
187        completed_paths: &mut usize,
188        hook: SymbolicStorageHook,
189        calldata: SymCalldata,
190    ) -> Result<StepOutcome, SymbolicError> {
191        let code = state.world.extcode(&mut self.cx, executor, hook.callback_target)?;
192        if code.is_empty() {
193            return Ok(StepOutcome::Continue);
194        }
195
196        let callvalue = SymExpr::zero(&mut self.cx);
197        let frame = CallFrame::new(
198            &mut self.cx,
199            hook.callback_target,
200            hook.callback_target,
201            CHEATCODE_ADDRESS,
202            callvalue,
203            false,
204            calldata,
205        );
206        let child = state.storage_hook_child(frame);
207        let outcomes = self.execute_external_call(executor, child, &code, completed_paths)?;
208        if outcomes.is_empty() {
209            return Ok(StepOutcome::AssumeRejected);
210        }
211
212        let mut parents = VecDeque::with_capacity(outcomes.len());
213        for mut outcome in outcomes {
214            let mut parent = state.clone();
215            parent.constraints = std::mem::take(&mut outcome.state.constraints);
216            parent.next_symbol = outcome.state.next_symbol;
217            parent.storage_load_hooks = std::mem::take(&mut outcome.state.storage_load_hooks);
218            parent.storage_store_hooks = std::mem::take(&mut outcome.state.storage_store_hooks);
219            parent.mapping_storage_store_hooks =
220                std::mem::take(&mut outcome.state.mapping_storage_store_hooks);
221            parent.mapping_hook_keccak_preimages =
222                std::mem::take(&mut outcome.state.mapping_hook_keccak_preimages);
223            parent.storage_hook_active = false;
224
225            match outcome.status {
226                CallStatus::Success => {
227                    parent.world = outcome.state.world;
228                    parent.block = outcome.state.block;
229                }
230                CallStatus::Revert | CallStatus::ExceptionalHalt | CallStatus::Failure => {
231                    parent.return_data = outcome.state.frame.return_data;
232                    parent.pending_storage_hook_revert = true;
233                }
234            }
235            parents.push_back(parent);
236        }
237
238        let Some(first) = self.pop_next_path(&mut parents) else {
239            return Ok(StepOutcome::AssumeRejected);
240        };
241        *state = first;
242        worklist.extend(parents);
243        Ok(if std::mem::take(&mut state.pending_storage_hook_revert) {
244            StepOutcome::Revert
245        } else {
246            StepOutcome::Continue
247        })
248    }
249
250    fn push_comparison_result(
251        &mut self,
252        state: &mut PathState,
253        op_pc: usize,
254        opcode: u8,
255        condition: SymBoolExpr,
256    ) -> Result<StepOutcome, SymbolicError> {
257        if !self.apply_branch_target_constraint(state, op_pc, opcode, &condition)? {
258            return Ok(StepOutcome::AssumeRejected);
259        }
260        let value = SymExpr::bool_word(&mut self.cx, condition);
261        state.stack.push(value)?;
262        Ok(StepOutcome::Continue)
263    }
264
265    fn apply_branch_target_constraint(
266        &mut self,
267        state: &mut PathState,
268        op_pc: usize,
269        opcode: u8,
270        condition: &SymBoolExpr,
271    ) -> Result<bool, SymbolicError> {
272        let Some(target) = state.branch_target() else {
273            return Ok(true);
274        };
275        if state.satisfies_branch_target() {
276            return Ok(true);
277        }
278        if !target.matches(state.address, op_pc, opcode) {
279            return Ok(true);
280        }
281
282        let desired =
283            if target.result() { condition.clone().not(&mut self.cx) } else { condition.clone() };
284        let mut constraints = state.constraints.clone();
285        constraints.push(desired);
286        if !self.branch_is_sat_or_defer(state, &constraints)? {
287            return Ok(false);
288        }
289        state.constraints = constraints;
290        state.mark_branch_target_reached();
291        Ok(true)
292    }
293
294    fn guard_fixed_memory_access<FEN: FoundryEvmNetwork>(
295        &mut self,
296        executor: &Executor<FEN>,
297        state: &mut PathState,
298        worklist: &mut VecDeque<PathState>,
299        offset: &SymExpr,
300        size: usize,
301    ) -> Result<Option<StepOutcome>, SymbolicError> {
302        let memory_limit = executor.evm_env().cfg_env.memory_limit();
303        let host_max_offset = (usize::MAX & !31usize).checked_sub(size);
304        let constrained_offset = state.constrained_usize_checked(&mut self.cx, offset);
305        if constrained_offset.as_ref().is_some_and(|offset| match offset {
306            Ok(offset) => host_max_offset.is_none_or(|max| *offset > max),
307            Err(_) => true,
308        }) {
309            state.return_data = SymReturnData::empty(&mut self.cx);
310            return Ok(Some(StepOutcome::Revert));
311        }
312
313        let expanded_size_bound = state
314            .upper_bound_usize(&mut self.cx, offset)
315            .and_then(|offset| offset.checked_add(size))
316            .and_then(|end| end.checked_add(31))
317            .and_then(|end| u64::try_from(end & !31usize).ok());
318        if expanded_size_bound.is_some_and(|size| size <= memory_limit) {
319            return Ok(None);
320        }
321
322        let representable = if let Some(host_max_offset) = host_max_offset {
323            let host_max_offset = SymExpr::constant(&mut self.cx, U256::from(host_max_offset));
324            SymBoolExpr::cmp_word_expr(&mut self.cx, SymCmpOp::Ule, offset, host_max_offset)
325        } else {
326            SymBoolExpr::constant(&mut self.cx, false)
327        };
328        let size = SymExpr::constant(&mut self.cx, U256::from(size));
329        let local_size =
330            state.memory.size_after_range_expansion_word(&mut self.cx, offset.clone(), size);
331        if let Some(local_size) = local_size.as_const() {
332            if local_size <= U256::from(memory_limit) {
333                return Ok(None);
334            }
335            state.return_data = SymReturnData::empty(&mut self.cx);
336            return Ok(Some(StepOutcome::Revert));
337        }
338        let memory_limit = SymExpr::constant(&mut self.cx, U256::from(memory_limit));
339        let within_limit = SymBoolExpr::cmp(&mut self.cx, SymCmpOp::Ule, local_size, memory_limit);
340        let valid_access = SymBoolExpr::and(&mut self.cx, vec![representable, within_limit]);
341        self.apply_memory_access_guard(state, worklist, valid_access)
342    }
343
344    fn apply_memory_access_guard(
345        &mut self,
346        state: &mut PathState,
347        worklist: &mut VecDeque<PathState>,
348        valid_access: SymBoolExpr,
349    ) -> Result<Option<StepOutcome>, SymbolicError> {
350        let (valid_constraints, valid_sat) =
351            self.constraints_with_condition(state, valid_access.clone())?;
352        let invalid = valid_access.clone().not(&mut self.cx);
353        let (invalid_constraints, invalid_sat) = self.constraints_with_condition(state, invalid)?;
354        match (valid_sat, invalid_sat) {
355            (true, true) => {
356                let (valid_seed_models, invalid_seed_models) =
357                    state.split_corpus_seed_models(&valid_access);
358                let mut valid = state.clone();
359                valid.pc = valid.pc.saturating_sub(1);
360                valid.depth = valid.depth.saturating_sub(1);
361                valid.constraints = valid_constraints;
362                valid.set_corpus_seed_models(valid_seed_models);
363                worklist.push_back(valid);
364                state.constraints = invalid_constraints;
365                state.set_corpus_seed_models(invalid_seed_models);
366                state.return_data = SymReturnData::empty(&mut self.cx);
367                Ok(Some(StepOutcome::Revert))
368            }
369            (true, false) => {
370                state.constraints = valid_constraints;
371                Ok(None)
372            }
373            (false, true) => {
374                state.constraints = invalid_constraints;
375                state.return_data = SymReturnData::empty(&mut self.cx);
376                Ok(Some(StepOutcome::Revert))
377            }
378            (false, false) => Ok(Some(StepOutcome::AssumeRejected)),
379        }
380    }
381
382    pub(super) fn guard_memory_range<FEN: FoundryEvmNetwork>(
383        &mut self,
384        executor: &Executor<FEN>,
385        state: &mut PathState,
386        worklist: &mut VecDeque<PathState>,
387        offset: &SymExpr,
388        size: &SymExpr,
389    ) -> Result<Option<StepOutcome>, SymbolicError> {
390        let memory_limit = executor.evm_env().cfg_env.memory_limit();
391        if let (Some(offset_value), Some(size_value)) = (offset.as_const(), size.as_const()) {
392            let valid = size_value.is_zero()
393                || usize::try_from(offset_value)
394                    .ok()
395                    .zip(usize::try_from(size_value).ok())
396                    .and_then(|(offset, size)| offset.checked_add(size))
397                    .and_then(|end| end.checked_add(31))
398                    .and_then(|end| u64::try_from(end & !31usize).ok())
399                    .is_some_and(|end| end <= memory_limit);
400            if !valid {
401                state.return_data = SymReturnData::empty(&mut self.cx);
402                return Ok(Some(StepOutcome::Revert));
403            }
404            state.memory.expand_range(&mut self.cx, offset.clone(), size.clone());
405            return Ok(None);
406        }
407
408        let offset_bound = state.upper_bound_usize(&mut self.cx, offset);
409        let size_bound = state.upper_bound_usize(&mut self.cx, size);
410        if offset_bound
411            .zip(size_bound)
412            .and_then(|(offset, size)| offset.checked_add(size))
413            .and_then(|end| end.checked_add(31))
414            .and_then(|end| u64::try_from(end & !31usize).ok())
415            .is_some_and(|end| end <= memory_limit)
416        {
417            state.memory.expand_range(&mut self.cx, offset.clone(), size.clone());
418            return Ok(None);
419        }
420
421        let zero_size = SymBoolExpr::eq_word_const(&mut self.cx, size, U256::ZERO);
422        let host_max = SymExpr::constant(&mut self.cx, U256::from(usize::MAX & !31usize));
423        let size_fits =
424            SymBoolExpr::cmp_word_expr(&mut self.cx, SymCmpOp::Ule, size, host_max.clone());
425        let max_offset = SymExpr::binop(&mut self.cx, SymBinOp::Sub, host_max, size.clone());
426        let offset_fits =
427            SymBoolExpr::cmp_word_expr(&mut self.cx, SymCmpOp::Ule, offset, max_offset);
428
429        let local_size = state.memory.size_after_range_expansion_word(
430            &mut self.cx,
431            offset.clone(),
432            size.clone(),
433        );
434        let memory_limit = SymExpr::constant(&mut self.cx, U256::from(memory_limit));
435        let local_fits = SymBoolExpr::cmp(&mut self.cx, SymCmpOp::Ule, local_size, memory_limit);
436        let nonzero_valid =
437            SymBoolExpr::and(&mut self.cx, vec![size_fits, offset_fits, local_fits]);
438        let valid_access = SymBoolExpr::or(&mut self.cx, vec![zero_size, nonzero_valid]);
439
440        let outcome = self.apply_memory_access_guard(state, worklist, valid_access)?;
441        if outcome.is_none() {
442            state.memory.expand_range(&mut self.cx, offset.clone(), size.clone());
443        }
444        Ok(outcome)
445    }
446
447    #[expect(clippy::too_many_arguments)]
448    pub(super) fn step<FEN: FoundryEvmNetwork>(
449        &mut self,
450        executor: &Executor<FEN>,
451        code: &SymCode,
452        jumpdests: &JumpTable,
453        state: &mut PathState,
454        worklist: &mut VecDeque<PathState>,
455        completed_paths: &mut usize,
456        op: u8,
457    ) -> Result<StepOutcome, SymbolicError> {
458        state.pc += 1;
459
460        if Into::<SpecId>::into(executor.spec_id()) < opcode_activation(op) {
461            state.return_data = SymReturnData::empty(&mut self.cx);
462            return Ok(StepOutcome::ExceptionalHalt);
463        }
464
465        match op {
466            opcode::PUSH0 => {
467                state.stack.push(SymExpr::zero(&mut self.cx))?;
468            }
469            opcode::PUSH1..=opcode::PUSH32 => {
470                let n = (op - opcode::PUSH1 + 1) as usize;
471                let end = state.pc.saturating_add(n);
472                if end > code.len() {
473                    return Err(SymbolicError::InvalidBytecode("truncated PUSH data"));
474                }
475                let value = code.push_data_word(&mut self.cx, state.pc, n);
476                state.pc = end;
477                state.stack.push(value)?;
478            }
479            opcode::DUP1..=opcode::DUP16 => {
480                let n = (op - opcode::DUP1 + 1) as usize;
481                let value = state.stack.peek(n - 1)?.clone();
482                state.stack.push(value)?;
483            }
484            opcode::SWAP1..=opcode::SWAP16 => {
485                let n = (op - opcode::SWAP1 + 1) as usize;
486                state.stack.swap(n)?;
487            }
488            opcode::STOP => return Ok(StepOutcome::Halt),
489            opcode::ADD | opcode::SUB | opcode::MUL | opcode::AND | opcode::OR | opcode::XOR => {
490                let bin_op = match op {
491                    opcode::ADD => SymBinOp::Add,
492                    opcode::SUB => SymBinOp::Sub,
493                    opcode::MUL => SymBinOp::Mul,
494                    opcode::AND => SymBinOp::And,
495                    opcode::OR => SymBinOp::Or,
496                    _ => SymBinOp::Xor,
497                };
498                state.bin_word(&mut self.cx, bin_op)?
499            }
500            opcode::EXP => state.exp_word(&mut self.cx)?,
501            opcode::DIV | opcode::SDIV | opcode::MOD | opcode::SMOD => {
502                let bin_op = match op {
503                    opcode::DIV => SymBinOp::UDiv,
504                    opcode::SDIV => SymBinOp::SDiv,
505                    opcode::MOD => SymBinOp::URem,
506                    _ => SymBinOp::SRem,
507                };
508                state.bin_word_div_zero_guard(&mut self.cx, bin_op)?
509            }
510            opcode::ADDMOD | opcode::MULMOD => {
511                let a = state.stack.pop()?;
512                let b = state.stack.pop()?;
513                let n = state.stack.pop()?;
514                let tern_op =
515                    if op == opcode::ADDMOD { SymTernOp::AddMod } else { SymTernOp::MulMod };
516                state.stack.push(SymExpr::ternop(&mut self.cx, tern_op, a, b, n))?;
517            }
518            opcode::LT | opcode::GT | opcode::SLT | opcode::SGT => {
519                let op_pc = state.pc - 1;
520                let cmp_op = match op {
521                    opcode::LT => SymCmpOp::Ult,
522                    opcode::GT => SymCmpOp::Ugt,
523                    opcode::SLT => SymCmpOp::Slt,
524                    _ => SymCmpOp::Sgt,
525                };
526                let condition = state.cmp_word_condition(&mut self.cx, cmp_op)?;
527                return self.push_comparison_result(state, op_pc, op, condition);
528            }
529            opcode::EQ => {
530                let op_pc = state.pc - 1;
531                let a = state.stack.pop()?;
532                let b = state.stack.pop()?;
533                let condition = SymBoolExpr::eq(&mut self.cx, b, a);
534                return self.push_comparison_result(state, op_pc, op, condition);
535            }
536            opcode::ISZERO => {
537                let op_pc = state.pc - 1;
538                let value = state.stack.pop()?;
539                let value = value.into_zero_bool(&mut self.cx);
540                return self.push_comparison_result(state, op_pc, op, value);
541            }
542            opcode::NOT => {
543                let value = state.stack.pop()?;
544                state.stack.push(SymExpr::not(&mut self.cx, value))?;
545            }
546            opcode::SIGNEXTEND => {
547                let byte_index = state.stack.pop()?;
548                let value = state.stack.pop()?;
549                state.stack.push(signextend_word_dynamic(&mut self.cx, byte_index, value))?;
550            }
551            opcode::BYTE => {
552                let index = state.stack.pop()?;
553                let word = state.stack.pop()?;
554                state.stack.push(byte_word_dynamic(&mut self.cx, index, word))?;
555            }
556            opcode::SHL => state.shift_word(&mut self.cx, SymBinOp::Shl)?,
557            opcode::SHR => state.shift_word(&mut self.cx, SymBinOp::Shr)?,
558            opcode::SAR => state.shift_word(&mut self.cx, SymBinOp::Sar)?,
559            opcode::KECCAK256 => {
560                let offset = state.stack.peek(0)?.clone();
561                let size = state.stack.peek(1)?.clone();
562                if let Some(outcome) =
563                    self.guard_memory_range(executor, state, worklist, &offset, &size)?
564                {
565                    return Ok(outcome);
566                }
567                let offset = state.stack.pop()?;
568                let size = state.stack.pop()?;
569                match state.constrained_usize_checked(&mut self.cx, &size) {
570                    Some(Ok(size)) => {
571                        let bytes = state.memory.read_byte_exprs_offset(&mut self.cx, offset, size);
572                        let hash = keccak_word(&mut self.cx, bytes.clone());
573                        let has_mapping_hook = state
574                            .mapping_storage_store_hooks
575                            .keys()
576                            .any(|(address, _)| *address == state.storage_address);
577                        if has_mapping_hook && !state.storage_hook_active && size == 64 {
578                            state
579                                .mapping_hook_keccak_preimages
580                                .entry((state.storage_address, hash.clone()))
581                                .or_insert_with(|| bytes.into());
582                        }
583                        state.stack.push(hash)?;
584                    }
585                    Some(Err(_)) => {
586                        return Ok(StepOutcome::Revert);
587                    }
588                    None => {
589                        let has_mapping_hook = state
590                            .mapping_storage_store_hooks
591                            .keys()
592                            .any(|(address, _)| *address == state.storage_address);
593                        if has_mapping_hook && !state.storage_hook_active {
594                            let mapping_size = SymExpr::constant(&mut self.cx, U256::from(64));
595                            let mapping_size_feasible =
596                                SymBoolExpr::eq(&mut self.cx, size.clone(), mapping_size);
597                            let (_, mapping_size_feasible) =
598                                self.constraints_with_condition(state, mapping_size_feasible)?;
599                            if mapping_size_feasible {
600                                self.defer_incomplete(
601                                    "symbolic KECCAK256 size may conceal mapping provenance",
602                                );
603                            }
604                        }
605                        let max_limit = self.config.max_calldata_bytes as usize;
606                        let max_size = self.solver_upper_bound_usize(
607                            state,
608                            &size,
609                            max_limit,
610                            "symbolic SHA3 size",
611                        )?;
612                        let bytes = state.memory.read_byte_exprs_symbolic_size(
613                            &mut self.cx,
614                            offset,
615                            size.clone(),
616                            max_size,
617                        );
618                        state.stack.push(keccak_word_with_len(&mut self.cx, bytes, size))?;
619                    }
620                }
621            }
622            opcode::ADDRESS | opcode::CALLER | opcode::ORIGIN | opcode::CALLVALUE => {
623                let value = match op {
624                    opcode::ADDRESS => state.address_word.clone(),
625                    opcode::CALLER => state.caller_word.clone(),
626                    opcode::ORIGIN => state.origin_word.clone(),
627                    _ => state.callvalue.clone(),
628                };
629                state.stack.push(value)?;
630            }
631            opcode::BLOCKHASH => {
632                let number = state.stack.pop()?;
633                let hash = state.block.block_hash_word(&mut self.cx, executor, number)?;
634                state.stack.push(hash)?;
635            }
636            opcode::BALANCE => {
637                let target = state.stack.pop()?;
638                let balance = state.balance_word(&mut self.cx, executor, target)?;
639                state.stack.push(balance)?;
640            }
641            opcode::SELFBALANCE => {
642                let balance = state.balance(&mut self.cx, executor, state.address);
643                state.stack.push(balance)?;
644            }
645            opcode::EXTCODESIZE => {
646                let target = state.stack.pop()?;
647                let size = state.extcode_size_word(&mut self.cx, executor, target)?;
648                state.stack.push(size)?;
649            }
650            opcode::EXTCODEHASH => {
651                let target = state.stack.pop()?;
652                let hash = state.extcode_hash_word(&mut self.cx, executor, target)?;
653                state.stack.push(hash)?;
654            }
655            opcode::EXTCODECOPY => {
656                let dest = state.stack.peek(1)?.clone();
657                let size = state.stack.peek(3)?.clone();
658                if let Some(outcome) =
659                    self.guard_memory_range(executor, state, worklist, &dest, &size)?
660                {
661                    return Ok(outcome);
662                }
663                let target = state.stack.pop()?;
664                let dest = state.stack.pop()?;
665                let offset = state.stack.pop()?;
666                let size = state.stack.pop()?;
667                match state.constrained_usize_checked(&mut self.cx, &size) {
668                    Some(Ok(size)) => {
669                        let bytes = state.extcode_bytes_word(
670                            &mut self.cx,
671                            executor,
672                            target,
673                            offset,
674                            size,
675                        )?;
676                        state.memory.store_bytes_offset(&mut self.cx, dest, bytes);
677                    }
678                    Some(Err(_)) => {
679                        return Ok(StepOutcome::Revert);
680                    }
681                    None => {
682                        let max_limit = self.config.max_calldata_bytes as usize;
683                        let max_size = self.solver_upper_bound_usize(
684                            state,
685                            &size,
686                            max_limit,
687                            "symbolic EXTCODECOPY size",
688                        )?;
689                        if max_size != 0 {
690                            let bytes = state.extcode_bytes_word(
691                                &mut self.cx,
692                                executor,
693                                target,
694                                offset,
695                                max_size,
696                            )?;
697                            state.memory.copy_bytes_size_offset(&mut self.cx, dest, size, bytes)?;
698                        }
699                    }
700                }
701            }
702            opcode::CALLDATALOAD => {
703                let offset = state.stack.pop()?;
704                let value = state.calldata.load_word(&mut self.cx, offset);
705                state.stack.push(value)?;
706            }
707            opcode::CALLDATASIZE => {
708                let size = state.calldata.size_word();
709                state.stack.push(size)?;
710            }
711            opcode::CALLDATACOPY => {
712                let dest = state.stack.peek(0)?.clone();
713                let size = state.stack.peek(2)?.clone();
714                if let Some(outcome) =
715                    self.guard_memory_range(executor, state, worklist, &dest, &size)?
716                {
717                    return Ok(outcome);
718                }
719                let dest = state.stack.pop()?;
720                let offset = state.stack.pop()?;
721                let size = state.stack.pop()?;
722                match state.constrained_usize_checked(&mut self.cx, &size) {
723                    Some(Ok(size)) => {
724                        if size != 0 {
725                            let CallFrame { memory, calldata, .. } = &mut state.frame;
726                            memory.copy_calldata_to_offset(
727                                &mut self.cx,
728                                dest,
729                                offset,
730                                size,
731                                calldata,
732                            );
733                        }
734                    }
735                    Some(Err(_)) => {
736                        return Ok(StepOutcome::Revert);
737                    }
738                    None => {
739                        let max_limit = self.config.max_calldata_bytes as usize;
740                        let max_size = self.solver_upper_bound_usize(
741                            state,
742                            &size,
743                            max_limit,
744                            "symbolic CALLDATACOPY size",
745                        )?;
746                        if max_size != 0 {
747                            let CallFrame { memory, calldata, .. } = &mut state.frame;
748                            memory.copy_calldata_symbolic_size(
749                                &mut self.cx,
750                                dest,
751                                offset,
752                                size,
753                                max_size,
754                                calldata,
755                            )?;
756                        }
757                    }
758                }
759            }
760            opcode::CODESIZE => {
761                let value = SymExpr::constant(&mut self.cx, U256::from(code.len()));
762                state.stack.push(value)?;
763            }
764            opcode::CODECOPY => {
765                let dest = state.stack.peek(0)?.clone();
766                let size = state.stack.peek(2)?.clone();
767                if let Some(outcome) =
768                    self.guard_memory_range(executor, state, worklist, &dest, &size)?
769                {
770                    return Ok(outcome);
771                }
772                let dest = state.stack.pop()?;
773                let offset = state.stack.pop()?;
774                let size = state.stack.pop()?;
775                match state.constrained_usize_checked(&mut self.cx, &size) {
776                    Some(Ok(size)) => {
777                        let bytes = code.read_bytes_offset(&mut self.cx, offset, size);
778                        state.memory.store_bytes_offset(&mut self.cx, dest, bytes);
779                    }
780                    Some(Err(_)) => {
781                        return Ok(StepOutcome::Revert);
782                    }
783                    None => {
784                        let max_limit = self.config.max_calldata_bytes as usize;
785                        let max_size = self.solver_upper_bound_usize(
786                            state,
787                            &size,
788                            max_limit,
789                            "symbolic CODECOPY size",
790                        )?;
791                        if max_size != 0 {
792                            let bytes = code.read_bytes_offset(&mut self.cx, offset, max_size);
793                            state.memory.copy_bytes_size_offset(&mut self.cx, dest, size, bytes)?;
794                        }
795                    }
796                }
797            }
798            opcode::RETURNDATASIZE => {
799                let size = state.return_data.len_word.clone();
800                state.stack.push(size)?;
801            }
802            opcode::RETURNDATACOPY => {
803                let dest = state.stack.peek(0)?.clone();
804                let offset = state.stack.peek(1)?.clone();
805                let size = state.stack.peek(2)?.clone();
806                if let Some(outcome) =
807                    self.guard_memory_range(executor, state, worklist, &dest, &size)?
808                {
809                    return Ok(outcome);
810                }
811                if let Some(outcome) =
812                    self.guard_returndata_copy_range(state, worklist, &offset, &size)?
813                {
814                    return Ok(outcome);
815                }
816                let dest = state.stack.pop()?;
817                let offset = state.stack.pop()?;
818                let size = state.stack.pop()?;
819                match state.constrained_usize_checked(&mut self.cx, &size) {
820                    Some(Ok(size)) => {
821                        let CallFrame { memory, return_data, .. } = &mut state.frame;
822                        memory.copy_return_data_to_offset(
823                            &mut self.cx,
824                            dest,
825                            offset,
826                            size,
827                            return_data,
828                        )?;
829                    }
830                    Some(Err(_)) => {
831                        return Ok(StepOutcome::Revert);
832                    }
833                    None => {
834                        let available = state
835                            .constrained_usize(&mut self.cx, &offset)
836                            .map(|offset| state.return_data.len().saturating_sub(offset))
837                            .unwrap_or(state.return_data.len());
838                        let max_limit = available.min(self.config.max_calldata_bytes as usize);
839                        let max_size = self.solver_upper_bound_usize(
840                            state,
841                            &size,
842                            max_limit,
843                            "symbolic RETURNDATACOPY size",
844                        )?;
845                        let CallFrame { memory, return_data, .. } = &mut state.frame;
846                        memory.copy_return_data_symbolic_size(
847                            &mut self.cx,
848                            dest,
849                            offset,
850                            size,
851                            max_size,
852                            return_data,
853                        )?;
854                    }
855                }
856            }
857            opcode::POP => {
858                state.stack.pop()?;
859            }
860            opcode::MLOAD => {
861                let offset = state.stack.peek(0)?.clone();
862                if let Some(outcome) =
863                    self.guard_fixed_memory_access(executor, state, worklist, &offset, 32)?
864                {
865                    return Ok(outcome);
866                }
867                let offset = state.stack.pop()?;
868                let value = state.memory.load_word_offset(&mut self.cx, offset)?;
869                state.stack.push(value)?;
870            }
871            opcode::MSTORE => {
872                let offset = state.stack.peek(0)?.clone();
873                if let Some(outcome) =
874                    self.guard_fixed_memory_access(executor, state, worklist, &offset, 32)?
875                {
876                    return Ok(outcome);
877                }
878                let offset = state.stack.pop()?;
879                let value = state.stack.pop()?;
880                let minimum_offset = state.lower_bound_usize(&offset);
881                state.memory.store_word_offset(&mut self.cx, offset, value, minimum_offset);
882            }
883            opcode::MSTORE8 => {
884                let offset = state.stack.peek(0)?.clone();
885                if let Some(outcome) =
886                    self.guard_fixed_memory_access(executor, state, worklist, &offset, 1)?
887                {
888                    return Ok(outcome);
889                }
890                let offset = state.stack.pop()?;
891                let value = state.stack.pop()?;
892                let minimum_offset = state.lower_bound_usize(&offset);
893                state.memory.store_byte_offset(&mut self.cx, offset, value, minimum_offset);
894            }
895            opcode::SLOAD => {
896                let key = state.stack.pop()?;
897                state.record_sload(state.storage_address, key.clone());
898                let concrete_key = state.constrained_word(&mut self.cx, &key);
899                let value = state.world.sload(
900                    &mut self.cx,
901                    executor,
902                    state.storage_address,
903                    key.clone(),
904                    concrete_key,
905                )?;
906                state.stack.push(value.clone())?;
907                if !state.storage_hook_active
908                    && let Some(hook) =
909                        state.storage_load_hooks.get(&state.storage_address).copied()
910                {
911                    let account =
912                        SymExpr::constant(&mut self.cx, address_word(state.storage_address));
913                    let calldata =
914                        self.storage_hook_calldata(hook.callback_selector, [account, key, value]);
915                    return self.invoke_storage_hook(
916                        executor,
917                        state,
918                        worklist,
919                        completed_paths,
920                        hook,
921                        calldata,
922                    );
923                }
924            }
925            opcode::SSTORE => {
926                if state.is_static {
927                    state.return_data = SymReturnData::empty(&mut self.cx);
928                    return Ok(StepOutcome::ExceptionalHalt);
929                }
930                let key = state.stack.peek(0)?.clone();
931                state.stack.peek(1)?;
932                let hook = (!state.storage_hook_active)
933                    .then(|| state.storage_store_hooks.get(&state.storage_address).copied())
934                    .flatten();
935                let mapping = if state.storage_hook_active {
936                    MappingStorageProvenance::None
937                } else {
938                    self.mapping_storage_provenance(state, &key)?
939                };
940                let mapping = match mapping {
941                    MappingStorageProvenance::None => None,
942                    MappingStorageProvenance::Exact(provenance) => state
943                        .mapping_storage_store_hooks
944                        .get(&(state.storage_address, provenance.root_slot))
945                        .copied()
946                        .map(|hook| (hook, provenance)),
947                    MappingStorageProvenance::Fork { equality, inequality } => {
948                        let store_pc = state.pc - 1;
949                        let mut equality_state = state.clone();
950                        equality_state.pc = store_pc;
951                        equality_state.depth = equality_state.depth.saturating_sub(1);
952                        equality_state.constraints = equality;
953                        let mut inequality_state = state.clone();
954                        inequality_state.pc = store_pc;
955                        inequality_state.depth = inequality_state.depth.saturating_sub(1);
956                        inequality_state.constraints = inequality;
957                        worklist.push_back(equality_state);
958                        worklist.push_back(inequality_state);
959                        return Ok(StepOutcome::Forked);
960                    }
961                };
962                state.stack.pop()?;
963                let value = state.stack.pop()?;
964                state.record_sstore(state.storage_address, key.clone());
965                let old_value = if hook.is_some() || mapping.is_some() {
966                    let concrete_key = state.constrained_word(&mut self.cx, &key);
967                    Some(state.world.sload(
968                        &mut self.cx,
969                        executor,
970                        state.storage_address,
971                        key.clone(),
972                        concrete_key,
973                    )?)
974                } else {
975                    None
976                };
977                state.world.sstore(state.storage_address, key.clone(), value.clone());
978                if let Some(hook) = hook {
979                    let account =
980                        SymExpr::constant(&mut self.cx, address_word(state.storage_address));
981                    let calldata = self.storage_hook_calldata(
982                        hook.callback_selector,
983                        [
984                            account,
985                            key,
986                            old_value.expect("old value loaded for storage hook"),
987                            value,
988                        ],
989                    );
990                    return self.invoke_storage_hook(
991                        executor,
992                        state,
993                        worklist,
994                        completed_paths,
995                        hook,
996                        calldata,
997                    );
998                } else if let Some((hook, provenance)) = mapping {
999                    let account =
1000                        SymExpr::constant(&mut self.cx, address_word(state.storage_address));
1001                    let root = SymExpr::constant(&mut self.cx, provenance.root_slot);
1002                    let calldata = self.mapping_storage_hook_calldata(
1003                        hook.callback_selector,
1004                        [
1005                            account,
1006                            key,
1007                            root,
1008                            old_value.expect("old value loaded for mapping storage hook"),
1009                            value,
1010                        ],
1011                        provenance.keys,
1012                    );
1013                    return self.invoke_storage_hook(
1014                        executor,
1015                        state,
1016                        worklist,
1017                        completed_paths,
1018                        hook,
1019                        calldata,
1020                    );
1021                }
1022            }
1023            opcode::TLOAD => {
1024                let key = state.stack.pop()?;
1025                let value = state.world.tload(&mut self.cx, state.storage_address, key);
1026                state.stack.push(value)?;
1027            }
1028            opcode::TSTORE => {
1029                if state.is_static {
1030                    state.return_data = SymReturnData::empty(&mut self.cx);
1031                    return Ok(StepOutcome::ExceptionalHalt);
1032                }
1033                let key = state.stack.pop()?;
1034                let value = state.stack.pop()?;
1035                state.world.tstore(state.storage_address, key, value);
1036            }
1037            opcode::JUMP => {
1038                let dest = state.stack.pop()?;
1039                let Some(dest) = self.resolve_jump_destination(
1040                    state,
1041                    jumpdests,
1042                    dest,
1043                    "symbolic JUMP destination",
1044                )?
1045                else {
1046                    state.return_data = SymReturnData::empty(&mut self.cx);
1047                    return Ok(StepOutcome::ExceptionalHalt);
1048                };
1049                if !self.take_loop_jump(state, state.pc, dest) {
1050                    return Ok(StepOutcome::AssumeRejected);
1051                }
1052                state.pc = dest;
1053            }
1054            opcode::JUMPI => {
1055                let dest = state.stack.pop()?;
1056                let cond = state.stack.pop()?;
1057                match cond.as_const().map(|value| !value.is_zero()) {
1058                    Some(true) => {
1059                        let Some(dest) = self.resolve_jump_destination(
1060                            state,
1061                            jumpdests,
1062                            dest,
1063                            "symbolic JUMPI destination",
1064                        )?
1065                        else {
1066                            state.return_data = SymReturnData::empty(&mut self.cx);
1067                            return Ok(StepOutcome::ExceptionalHalt);
1068                        };
1069                        if !self.take_loop_jump(state, state.pc, dest) {
1070                            return Ok(StepOutcome::AssumeRejected);
1071                        }
1072                        state.pc = dest;
1073                    }
1074                    Some(false) => {}
1075                    None => {
1076                        let true_cond = cond.nonzero_bool(&mut self.cx);
1077                        let dest = match self.resolve_jump_destination(
1078                            state,
1079                            jumpdests,
1080                            dest,
1081                            "symbolic JUMPI destination",
1082                        ) {
1083                            Ok(Some(dest)) => dest,
1084                            Ok(None) => {
1085                                return self.branch_invalid_jumpi(state, worklist, true_cond);
1086                            }
1087                            Err(err) => {
1088                                let (_, taken_sat) =
1089                                    self.constraints_with_condition(state, true_cond.clone())?;
1090                                if taken_sat {
1091                                    return Err(err);
1092                                }
1093                                let (_, not_taken_seed_models) =
1094                                    state.split_corpus_seed_models(&true_cond);
1095                                state.constraints.push(true_cond.not(&mut self.cx));
1096                                state.set_corpus_seed_models(not_taken_seed_models);
1097                                return Ok(StepOutcome::Continue);
1098                            }
1099                        };
1100                        let op_pc = state.pc.saturating_sub(1);
1101                        let _branch_span = trace_span!("jumpi_branch", pc = op_pc, dest).entered();
1102                        let false_cond = true_cond.clone().not(&mut self.cx);
1103                        let fallthrough = state.pc;
1104                        let (true_seed_models, false_seed_models) =
1105                            state.split_corpus_seed_models(&true_cond);
1106                        let mut true_state = state.clone();
1107                        true_state.constraints.push(true_cond);
1108                        true_state.set_corpus_seed_models(true_seed_models);
1109                        true_state.pc = dest;
1110                        let mut false_state = state.clone();
1111                        false_state.constraints.push(false_cond);
1112                        false_state.set_corpus_seed_models(false_seed_models);
1113                        false_state.pc = fallthrough;
1114
1115                        let true_pending = self.take_loop_jump(&mut true_state, fallthrough, dest);
1116                        if true_pending {
1117                            true_state.defer_feasibility_check();
1118                        }
1119                        false_state.defer_feasibility_check();
1120                        trace!(true_pending, false_pending = true, "JUMPI symbolic branch");
1121                        if true_pending {
1122                            let true_seed_count = true_state.corpus_seed_model_count();
1123                            let false_seed_count = false_state.corpus_seed_model_count();
1124                            match (
1125                                false_seed_count.cmp(&true_seed_count),
1126                                self.config.exploration_order,
1127                            ) {
1128                                (std::cmp::Ordering::Greater, SymbolicExplorationOrder::Bfs)
1129                                | (std::cmp::Ordering::Less, SymbolicExplorationOrder::Dfs) => {
1130                                    worklist.push_back(false_state);
1131                                    worklist.push_back(true_state);
1132                                }
1133                                (std::cmp::Ordering::Greater, SymbolicExplorationOrder::Dfs)
1134                                | (std::cmp::Ordering::Less, SymbolicExplorationOrder::Bfs)
1135                                | (std::cmp::Ordering::Equal, _) => {
1136                                    worklist.push_back(true_state);
1137                                    worklist.push_back(false_state);
1138                                }
1139                            }
1140                        } else {
1141                            worklist.push_back(false_state);
1142                        }
1143                        return Ok(StepOutcome::Forked);
1144                    }
1145                }
1146            }
1147            opcode::PC => {
1148                let pc = state.pc - 1;
1149                let pc = SymExpr::constant(&mut self.cx, U256::from(pc));
1150                state.stack.push(pc)?;
1151            }
1152            opcode::MSIZE => {
1153                let size = state.memory.size_word(&mut self.cx);
1154                state.stack.push(size)?;
1155            }
1156            opcode::GAS => {
1157                let gas = state.fresh_gasleft(&mut self.cx);
1158                state.stack.push(gas)?;
1159            }
1160            opcode::JUMPDEST => {}
1161            opcode::MCOPY => {
1162                let dest = state.stack.peek(0)?.clone();
1163                let src = state.stack.peek(1)?.clone();
1164                let size = state.stack.peek(2)?.clone();
1165                if let Some(outcome) =
1166                    self.guard_memory_range(executor, state, worklist, &dest, &size)?
1167                {
1168                    return Ok(outcome);
1169                }
1170                if let Some(outcome) =
1171                    self.guard_memory_range(executor, state, worklist, &src, &size)?
1172                {
1173                    return Ok(outcome);
1174                }
1175                let dest = state.stack.pop()?;
1176                let src = state.stack.pop()?;
1177                let size = state.stack.pop()?;
1178                match state.constrained_usize_checked(&mut self.cx, &size) {
1179                    Some(Ok(size)) => {
1180                        state.memory.copy_memory_to_offset(&mut self.cx, dest, src, size)?;
1181                    }
1182                    Some(Err(_)) => {
1183                        return Ok(StepOutcome::Revert);
1184                    }
1185                    None => {
1186                        let max_limit = self.config.max_calldata_bytes as usize;
1187                        let max_size = self.solver_upper_bound_usize(
1188                            state,
1189                            &size,
1190                            max_limit,
1191                            "symbolic MCOPY size",
1192                        )?;
1193                        if max_size != 0 {
1194                            state.memory.copy_memory_symbolic_size(
1195                                &mut self.cx,
1196                                dest,
1197                                src,
1198                                size,
1199                                max_size,
1200                            )?;
1201                        }
1202                    }
1203                }
1204            }
1205            opcode::RETURN | opcode::REVERT => {
1206                let offset = state.stack.peek(0)?.clone();
1207                let size = state.stack.peek(1)?.clone();
1208                if let Some(outcome) =
1209                    self.guard_memory_range(executor, state, worklist, &offset, &size)?
1210                {
1211                    return Ok(outcome);
1212                }
1213                return self.return_or_revert(state, op == opcode::REVERT);
1214            }
1215            opcode::INVALID => return Ok(StepOutcome::ExceptionalHalt),
1216            opcode::CALL => {
1217                return self.call(executor, state, worklist, completed_paths, CallKind::Call);
1218            }
1219            opcode::CALLCODE => {
1220                return self.call(executor, state, worklist, completed_paths, CallKind::CallCode);
1221            }
1222            opcode::DELEGATECALL => {
1223                return self.call(
1224                    executor,
1225                    state,
1226                    worklist,
1227                    completed_paths,
1228                    CallKind::DelegateCall,
1229                );
1230            }
1231            opcode::STATICCALL => {
1232                return self.call(executor, state, worklist, completed_paths, CallKind::StaticCall);
1233            }
1234            opcode::CREATE => {
1235                return self.create(executor, state, worklist, completed_paths, CreateKind::Create);
1236            }
1237            opcode::CREATE2 => {
1238                return self.create(
1239                    executor,
1240                    state,
1241                    worklist,
1242                    completed_paths,
1243                    CreateKind::Create2,
1244                );
1245            }
1246            opcode::SELFDESTRUCT => {
1247                if state.is_static {
1248                    state.return_data = SymReturnData::empty(&mut self.cx);
1249                    return Ok(StepOutcome::ExceptionalHalt);
1250                }
1251                let spec_id: SpecId = executor.spec_id().into();
1252                let (beneficiary_word, beneficiary) =
1253                    state.pop_address_word_or_symbolic_slot(&mut self.cx)?;
1254                if spec_id < SpecId::CANCUN
1255                    || state.world.was_created_in_current_transaction(state.address)
1256                {
1257                    state.world.selfdestruct_legacy(
1258                        &mut self.cx,
1259                        executor,
1260                        state.address,
1261                        beneficiary,
1262                    )?;
1263                } else {
1264                    if state.constrained_word(&mut self.cx, &beneficiary_word).is_none() {
1265                        return Err(SymbolicError::Unsupported(
1266                            "symbolic SELFDESTRUCT beneficiary",
1267                        ));
1268                    }
1269                    state.world.selfdestruct_cancun_existing(
1270                        &mut self.cx,
1271                        executor,
1272                        state.address,
1273                        beneficiary,
1274                    );
1275                }
1276                state.return_data = SymReturnData::empty(&mut self.cx);
1277                return Ok(StepOutcome::Halt);
1278            }
1279            opcode::CHAINID => {
1280                let value = state.block.chain_id.clone();
1281                state.stack.push(value)?;
1282            }
1283            opcode::BASEFEE => {
1284                let value = state.block.basefee.clone();
1285                state.stack.push(value)?;
1286            }
1287            opcode::GASPRICE => {
1288                let gas_price = state.gas_price.clone();
1289                state.stack.push(gas_price)?;
1290            }
1291            opcode::BLOBHASH => {
1292                let index = state.stack.pop()?;
1293                let index = state.expect_constrained_usize(
1294                    &mut self.cx,
1295                    index,
1296                    "symbolic BLOBHASH index",
1297                )?;
1298                let hash = state.block.blob_hashes.get(index).copied().unwrap_or_default();
1299                let hash = SymExpr::constant(&mut self.cx, U256::from_be_slice(hash.as_slice()));
1300                state.stack.push(hash)?;
1301            }
1302            opcode::COINBASE => {
1303                let coinbase = state.block.coinbase;
1304                let coinbase = SymExpr::constant(&mut self.cx, address_word(coinbase));
1305                state.stack.push(coinbase)?;
1306            }
1307            opcode::TIMESTAMP => {
1308                let value = state.block.timestamp.clone();
1309                state.stack.push(value)?;
1310            }
1311            opcode::NUMBER => {
1312                let value = state.block.number.clone();
1313                state.stack.push(value)?;
1314            }
1315            opcode::DIFFICULTY => {
1316                let value = state.block.difficulty.clone();
1317                state.stack.push(value)?;
1318            }
1319            opcode::GASLIMIT => {
1320                let value = state.block.gaslimit.clone();
1321                state.stack.push(value)?;
1322            }
1323            opcode::BLOBBASEFEE => {
1324                let value = state.block.blob_basefee.clone();
1325                state.stack.push(value)?;
1326            }
1327            opcode::LOG0 | opcode::LOG1 | opcode::LOG2 | opcode::LOG3 | opcode::LOG4 => {
1328                if state.is_static {
1329                    state.return_data = SymReturnData::empty(&mut self.cx);
1330                    return Ok(StepOutcome::ExceptionalHalt);
1331                }
1332                let topics = (op - opcode::LOG0) as usize;
1333                let offset = state.stack.peek(0)?.clone();
1334                let size = state.stack.peek(1)?.clone();
1335                if let Some(outcome) =
1336                    self.guard_memory_range(executor, state, worklist, &offset, &size)?
1337                {
1338                    return Ok(outcome);
1339                }
1340                let offset = state.stack.pop()?;
1341                if offset.contains_gasleft() {
1342                    return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
1343                }
1344                let size = state.stack.pop()?;
1345                if size.contains_gasleft() {
1346                    return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
1347                }
1348                let (data_len, data) = match state.constrained_usize_checked(&mut self.cx, &size) {
1349                    Some(Ok(size)) => (
1350                        SymExpr::constant(&mut self.cx, U256::from(size)),
1351                        state.memory.read_bytes_offset(&mut self.cx, offset, size),
1352                    ),
1353                    Some(Err(_)) => {
1354                        return Ok(StepOutcome::Revert);
1355                    }
1356                    None => {
1357                        let max_limit = self.config.max_calldata_bytes as usize;
1358                        let max_size = self.solver_upper_bound_usize(
1359                            state,
1360                            &size,
1361                            max_limit,
1362                            "symbolic LOG size",
1363                        )?;
1364                        let data = state.memory.read_bytes_symbolic_size(
1365                            &mut self.cx,
1366                            offset,
1367                            size.clone(),
1368                            max_size,
1369                        );
1370                        (size, data)
1371                    }
1372                };
1373                let mut log_topics = Vec::with_capacity(topics);
1374                for _ in 0..topics {
1375                    let topic = state.stack.pop()?;
1376                    if topic.contains_gasleft() {
1377                        return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
1378                    }
1379                    log_topics.push(topic);
1380                }
1381                return self.handle_log(
1382                    state,
1383                    SymbolicLog::new(log_topics, data_len, data, state.address),
1384                );
1385            }
1386            _ => return Err(SymbolicError::UnsupportedOpcode(op)),
1387        };
1388
1389        Ok(StepOutcome::Continue)
1390    }
1391
1392    fn resolve_jump_destination(
1393        &mut self,
1394        state: &PathState,
1395        jumpdests: &JumpTable,
1396        dest: SymExpr,
1397        unsupported: &'static str,
1398    ) -> Result<Option<usize>, SymbolicError> {
1399        let dest = state.expect_constrained_word(&mut self.cx, dest, unsupported)?;
1400        let Ok(dest) = usize::try_from(dest) else { return Ok(None) };
1401        Ok(jumpdests.is_valid(dest).then_some(dest))
1402    }
1403
1404    fn branch_invalid_jumpi(
1405        &mut self,
1406        state: &mut PathState,
1407        worklist: &mut VecDeque<PathState>,
1408        taken: SymBoolExpr,
1409    ) -> Result<StepOutcome, SymbolicError> {
1410        let (taken_constraints, taken_sat) =
1411            self.constraints_with_condition(state, taken.clone())?;
1412        let not_taken = taken.clone().not(&mut self.cx);
1413        let (taken_seed_models, not_taken_seed_models) = state.split_corpus_seed_models(&taken);
1414        if !taken_sat {
1415            state.constraints.push(not_taken);
1416            state.set_corpus_seed_models(not_taken_seed_models);
1417            return Ok(StepOutcome::Continue);
1418        }
1419
1420        let (not_taken_constraints, not_taken_sat) =
1421            self.constraints_with_condition(state, not_taken)?;
1422        if not_taken_sat {
1423            let mut fallthrough = state.clone();
1424            fallthrough.constraints = not_taken_constraints;
1425            fallthrough.set_corpus_seed_models(not_taken_seed_models);
1426            worklist.push_back(fallthrough);
1427        }
1428        state.constraints = taken_constraints;
1429        state.set_corpus_seed_models(taken_seed_models);
1430        state.return_data = SymReturnData::empty(&mut self.cx);
1431        Ok(StepOutcome::ExceptionalHalt)
1432    }
1433
1434    fn guard_returndata_copy_range(
1435        &mut self,
1436        state: &mut PathState,
1437        worklist: &mut VecDeque<PathState>,
1438        offset: &SymExpr,
1439        size: &SymExpr,
1440    ) -> Result<Option<StepOutcome>, SymbolicError> {
1441        if offset.contains_gasleft() || size.contains_gasleft() {
1442            return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled"));
1443        }
1444        let return_data_len = state.return_data.len_word.clone();
1445        let offset_in_bounds = SymBoolExpr::cmp_word_expr(
1446            &mut self.cx,
1447            SymCmpOp::Ule,
1448            offset,
1449            return_data_len.clone(),
1450        );
1451        let remaining =
1452            SymExpr::binop(&mut self.cx, SymBinOp::Sub, return_data_len, offset.clone());
1453        let size_in_bounds =
1454            SymBoolExpr::cmp_word_expr(&mut self.cx, SymCmpOp::Ule, size, remaining);
1455        let valid_access = SymBoolExpr::and(&mut self.cx, vec![offset_in_bounds, size_in_bounds]);
1456        self.apply_memory_access_guard(state, worklist, valid_access)
1457    }
1458
1459    pub(super) fn return_or_revert(
1460        &mut self,
1461        state: &mut PathState,
1462        is_revert: bool,
1463    ) -> Result<StepOutcome, SymbolicError> {
1464        let offset = state.stack.pop()?;
1465        let size = state.stack.pop()?;
1466        match state.constrained_usize_checked(&mut self.cx, &size) {
1467            Some(Ok(size)) => {
1468                state.return_data = state.memory.return_data(&mut self.cx, offset.clone(), size)?;
1469                if is_revert {
1470                    Ok(self.classify_revert(state, offset, size))
1471                } else {
1472                    Ok(StepOutcome::Halt)
1473                }
1474            }
1475            Some(Err(_)) => Ok(StepOutcome::Revert),
1476            None => {
1477                let max_limit = self.config.max_calldata_bytes as usize;
1478                let reason =
1479                    if is_revert { "symbolic REVERT size" } else { "symbolic RETURN size" };
1480                let max_size = self.solver_upper_bound_usize(state, &size, max_limit, reason)?;
1481                state.return_data =
1482                    state.memory.return_data_symbolic_size(&mut self.cx, offset, size, max_size)?;
1483                Ok(if is_revert { StepOutcome::Revert } else { StepOutcome::Halt })
1484            }
1485        }
1486    }
1487
1488    pub(super) fn classify_revert(
1489        &mut self,
1490        state: &PathState,
1491        offset: SymExpr,
1492        size: usize,
1493    ) -> StepOutcome {
1494        if state.call_depth == 0
1495            && let Some(offset) = offset.as_const()
1496            && let Ok(offset) = usize::try_from(offset)
1497            && let Ok(data) = state.memory.read_concrete(&mut self.cx, offset, size)
1498            && is_assertion_revert(&data)
1499        {
1500            StepOutcome::Failure
1501        } else {
1502            StepOutcome::Revert
1503        }
1504    }
1505}
1506
1507/// Returns the activation fork for opcodes implemented by the symbolic executor.
1508const fn opcode_activation(op: u8) -> SpecId {
1509    match op {
1510        opcode::DELEGATECALL => SpecId::HOMESTEAD,
1511        opcode::RETURNDATASIZE | opcode::RETURNDATACOPY | opcode::STATICCALL | opcode::REVERT => {
1512            SpecId::BYZANTIUM
1513        }
1514        opcode::SHL | opcode::SHR | opcode::SAR | opcode::EXTCODEHASH | opcode::CREATE2 => {
1515            SpecId::PETERSBURG
1516        }
1517        opcode::CHAINID | opcode::SELFBALANCE => SpecId::ISTANBUL,
1518        opcode::BASEFEE => SpecId::LONDON,
1519        opcode::PUSH0 => SpecId::SHANGHAI,
1520        opcode::TLOAD | opcode::TSTORE | opcode::MCOPY | opcode::BLOBHASH | opcode::BLOBBASEFEE => {
1521            SpecId::CANCUN
1522        }
1523        _ => SpecId::FRONTIER,
1524    }
1525}