Skip to main content

foundry_cheatcodes/test/
expect.rs

1use std::{
2    collections::VecDeque,
3    fmt::{self, Display},
4};
5
6use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*};
7use alloy_dyn_abi::{DynSolValue, EventExt};
8use alloy_json_abi::Event;
9use alloy_primitives::{
10    Address, Bytes, LogData as RawLog, U256, hex, keccak256,
11    map::{AddressHashMap, HashMap, hash_map::Entry},
12};
13use alloy_sol_types::SolValue;
14use foundry_common::{abi::get_indexed_event, fmt::format_token};
15use foundry_evm_core::evm::FoundryEvmNetwork;
16use foundry_evm_traces::DecodedCallLog;
17use revm::{
18    context::{ContextTr, JournalTr},
19    interpreter::{
20        InstructionResult, Interpreter, InterpreterAction, interpreter_types::LoopControl,
21    },
22};
23
24use super::revert_handlers::RevertParameters;
25/// Tracks the expected calls per address.
26///
27/// For each address, we track the expected calls per call data. We track it in such manner
28/// so that we don't mix together calldatas that only contain selectors and calldatas that contain
29/// selector and arguments (partial and full matches).
30///
31/// This then allows us to customize the matching behavior for each call data on the
32/// `ExpectedCallData` struct and track how many times we've actually seen the call on the second
33/// element of the tuple.
34pub type ExpectedCallTracker = HashMap<Address, HashMap<Bytes, (ExpectedCallData, u64)>>;
35
36#[derive(Clone, Debug)]
37pub struct ExpectedCallData {
38    /// The expected value sent in the call
39    pub value: Option<U256>,
40    /// The expected gas supplied to the call
41    pub gas: Option<u64>,
42    /// The expected *minimum* gas supplied to the call
43    pub min_gas: Option<u64>,
44    /// The number of times the call is expected to be made.
45    /// If the type of call is `NonCount`, this is the lower bound for the number of calls
46    /// that must be seen.
47    /// If the type of call is `Count`, this is the exact number of calls that must be seen.
48    pub count: u64,
49    /// The type of expected call.
50    pub call_type: ExpectedCallType,
51}
52
53/// The type of expected call.
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub enum ExpectedCallType {
56    /// The call is expected to be made at least once.
57    NonCount,
58    /// The exact number of calls expected.
59    Count,
60}
61
62/// The type of expected revert.
63#[derive(Clone, Debug)]
64pub enum ExpectedRevertKind {
65    /// Expects revert from the next non-cheatcode call.
66    Default,
67    /// Expects revert from the next cheatcode call.
68    ///
69    /// The `pending_processing` flag is used to track whether we have exited
70    /// `expectCheatcodeRevert` context or not.
71    /// We have to track it to avoid expecting `expectCheatcodeRevert` call to revert itself.
72    Cheatcode { pending_processing: bool },
73}
74
75#[derive(Clone, Debug)]
76pub struct ExpectedRevert {
77    /// The expected data returned by the revert, None being any.
78    pub reason: Option<Bytes>,
79    /// The depth at which the revert is expected.
80    pub depth: usize,
81    /// The type of expected revert.
82    pub kind: ExpectedRevertKind,
83    /// If true then only the first 4 bytes of expected data returned by the revert are checked.
84    pub partial_match: bool,
85    /// Contract expected to revert next call.
86    pub reverter: Option<Address>,
87    /// Address that reverted the call.
88    pub reverted_by: Option<Address>,
89    /// Max call depth reached during next call execution.
90    pub max_depth: usize,
91    /// Number of times this revert is expected.
92    pub count: u64,
93    /// Actual number of times this revert has been seen.
94    pub actual_count: u64,
95}
96
97#[derive(Clone, Debug)]
98pub struct ExpectedEmit {
99    /// The depth at which we expect this emit to have occurred
100    pub depth: usize,
101    /// The log we expect
102    pub log: Option<RawLog>,
103    /// The checks to perform:
104    /// ```text
105    /// ┌───────┬───────┬───────┬───────┬────┐
106    /// │topic 0│topic 1│topic 2│topic 3│data│
107    /// └───────┴───────┴───────┴───────┴────┘
108    /// ```
109    pub checks: [bool; 5],
110    /// If present, check originating address against this
111    pub address: Option<Address>,
112    /// If present, relax the requirement that topic 0 must be present. This allows anonymous
113    /// events with no indexed topics to be matched.
114    pub anonymous: bool,
115    /// Whether the log was actually found in the subcalls
116    pub found: bool,
117    /// Number of times the log is expected to be emitted
118    pub count: u64,
119    /// Stores mismatch details if a log didn't match
120    pub mismatch_error: Option<String>,
121}
122
123#[derive(Clone, Debug)]
124pub struct ExpectedCreate {
125    /// The address that deployed the contract
126    pub deployer: Address,
127    /// Runtime bytecode of the contract
128    pub bytecode: Bytes,
129    /// Whether deployed with CREATE or CREATE2
130    pub create_scheme: CreateScheme,
131}
132
133#[derive(Clone, Debug)]
134pub enum CreateScheme {
135    Create,
136    Create2,
137}
138
139impl Display for CreateScheme {
140    fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
141        match self {
142            Self::Create => write!(f, "CREATE"),
143            Self::Create2 => write!(f, "CREATE2"),
144        }
145    }
146}
147
148impl From<revm::context_interface::CreateScheme> for CreateScheme {
149    fn from(scheme: revm::context_interface::CreateScheme) -> Self {
150        match scheme {
151            revm::context_interface::CreateScheme::Create => Self::Create,
152            revm::context_interface::CreateScheme::Create2 { .. } => Self::Create2,
153            _ => unimplemented!("Unsupported create scheme"),
154        }
155    }
156}
157
158impl CreateScheme {
159    pub const fn eq(&self, create_scheme: Self) -> bool {
160        matches!(
161            (self, create_scheme),
162            (Self::Create, Self::Create) | (Self::Create2, Self::Create2 { .. })
163        )
164    }
165}
166
167impl Cheatcode for expectCall_0Call {
168    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
169        let Self { callee, data } = self;
170        expect_call(state, callee, data, None, None, None, 1, ExpectedCallType::NonCount)
171    }
172}
173
174impl Cheatcode for expectCall_1Call {
175    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
176        let Self { callee, data, count } = self;
177        expect_call(state, callee, data, None, None, None, *count, ExpectedCallType::Count)
178    }
179}
180
181impl Cheatcode for expectCall_2Call {
182    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
183        let Self { callee, msgValue, data } = self;
184        expect_call(state, callee, data, Some(msgValue), None, None, 1, ExpectedCallType::NonCount)
185    }
186}
187
188impl Cheatcode for expectCall_3Call {
189    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
190        let Self { callee, msgValue, data, count } = self;
191        expect_call(
192            state,
193            callee,
194            data,
195            Some(msgValue),
196            None,
197            None,
198            *count,
199            ExpectedCallType::Count,
200        )
201    }
202}
203
204impl Cheatcode for expectCall_4Call {
205    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
206        let Self { callee, msgValue, gas, data } = self;
207        expect_call(
208            state,
209            callee,
210            data,
211            Some(msgValue),
212            Some(*gas),
213            None,
214            1,
215            ExpectedCallType::NonCount,
216        )
217    }
218}
219
220impl Cheatcode for expectCall_5Call {
221    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
222        let Self { callee, msgValue, gas, data, count } = self;
223        expect_call(
224            state,
225            callee,
226            data,
227            Some(msgValue),
228            Some(*gas),
229            None,
230            *count,
231            ExpectedCallType::Count,
232        )
233    }
234}
235
236impl Cheatcode for expectCallMinGas_0Call {
237    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
238        let Self { callee, msgValue, minGas, data } = self;
239        expect_call(
240            state,
241            callee,
242            data,
243            Some(msgValue),
244            None,
245            Some(*minGas),
246            1,
247            ExpectedCallType::NonCount,
248        )
249    }
250}
251
252impl Cheatcode for expectCallMinGas_1Call {
253    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
254        let Self { callee, msgValue, minGas, data, count } = self;
255        expect_call(
256            state,
257            callee,
258            data,
259            Some(msgValue),
260            None,
261            Some(*minGas),
262            *count,
263            ExpectedCallType::Count,
264        )
265    }
266}
267
268impl Cheatcode for expectEmit_0Call {
269    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
270        let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self;
271        expect_emit(
272            ccx.state,
273            ccx.ecx.journal().depth(),
274            [true, checkTopic1, checkTopic2, checkTopic3, checkData],
275            None,
276            false,
277            1,
278        )
279    }
280}
281
282impl Cheatcode for expectEmit_1Call {
283    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
284        let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self;
285        expect_emit(
286            ccx.state,
287            ccx.ecx.journal().depth(),
288            [true, checkTopic1, checkTopic2, checkTopic3, checkData],
289            Some(emitter),
290            false,
291            1,
292        )
293    }
294}
295
296impl Cheatcode for expectEmit_2Call {
297    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
298        let Self {} = self;
299        expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, 1)
300    }
301}
302
303impl Cheatcode for expectEmit_3Call {
304    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
305        let Self { emitter } = *self;
306        expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, 1)
307    }
308}
309
310impl Cheatcode for expectEmit_4Call {
311    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
312        let Self { checkTopic1, checkTopic2, checkTopic3, checkData, count } = *self;
313        expect_emit(
314            ccx.state,
315            ccx.ecx.journal().depth(),
316            [true, checkTopic1, checkTopic2, checkTopic3, checkData],
317            None,
318            false,
319            count,
320        )
321    }
322}
323
324impl Cheatcode for expectEmit_5Call {
325    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
326        let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter, count } = *self;
327        expect_emit(
328            ccx.state,
329            ccx.ecx.journal().depth(),
330            [true, checkTopic1, checkTopic2, checkTopic3, checkData],
331            Some(emitter),
332            false,
333            count,
334        )
335    }
336}
337
338impl Cheatcode for expectEmit_6Call {
339    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
340        let Self { count } = *self;
341        expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, false, count)
342    }
343}
344
345impl Cheatcode for expectEmit_7Call {
346    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
347        let Self { emitter, count } = *self;
348        expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), false, count)
349    }
350}
351
352impl Cheatcode for expectEmitAnonymous_0Call {
353    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
354        let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self;
355        expect_emit(
356            ccx.state,
357            ccx.ecx.journal().depth(),
358            [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData],
359            None,
360            true,
361            1,
362        )
363    }
364}
365
366impl Cheatcode for expectEmitAnonymous_1Call {
367    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
368        let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self;
369        expect_emit(
370            ccx.state,
371            ccx.ecx.journal().depth(),
372            [checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData],
373            Some(emitter),
374            true,
375            1,
376        )
377    }
378}
379
380impl Cheatcode for expectEmitAnonymous_2Call {
381    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
382        let Self {} = self;
383        expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], None, true, 1)
384    }
385}
386
387impl Cheatcode for expectEmitAnonymous_3Call {
388    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
389        let Self { emitter } = *self;
390        expect_emit(ccx.state, ccx.ecx.journal().depth(), [true; 5], Some(emitter), true, 1)
391    }
392}
393
394impl Cheatcode for expectCreateCall {
395    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
396        let Self { bytecode, deployer } = self;
397        expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create)
398    }
399}
400
401impl Cheatcode for expectCreate2Call {
402    fn apply<FEN: FoundryEvmNetwork>(&self, state: &mut Cheatcodes<FEN>) -> Result {
403        let Self { bytecode, deployer } = self;
404        expect_create(state, bytecode.clone(), *deployer, CreateScheme::Create2)
405    }
406}
407
408impl Cheatcode for expectTip20LogoURIUpdatedCall {
409    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
410        let Self { token, updater, newLogoURI } = self;
411        expect_logo_uri_updated(ccx, token, updater, newLogoURI)
412    }
413}
414
415impl Cheatcode for expectLogoURIUpdatedCall {
416    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
417        let Self { token, updater, newLogoURI } = self;
418        expect_logo_uri_updated(ccx, token, updater, newLogoURI)
419    }
420}
421
422fn expect_logo_uri_updated<FEN: FoundryEvmNetwork>(
423    ccx: &mut CheatsCtxt<'_, '_, FEN>,
424    token: &Address,
425    updater: &Address,
426    new_logo_uri: &str,
427) -> Result {
428    let expected_emit = ExpectedEmit {
429        depth: ccx.ecx.journal().depth(),
430        log: Some(RawLog::new_unchecked(
431            vec![keccak256("LogoURIUpdated(address,string)"), updater.into_word()],
432            new_logo_uri.abi_encode().into(),
433        )),
434        checks: [true, true, false, false, true],
435        address: Some(*token),
436        anonymous: false,
437        found: false,
438        count: 1,
439        mismatch_error: None,
440    };
441    ccx.state.expected_emits.push_back((expected_emit, Default::default()));
442    Ok(Default::default())
443}
444
445impl Cheatcode for expectRevert_0Call {
446    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
447        let Self {} = self;
448        expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, 1)
449    }
450}
451
452impl Cheatcode for expectRevert_1Call {
453    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
454        let Self { revertData } = self;
455        expect_revert(
456            ccx.state,
457            Some(revertData.as_ref()),
458            ccx.ecx.journal().depth(),
459            false,
460            false,
461            None,
462            1,
463        )
464    }
465}
466
467impl Cheatcode for expectRevert_2Call {
468    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
469        let Self { revertData } = self;
470        expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), false, false, None, 1)
471    }
472}
473
474impl Cheatcode for expectRevert_3Call {
475    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
476        let Self { reverter } = self;
477        expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, Some(*reverter), 1)
478    }
479}
480
481impl Cheatcode for expectRevert_4Call {
482    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
483        let Self { revertData, reverter } = self;
484        expect_revert(
485            ccx.state,
486            Some(revertData.as_ref()),
487            ccx.ecx.journal().depth(),
488            false,
489            false,
490            Some(*reverter),
491            1,
492        )
493    }
494}
495
496impl Cheatcode for expectRevert_5Call {
497    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
498        let Self { revertData, reverter } = self;
499        expect_revert(
500            ccx.state,
501            Some(revertData),
502            ccx.ecx.journal().depth(),
503            false,
504            false,
505            Some(*reverter),
506            1,
507        )
508    }
509}
510
511impl Cheatcode for expectRevert_6Call {
512    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
513        let Self { count } = self;
514        expect_revert(ccx.state, None, ccx.ecx.journal().depth(), false, false, None, *count)
515    }
516}
517
518impl Cheatcode for expectRevert_7Call {
519    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
520        let Self { revertData, count } = self;
521        expect_revert(
522            ccx.state,
523            Some(revertData.as_ref()),
524            ccx.ecx.journal().depth(),
525            false,
526            false,
527            None,
528            *count,
529        )
530    }
531}
532
533impl Cheatcode for expectRevert_8Call {
534    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
535        let Self { revertData, count } = self;
536        expect_revert(
537            ccx.state,
538            Some(revertData),
539            ccx.ecx.journal().depth(),
540            false,
541            false,
542            None,
543            *count,
544        )
545    }
546}
547
548impl Cheatcode for expectRevert_9Call {
549    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
550        let Self { reverter, count } = self;
551        expect_revert(
552            ccx.state,
553            None,
554            ccx.ecx.journal().depth(),
555            false,
556            false,
557            Some(*reverter),
558            *count,
559        )
560    }
561}
562
563impl Cheatcode for expectRevert_10Call {
564    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
565        let Self { revertData, reverter, count } = self;
566        expect_revert(
567            ccx.state,
568            Some(revertData.as_ref()),
569            ccx.ecx.journal().depth(),
570            false,
571            false,
572            Some(*reverter),
573            *count,
574        )
575    }
576}
577
578impl Cheatcode for expectRevert_11Call {
579    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
580        let Self { revertData, reverter, count } = self;
581        expect_revert(
582            ccx.state,
583            Some(revertData),
584            ccx.ecx.journal().depth(),
585            false,
586            false,
587            Some(*reverter),
588            *count,
589        )
590    }
591}
592
593impl Cheatcode for expectPartialRevert_0Call {
594    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
595        let Self { revertData } = self;
596        expect_revert(
597            ccx.state,
598            Some(revertData.as_ref()),
599            ccx.ecx.journal().depth(),
600            false,
601            true,
602            None,
603            1,
604        )
605    }
606}
607
608impl Cheatcode for expectPartialRevert_1Call {
609    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
610        let Self { revertData, reverter } = self;
611        expect_revert(
612            ccx.state,
613            Some(revertData.as_ref()),
614            ccx.ecx.journal().depth(),
615            false,
616            true,
617            Some(*reverter),
618            1,
619        )
620    }
621}
622
623impl Cheatcode for _expectCheatcodeRevert_0Call {
624    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
625        expect_revert(ccx.state, None, ccx.ecx.journal().depth(), true, false, None, 1)
626    }
627}
628
629impl Cheatcode for _expectCheatcodeRevert_1Call {
630    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
631        let Self { revertData } = self;
632        expect_revert(
633            ccx.state,
634            Some(revertData.as_ref()),
635            ccx.ecx.journal().depth(),
636            true,
637            false,
638            None,
639            1,
640        )
641    }
642}
643
644impl Cheatcode for _expectCheatcodeRevert_2Call {
645    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
646        let Self { revertData } = self;
647        expect_revert(ccx.state, Some(revertData), ccx.ecx.journal().depth(), true, false, None, 1)
648    }
649}
650
651impl Cheatcode for expectSafeMemoryCall {
652    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
653        let Self { min, max } = *self;
654        expect_safe_memory(ccx.state, min, max, ccx.ecx.journal().depth().try_into()?)
655    }
656}
657
658impl Cheatcode for stopExpectSafeMemoryCall {
659    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
660        let Self {} = self;
661        ccx.state.allowed_mem_writes.remove(&ccx.ecx.journal().depth().try_into()?);
662        Ok(Default::default())
663    }
664}
665
666impl Cheatcode for expectSafeMemoryCallCall {
667    fn apply_stateful<FEN: FoundryEvmNetwork>(&self, ccx: &mut CheatsCtxt<'_, '_, FEN>) -> Result {
668        let Self { min, max } = *self;
669        expect_safe_memory(ccx.state, min, max, (ccx.ecx.journal().depth() + 1).try_into()?)
670    }
671}
672
673impl RevertParameters for ExpectedRevert {
674    fn reverter(&self) -> Option<Address> {
675        self.reverter
676    }
677
678    fn reason(&self) -> Option<&[u8]> {
679        self.reason.as_ref().map(|b| &***b)
680    }
681
682    fn partial_match(&self) -> bool {
683        self.partial_match
684    }
685}
686
687/// Handles expected calls specified by the `expectCall` cheatcodes.
688///
689/// It can handle calls in two ways:
690/// - If the cheatcode was used with a `count` argument, it will expect the call to be made exactly
691///   `count` times. e.g. `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f), 4)`
692///   will expect the call to address(0xc4f3) with selector `0xd34db33f` to be made exactly 4 times.
693///   If the amount of calls is less or more than 4, the test will fail. Note that the `count`
694///   argument cannot be overwritten with another `vm.expectCall`. If this is attempted,
695///   `expectCall` will revert.
696/// - If the cheatcode was used without a `count` argument, it will expect the call to be made at
697///   least the amount of times the cheatcode was called. This means that `vm.expectCall` without a
698///   count argument can be called many times, but cannot be called with a `count` argument after it
699///   was called without one. If the latter happens, `expectCall` will revert. e.g
700///   `vm.expectCall(address(0xc4f3), abi.encodeWithSelector(0xd34db33f))` will expect the call to
701///   address(0xc4f3) and selector `0xd34db33f` to be made at least once. If the amount of calls is
702///   0, the test will fail. If the call is made more than once, the test will pass.
703#[expect(clippy::too_many_arguments)] // It is what it is
704fn expect_call<FEN: FoundryEvmNetwork>(
705    state: &mut Cheatcodes<FEN>,
706    target: &Address,
707    calldata: &Bytes,
708    value: Option<&U256>,
709    mut gas: Option<u64>,
710    mut min_gas: Option<u64>,
711    count: u64,
712    call_type: ExpectedCallType,
713) -> Result {
714    let expecteds = state.expected_calls.entry(*target).or_default();
715
716    if let Some(val) = value
717        && *val > U256::ZERO
718    {
719        // If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas
720        // to ensure that the basic fallback function can be called.
721        let positive_value_cost_stipend = 2300;
722        if let Some(gas) = &mut gas {
723            *gas += positive_value_cost_stipend;
724        }
725        if let Some(min_gas) = &mut min_gas {
726            *min_gas += positive_value_cost_stipend;
727        }
728    }
729
730    match call_type {
731        ExpectedCallType::Count => {
732            // Get the expected calls for this target.
733            // In this case, as we're using counted expectCalls, we should not be able to set them
734            // more than once.
735            ensure!(
736                !expecteds.contains_key(calldata),
737                "counted expected calls can only bet set once"
738            );
739            expecteds.insert(
740                calldata.clone(),
741                (ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type }, 0),
742            );
743        }
744        ExpectedCallType::NonCount => {
745            // Check if the expected calldata exists.
746            // If it does, increment the count by one as we expect to see it one more time.
747            match expecteds.entry(calldata.clone()) {
748                Entry::Occupied(mut entry) => {
749                    let (expected, _) = entry.get_mut();
750                    // Ensure we're not overwriting a counted expectCall.
751                    ensure!(
752                        expected.call_type == ExpectedCallType::NonCount,
753                        "cannot overwrite a counted expectCall with a non-counted expectCall"
754                    );
755                    expected.count += 1;
756                }
757                // If it does not exist, then create it.
758                Entry::Vacant(entry) => {
759                    entry.insert((
760                        ExpectedCallData { value: value.copied(), gas, min_gas, count, call_type },
761                        0,
762                    ));
763                }
764            }
765        }
766    }
767
768    Ok(Default::default())
769}
770
771fn expect_emit<FEN: FoundryEvmNetwork>(
772    state: &mut Cheatcodes<FEN>,
773    depth: usize,
774    checks: [bool; 5],
775    address: Option<Address>,
776    anonymous: bool,
777    count: u64,
778) -> Result {
779    let expected_emit = ExpectedEmit {
780        depth,
781        checks,
782        address,
783        found: false,
784        log: None,
785        anonymous,
786        count,
787        mismatch_error: None,
788    };
789    if let Some(found_emit_pos) = state.expected_emits.iter().position(|(emit, _)| emit.found) {
790        // The order of emits already found (back of queue) should not be modified, hence push any
791        // new emit before first found emit.
792        state.expected_emits.insert(found_emit_pos, (expected_emit, Default::default()));
793    } else {
794        // If no expected emits then push new one at the back of queue.
795        state.expected_emits.push_back((expected_emit, Default::default()));
796    }
797
798    Ok(Default::default())
799}
800
801pub(crate) fn handle_expect_emit<FEN: FoundryEvmNetwork>(
802    state: &mut Cheatcodes<FEN>,
803    log: &alloy_primitives::Log,
804    mut interpreter: Option<&mut Interpreter>,
805) -> Option<&'static str> {
806    // This function returns an optional string indicating a failure reason.
807    // If the string is `Some`, it indicates that the expectation failed with the provided reason.
808    let mut failure_reason = None;
809
810    // Fill or check the expected emits.
811    // We expect for emit checks to be filled as they're declared (from oldest to newest),
812    // so we fill them and push them to the back of the queue.
813    // If the user has properly filled all the emits, they'll end up in their original order.
814    // If not, the queue will not be in the order the events will be intended to be filled,
815    // and we'll be able to later detect this and bail.
816
817    // First, we can return early if all events have been matched.
818    // This allows a contract to arbitrarily emit more events than expected (additive behavior),
819    // as long as all the previous events were matched in the order they were expected to be.
820    if state.expected_emits.iter().all(|(expected, _)| expected.found) {
821        return failure_reason;
822    }
823
824    // Check count=0 expectations against this log - fail immediately if violated
825    for (expected_emit, _) in &state.expected_emits {
826        if expected_emit.count == 0
827            && !expected_emit.found
828            && let Some(expected_log) = &expected_emit.log
829            && checks_topics_and_data(expected_emit.checks, expected_log, log)
830            // Check revert address 
831            && (expected_emit.address.is_none_or(|address| address == log.address))
832        {
833            if let Some(interpreter) = &mut interpreter {
834                // This event was emitted but we expected it NOT to be (count=0)
835                // Fail immediately
836                interpreter.bytecode.set_action(InterpreterAction::new_return(
837                    InstructionResult::Revert,
838                    Error::encode("log emitted but expected 0 times"),
839                    interpreter.gas,
840                ));
841            } else {
842                failure_reason = Some("log emitted but expected 0 times");
843            }
844
845            return failure_reason;
846        }
847    }
848
849    let should_fill_logs = state.expected_emits.iter().any(|(expected, _)| expected.log.is_none());
850    let index_to_fill_or_check = if should_fill_logs {
851        // If there's anything to fill, we start with the last event to match in the queue
852        // (without taking into account events already matched).
853        state
854            .expected_emits
855            .iter()
856            .position(|(emit, _)| emit.found)
857            .unwrap_or(state.expected_emits.len())
858            .saturating_sub(1)
859    } else {
860        // if all expected logs are filled, check any unmatched event
861        // in the declared order, so we start from the front (like a queue).
862        // Skip count=0 expectations as they are handled separately above
863        state.expected_emits.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0)
864    };
865
866    // If there are only count=0 expectations left, we can return early
867    if !should_fill_logs
868        && state.expected_emits.iter().all(|(emit, _)| emit.found || emit.count == 0)
869    {
870        return failure_reason;
871    }
872
873    let (mut event_to_fill_or_check, mut count_map) = state
874        .expected_emits
875        .remove(index_to_fill_or_check)
876        .expect("we should have an emit to fill or check");
877
878    let Some(expected) = &event_to_fill_or_check.log else {
879        // Unless the caller is trying to match an anonymous event, the first topic must be
880        // filled.
881        if event_to_fill_or_check.anonymous || !log.topics().is_empty() {
882            event_to_fill_or_check.log = Some(log.data.clone());
883            // If we only filled the expected log then we put it back at the same position.
884            state
885                .expected_emits
886                .insert(index_to_fill_or_check, (event_to_fill_or_check, count_map));
887        } else if let Some(interpreter) = &mut interpreter {
888            interpreter.bytecode.set_action(InterpreterAction::new_return(
889                InstructionResult::Revert,
890                Error::encode("use vm.expectEmitAnonymous to match anonymous events"),
891                interpreter.gas,
892            ));
893        } else {
894            failure_reason = Some("use vm.expectEmitAnonymous to match anonymous events");
895        }
896
897        return failure_reason;
898    };
899
900    // Increment/set `count` for `log.address` and `log.data`
901    match count_map.entry(log.address) {
902        Entry::Occupied(mut entry) => {
903            let log_count_map = entry.get_mut();
904            log_count_map.insert(&log.data);
905        }
906        Entry::Vacant(entry) => {
907            let mut log_count_map = LogCountMap::new(&event_to_fill_or_check);
908            if log_count_map.satisfies_checks(&log.data) {
909                log_count_map.insert(&log.data);
910                entry.insert(log_count_map);
911            }
912        }
913    }
914
915    event_to_fill_or_check.found = || -> bool {
916        if !checks_topics_and_data(event_to_fill_or_check.checks, expected, log) {
917            // Store detailed mismatch information
918
919            // Try to decode the events if we have a signature identifier
920            let (expected_decoded, actual_decoded) = if let Some(signatures_identifier) =
921                state.signatures_identifier()
922                && !event_to_fill_or_check.anonymous
923            {
924                (
925                    decode_event(signatures_identifier, expected),
926                    decode_event(signatures_identifier, log),
927                )
928            } else {
929                (None, None)
930            };
931            event_to_fill_or_check.mismatch_error = Some(get_emit_mismatch_message(
932                event_to_fill_or_check.checks,
933                expected,
934                log,
935                event_to_fill_or_check.anonymous,
936                expected_decoded.as_ref(),
937                actual_decoded.as_ref(),
938            ));
939            return false;
940        }
941
942        // Maybe match source address.
943        if event_to_fill_or_check.address.is_some_and(|addr| addr != log.address) {
944            event_to_fill_or_check.mismatch_error = Some(format!(
945                "log emitter mismatch: expected={:#x}, got={:#x}",
946                event_to_fill_or_check.address.unwrap(),
947                log.address
948            ));
949            return false;
950        }
951
952        let expected_count = event_to_fill_or_check.count;
953        match event_to_fill_or_check.address {
954            Some(emitter) => count_map
955                .get(&emitter)
956                .is_some_and(|log_map| log_map.count(&log.data) >= expected_count),
957            None => count_map
958                .values()
959                .find(|log_map| log_map.satisfies_checks(&log.data))
960                .is_some_and(|map| map.count(&log.data) >= expected_count),
961        }
962    }();
963
964    // If we found the event, we can push it to the back of the queue
965    // and begin expecting the next event.
966    if event_to_fill_or_check.found {
967        state.expected_emits.push_back((event_to_fill_or_check, count_map));
968    } else {
969        // We did not match this event, so we need to keep waiting for the right one to
970        // appear.
971        state.expected_emits.push_front((event_to_fill_or_check, count_map));
972    }
973
974    failure_reason
975}
976
977/// Handles expected emits specified by the `expectEmit` cheatcodes.
978///
979/// The second element of the tuple counts the number of times the log has been emitted by a
980/// particular address
981pub type ExpectedEmitTracker = VecDeque<(ExpectedEmit, AddressHashMap<LogCountMap>)>;
982
983#[derive(Clone, Debug, Default)]
984pub struct LogCountMap {
985    checks: [bool; 5],
986    expected_log: RawLog,
987    map: HashMap<RawLog, u64>,
988}
989
990impl LogCountMap {
991    /// Instantiates `LogCountMap`.
992    fn new(expected_emit: &ExpectedEmit) -> Self {
993        Self {
994            checks: expected_emit.checks,
995            expected_log: expected_emit.log.clone().expect("log should be filled here"),
996            map: Default::default(),
997        }
998    }
999
1000    /// Inserts a log into the map and increments the count.
1001    ///
1002    /// The log must pass all checks against the expected log for the count to increment.
1003    ///
1004    /// Returns true if the log was inserted and count was incremented.
1005    fn insert(&mut self, log: &RawLog) -> bool {
1006        // If its already in the map, increment the count without checking.
1007        if self.map.contains_key(log) {
1008            self.map.entry(log.clone()).and_modify(|c| *c += 1);
1009
1010            return true;
1011        }
1012
1013        if !self.satisfies_checks(log) {
1014            return false;
1015        }
1016
1017        self.map.entry(log.clone()).and_modify(|c| *c += 1).or_insert(1);
1018
1019        true
1020    }
1021
1022    /// Checks the incoming raw log against the expected logs topics and data.
1023    fn satisfies_checks(&self, log: &RawLog) -> bool {
1024        checks_topics_and_data(self.checks, &self.expected_log, log)
1025    }
1026
1027    pub fn count(&self, log: &RawLog) -> u64 {
1028        if !self.satisfies_checks(log) {
1029            return 0;
1030        }
1031
1032        self.count_unchecked()
1033    }
1034
1035    pub fn count_unchecked(&self) -> u64 {
1036        self.map.values().sum()
1037    }
1038}
1039
1040fn expect_create<FEN: FoundryEvmNetwork>(
1041    state: &mut Cheatcodes<FEN>,
1042    bytecode: Bytes,
1043    deployer: Address,
1044    create_scheme: CreateScheme,
1045) -> Result {
1046    let expected_create = ExpectedCreate { bytecode, deployer, create_scheme };
1047    state.expected_creates.push(expected_create);
1048
1049    Ok(Default::default())
1050}
1051
1052fn expect_revert<FEN: FoundryEvmNetwork>(
1053    state: &mut Cheatcodes<FEN>,
1054    reason: Option<&[u8]>,
1055    depth: usize,
1056    cheatcode: bool,
1057    partial_match: bool,
1058    reverter: Option<Address>,
1059    count: u64,
1060) -> Result {
1061    ensure!(
1062        state.expected_revert.is_none(),
1063        "you must call another function prior to expecting a second revert"
1064    );
1065    state.expected_revert = Some(ExpectedRevert {
1066        reason: reason.map(Bytes::copy_from_slice),
1067        depth,
1068        kind: if cheatcode {
1069            ExpectedRevertKind::Cheatcode { pending_processing: true }
1070        } else {
1071            ExpectedRevertKind::Default
1072        },
1073        partial_match,
1074        reverter,
1075        reverted_by: None,
1076        max_depth: depth,
1077        count,
1078        actual_count: 0,
1079    });
1080    Ok(Default::default())
1081}
1082
1083fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> bool {
1084    if log.topics().len() != expected.topics().len() {
1085        return false;
1086    }
1087
1088    // Check topics.
1089    if !log
1090        .topics()
1091        .iter()
1092        .enumerate()
1093        .filter(|(i, _)| checks[*i])
1094        .all(|(i, topic)| topic == &expected.topics()[i])
1095    {
1096        return false;
1097    }
1098
1099    // Check data
1100    if checks[4] && expected.data.as_ref() != log.data.as_ref() {
1101        return false;
1102    }
1103
1104    true
1105}
1106
1107fn decode_event(
1108    identifier: &foundry_evm_traces::identifier::SignaturesIdentifier,
1109    log: &RawLog,
1110) -> Option<DecodedCallLog> {
1111    let topics = log.topics();
1112    if topics.is_empty() {
1113        return None;
1114    }
1115    let t0 = topics[0]; // event sig
1116    // Try to identify the event
1117    let event = foundry_common::block_on(
1118        identifier.identify_event_with_indexed_count(t0, topics.len().saturating_sub(1)),
1119    )?;
1120
1121    // Check if event already has indexed information from signatures
1122    let has_indexed_info = event.inputs.iter().any(|p| p.indexed);
1123    // Only use get_indexed_event if the event doesn't have indexing info
1124    let indexed_event = if has_indexed_info { event } else { get_indexed_event(event, log) };
1125
1126    // Try to decode the event
1127    if let Ok(decoded) = indexed_event.decode_log(log) {
1128        let params = reconstruct_params(&indexed_event, &decoded);
1129
1130        let decoded_params = params
1131            .into_iter()
1132            .zip(indexed_event.inputs.iter())
1133            .map(|(param, input)| (input.name.clone(), format_token(&param)))
1134            .collect();
1135
1136        return Some(DecodedCallLog {
1137            name: Some(indexed_event.name),
1138            params: Some(decoded_params),
1139        });
1140    }
1141
1142    None
1143}
1144
1145/// Restore the order of the params of a decoded event
1146fn reconstruct_params(event: &Event, decoded: &alloy_dyn_abi::DecodedEvent) -> Vec<DynSolValue> {
1147    let mut indexed = 0;
1148    let mut unindexed = 0;
1149    let mut inputs = vec![];
1150    for input in &event.inputs {
1151        if input.indexed && indexed < decoded.indexed.len() {
1152            inputs.push(decoded.indexed[indexed].clone());
1153            indexed += 1;
1154        } else if unindexed < decoded.body.len() {
1155            inputs.push(decoded.body[unindexed].clone());
1156            unindexed += 1;
1157        }
1158    }
1159    inputs
1160}
1161
1162/// Gets a detailed mismatch message for emit assertions
1163pub(crate) fn get_emit_mismatch_message(
1164    checks: [bool; 5],
1165    expected: &RawLog,
1166    actual: &RawLog,
1167    is_anonymous: bool,
1168    expected_decoded: Option<&DecodedCallLog>,
1169    actual_decoded: Option<&DecodedCallLog>,
1170) -> String {
1171    // Early return for completely different events or incompatible structures
1172
1173    // 1. Different number of topics
1174    if actual.topics().len() != expected.topics().len() {
1175        let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1176        let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1177        let expected_topics = checked_topic_count(expected, is_anonymous);
1178        let actual_topics = checked_topic_count(actual, is_anonymous);
1179
1180        if expected_name == actual_name {
1181            return format!(
1182                "{actual_name} indexed topic count mismatch: expected {expected_topics}, got {actual_topics}"
1183            );
1184        }
1185
1186        return name_mismatched_logs(expected_decoded, actual_decoded);
1187    }
1188
1189    // 2. Different event signatures (for non-anonymous events)
1190    if !is_anonymous
1191        && checks[0]
1192        && (!expected.topics().is_empty() && !actual.topics().is_empty())
1193        && expected.topics()[0] != actual.topics()[0]
1194    {
1195        return name_mismatched_logs(expected_decoded, actual_decoded);
1196    }
1197
1198    let expected_data = expected.data.as_ref();
1199    let actual_data = actual.data.as_ref();
1200
1201    // 3. Check data
1202    if checks[4] && expected_data != actual_data {
1203        // Different lengths or not ABI-encoded
1204        if expected_data.len() != actual_data.len()
1205            || !expected_data.len().is_multiple_of(32)
1206            || expected_data.is_empty()
1207        {
1208            return name_mismatched_logs(expected_decoded, actual_decoded);
1209        }
1210    }
1211
1212    // expected and actual events are the same, so check individual parameters
1213    let mut mismatches = Vec::new();
1214
1215    // Check topics (indexed parameters)
1216    for (i, (expected_topic, actual_topic)) in
1217        expected.topics().iter().zip(actual.topics().iter()).enumerate()
1218    {
1219        // Skip topic[0] for non-anonymous events (already checked above)
1220        if i == 0 && !is_anonymous {
1221            continue;
1222        }
1223
1224        // Only check if the corresponding check flag is set
1225        if i < checks.len() && checks[i] && expected_topic != actual_topic {
1226            let param_idx = if is_anonymous {
1227                i // For anonymous events, topic[0] is param 0
1228            } else {
1229                i - 1 // For regular events, topic[0] is event signature, so topic[1] is param 0
1230            };
1231            mismatches
1232                .push(format!("param {param_idx}: expected={expected_topic}, got={actual_topic}"));
1233        }
1234    }
1235
1236    // Check data (non-indexed parameters)
1237    if checks[4] && expected_data != actual_data {
1238        let num_indexed_params = if is_anonymous {
1239            expected.topics().len()
1240        } else {
1241            expected.topics().len().saturating_sub(1)
1242        };
1243
1244        for (i, (expected_chunk, actual_chunk)) in
1245            expected_data.chunks(32).zip(actual_data.chunks(32)).enumerate()
1246        {
1247            if expected_chunk != actual_chunk {
1248                let param_idx = num_indexed_params + i;
1249                mismatches.push(format!(
1250                    "param {}: expected={}, got={}",
1251                    param_idx,
1252                    hex::encode_prefixed(expected_chunk),
1253                    hex::encode_prefixed(actual_chunk)
1254                ));
1255            }
1256        }
1257    }
1258
1259    if mismatches.is_empty() {
1260        name_mismatched_logs(expected_decoded, actual_decoded)
1261    } else {
1262        // Build the error message with event names if available
1263        let event_prefix = match (expected_decoded, actual_decoded) {
1264            (Some(expected_dec), Some(actual_dec)) if expected_dec.name == actual_dec.name => {
1265                format!(
1266                    "{} param mismatch",
1267                    expected_dec.name.as_ref().unwrap_or(&"log".to_string())
1268                )
1269            }
1270            _ => {
1271                if is_anonymous {
1272                    "anonymous log mismatch".to_string()
1273                } else {
1274                    "log mismatch".to_string()
1275                }
1276            }
1277        };
1278
1279        // Add parameter details if available from decoded events
1280        let detailed_mismatches = if let (Some(expected_dec), Some(actual_dec)) =
1281            (expected_decoded, actual_decoded)
1282            && let (Some(expected_params), Some(actual_params)) =
1283                (&expected_dec.params, &actual_dec.params)
1284        {
1285            mismatches
1286                .into_iter()
1287                .map(|basic_mismatch| {
1288                    // Try to find the parameter name and decoded value
1289                    if let Some(param_idx) = basic_mismatch
1290                        .split(' ')
1291                        .nth(1)
1292                        .and_then(|s| s.trim_end_matches(':').parse::<usize>().ok())
1293                        && param_idx < expected_params.len()
1294                        && param_idx < actual_params.len()
1295                    {
1296                        let (expected_name, expected_value) = &expected_params[param_idx];
1297                        let (_actual_name, actual_value) = &actual_params[param_idx];
1298                        let param_name = if expected_name.is_empty() {
1299                            &format!("param{param_idx}")
1300                        } else {
1301                            expected_name
1302                        };
1303                        return format!(
1304                            "{param_name}: expected={expected_value}, got={actual_value}",
1305                        );
1306                    }
1307                    basic_mismatch
1308                })
1309                .collect::<Vec<_>>()
1310        } else {
1311            mismatches
1312        };
1313
1314        format!("{} at {}", event_prefix, detailed_mismatches.join(", "))
1315    }
1316}
1317
1318/// Formats the generic mismatch message: "log != expected log" to include event names if available
1319fn name_mismatched_logs(
1320    expected_decoded: Option<&DecodedCallLog>,
1321    actual_decoded: Option<&DecodedCallLog>,
1322) -> String {
1323    let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1324    let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
1325    format!("{actual_name} != expected {expected_name}")
1326}
1327
1328fn checked_topic_count(log: &RawLog, is_anonymous: bool) -> usize {
1329    if is_anonymous { log.topics().len() } else { log.topics().len().saturating_sub(1) }
1330}
1331
1332fn expect_safe_memory<FEN: FoundryEvmNetwork>(
1333    state: &mut Cheatcodes<FEN>,
1334    start: u64,
1335    end: u64,
1336    depth: u64,
1337) -> Result {
1338    ensure!(start < end, "memory range start ({start}) is greater than end ({end})");
1339    #[expect(clippy::single_range_in_vec_init)] // Wanted behaviour
1340    let offsets = state.allowed_mem_writes.entry(depth).or_insert_with(|| vec![0..0x60]);
1341    offsets.push(start..end);
1342    Ok(Default::default())
1343}