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