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