Skip to main content

foundry_evm_symbolic/runtime/
cheatcodes.rs

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