Skip to main content

foundry_evm_symbolic/runtime/
cheatcodes.rs

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
1045/// Returns fallback paths for symbolic artifact lookup.
1046pub(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}