1use alloy_sol_types::SolError;
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 let bytes = bytes.iter().map(|byte| SymExpr::constant(cx, U256::from(*byte))).collect();
298 abi_bytes_return(cx, bytes)
299}
300
301pub(crate) fn abi_concrete_value_return(cx: &mut SymCx, value: DynSolValue) -> SymReturnData {
302 let bytes = value
303 .abi_encode()
304 .into_iter()
305 .map(|byte| SymExpr::constant(cx, U256::from(byte)))
306 .collect();
307 SymReturnData::from_byte_exprs(cx, bytes)
308}
309
310pub(crate) fn error_string_return_data(cx: &mut SymCx, reason: &str) -> SymReturnData {
311 let bytes = Vm::CheatcodeError { message: reason.to_string() }.abi_encode();
312 let bytes = bytes.into_iter().map(|byte| SymExpr::constant(cx, U256::from(byte))).collect();
313 SymReturnData::from_byte_exprs(cx, bytes)
314}
315
316pub(crate) fn recorded_logs_return_data(cx: &mut SymCx, logs: Vec<SymbolicLog>) -> SymReturnData {
317 let value = SymbolicAbiValue::Array {
318 elements: logs
319 .into_iter()
320 .map(|log| {
321 let (topics, data_len, data, emitter) = log.into_parts();
322 let topics = topics
323 .iter()
324 .cloned()
325 .map(|topic| SymbolicAbiValue::FixedBytes {
326 bytes: topic.into_bytes(cx),
327 size: 32,
328 })
329 .collect();
330 SymbolicAbiValue::Tuple {
331 elements: vec![
332 SymbolicAbiValue::Array { elements: topics },
333 SymbolicAbiValue::Bytes { len: data_len, bytes: data },
334 SymbolicAbiValue::Address {
335 word: SymExpr::constant(cx, address_word(emitter)),
336 },
337 ],
338 }
339 })
340 .collect(),
341 };
342 let bytes = encode_sequence(cx, std::iter::once(&value));
343 SymReturnData::from_bytes(cx, bytes)
344}
345
346pub(crate) fn recorded_logs_json_return_data(
347 cx: &mut SymCx,
348 logs: Vec<SymbolicLog>,
349) -> Result<SymReturnData, SymbolicError> {
350 let mut bytes = Vec::new();
351 push_ascii(cx, &mut bytes, "[");
352 for (log_idx, log) in logs.into_iter().enumerate() {
353 let (topics, data_len, data, emitter) = log.into_parts();
354 if log_idx > 0 {
355 push_ascii(cx, &mut bytes, ",");
356 }
357 push_ascii(cx, &mut bytes, "{\"topics\":[");
358 for (topic_idx, topic) in topics.iter().cloned().enumerate() {
359 if topic_idx > 0 {
360 push_ascii(cx, &mut bytes, ",");
361 }
362 push_ascii(cx, &mut bytes, "\"0x");
363 push_hex_word(cx, &mut bytes, topic);
364 push_ascii(cx, &mut bytes, "\"");
365 }
366 push_ascii(cx, &mut bytes, "],\"data\":\"0x");
367
368 let len = data_len.as_const_or("symbolic vm.getRecordedLogsJson data length").and_then(
369 |len| {
370 usize::try_from(len).map_err(|_| {
371 SymbolicError::Unsupported("symbolic vm.getRecordedLogsJson data length")
372 })
373 },
374 )?;
375 if len > data.len() {
376 return Err(SymbolicError::Unsupported("symbolic vm.getRecordedLogsJson data length"));
377 }
378 for idx in 0..len {
379 let byte = data.byte(cx, idx);
380 push_hex_byte(cx, &mut bytes, byte);
381 }
382
383 push_ascii(cx, &mut bytes, "\",\"emitter\":\"");
384 push_ascii(cx, &mut bytes, &format!("{emitter}"));
385 push_ascii(cx, &mut bytes, "\"}");
386 }
387 push_ascii(cx, &mut bytes, "]");
388 Ok(abi_bytes_return(cx, bytes))
389}
390
391pub(crate) fn accesses_return_data(
392 cx: &mut SymCx,
393 record: Option<&AccessRecord>,
394 target: Address,
395) -> SymReturnData {
396 let reads = record.map(|record| record.read_slots(target)).unwrap_or_default();
397 let writes = record.map(|record| record.write_slots(target)).unwrap_or_default();
398 let values = [storage_slots_abi_array(cx, reads), storage_slots_abi_array(cx, writes)];
399 let bytes = encode_sequence(cx, values.iter());
400 SymReturnData::from_bytes(cx, bytes)
401}
402
403pub(crate) fn complete_cheatcode_call(
404 cx: &mut SymCx,
405 state: &mut PathState,
406 out_offset: SymExpr,
407 out_size: &BoundedCopySize,
408 return_data: SymReturnData,
409) -> Result<(), SymbolicError> {
410 state.return_data = return_data;
411 state.copy_call_output_offset(cx, out_offset, out_size)?;
412 state.stack.push(SymExpr::one(cx))?;
413 Ok(())
414}
415
416pub(crate) fn storage_slots_abi_array(cx: &mut SymCx, slots: Vec<SymExpr>) -> SymbolicAbiValue {
417 SymbolicAbiValue::Array {
418 elements: slots
419 .into_iter()
420 .map(|slot| SymbolicAbiValue::FixedBytes { bytes: slot.into_bytes(cx), size: 32 })
421 .collect(),
422 }
423}
424
425pub(crate) fn push_ascii(cx: &mut SymCx, out: &mut Vec<SymExpr>, value: &str) {
426 out.extend(value.bytes().map(|byte| SymExpr::constant(cx, U256::from(byte))));
427}
428
429pub(crate) fn push_hex_word(cx: &mut SymCx, out: &mut Vec<SymExpr>, word: SymExpr) {
430 for byte in word.into_byte_exprs(cx) {
431 push_hex_byte(cx, out, byte);
432 }
433}
434
435pub(crate) fn push_hex_byte(cx: &mut SymCx, out: &mut Vec<SymExpr>, byte: SymExpr) {
436 let byte = byte.low_byte(cx);
437 let (high, low) = if let Some(value) = byte.as_const() {
438 (
439 SymExpr::constant(cx, U256::from(value.to::<u8>() >> 4)),
440 SymExpr::constant(cx, U256::from(value.to::<u8>() & 0x0f)),
441 )
442 } else {
443 let expr = byte;
444 let shift = SymExpr::constant(cx, U256::from(4));
445 let mask = SymExpr::constant(cx, U256::from(0x0f));
446 (
447 SymExpr::binop(cx, SymBinOp::Shr, expr.clone(), shift),
448 SymExpr::binop(cx, SymBinOp::And, expr, mask),
449 )
450 };
451 out.push(hex_nibble_ascii(cx, high));
452 out.push(hex_nibble_ascii(cx, low));
453}
454
455pub(crate) fn hex_nibble_ascii(cx: &mut SymCx, nibble: SymExpr) -> SymExpr {
456 let nibble = nibble.low_byte(cx);
457 if let Some(value) = nibble.as_const() {
458 let nibble = value.to::<u8>() & 0x0f;
459 let byte = if nibble < 10 { b'0' + nibble } else { b'a' + (nibble - 10) };
460 SymExpr::constant(cx, U256::from(byte))
461 } else {
462 let ten = SymExpr::constant(cx, U256::from(10));
463 let condition = SymBoolExpr::cmp(cx, SymCmpOp::Ult, nibble.clone(), ten);
464 let zero = SymExpr::constant(cx, U256::from(b'0'));
465 let digit = SymExpr::binop(cx, SymBinOp::Add, nibble.clone(), zero);
466 let alpha_base = SymExpr::constant(cx, U256::from(b'a' - 10));
467 let alpha = SymExpr::binop(cx, SymBinOp::Add, nibble, alpha_base);
468 SymExpr::ite(cx, condition, digit, alpha)
469 }
470}
471
472pub(crate) fn read_abi_word_arg(
473 cx: &mut SymCx,
474 memory: &SymMemory,
475 args_offset: usize,
476 index: usize,
477) -> Result<SymExpr, SymbolicError> {
478 memory.load_word(cx, args_offset + index * 32)
479}
480
481pub(crate) fn read_abi_concrete_word_arg(
482 cx: &mut SymCx,
483 memory: &SymMemory,
484 args_offset: usize,
485 index: usize,
486 reason: &'static str,
487) -> Result<U256, SymbolicError> {
488 read_abi_word_arg(cx, memory, args_offset, index)?.as_const_or(reason)
489}
490
491pub(crate) fn read_abi_constrained_word_arg(
492 cx: &mut SymCx,
493 state: &PathState,
494 args_offset: usize,
495 index: usize,
496 reason: &'static str,
497) -> Result<U256, SymbolicError> {
498 let word = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
499 state.expect_constrained_word(cx, word, reason)
500}
501
502pub(crate) fn read_abi_constrained_address_arg(
503 cx: &mut SymCx,
504 state: &PathState,
505 args_offset: usize,
506 index: usize,
507 reason: &'static str,
508) -> Result<Address, SymbolicError> {
509 Ok(word_to_address(read_abi_constrained_word_arg(cx, state, args_offset, index, reason)?))
510}
511
512pub(crate) fn read_abi_address_or_symbolic_slot_arg(
513 cx: &mut SymCx,
514 state: &mut PathState,
515 args_offset: usize,
516 index: usize,
517) -> Result<Address, SymbolicError> {
518 let word = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
519 Ok(state.address_or_symbolic_slot(cx, word))
520}
521
522pub(crate) fn read_abi_address_word_or_symbolic_slot_arg(
523 cx: &mut SymCx,
524 state: &mut PathState,
525 args_offset: usize,
526 index: usize,
527) -> Result<(Address, SymExpr), SymbolicError> {
528 let word = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
529 let address = state.address_or_symbolic_slot(cx, word.clone());
530 Ok((address, word))
531}
532
533pub(crate) fn read_abi_address_arg(
534 cx: &mut SymCx,
535 memory: &SymMemory,
536 args_offset: usize,
537 index: usize,
538 reason: &'static str,
539) -> Result<Address, SymbolicError> {
540 Ok(word_to_address(read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?))
541}
542
543pub(crate) fn read_abi_bool_arg(
544 cx: &mut SymCx,
545 memory: &SymMemory,
546 args_offset: usize,
547 index: usize,
548 reason: &'static str,
549) -> Result<bool, SymbolicError> {
550 Ok(!read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?.is_zero())
551}
552
553pub(crate) fn read_abi_u64_arg(
554 cx: &mut SymCx,
555 memory: &SymMemory,
556 args_offset: usize,
557 index: usize,
558 reason: &'static str,
559) -> Result<u64, SymbolicError> {
560 let value = read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?;
561 u64::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
562}
563
564pub(crate) fn read_abi_u32_arg(
565 cx: &mut SymCx,
566 memory: &SymMemory,
567 args_offset: usize,
568 index: usize,
569 reason: &'static str,
570) -> Result<u32, SymbolicError> {
571 let value = read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?;
572 u32::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
573}
574
575pub(crate) fn read_abi_bytes4_words_arg(
576 cx: &mut SymCx,
577 memory: &SymMemory,
578 args_offset: usize,
579 index: usize,
580) -> Vec<SymExpr> {
581 memory.read_byte_exprs(cx, args_offset + index * 32, 4)
582}
583
584pub(crate) fn read_abi_dynamic_bytes_arg(
585 cx: &mut SymCx,
586 memory: &SymMemory,
587 args_offset: usize,
588 index: usize,
589 reason: &'static str,
590) -> Result<Vec<u8>, SymbolicError> {
591 let offset =
592 usize::try_from(read_abi_concrete_word_arg(cx, memory, args_offset, index, reason)?)
593 .map_err(|_| SymbolicError::Unsupported(reason))?;
594 let len_offset = args_offset.checked_add(offset).ok_or(SymbolicError::Unsupported(reason))?;
595 let data_offset = len_offset.checked_add(32).ok_or(SymbolicError::Unsupported(reason))?;
596 let len = memory.load_word(cx, len_offset)?.as_usize_or(reason)?;
597 memory.read_concrete(cx, data_offset, len)
598}
599
600pub(crate) fn read_abi_symbolic_dynamic_bytes_arg(
601 cx: &mut SymCx,
602 state: &PathState,
603 args_offset: usize,
604 index: usize,
605 max_len: usize,
606 reason: &'static str,
607) -> Result<SymBytes, SymbolicError> {
608 let offset = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
609 let offset = state.expect_constrained_usize(cx, offset, reason)?;
610 let len_offset = args_offset.checked_add(offset).ok_or(SymbolicError::Unsupported(reason))?;
611 let len = state.memory.load_word(cx, len_offset)?;
612 let len = state.expect_constrained_usize(cx, len, reason)?;
613 if len > max_len {
614 return Err(SymbolicError::Unsupported(reason));
615 }
616 let data_offset = len_offset.checked_add(32).ok_or(SymbolicError::Unsupported(reason))?;
617 Ok(state.memory.read_bytes(cx, data_offset, len))
618}
619
620pub(crate) fn read_abi_symbolic_dynamic_byte_exprs_arg(
621 cx: &mut SymCx,
622 state: &PathState,
623 args_offset: usize,
624 index: usize,
625 max_len: usize,
626 reason: &'static str,
627) -> Result<Vec<SymExpr>, SymbolicError> {
628 Ok(read_abi_symbolic_dynamic_bytes_arg(cx, state, args_offset, index, max_len, reason)?
629 .materialize(cx))
630}
631
632pub(crate) fn read_abi_dynamic_return_data_arg(
633 cx: &mut SymCx,
634 state: &PathState,
635 args_offset: usize,
636 index: usize,
637 max_len: usize,
638 reason: &'static str,
639) -> Result<SymReturnData, SymbolicError> {
640 let bytes =
641 read_abi_symbolic_dynamic_bytes_arg(cx, state, args_offset, index, max_len, reason)?;
642 Ok(SymReturnData::from_bytes(cx, bytes))
643}
644
645pub(crate) fn read_abi_symbolic_dynamic_bytes_array_arg(
646 cx: &mut SymCx,
647 state: &PathState,
648 args_offset: usize,
649 index: usize,
650 max_array_len: usize,
651 max_bytes_len: usize,
652) -> Result<Vec<SymReturnData>, SymbolicError> {
653 let offset = read_abi_word_arg(cx, &state.memory, args_offset, index)?;
654 let offset =
655 state.expect_constrained_usize(cx, offset, "symbolic vm.mockCalls returns offset")?;
656 let array_offset = args_offset
657 .checked_add(offset)
658 .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns offset"))?;
659 let array_data_offset = array_offset
660 .checked_add(32)
661 .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns offset"))?;
662 let len = state.memory.load_word(cx, array_offset)?;
663 let len = state.expect_constrained_usize(cx, len, "symbolic vm.mockCalls returns length")?;
664 if len > max_array_len {
665 return Err(SymbolicError::Unsupported("symbolic vm.mockCalls returns length"));
666 }
667
668 let mut values = Vec::with_capacity(len);
669 for value_idx in 0..len {
670 let head_offset = array_offset
671 .checked_add(32)
672 .and_then(|offset| offset.checked_add(value_idx.saturating_mul(32)))
673 .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns element offset"))?;
674 let value_offset = state.memory.load_word(cx, head_offset)?;
675 let value_offset = state.expect_constrained_usize(
676 cx,
677 value_offset,
678 "symbolic vm.mockCalls returns element offset",
679 )?;
680 let len_offset = array_data_offset
681 .checked_add(value_offset)
682 .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns element offset"))?;
683 let value_len = state.memory.load_word(cx, len_offset)?;
684 let value_len = state.expect_constrained_usize(
685 cx,
686 value_len,
687 "symbolic vm.mockCalls returns element length",
688 )?;
689 if value_len > max_bytes_len {
690 return Err(SymbolicError::Unsupported("symbolic vm.mockCalls returns element length"));
691 }
692 let data_offset = len_offset
693 .checked_add(32)
694 .ok_or(SymbolicError::Unsupported("symbolic vm.mockCalls returns element offset"))?;
695 let bytes = state.memory.read_bytes(cx, data_offset, value_len);
696 values.push(SymReturnData::from_bytes(cx, bytes));
697 }
698
699 Ok(values)
700}
701
702pub(crate) fn read_abi_string_arg(
703 cx: &mut SymCx,
704 memory: &SymMemory,
705 args_offset: usize,
706 index: usize,
707 reason: &'static str,
708) -> Result<String, SymbolicError> {
709 String::from_utf8(read_abi_dynamic_bytes_arg(cx, memory, args_offset, index, reason)?)
710 .map_err(|_| SymbolicError::Unsupported(reason))
711}
712
713pub(crate) fn decode_cheatcode_args(
714 cx: &mut SymCx,
715 state: &PathState,
716 in_offset: usize,
717 in_size: usize,
718 tys: Vec<DynSolType>,
719) -> Result<Vec<DynSolValue>, SymbolicError> {
720 let data = state.memory.read_concrete(cx, in_offset + 4, in_size.saturating_sub(4))?;
721 let value = DynSolType::Tuple(tys)
722 .abi_decode_sequence(&data)
723 .map_err(|_| SymbolicError::Unsupported("symbolic cheatcode ABI decode"))?;
724 let DynSolValue::Tuple(values) = value else {
725 return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
726 };
727 Ok(values)
728}
729
730pub(crate) const fn selector_has_string_reason(selector: [u8; 4]) -> bool {
731 matches!(
732 selector,
733 assertEq_15Call::SELECTOR
734 | assertEq_17Call::SELECTOR
735 | assertEq_19Call::SELECTOR
736 | assertEq_21Call::SELECTOR
737 | assertEq_23Call::SELECTOR
738 | assertEq_25Call::SELECTOR
739 | assertEq_27Call::SELECTOR
740 | assertNotEq_15Call::SELECTOR
741 | assertNotEq_17Call::SELECTOR
742 | assertNotEq_19Call::SELECTOR
743 | assertNotEq_21Call::SELECTOR
744 | assertNotEq_23Call::SELECTOR
745 | assertNotEq_25Call::SELECTOR
746 | assertNotEq_27Call::SELECTOR
747 | assertEqDecimal_1Call::SELECTOR
748 | assertEqDecimal_3Call::SELECTOR
749 )
750}
751
752pub(crate) const fn array_assertion_element_type(
753 selector: [u8; 4],
754) -> Result<DynSolType, SymbolicError> {
755 match selector {
756 assertEq_14Call::SELECTOR
757 | assertEq_15Call::SELECTOR
758 | assertNotEq_14Call::SELECTOR
759 | assertNotEq_15Call::SELECTOR => Ok(DynSolType::Bool),
760 assertEq_16Call::SELECTOR
761 | assertEq_17Call::SELECTOR
762 | assertNotEq_16Call::SELECTOR
763 | assertNotEq_17Call::SELECTOR => Ok(DynSolType::Uint(256)),
764 assertEq_18Call::SELECTOR
765 | assertEq_19Call::SELECTOR
766 | assertNotEq_18Call::SELECTOR
767 | assertNotEq_19Call::SELECTOR => Ok(DynSolType::Int(256)),
768 assertEq_20Call::SELECTOR
769 | assertEq_21Call::SELECTOR
770 | assertNotEq_20Call::SELECTOR
771 | assertNotEq_21Call::SELECTOR => Ok(DynSolType::Address),
772 assertEq_22Call::SELECTOR
773 | assertEq_23Call::SELECTOR
774 | assertNotEq_22Call::SELECTOR
775 | assertNotEq_23Call::SELECTOR => Ok(DynSolType::FixedBytes(32)),
776 assertEq_24Call::SELECTOR
777 | assertEq_25Call::SELECTOR
778 | assertNotEq_24Call::SELECTOR
779 | assertNotEq_25Call::SELECTOR => Ok(DynSolType::String),
780 assertEq_26Call::SELECTOR
781 | assertEq_27Call::SELECTOR
782 | assertNotEq_26Call::SELECTOR
783 | assertNotEq_27Call::SELECTOR => Ok(DynSolType::Bytes),
784 _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
785 }
786}
787
788pub(crate) fn is_full_word_array_assertion(selector: [u8; 4]) -> bool {
789 !selector_has_string_reason(selector)
790 && matches!(
791 array_assertion_element_type(selector),
792 Ok(DynSolType::Uint(256) | DynSolType::Int(256) | DynSolType::FixedBytes(32))
793 )
794}
795
796pub(crate) fn dyn_string(value: &DynSolValue) -> Result<String, SymbolicError> {
797 match value {
798 DynSolValue::String(value) => Ok(value.clone()),
799 _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
800 }
801}
802
803pub(crate) fn dyn_bytes(value: &DynSolValue) -> Result<Vec<u8>, SymbolicError> {
804 match value {
805 DynSolValue::Bytes(value) => Ok(value.clone()),
806 _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
807 }
808}
809
810pub(crate) const fn dyn_bool(value: &DynSolValue) -> Result<bool, SymbolicError> {
811 match value {
812 DynSolValue::Bool(value) => Ok(*value),
813 _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
814 }
815}
816
817pub(crate) const fn dyn_address(value: &DynSolValue) -> Result<Address, SymbolicError> {
818 match value {
819 DynSolValue::Address(value) => Ok(*value),
820 _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
821 }
822}
823
824pub(crate) fn dyn_potential_revert(
825 cx: &mut SymCx,
826 value: &DynSolValue,
827) -> Result<ExpectedRevert, SymbolicError> {
828 let DynSolValue::Tuple(values) = value else {
829 return Err(SymbolicError::Unsupported("symbolic vm.assumeNoRevert decode"));
830 };
831 let [reverter, partial_match, revert_data] = values.as_slice() else {
832 return Err(SymbolicError::Unsupported("symbolic vm.assumeNoRevert decode"));
833 };
834
835 let reverter = dyn_address(reverter)?;
836 let reverter =
837 (reverter != Address::ZERO).then(|| SymExpr::constant(cx, address_word(reverter)));
838 let revert_data = SymBytes::concrete(cx, dyn_bytes(revert_data)?);
839 let data = if dyn_bool(partial_match)? {
840 ExpectedRevertData::prefix(revert_data)
841 } else {
842 ExpectedRevertData::exact(revert_data)
843 };
844 Ok(ExpectedRevert::new(data, reverter, 1))
845}
846
847pub(crate) fn dyn_potential_reverts(
848 cx: &mut SymCx,
849 value: &DynSolValue,
850) -> Result<Vec<ExpectedRevert>, SymbolicError> {
851 let DynSolValue::Array(values) = value else {
852 return Err(SymbolicError::Unsupported("symbolic vm.assumeNoRevert decode"));
853 };
854 values.iter().map(|value| dyn_potential_revert(cx, value)).collect()
855}
856
857pub(crate) fn dyn_address_array(value: &DynSolValue) -> Result<Vec<Address>, SymbolicError> {
858 let DynSolValue::Array(values) = value else {
859 return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
860 };
861 values.iter().map(dyn_address).collect()
862}
863
864pub(crate) fn dyn_bytes32_array(value: &DynSolValue) -> Result<Vec<B256>, SymbolicError> {
865 let DynSolValue::Array(values) = value else {
866 return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
867 };
868 values
869 .iter()
870 .map(|value| match value {
871 DynSolValue::FixedBytes(bytes, 32) => Ok(*bytes),
872 _ => Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode")),
873 })
874 .collect()
875}
876
877pub(crate) fn dyn_string_array(value: &DynSolValue) -> Result<Vec<String>, SymbolicError> {
878 let DynSolValue::Array(values) = value else {
879 return Err(SymbolicError::Unsupported("symbolic cheatcode ABI decode"));
880 };
881 values.iter().map(dyn_string).collect()
882}
883
884pub(crate) fn parse_env_array<F>(
885 value: &str,
886 delimiter: &str,
887 mut parser: F,
888) -> Result<DynSolValue, SymbolicError>
889where
890 F: FnMut(&str) -> Result<DynSolValue, SymbolicError>,
891{
892 if delimiter.is_empty() {
893 return Err(SymbolicError::Unsupported("symbolic env delimiter"));
894 }
895 value.split(delimiter).map(&mut parser).collect::<Result<Vec<_>, _>>().map(DynSolValue::Array)
896}
897
898pub(crate) fn parse_env_bool_value(value: &str) -> Result<DynSolValue, SymbolicError> {
899 Ok(DynSolValue::Bool(parse_env_bool(value)?))
900}
901
902pub(crate) fn parse_env_uint_value(value: &str) -> Result<DynSolValue, SymbolicError> {
903 Ok(DynSolValue::Uint(parse_env_uint(value)?, 256))
904}
905
906pub(crate) fn parse_env_int_value(value: &str) -> Result<DynSolValue, SymbolicError> {
907 Ok(DynSolValue::Int(I256::from_raw(parse_env_int(value)?), 256))
908}
909
910pub(crate) fn parse_env_address_value(value: &str) -> Result<DynSolValue, SymbolicError> {
911 Ok(DynSolValue::Address(parse_env_address(value)?))
912}
913
914pub(crate) fn parse_env_bytes32_value(value: &str) -> Result<DynSolValue, SymbolicError> {
915 Ok(DynSolValue::FixedBytes(B256::from(parse_env_bytes32(value)?.to_be_bytes::<32>()), 32))
916}
917
918pub(crate) fn parse_env_string_value(value: &str) -> Result<DynSolValue, SymbolicError> {
919 Ok(DynSolValue::String(value.to_string()))
920}
921
922pub(crate) fn parse_env_bytes_value(value: &str) -> Result<DynSolValue, SymbolicError> {
923 Ok(DynSolValue::Bytes(parse_env_bytes(value)?))
924}
925
926pub(crate) fn parse_env_uint(value: &str) -> Result<U256, SymbolicError> {
927 value.parse::<U256>().map_err(|_| SymbolicError::Unsupported("symbolic env uint parse"))
928}
929
930pub(crate) fn parse_env_int(value: &str) -> Result<U256, SymbolicError> {
931 if let Some(value) = value.strip_prefix('-') {
932 let magnitude = parse_env_uint(value)?;
933 Ok(U256::ZERO.wrapping_sub(magnitude))
934 } else {
935 parse_env_uint(value)
936 }
937}
938
939pub(crate) fn parse_env_bool(value: &str) -> Result<bool, SymbolicError> {
940 match value {
941 "true" | "1" | "TRUE" | "True" => Ok(true),
942 "false" | "0" | "FALSE" | "False" => Ok(false),
943 _ => Err(SymbolicError::Unsupported("symbolic env bool parse")),
944 }
945}
946
947pub(crate) fn parse_env_bytes(value: &str) -> Result<Vec<u8>, SymbolicError> {
948 let value = value.strip_prefix("0x").unwrap_or(value);
949 hex::decode(value).map_err(|_| SymbolicError::Unsupported("symbolic env bytes parse"))
950}
951
952pub(crate) fn parse_env_bytes32(value: &str) -> Result<U256, SymbolicError> {
953 let bytes = parse_env_bytes(value)?;
954 if bytes.len() != 32 {
955 return Err(SymbolicError::Unsupported("symbolic env bytes32 parse"));
956 }
957 Ok(U256::from_be_slice(&bytes))
958}
959
960pub(crate) fn parse_env_address(value: &str) -> Result<Address, SymbolicError> {
961 value.parse::<Address>().map_err(|_| SymbolicError::Unsupported("symbolic env address parse"))
962}
963
964pub(crate) fn private_key_signer(private_key: U256) -> Result<PrivateKeySigner, SymbolicError> {
965 private_key_from_u256(private_key)
966 .map_err(|_| SymbolicError::Unsupported("symbolic private key parse"))
967}
968
969pub(crate) fn private_key_address(private_key: U256) -> Result<Address, SymbolicError> {
970 Ok(private_key_signer(private_key)?.address())
971}
972
973pub(crate) fn sign_hash_words(
974 cx: &mut SymCx,
975 private_key: U256,
976 digest: U256,
977) -> Result<Vec<SymExpr>, SymbolicError> {
978 let signer = private_key_signer(private_key)?;
979 let digest = B256::from(digest.to_be_bytes::<32>());
980 let sig = signer
981 .sign_hash_sync(&digest)
982 .map_err(|_| SymbolicError::Unsupported("symbolic vm.sign"))?;
983 Ok(vec![
984 SymExpr::constant(cx, U256::from(sig.v_byte())),
985 SymExpr::constant(cx, sig.r()),
986 SymExpr::constant(cx, sig.s()),
987 ])
988}
989
990pub(crate) fn sign_compact_hash_words(
991 cx: &mut SymCx,
992 private_key: U256,
993 digest: U256,
994) -> Result<Vec<SymExpr>, SymbolicError> {
995 let signer = private_key_signer(private_key)?;
996 let digest = B256::from(digest.to_be_bytes::<32>());
997 let sig = signer
998 .sign_hash_sync(&digest)
999 .map_err(|_| SymbolicError::Unsupported("symbolic vm.signCompact"))?;
1000 let y_parity = U256::from(sig.v() as u64) << 255;
1001 Ok(vec![SymExpr::constant(cx, sig.r()), SymExpr::constant(cx, sig.s() | y_parity)])
1002}
1003
1004pub(crate) fn derive_private_key<W: Wordlist>(
1005 mnemonic: &str,
1006 path: &str,
1007 index: u32,
1008) -> Result<U256, SymbolicError> {
1009 foundry_common::wallet::derive_private_key::<W>(mnemonic, path, index)
1010 .map_err(|_| SymbolicError::Unsupported("symbolic vm.deriveKey"))
1011}
1012
1013pub(crate) fn derive_private_key_with_language(
1014 mnemonic: &str,
1015 path: &str,
1016 index: u32,
1017 language: &str,
1018) -> Result<U256, SymbolicError> {
1019 foundry_common::wallet::derive_private_key_with_language(mnemonic, path, index, language)
1020 .map_err(|_| SymbolicError::Unsupported("symbolic vm.deriveKey language"))
1021}
1022
1023pub(crate) fn artifact_json_path(path: &str) -> PathBuf {
1024 if path.ends_with(".json") {
1025 return PathBuf::from(path);
1026 }
1027
1028 let mut parts = path.split(':');
1029 let first = parts.next().unwrap_or_default();
1030 let second = parts.next();
1031
1032 if first.contains('.') {
1033 let file = Path::new(first);
1034 let contract = second
1035 .map(str::to_string)
1036 .or_else(|| file.file_stem().map(|stem| stem.to_string_lossy().to_string()))
1037 .unwrap_or_else(|| first.to_string());
1038 PathBuf::from("out").join(first).join(format!("{contract}.json"))
1039 } else {
1040 let contract = first;
1041 PathBuf::from("out").join(format!("{contract}.sol")).join(format!("{contract}.json"))
1042 }
1043}
1044
1045pub(crate) fn artifact_json_fallback_paths(path: &str) -> Vec<PathBuf> {
1047 if path.ends_with(".json") {
1048 return Vec::new();
1049 }
1050
1051 let mut parts = path.split(':');
1052 let first = parts.next().unwrap_or_default();
1053 let second = parts.next();
1054 let Some(file_name) = first.rsplit(['/', '\\']).find(|part| !part.is_empty()) else {
1055 return Vec::new();
1056 };
1057
1058 if !first.contains('/') && !first.contains('\\') {
1059 return Vec::new();
1060 }
1061
1062 let contract = second.map(str::to_string).unwrap_or_else(|| {
1063 file_name.rsplit_once('.').map(|(stem, _)| stem).unwrap_or(file_name).to_string()
1064 });
1065 vec![PathBuf::from("out").join(file_name).join(format!("{contract}.json"))]
1066}
1067
1068pub(crate) fn artifact_code(path: &str, deployed: bool) -> Result<Vec<u8>, SymbolicError> {
1069 let mut data = None;
1070 for path in std::iter::once(artifact_json_path(path)).chain(artifact_json_fallback_paths(path))
1071 {
1072 if let Ok(contents) = std::fs::read_to_string(path) {
1073 data = Some(contents);
1074 break;
1075 }
1076 }
1077 let data = data.ok_or(SymbolicError::Unsupported("symbolic vm.getCode artifact"))?;
1078 let artifact: serde_json::Value = serde_json::from_str(&data)
1079 .map_err(|_| SymbolicError::Unsupported("symbolic vm.getCode artifact"))?;
1080 let key = if deployed { "deployedBytecode" } else { "bytecode" };
1081 let object = artifact
1082 .get(key)
1083 .and_then(|value| value.get("object").or(Some(value)))
1084 .and_then(serde_json::Value::as_str)
1085 .ok_or(SymbolicError::Unsupported("symbolic vm.getCode artifact"))?;
1086 hex::decode(object).map_err(|_| SymbolicError::Unsupported("symbolic vm.getCode artifact"))
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091 use super::*;
1092
1093 #[test]
1094 fn mapping_hook_registrations_require_three_words() {
1095 assert_eq!(
1096 foundry_cheatcode_min_input_size(registerMappingSstoreHookCall::SELECTOR),
1097 Some(abi_static_input_size(3))
1098 );
1099 }
1100
1101 #[test]
1102 fn symbolic_full_word_array_assertions_exclude_normalized_types_and_reasons() {
1103 for selector in [
1104 assertEq_16Call::SELECTOR,
1105 assertEq_18Call::SELECTOR,
1106 assertEq_22Call::SELECTOR,
1107 assertNotEq_16Call::SELECTOR,
1108 assertNotEq_18Call::SELECTOR,
1109 assertNotEq_22Call::SELECTOR,
1110 ] {
1111 assert!(is_full_word_array_assertion(selector));
1112 }
1113 for selector in [
1114 assertEq_14Call::SELECTOR,
1115 assertEq_17Call::SELECTOR,
1116 assertEq_20Call::SELECTOR,
1117 assertNotEq_15Call::SELECTOR,
1118 assertNotEq_20Call::SELECTOR,
1119 ] {
1120 assert!(!is_full_word_array_assertion(selector));
1121 }
1122 }
1123}