Skip to main content

foundry_evm_symbolic/runtime/
cheatcodes.rs

1use foundry_cheatcodes_spec::Vm::*;
2use foundry_common::wallet::private_key_from_u256;
3
4use super::*;
5
6pub(crate) const fn foundry_cheatcode_min_input_size(selector: [u8; 4]) -> Option<usize> {
7    match selector {
8        recordLogsCall::SELECTOR
9        | recordCall::SELECTOR
10        | stopRecordCall::SELECTOR
11        | assumeNoRevert_0Call::SELECTOR
12        | getRecordedLogsCall::SELECTOR
13        | getRecordedLogsJsonCall::SELECTOR
14        | expectRevert_0Call::SELECTOR
15        | expectEmit_2Call::SELECTOR
16        | expectEmitAnonymous_2Call::SELECTOR
17        | clearMockedCallsCall::SELECTOR
18        | stopPrankCall::SELECTOR
19        | readCallersCall::SELECTOR
20        | getWalletsCall::SELECTOR
21        | snapshotCall::SELECTOR
22        | snapshotStateCall::SELECTOR
23        | deleteSnapshotsCall::SELECTOR
24        | deleteStateSnapshotsCall::SELECTOR
25        | activeForkCall::SELECTOR
26        | getChainIdCall::SELECTOR
27        | getBlobhashesCall::SELECTOR
28        | getBlobBaseFeeCall::SELECTOR
29        | getBlockNumberCall::SELECTOR
30        | getBlockTimestampCall::SELECTOR
31        | pauseGasMeteringCall::SELECTOR
32        | resumeGasMeteringCall::SELECTOR
33        | resetGasMeteringCall::SELECTOR
34        | isIsolateModeCall::SELECTOR
35        | lastCallGasCall::SELECTOR
36        | lastFrameGasCall::SELECTOR
37        | stopExpectSafeMemoryCall::SELECTOR
38        | stopSnapshotGas_0Call::SELECTOR
39        | getEvmVersionCall::SELECTOR
40        | getFoundryVersionCall::SELECTOR
41        | projectRootCall::SELECTOR
42        | unixTimeCall::SELECTOR
43        | noAccessListCall::SELECTOR
44        | randomUint_0Call::SELECTOR
45        | randomInt_0Call::SELECTOR
46        | randomAddressCall::SELECTOR
47        | randomBoolCall::SELECTOR
48        | randomBytes4Call::SELECTOR
49        | randomBytes8Call::SELECTOR => Some(abi_static_input_size(0)),
50        assumeCall::SELECTOR
51        | skip_0Call::SELECTOR
52        | skip_1Call::SELECTOR
53        | assumeNoRevert_1Call::SELECTOR
54        | assumeNoRevert_2Call::SELECTOR
55        | accessesCall::SELECTOR
56        | expectRevert_1Call::SELECTOR
57        | expectRevert_3Call::SELECTOR
58        | expectRevert_6Call::SELECTOR
59        | expectPartialRevert_0Call::SELECTOR
60        | expectEmit_3Call::SELECTOR
61        | expectEmit_6Call::SELECTOR
62        | expectEmitAnonymous_3Call::SELECTOR
63        | prank_0Call::SELECTOR
64        | startPrank_0Call::SELECTOR
65        | addrCall::SELECTOR
66        | rememberKeyCall::SELECTOR
67        | toString_0Call::SELECTOR
68        | toString_1Call::SELECTOR
69        | toString_2Call::SELECTOR
70        | toString_3Call::SELECTOR
71        | toString_4Call::SELECTOR
72        | toString_5Call::SELECTOR
73        | parseBytesCall::SELECTOR
74        | parseAddressCall::SELECTOR
75        | parseUintCall::SELECTOR
76        | parseIntCall::SELECTOR
77        | parseBytes32Call::SELECTOR
78        | parseBoolCall::SELECTOR
79        | toLowercaseCall::SELECTOR
80        | toUppercaseCall::SELECTOR
81        | trimCall::SELECTOR
82        | toBase64_0Call::SELECTOR
83        | toBase64_1Call::SELECTOR
84        | toBase64URL_0Call::SELECTOR
85        | toBase64URL_1Call::SELECTOR
86        | breakpoint_0Call::SELECTOR
87        | setEvmVersionCall::SELECTOR
88        | sleepCall::SELECTOR
89        | accessListCall::SELECTOR
90        | getNonce_0Call::SELECTOR
91        | resetNonceCall::SELECTOR
92        | allowCheatcodesCall::SELECTOR
93        | makePersistent_0Call::SELECTOR
94        | makePersistent_3Call::SELECTOR
95        | revokePersistent_0Call::SELECTOR
96        | revokePersistent_1Call::SELECTOR
97        | isPersistentCall::SELECTOR
98        | selectForkCall::SELECTOR
99        | createFork_0Call::SELECTOR
100        | createSelectFork_0Call::SELECTOR
101        | rollFork_0Call::SELECTOR
102        | rollFork_1Call::SELECTOR
103        | revertToCall::SELECTOR
104        | revertToStateCall::SELECTOR
105        | revertToAndDeleteCall::SELECTOR
106        | revertToStateAndDeleteCall::SELECTOR
107        | deleteSnapshotCall::SELECTOR
108        | deleteStateSnapshotCall::SELECTOR
109        | warpCall::SELECTOR
110        | rollCall::SELECTOR
111        | prevrandao_0Call::SELECTOR
112        | prevrandao_1Call::SELECTOR
113        | feeCall::SELECTOR
114        | blobBaseFeeCall::SELECTOR
115        | chainIdCall::SELECTOR
116        | difficultyCall::SELECTOR
117        | coinbaseCall::SELECTOR
118        | txGasPriceCall::SELECTOR
119        | getLabelCall::SELECTOR
120        | snapshotGasLastCall_0Call::SELECTOR
121        | snapshotGasLastFrame_0Call::SELECTOR
122        | startSnapshotGas_0Call::SELECTOR
123        | stopSnapshotGas_1Call::SELECTOR
124        | coolCall::SELECTOR
125        | isContextCall::SELECTOR
126        | assertTrue_0Call::SELECTOR
127        | assertTrue_1Call::SELECTOR
128        | assertFalse_0Call::SELECTOR
129        | assertFalse_1Call::SELECTOR
130        | randomUint_2Call::SELECTOR
131        | randomInt_1Call::SELECTOR
132        | randomBytesCall::SELECTOR => Some(abi_static_input_size(1)),
133        expectRevert_4Call::SELECTOR
134        | expectRevert_7Call::SELECTOR
135        | expectRevert_9Call::SELECTOR
136        | expectPartialRevert_1Call::SELECTOR
137        | expectEmit_7Call::SELECTOR
138        | prank_1Call::SELECTOR
139        | prank_2Call::SELECTOR
140        | startPrank_1Call::SELECTOR
141        | startPrank_2Call::SELECTOR
142        | sign_1Call::SELECTOR
143        | signCompact_1Call::SELECTOR
144        | deriveKey_0Call::SELECTOR
145        | splitCall::SELECTOR
146        | indexOfCall::SELECTOR
147        | containsCall::SELECTOR
148        | breakpoint_1Call::SELECTOR
149        | expectSafeMemoryCall::SELECTOR
150        | expectSafeMemoryCallCall::SELECTOR
151        | loadCall::SELECTOR
152        | makePersistent_1Call::SELECTOR
153        | computeCreateAddressCall::SELECTOR
154        | computeCreate2Address_1Call::SELECTOR
155        | setBlockhashCall::SELECTOR
156        | dealCall::SELECTOR
157        | setNonceCall::SELECTOR
158        | setNonceUnsafeCall::SELECTOR
159        | createFork_1Call::SELECTOR
160        | createFork_2Call::SELECTOR
161        | createSelectFork_1Call::SELECTOR
162        | createSelectFork_2Call::SELECTOR
163        | rollFork_2Call::SELECTOR
164        | rollFork_3Call::SELECTOR
165        | labelCall::SELECTOR
166        | snapshotValue_0Call::SELECTOR
167        | snapshotGasLastCall_1Call::SELECTOR
168        | snapshotGasLastFrame_1Call::SELECTOR
169        | startSnapshotGas_1Call::SELECTOR
170        | stopSnapshotGas_2Call::SELECTOR
171        | warmSlotCall::SELECTOR
172        | coolSlotCall::SELECTOR
173        | assertEq_2Call::SELECTOR
174        | assertEq_3Call::SELECTOR
175        | assertEq_4Call::SELECTOR
176        | assertEq_5Call::SELECTOR
177        | assertEq_6Call::SELECTOR
178        | assertEq_7Call::SELECTOR
179        | assertEq_8Call::SELECTOR
180        | assertEq_9Call::SELECTOR
181        | assertEq_0Call::SELECTOR
182        | assertEq_1Call::SELECTOR
183        | assertNotEq_2Call::SELECTOR
184        | assertNotEq_3Call::SELECTOR
185        | assertNotEq_4Call::SELECTOR
186        | assertNotEq_5Call::SELECTOR
187        | assertNotEq_6Call::SELECTOR
188        | assertNotEq_7Call::SELECTOR
189        | assertNotEq_8Call::SELECTOR
190        | assertNotEq_9Call::SELECTOR
191        | assertNotEq_0Call::SELECTOR
192        | assertNotEq_1Call::SELECTOR
193        | assertLt_0Call::SELECTOR
194        | assertLt_1Call::SELECTOR
195        | assertLe_0Call::SELECTOR
196        | assertLe_1Call::SELECTOR
197        | assertGt_0Call::SELECTOR
198        | assertGt_1Call::SELECTOR
199        | assertGe_0Call::SELECTOR
200        | assertGe_1Call::SELECTOR
201        | assertLt_2Call::SELECTOR
202        | assertLt_3Call::SELECTOR
203        | assertGt_2Call::SELECTOR
204        | assertGt_3Call::SELECTOR
205        | assertLe_2Call::SELECTOR
206        | assertLe_3Call::SELECTOR
207        | assertGe_2Call::SELECTOR
208        | assertGe_3Call::SELECTOR
209        | assertEq_14Call::SELECTOR
210        | assertEq_15Call::SELECTOR
211        | assertEq_16Call::SELECTOR
212        | assertEq_17Call::SELECTOR
213        | assertEq_18Call::SELECTOR
214        | assertEq_19Call::SELECTOR
215        | assertEq_20Call::SELECTOR
216        | assertEq_21Call::SELECTOR
217        | assertEq_22Call::SELECTOR
218        | assertEq_23Call::SELECTOR
219        | assertEq_24Call::SELECTOR
220        | assertEq_25Call::SELECTOR
221        | assertEq_26Call::SELECTOR
222        | assertEq_27Call::SELECTOR
223        | assertNotEq_14Call::SELECTOR
224        | assertNotEq_15Call::SELECTOR
225        | assertNotEq_16Call::SELECTOR
226        | assertNotEq_17Call::SELECTOR
227        | assertNotEq_18Call::SELECTOR
228        | assertNotEq_19Call::SELECTOR
229        | assertNotEq_20Call::SELECTOR
230        | assertNotEq_21Call::SELECTOR
231        | assertNotEq_22Call::SELECTOR
232        | assertNotEq_23Call::SELECTOR
233        | assertNotEq_24Call::SELECTOR
234        | assertNotEq_25Call::SELECTOR
235        | assertNotEq_26Call::SELECTOR
236        | assertNotEq_27Call::SELECTOR
237        | randomUint_1Call::SELECTOR => Some(abi_static_input_size(2)),
238        expectRevert_10Call::SELECTOR
239        | deriveKey_1Call::SELECTOR
240        | deriveKey_2Call::SELECTOR
241        | rememberKeys_0Call::SELECTOR
242        | storeCall::SELECTOR
243        | makePersistent_2Call::SELECTOR
244        | snapshotValue_1Call::SELECTOR
245        | replaceCall::SELECTOR
246        | assertEqDecimal_0Call::SELECTOR
247        | assertEqDecimal_2Call::SELECTOR
248        | computeCreate2Address_0Call::SELECTOR
249        | bound_0Call::SELECTOR
250        | bound_1Call::SELECTOR => Some(abi_static_input_size(3)),
251        expectEmit_0Call::SELECTOR
252        | deriveKey_3Call::SELECTOR
253        | rememberKeys_1Call::SELECTOR
254        | assertEqDecimal_1Call::SELECTOR
255        | assertEqDecimal_3Call::SELECTOR => Some(abi_static_input_size(4)),
256        expectEmitAnonymous_0Call::SELECTOR
257        | expectEmit_1Call::SELECTOR
258        | expectEmit_4Call::SELECTOR => Some(abi_static_input_size(5)),
259        expectEmit_5Call::SELECTOR | expectEmitAnonymous_1Call::SELECTOR => {
260            Some(abi_static_input_size(6))
261        }
262        _ => None,
263    }
264}
265
266pub(crate) fn symbolic_vm_cheatcode_min_input_size(selector: [u8; 4]) -> Option<usize> {
267    SymbolicVmCheatcode::from_selector(selector)
268        .map(|cheatcode| abi_static_input_size(cheatcode.min_input_words()))
269}
270
271pub(crate) const fn abi_static_input_size(words: usize) -> usize {
272    4 + words * 32
273}
274
275pub(crate) fn abi_bytes_return(cx: &mut SymCx, bytes: Vec<SymExpr>) -> SymReturnData {
276    let len = SymExpr::constant(cx, U256::from(bytes.len()));
277    abi_bytes_return_with_len(cx, len, bytes)
278}
279
280pub(crate) fn abi_bytes_return_with_len(
281    cx: &mut SymCx,
282    len: SymExpr,
283    bytes: Vec<SymExpr>,
284) -> SymReturnData {
285    let mut out = SymExpr::constant(cx, U256::from(32)).into_byte_exprs(cx);
286    out.extend(len.into_byte_exprs(cx));
287    out.extend(bytes.iter().cloned());
288    out.resize_with(64 + bytes.len().next_multiple_of(32), || SymExpr::zero(cx));
289    SymReturnData::from_byte_exprs(cx, out)
290}
291
292pub(crate) fn abi_concrete_bytes_return(cx: &mut SymCx, bytes: &[u8]) -> SymReturnData {
293    let bytes = bytes.iter().map(|byte| SymExpr::constant(cx, U256::from(*byte))).collect();
294    abi_bytes_return(cx, bytes)
295}
296
297pub(crate) fn abi_concrete_value_return(cx: &mut SymCx, value: DynSolValue) -> SymReturnData {
298    let bytes = value
299        .abi_encode()
300        .into_iter()
301        .map(|byte| SymExpr::constant(cx, U256::from(byte)))
302        .collect();
303    SymReturnData::from_byte_exprs(cx, bytes)
304}
305
306pub(crate) fn recorded_logs_return_data(cx: &mut SymCx, logs: Vec<SymbolicLog>) -> SymReturnData {
307    let value = SymbolicAbiValue::Array {
308        elements: logs
309            .into_iter()
310            .map(|log| {
311                let (topics, data_len, data, emitter) = log.into_parts();
312                let topics = topics
313                    .iter()
314                    .cloned()
315                    .map(|topic| SymbolicAbiValue::FixedBytes {
316                        bytes: topic.into_bytes(cx),
317                        size: 32,
318                    })
319                    .collect();
320                SymbolicAbiValue::Tuple {
321                    elements: vec![
322                        SymbolicAbiValue::Array { elements: topics },
323                        SymbolicAbiValue::Bytes { len: data_len, bytes: data },
324                        SymbolicAbiValue::Address {
325                            word: SymExpr::constant(cx, address_word(emitter)),
326                        },
327                    ],
328                }
329            })
330            .collect(),
331    };
332    let bytes = encode_sequence(cx, std::iter::once(&value));
333    SymReturnData::from_bytes(cx, bytes)
334}
335
336pub(crate) fn recorded_logs_json_return_data(
337    cx: &mut SymCx,
338    logs: Vec<SymbolicLog>,
339) -> Result<SymReturnData, SymbolicError> {
340    let mut bytes = Vec::new();
341    push_ascii(cx, &mut bytes, "[");
342    for (log_idx, log) in logs.into_iter().enumerate() {
343        let (topics, data_len, data, emitter) = log.into_parts();
344        if log_idx > 0 {
345            push_ascii(cx, &mut bytes, ",");
346        }
347        push_ascii(cx, &mut bytes, "{\"topics\":[");
348        for (topic_idx, topic) in topics.iter().cloned().enumerate() {
349            if topic_idx > 0 {
350                push_ascii(cx, &mut bytes, ",");
351            }
352            push_ascii(cx, &mut bytes, "\"0x");
353            push_hex_word(cx, &mut bytes, topic);
354            push_ascii(cx, &mut bytes, "\"");
355        }
356        push_ascii(cx, &mut bytes, "],\"data\":\"0x");
357
358        let len = data_len.as_const_or("symbolic vm.getRecordedLogsJson data length").and_then(
359            |len| {
360                usize::try_from(len).map_err(|_| {
361                    SymbolicError::Unsupported("symbolic vm.getRecordedLogsJson data length")
362                })
363            },
364        )?;
365        if len > data.len() {
366            return Err(SymbolicError::Unsupported("symbolic vm.getRecordedLogsJson data length"));
367        }
368        for idx in 0..len {
369            let byte = data.byte(cx, idx);
370            push_hex_byte(cx, &mut bytes, byte);
371        }
372
373        push_ascii(cx, &mut bytes, "\",\"emitter\":\"");
374        push_ascii(cx, &mut bytes, &format!("{emitter}"));
375        push_ascii(cx, &mut bytes, "\"}");
376    }
377    push_ascii(cx, &mut bytes, "]");
378    Ok(abi_bytes_return(cx, bytes))
379}
380
381pub(crate) fn accesses_return_data(
382    cx: &mut SymCx,
383    record: Option<&AccessRecord>,
384    target: Address,
385) -> SymReturnData {
386    let reads = record.map(|record| record.read_slots(target)).unwrap_or_default();
387    let writes = record.map(|record| record.write_slots(target)).unwrap_or_default();
388    let values = [storage_slots_abi_array(cx, reads), storage_slots_abi_array(cx, writes)];
389    let bytes = encode_sequence(cx, values.iter());
390    SymReturnData::from_bytes(cx, bytes)
391}
392
393pub(crate) fn complete_cheatcode_call(
394    cx: &mut SymCx,
395    state: &mut PathState,
396    out_offset: SymExpr,
397    out_size: &BoundedCopySize,
398    return_data: SymReturnData,
399) -> Result<(), SymbolicError> {
400    state.return_data = return_data;
401    state.copy_call_output_offset(cx, out_offset, out_size)?;
402    state.stack.push(SymExpr::one(cx))?;
403    Ok(())
404}
405
406pub(crate) fn storage_slots_abi_array(cx: &mut SymCx, slots: Vec<SymExpr>) -> SymbolicAbiValue {
407    SymbolicAbiValue::Array {
408        elements: slots
409            .into_iter()
410            .map(|slot| SymbolicAbiValue::FixedBytes { bytes: slot.into_bytes(cx), size: 32 })
411            .collect(),
412    }
413}
414
415pub(crate) fn push_ascii(cx: &mut SymCx, out: &mut Vec<SymExpr>, value: &str) {
416    out.extend(value.bytes().map(|byte| SymExpr::constant(cx, U256::from(byte))));
417}
418
419pub(crate) fn push_hex_word(cx: &mut SymCx, out: &mut Vec<SymExpr>, word: SymExpr) {
420    for byte in word.into_byte_exprs(cx) {
421        push_hex_byte(cx, out, byte);
422    }
423}
424
425pub(crate) fn push_hex_byte(cx: &mut SymCx, out: &mut Vec<SymExpr>, byte: SymExpr) {
426    let byte = byte.low_byte(cx);
427    let (high, low) = if let Some(value) = byte.as_const() {
428        (
429            SymExpr::constant(cx, U256::from(value.to::<u8>() >> 4)),
430            SymExpr::constant(cx, U256::from(value.to::<u8>() & 0x0f)),
431        )
432    } else {
433        let expr = byte;
434        let shift = SymExpr::constant(cx, U256::from(4));
435        let mask = SymExpr::constant(cx, U256::from(0x0f));
436        (
437            SymExpr::binop(cx, SymBinOp::Shr, expr.clone(), shift),
438            SymExpr::binop(cx, SymBinOp::And, expr, mask),
439        )
440    };
441    out.push(hex_nibble_ascii(cx, high));
442    out.push(hex_nibble_ascii(cx, low));
443}
444
445pub(crate) fn hex_nibble_ascii(cx: &mut SymCx, nibble: SymExpr) -> SymExpr {
446    let nibble = nibble.low_byte(cx);
447    if let Some(value) = nibble.as_const() {
448        let nibble = value.to::<u8>() & 0x0f;
449        let byte = if nibble < 10 { b'0' + nibble } else { b'a' + (nibble - 10) };
450        SymExpr::constant(cx, U256::from(byte))
451    } else {
452        let ten = SymExpr::constant(cx, U256::from(10));
453        let condition = SymBoolExpr::cmp(cx, SymCmpOp::Ult, nibble.clone(), ten);
454        let zero = SymExpr::constant(cx, U256::from(b'0'));
455        let digit = SymExpr::binop(cx, SymBinOp::Add, nibble.clone(), zero);
456        let alpha_base = SymExpr::constant(cx, U256::from(b'a' - 10));
457        let alpha = SymExpr::binop(cx, SymBinOp::Add, nibble, alpha_base);
458        SymExpr::ite(cx, condition, digit, alpha)
459    }
460}
461
462pub(crate) fn read_abi_word_arg(
463    cx: &mut SymCx,
464    memory: &SymMemory,
465    args_offset: usize,
466    index: usize,
467) -> Result<SymExpr, SymbolicError> {
468    memory.load_word(cx, args_offset + index * 32)
469}
470
471pub(crate) fn read_abi_concrete_word_arg(
472    cx: &mut SymCx,
473    memory: &SymMemory,
474    args_offset: usize,
475    index: usize,
476    reason: &'static str,
477) -> Result<U256, SymbolicError> {
478    read_abi_word_arg(cx, memory, args_offset, index)?.as_const_or(reason)
479}
480
481pub(crate) fn read_abi_constrained_word_arg(
482    cx: &mut SymCx,
483    state: &PathState,
484    args_offset: usize,
485    index: usize,
486    reason: &'static str,
487) -> Result<U256, SymbolicError> {
488    let word = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
489    state.expect_constrained_word(cx, word, reason)
490}
491
492pub(crate) fn read_abi_constrained_address_arg(
493    cx: &mut SymCx,
494    state: &PathState,
495    args_offset: usize,
496    index: usize,
497    reason: &'static str,
498) -> Result<Address, SymbolicError> {
499    Ok(word_to_address(read_abi_constrained_word_arg(cx, state, args_offset, index, reason)?))
500}
501
502pub(crate) fn read_abi_address_or_symbolic_slot_arg(
503    cx: &mut SymCx,
504    state: &mut PathState,
505    args_offset: usize,
506    index: usize,
507) -> Result<Address, SymbolicError> {
508    let word = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
509    Ok(state.address_or_symbolic_slot(cx, word))
510}
511
512pub(crate) fn read_abi_address_word_or_symbolic_slot_arg(
513    cx: &mut SymCx,
514    state: &mut PathState,
515    args_offset: usize,
516    index: usize,
517) -> Result<(Address, SymExpr), SymbolicError> {
518    let word = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
519    let address = state.address_or_symbolic_slot(cx, word.clone());
520    Ok((address, word))
521}
522
523pub(crate) fn read_abi_address_arg(
524    cx: &mut SymCx,
525    memory: &SymMemory,
526    args_offset: usize,
527    index: usize,
528    reason: &'static str,
529) -> Result<Address, SymbolicError> {
530    Ok(word_to_address(read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?))
531}
532
533pub(crate) fn read_abi_bool_arg(
534    cx: &mut SymCx,
535    memory: &SymMemory,
536    args_offset: usize,
537    index: usize,
538    reason: &'static str,
539) -> Result<bool, SymbolicError> {
540    Ok(!read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?.is_zero())
541}
542
543pub(crate) fn read_abi_u64_arg(
544    cx: &mut SymCx,
545    memory: &SymMemory,
546    args_offset: usize,
547    index: usize,
548    reason: &'static str,
549) -> Result<u64, SymbolicError> {
550    let value = read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?;
551    u64::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
552}
553
554pub(crate) fn read_abi_u32_arg(
555    cx: &mut SymCx,
556    memory: &SymMemory,
557    args_offset: usize,
558    index: usize,
559    reason: &'static str,
560) -> Result<u32, SymbolicError> {
561    let value = read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?;
562    u32::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
563}
564
565pub(crate) fn read_abi_bytes4_words_arg(
566    cx: &mut SymCx,
567    memory: &SymMemory,
568    args_offset: usize,
569    index: usize,
570) -> Vec<SymExpr> {
571    memory.read_byte_exprs(cx, args_offset + index * 32, 4)
572}
573
574pub(crate) fn read_abi_dynamic_bytes_arg(
575    cx: &mut SymCx,
576    memory: &SymMemory,
577    args_offset: usize,
578    index: usize,
579    reason: &'static str,
580) -> Result<Vec<u8>, SymbolicError> {
581    let offset =
582        usize::try_from(read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?)
583            .map_err(|_| SymbolicError::Unsupported(reason))?;
584    let len_offset = args_offset.checked_add(offset).ok_or(SymbolicError::Unsupported(reason))?;
585    let data_offset = len_offset.checked_add(32).ok_or(SymbolicError::Unsupported(reason))?;
586    let len = memory.load_word(cx, len_offset)?.as_usize_or(reason)?;
587    memory.read_concrete(cx, data_offset, len)
588}
589
590pub(crate) fn read_abi_symbolic_dynamic_bytes_arg(
591    cx: &mut SymCx,
592    state: &PathState,
593    args_offset: usize,
594    index: usize,
595    max_len: usize,
596    reason: &'static str,
597) -> Result<SymBytes, SymbolicError> {
598    let offset = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
599    let offset = state.expect_constrained_usize(cx, offset, reason)?;
600    let len_offset = args_offset.checked_add(offset).ok_or(SymbolicError::Unsupported(reason))?;
601    let len = state.memory.load_word(cx, len_offset)?;
602    let len = state.expect_constrained_usize(cx, len, reason)?;
603    if len > max_len {
604        return Err(SymbolicError::Unsupported(reason));
605    }
606    let data_offset = len_offset.checked_add(32).ok_or(SymbolicError::Unsupported(reason))?;
607    Ok(state.memory.read_bytes(cx, data_offset, len))
608}
609
610pub(crate) fn read_abi_symbolic_dynamic_byte_exprs_arg(
611    cx: &mut SymCx,
612    state: &PathState,
613    args_offset: usize,
614    index: usize,
615    max_len: usize,
616    reason: &'static str,
617) -> Result<Vec<SymExpr>, SymbolicError> {
618    Ok(read_abi_symbolic_dynamic_bytes_arg(cx, state, args_offset, index, max_len, reason)?
619        .materialize(cx))
620}
621
622pub(crate) fn read_abi_dynamic_return_data_arg(
623    cx: &mut SymCx,
624    state: &PathState,
625    args_offset: usize,
626    index: usize,
627    max_len: usize,
628    reason: &'static str,
629) -> Result<SymReturnData, SymbolicError> {
630    let bytes =
631        read_abi_symbolic_dynamic_bytes_arg(cx, state, args_offset, index, max_len, reason)?;
632    Ok(SymReturnData::from_bytes(cx, bytes))
633}
634
635pub(crate) fn read_abi_symbolic_dynamic_bytes_array_arg(
636    cx: &mut SymCx,
637    state: &PathState,
638    args_offset: usize,
639    index: usize,
640    max_array_len: usize,
641    max_bytes_len: usize,
642) -> Result<Vec<SymReturnData>, SymbolicError> {
643    let offset = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
644    let offset =
645        state.expect_constrained_usize(cx, offset, "symbolic vm.mockCalls returns offset")?;
646    let array_offset = args_offset
647        .checked_add(offset)
648        .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns offset"))?;
649    let array_data_offset = array_offset
650        .checked_add(32)
651        .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns offset"))?;
652    let len = state.memory.load_word(cx, array_offset)?;
653    let len = state.expect_constrained_usize(cx, len, "symbolic vm.mockCalls returns length")?;
654    if len > max_array_len {
655        return Err(SymbolicError::Unsupported("symbolic vm.mockCalls returns length"));
656    }
657
658    let mut values = Vec::with_capacity(len);
659    for value_idx in 0..len {
660        let head_offset = array_offset
661            .checked_add(32)
662            .and_then(|offset| offset.checked_add(value_idx.saturating_mul(32)))
663            .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns element offset"))?;
664        let value_offset = state.memory.load_word(cx, head_offset)?;
665        let value_offset = state.expect_constrained_usize(
666            cx,
667            value_offset,
668            "symbolic vm.mockCalls returns element offset",
669        )?;
670        let len_offset = array_data_offset
671            .checked_add(value_offset)
672            .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns element offset"))?;
673        let value_len = state.memory.load_word(cx, len_offset)?;
674        let value_len = state.expect_constrained_usize(
675            cx,
676            value_len,
677            "symbolic vm.mockCalls returns element length",
678        )?;
679        if value_len > max_bytes_len {
680            return Err(SymbolicError::Unsupported("symbolic vm.mockCalls returns element length"));
681        }
682        let data_offset = len_offset
683            .checked_add(32)
684            .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns element offset"))?;
685        let bytes = state.memory.read_bytes(cx, data_offset, value_len);
686        values.push(SymReturnData::from_bytes(cx, bytes));
687    }
688
689    Ok(values)
690}
691
692pub(crate) fn read_abi_string_arg(
693    cx: &mut SymCx,
694    memory: &SymMemory,
695    args_offset: usize,
696    index: usize,
697    reason: &'static str,
698) -> Result<String, SymbolicError> {
699    String::from_utf8(read_abi_dynamic_bytes_arg(cx, memory, args_offset, index, reason)?)
700        .map_err(|_| SymbolicError::Unsupported(reason))
701}
702
703pub(crate) fn decode_cheatcode_args(
704    cx: &mut SymCx,
705    state: &PathState,
706    in_offset: usize,
707    in_size: usize,
708    tys: Vec<DynSolType>,
709) -> Result<Vec<DynSolValue>, SymbolicError> {
710    let data = state.memory.read_concrete(cx, in_offset + 4, in_size.saturating_sub(4))?;
711    let value = DynSolType::Tuple(tys)
712        .abi_decode_sequence(&data)
713        .map_err(|_| SymbolicError::Unsupported("symbolic cheatcode ABI decode"))?;
714    let DynSolValue::Tuple(values) = value else {
715        return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
716    };
717    Ok(values)
718}
719
720pub(crate) const fn selector_has_string_reason(selector: [u8; 4]) -> bool {
721    matches!(
722        selector,
723        assertEq_15Call::SELECTOR
724            | assertEq_17Call::SELECTOR
725            | assertEq_19Call::SELECTOR
726            | assertEq_21Call::SELECTOR
727            | assertEq_23Call::SELECTOR
728            | assertEq_25Call::SELECTOR
729            | assertEq_27Call::SELECTOR
730            | assertNotEq_15Call::SELECTOR
731            | assertNotEq_17Call::SELECTOR
732            | assertNotEq_19Call::SELECTOR
733            | assertNotEq_21Call::SELECTOR
734            | assertNotEq_23Call::SELECTOR
735            | assertNotEq_25Call::SELECTOR
736            | assertNotEq_27Call::SELECTOR
737            | assertEqDecimal_1Call::SELECTOR
738            | assertEqDecimal_3Call::SELECTOR
739    )
740}
741
742pub(crate) const fn array_assertion_element_type(
743    selector: [u8; 4],
744) -> Result<DynSolType, SymbolicError> {
745    match selector {
746        assertEq_14Call::SELECTOR
747        | assertEq_15Call::SELECTOR
748        | assertNotEq_14Call::SELECTOR
749        | assertNotEq_15Call::SELECTOR => Ok(DynSolType::Bool),
750        assertEq_16Call::SELECTOR
751        | assertEq_17Call::SELECTOR
752        | assertNotEq_16Call::SELECTOR
753        | assertNotEq_17Call::SELECTOR => Ok(DynSolType::Uint(256)),
754        assertEq_18Call::SELECTOR
755        | assertEq_19Call::SELECTOR
756        | assertNotEq_18Call::SELECTOR
757        | assertNotEq_19Call::SELECTOR => Ok(DynSolType::Int(256)),
758        assertEq_20Call::SELECTOR
759        | assertEq_21Call::SELECTOR
760        | assertNotEq_20Call::SELECTOR
761        | assertNotEq_21Call::SELECTOR => Ok(DynSolType::Address),
762        assertEq_22Call::SELECTOR
763        | assertEq_23Call::SELECTOR
764        | assertNotEq_22Call::SELECTOR
765        | assertNotEq_23Call::SELECTOR => Ok(DynSolType::FixedBytes(32)),
766        assertEq_24Call::SELECTOR
767        | assertEq_25Call::SELECTOR
768        | assertNotEq_24Call::SELECTOR
769        | assertNotEq_25Call::SELECTOR => Ok(DynSolType::String),
770        assertEq_26Call::SELECTOR
771        | assertEq_27Call::SELECTOR
772        | assertNotEq_26Call::SELECTOR
773        | assertNotEq_27Call::SELECTOR => Ok(DynSolType::Bytes),
774        _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
775    }
776}
777
778pub(crate) fn dyn_string(value: &DynSolValue) -> Result<String, SymbolicError> {
779    match value {
780        DynSolValue::String(value) => Ok(value.clone()),
781        _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
782    }
783}
784
785pub(crate) fn dyn_bytes(value: &DynSolValue) -> Result<Vec<u8>, SymbolicError> {
786    match value {
787        DynSolValue::Bytes(value) => Ok(value.clone()),
788        _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
789    }
790}
791
792pub(crate) const fn dyn_bool(value: &DynSolValue) -> Result<bool, SymbolicError> {
793    match value {
794        DynSolValue::Bool(value) => Ok(*value),
795        _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
796    }
797}
798
799pub(crate) const fn dyn_address(value: &DynSolValue) -> Result<Address, SymbolicError> {
800    match value {
801        DynSolValue::Address(value) => Ok(*value),
802        _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
803    }
804}
805
806pub(crate) fn dyn_potential_revert(
807    cx: &mut SymCx,
808    value: &DynSolValue,
809) -> Result<ExpectedRevert, SymbolicError> {
810    let DynSolValue::Tuple(values) = value else {
811        return Err(SymbolicError::Unsupported("symbolic vm.assumeNoRevert decode"));
812    };
813    let [reverter, partial_match, revert_data] = values.as_slice() else {
814        return Err(SymbolicError::Unsupported("symbolic vm.assumeNoRevert decode"));
815    };
816
817    let reverter = dyn_address(reverter)?;
818    let reverter =
819        (reverter != Address::ZERO).then(|| SymExpr::constant(cx, address_word(reverter)));
820    let revert_data = SymBytes::concrete(cx, dyn_bytes(revert_data)?);
821    let data = if dyn_bool(partial_match)? {
822        ExpectedRevertData::prefix(revert_data)
823    } else {
824        ExpectedRevertData::exact(revert_data)
825    };
826    Ok(ExpectedRevert::new(data, reverter, 1))
827}
828
829pub(crate) fn dyn_potential_reverts(
830    cx: &mut SymCx,
831    value: &DynSolValue,
832) -> Result<Vec<ExpectedRevert>, SymbolicError> {
833    let DynSolValue::Array(values) = value else {
834        return Err(SymbolicError::Unsupported("symbolic vm.assumeNoRevert decode"));
835    };
836    values.iter().map(|value| dyn_potential_revert(cx, value)).collect()
837}
838
839pub(crate) fn dyn_address_array(value: &DynSolValue) -> Result<Vec<Address>, SymbolicError> {
840    let DynSolValue::Array(values) = value else {
841        return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
842    };
843    values.iter().map(dyn_address).collect()
844}
845
846pub(crate) fn dyn_bytes32_array(value: &DynSolValue) -> Result<Vec<B256>, SymbolicError> {
847    let DynSolValue::Array(values) = value else {
848        return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
849    };
850    values
851        .iter()
852        .map(|value| match value {
853            DynSolValue::FixedBytes(bytes, 32) => Ok(*bytes),
854            _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
855        })
856        .collect()
857}
858
859pub(crate) fn dyn_string_array(value: &DynSolValue) -> Result<Vec<String>, SymbolicError> {
860    let DynSolValue::Array(values) = value else {
861        return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
862    };
863    values.iter().map(dyn_string).collect()
864}
865
866pub(crate) fn parse_env_array<F>(
867    value: &str,
868    delimiter: &str,
869    mut parser: F,
870) -> Result<DynSolValue, SymbolicError>
871where
872    F: FnMut(&str) -> Result<DynSolValue, SymbolicError>,
873{
874    if delimiter.is_empty() {
875        return Err(SymbolicError::Unsupported("symbolic env delimiter"));
876    }
877    value.split(delimiter).map(&mut parser).collect::<Result<Vec<_>, _>>().map(DynSolValue::Array)
878}
879
880pub(crate) fn parse_env_bool_value(value: &str) -> Result<DynSolValue, SymbolicError> {
881    Ok(DynSolValue::Bool(parse_env_bool(value)?))
882}
883
884pub(crate) fn parse_env_uint_value(value: &str) -> Result<DynSolValue, SymbolicError> {
885    Ok(DynSolValue::Uint(parse_env_uint(value)?, 256))
886}
887
888pub(crate) fn parse_env_int_value(value: &str) -> Result<DynSolValue, SymbolicError> {
889    Ok(DynSolValue::Int(I256::from_raw(parse_env_int(value)?), 256))
890}
891
892pub(crate) fn parse_env_address_value(value: &str) -> Result<DynSolValue, SymbolicError> {
893    Ok(DynSolValue::Address(parse_env_address(value)?))
894}
895
896pub(crate) fn parse_env_bytes32_value(value: &str) -> Result<DynSolValue, SymbolicError> {
897    Ok(DynSolValue::FixedBytes(B256::from(parse_env_bytes32(value)?.to_be_bytes::<32>()), 32))
898}
899
900pub(crate) fn parse_env_string_value(value: &str) -> Result<DynSolValue, SymbolicError> {
901    Ok(DynSolValue::String(value.to_string()))
902}
903
904pub(crate) fn parse_env_bytes_value(value: &str) -> Result<DynSolValue, SymbolicError> {
905    Ok(DynSolValue::Bytes(parse_env_bytes(value)?))
906}
907
908pub(crate) fn parse_env_uint(value: &str) -> Result<U256, SymbolicError> {
909    value.parse::<U256>().map_err(|_| SymbolicError::Unsupported("symbolic env uint parse"))
910}
911
912pub(crate) fn parse_env_int(value: &str) -> Result<U256, SymbolicError> {
913    if let Some(value) = value.strip_prefix('-') {
914        let magnitude = parse_env_uint(value)?;
915        Ok(U256::ZERO.wrapping_sub(magnitude))
916    } else {
917        parse_env_uint(value)
918    }
919}
920
921pub(crate) fn parse_env_bool(value: &str) -> Result<bool, SymbolicError> {
922    match value {
923        "true" | "1" | "TRUE" | "True" => Ok(true),
924        "false" | "0" | "FALSE" | "False" => Ok(false),
925        _ => Err(SymbolicError::Unsupported("symbolic env bool parse")),
926    }
927}
928
929pub(crate) fn parse_env_bytes(value: &str) -> Result<Vec<u8>, SymbolicError> {
930    let value = value.strip_prefix("0x").unwrap_or(value);
931    hex::decode(value).map_err(|_| SymbolicError::Unsupported("symbolic env bytes parse"))
932}
933
934pub(crate) fn parse_env_bytes32(value: &str) -> Result<U256, SymbolicError> {
935    let bytes = parse_env_bytes(value)?;
936    if bytes.len() != 32 {
937        return Err(SymbolicError::Unsupported("symbolic env bytes32 parse"));
938    }
939    Ok(U256::from_be_slice(&bytes))
940}
941
942pub(crate) fn parse_env_address(value: &str) -> Result<Address, SymbolicError> {
943    value.parse::<Address>().map_err(|_| SymbolicError::Unsupported("symbolic env address parse"))
944}
945
946pub(crate) fn private_key_signer(private_key: U256) -> Result<PrivateKeySigner, SymbolicError> {
947    private_key_from_u256(private_key)
948        .map_err(|_| SymbolicError::Unsupported("symbolic private key parse"))
949}
950
951pub(crate) fn private_key_address(private_key: U256) -> Result<Address, SymbolicError> {
952    Ok(private_key_signer(private_key)?.address())
953}
954
955pub(crate) fn sign_hash_words(
956    cx: &mut SymCx,
957    private_key: U256,
958    digest: U256,
959) -> Result<Vec<SymExpr>, SymbolicError> {
960    let signer = private_key_signer(private_key)?;
961    let digest = B256::from(digest.to_be_bytes::<32>());
962    let sig = signer
963        .sign_hash_sync(&digest)
964        .map_err(|_| SymbolicError::Unsupported("symbolic vm.sign"))?;
965    Ok(vec![
966        SymExpr::constant(cx, U256::from(sig.v_byte())),
967        SymExpr::constant(cx, sig.r()),
968        SymExpr::constant(cx, sig.s()),
969    ])
970}
971
972pub(crate) fn sign_compact_hash_words(
973    cx: &mut SymCx,
974    private_key: U256,
975    digest: U256,
976) -> Result<Vec<SymExpr>, SymbolicError> {
977    let signer = private_key_signer(private_key)?;
978    let digest = B256::from(digest.to_be_bytes::<32>());
979    let sig = signer
980        .sign_hash_sync(&digest)
981        .map_err(|_| SymbolicError::Unsupported("symbolic vm.signCompact"))?;
982    let y_parity = U256::from(sig.v() as u64) << 255;
983    Ok(vec![SymExpr::constant(cx, sig.r()), SymExpr::constant(cx, sig.s() | y_parity)])
984}
985
986pub(crate) fn derive_private_key<W: Wordlist>(
987    mnemonic: &str,
988    path: &str,
989    index: u32,
990) -> Result<U256, SymbolicError> {
991    foundry_common::wallet::derive_private_key::<W>(mnemonic, path, index)
992        .map_err(|_| SymbolicError::Unsupported("symbolic vm.deriveKey"))
993}
994
995pub(crate) fn derive_private_key_with_language(
996    mnemonic: &str,
997    path: &str,
998    index: u32,
999    language: &str,
1000) -> Result<U256, SymbolicError> {
1001    foundry_common::wallet::derive_private_key_with_language(mnemonic, path, index, language)
1002        .map_err(|_| SymbolicError::Unsupported("symbolic vm.deriveKey language"))
1003}
1004
1005pub(crate) fn artifact_json_path(path: &str) -> PathBuf {
1006    if path.ends_with(".json") {
1007        return PathBuf::from(path);
1008    }
1009
1010    let mut parts = path.split(':');
1011    let first = parts.next().unwrap_or_default();
1012    let second = parts.next();
1013
1014    if first.contains('.') {
1015        let file = Path::new(first);
1016        let contract = second
1017            .map(str::to_string)
1018            .or_else(|| file.file_stem().map(|stem| stem.to_string_lossy().to_string()))
1019            .unwrap_or_else(|| first.to_string());
1020        PathBuf::from("out").join(first).join(format!("{contract}.json"))
1021    } else {
1022        let contract = first;
1023        PathBuf::from("out").join(format!("{contract}.sol")).join(format!("{contract}.json"))
1024    }
1025}
1026
1027/// Returns fallback paths for symbolic artifact lookup.
1028pub(crate) fn artifact_json_fallback_paths(path: &str) -> Vec<PathBuf> {
1029    if path.ends_with(".json") {
1030        return Vec::new();
1031    }
1032
1033    let mut parts = path.split(':');
1034    let first = parts.next().unwrap_or_default();
1035    let second = parts.next();
1036    let Some(file_name) = first.rsplit(['/', '\\']).find(|part| !part.is_empty()) else {
1037        return Vec::new();
1038    };
1039
1040    if !first.contains('/') && !first.contains('\\') {
1041        return Vec::new();
1042    }
1043
1044    let contract = second.map(str::to_string).unwrap_or_else(|| {
1045        file_name.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(file_name).to_string()
1046    });
1047    vec![PathBuf::from("out").join(file_name).join(format!("{contract}.json"))]
1048}
1049
1050pub(crate) fn artifact_code(path: &str, deployed: bool) -> Result<Vec<u8>, SymbolicError> {
1051    let mut data = None;
1052    for path in std::iter::once(artifact_json_path(path)).chain(artifact_json_fallback_paths(path))
1053    {
1054        if let Ok(contents) = std::fs::read_to_string(path) {
1055            data = Some(contents);
1056            break;
1057        }
1058    }
1059    let data = data.ok_or(SymbolicError::Unsupported("symbolic vm.getCode artifact"))?;
1060    let artifact: serde_json::Value = serde_json::from_str(&data)
1061        .map_err(|_| SymbolicError::Unsupported("symbolic vm.getCode artifact"))?;
1062    let key = if deployed { "deployedBytecode" } else { "bytecode" };
1063    let object = artifact
1064        .get(key)
1065        .and_then(|value| value.get("object").or(Some(value)))
1066        .and_then(serde_json::Value::as_str)
1067        .ok_or(SymbolicError::Unsupported("symbolic vm.getCode artifact"))?;
1068    hex::decode(object).map_err(|_| SymbolicError::Unsupported("symbolic vm.getCode artifact"))
1069}