Skip to main content

foundry_cheatcodes/
expected_emit.rs

1//! Engine-free matching for the `expectEmit` cheatcodes.
2
3use crate::Error;
4use alloy_dyn_abi::EventExt;
5use alloy_primitives::{
6    Address, Bytes, Log, LogData as RawLog, hex,
7    map::{AddressHashMap, HashMap, hash_map::Entry},
8};
9use alloy_sol_types::SolValue;
10use foundry_common::{
11    abi::{get_indexed_event, reconstruct_event_params},
12    fmt::format_token,
13};
14use foundry_evm_traces::{DecodedCallLog, identifier::SignaturesIdentifier};
15use std::collections::VecDeque;
16
17#[derive(Clone, Debug)]
18pub struct ExpectedEmit {
19    /// The depth at which we expect this emit to have occurred
20    pub depth: usize,
21    /// The log we expect
22    pub log: Option<RawLog>,
23    /// The checks to perform:
24    /// ```text
25    /// ┌───────┬───────┬───────┬───────┬────┐
26    /// │topic 0│topic 1│topic 2│topic 3│data│
27    /// └───────┴───────┴───────┴───────┴────┘
28    /// ```
29    pub checks: [bool; 5],
30    /// If present, check originating address against this
31    pub address: Option<Address>,
32    /// If present, relax the requirement that topic 0 must be present. This allows anonymous
33    /// events with no indexed topics to be matched.
34    pub anonymous: bool,
35    /// Whether the log was actually found in the subcalls
36    pub found: bool,
37    /// Number of times the log is expected to be emitted
38    pub count: u64,
39    /// Stores mismatch details if a log didn't match.
40    pub mismatch_error: Option<EmitMismatch>,
41}
42
43#[derive(Clone, Debug)]
44pub enum EmitMismatch {
45    Log { actual: RawLog },
46    Emitter { expected: Address, actual: Address },
47}
48
49impl EmitMismatch {
50    /// Formats the mismatch, calling `identifier` only when event signatures are needed.
51    pub fn to_error_msg<'a>(
52        &self,
53        identifier: impl FnOnce() -> Option<&'a SignaturesIdentifier>,
54        checks: [bool; 5],
55        expected: Option<&RawLog>,
56        anonymous: bool,
57    ) -> String {
58        match self {
59            Self::Log { actual } => {
60                let Some(expected) = expected else {
61                    return "log != expected log".to_string();
62                };
63                let (expected_decoded, actual_decoded) = if anonymous {
64                    (None, None)
65                } else {
66                    identifier()
67                        .map(|identifier| {
68                            (decode_event(identifier, expected), decode_event(identifier, actual))
69                        })
70                        .unwrap_or_default()
71                };
72                get_emit_mismatch_message(
73                    checks,
74                    expected,
75                    actual,
76                    anonymous,
77                    expected_decoded.as_ref(),
78                    actual_decoded.as_ref(),
79                )
80            }
81            Self::Emitter { expected, actual } => {
82                format!("log emitter mismatch: expected={expected:#x}, got={actual:#x}")
83            }
84        }
85    }
86}
87
88/// Fills or checks the expected emits against an emitted log.
89///
90/// Returns the failure reason if the log violates an expectation.
91pub(crate) fn observe_log(tracker: &mut ExpectedEmitTracker, log: &Log) -> Option<&'static str> {
92    // Fill or check the expected emits.
93    // We expect for emit checks to be filled as they're declared (from oldest to newest),
94    // so we fill them and push them to the back of the queue.
95    // If the user has properly filled all the emits, they'll end up in their original order.
96    // If not, the queue will not be in the order the events will be intended to be filled,
97    // and we'll be able to later detect this and bail.
98
99    // First, we can return early if all events have been matched.
100    // This allows a contract to arbitrarily emit more events than expected (additive behavior),
101    // as long as all the previous events were matched in the order they were expected to be.
102    if tracker.iter().all(|(expected, _)| expected.found) {
103        return None;
104    }
105
106    // Check count=0 expectations against this log - fail immediately if violated
107    for (expected_emit, _) in tracker.iter() {
108        if expected_emit.count == 0
109            && !expected_emit.found
110            && let Some(expected_log) = &expected_emit.log
111            && checks_topics_and_data(expected_emit.checks, expected_log, log)
112            // Check revert address 
113            && (expected_emit.address.is_none_or(|address| address == log.address))
114        {
115            // This event was emitted but we expected it NOT to be (count=0).
116            return Some("log emitted but expected 0 times");
117        }
118    }
119
120    let should_fill_logs = tracker.iter().any(|(expected, _)| expected.log.is_none());
121    let index_to_fill_or_check = if should_fill_logs {
122        // If there's anything to fill, we start with the last event to match in the queue
123        // (without taking into account events already matched).
124        tracker.iter().position(|(emit, _)| emit.found).unwrap_or(tracker.len()).saturating_sub(1)
125    } else {
126        // if all expected logs are filled, check any unmatched event
127        // in the declared order, so we start from the front (like a queue).
128        // Skip count=0 expectations as they are handled separately above
129        tracker.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0)
130    };
131
132    // If there are only count=0 expectations left, we can return early
133    if !should_fill_logs && tracker.iter().all(|(emit, _)| emit.found || emit.count == 0) {
134        return None;
135    }
136
137    let (mut event_to_fill_or_check, mut count_map) =
138        tracker.remove(index_to_fill_or_check).expect("we should have an emit to fill or check");
139
140    let Some(expected) = &event_to_fill_or_check.log else {
141        // Unless the caller is trying to match an anonymous event, the first topic must be
142        // filled.
143        if event_to_fill_or_check.anonymous || !log.topics().is_empty() {
144            event_to_fill_or_check.log = Some(log.data.clone());
145            // If we only filled the expected log then we put it back at the same position.
146            tracker.insert(index_to_fill_or_check, (event_to_fill_or_check, count_map));
147        } else {
148            return Some("use vm.expectEmitAnonymous to match anonymous events");
149        }
150
151        return None;
152    };
153
154    // Increment/set `count` for `log.address` and `log.data`
155    match count_map.entry(log.address) {
156        Entry::Occupied(mut entry) => {
157            let log_count_map = entry.get_mut();
158            log_count_map.insert(&log.data);
159        }
160        Entry::Vacant(entry) => {
161            let mut log_count_map = LogCountMap::new(&event_to_fill_or_check);
162            if log_count_map.satisfies_checks(&log.data) {
163                log_count_map.insert(&log.data);
164                entry.insert(log_count_map);
165            }
166        }
167    }
168
169    event_to_fill_or_check.found = || -> bool {
170        if !checks_topics_and_data(event_to_fill_or_check.checks, expected, log) {
171            event_to_fill_or_check.mismatch_error =
172                Some(EmitMismatch::Log { actual: log.data.clone() });
173            return false;
174        }
175
176        // Maybe match source address.
177        if let Some(expected) = event_to_fill_or_check.address
178            && expected != log.address
179        {
180            event_to_fill_or_check.mismatch_error =
181                Some(EmitMismatch::Emitter { expected, actual: log.address });
182            return false;
183        }
184
185        let expected_count = event_to_fill_or_check.count;
186        match event_to_fill_or_check.address {
187            Some(emitter) => count_map
188                .get(&emitter)
189                .is_some_and(|log_map| log_map.count(&log.data) >= expected_count),
190            None => count_map
191                .values()
192                .find(|log_map| log_map.satisfies_checks(&log.data))
193                .is_some_and(|map| map.count(&log.data) >= expected_count),
194        }
195    }();
196
197    // If we found the event, we can push it to the back of the queue
198    // and begin expecting the next event.
199    if event_to_fill_or_check.found {
200        tracker.push_back((event_to_fill_or_check, count_map));
201    } else {
202        // We did not match this event, so we need to keep waiting for the right one to
203        // appear.
204        tracker.push_front((event_to_fill_or_check, count_map));
205    }
206
207    None
208}
209
210/// Handles expected emits specified by the `expectEmit` cheatcodes.
211///
212/// The second element of the tuple counts the number of times the log has been emitted by a
213/// particular address
214pub type ExpectedEmitTracker = VecDeque<(ExpectedEmit, AddressHashMap<LogCountMap>)>;
215
216#[derive(Clone, Debug, Default)]
217pub struct LogCountMap {
218    checks: [bool; 5],
219    expected_log: RawLog,
220    map: HashMap<RawLog, u64>,
221}
222
223impl LogCountMap {
224    /// Instantiates `LogCountMap`.
225    fn new(expected_emit: &ExpectedEmit) -> Self {
226        Self {
227            checks: expected_emit.checks,
228            expected_log: expected_emit.log.clone().expect("log should be filled here"),
229            map: Default::default(),
230        }
231    }
232
233    /// Inserts a log into the map and increments the count.
234    ///
235    /// The log must pass all checks against the expected log for the count to increment.
236    ///
237    /// Returns true if the log was inserted and count was incremented.
238    fn insert(&mut self, log: &RawLog) -> bool {
239        // If its already in the map, increment the count without checking.
240        if self.map.contains_key(log) {
241            self.map.entry(log.clone()).and_modify(|c| *c += 1);
242
243            return true;
244        }
245
246        if !self.satisfies_checks(log) {
247            return false;
248        }
249
250        self.map.entry(log.clone()).and_modify(|c| *c += 1).or_insert(1);
251
252        true
253    }
254
255    /// Checks the incoming raw log against the expected logs topics and data.
256    fn satisfies_checks(&self, log: &RawLog) -> bool {
257        checks_topics_and_data(self.checks, &self.expected_log, log)
258    }
259
260    pub fn count(&self, log: &RawLog) -> u64 {
261        if !self.satisfies_checks(log) {
262            return 0;
263        }
264
265        self.count_unchecked()
266    }
267
268    pub fn count_unchecked(&self) -> u64 {
269        self.map.values().sum()
270    }
271}
272
273fn checks_topics_and_data(checks: [bool; 5], expected: &RawLog, log: &RawLog) -> bool {
274    if log.topics().len() != expected.topics().len() {
275        return false;
276    }
277
278    // Check topics.
279    if !log
280        .topics()
281        .iter()
282        .enumerate()
283        .filter(|(i, _)| checks[*i])
284        .all(|(i, topic)| topic == &expected.topics()[i])
285    {
286        return false;
287    }
288
289    // Check data
290    if checks[4] && expected.data.as_ref() != log.data.as_ref() {
291        return false;
292    }
293
294    true
295}
296
297fn decode_event(
298    identifier: &foundry_evm_traces::identifier::SignaturesIdentifier,
299    log: &RawLog,
300) -> Option<DecodedCallLog> {
301    let topics = log.topics();
302    if topics.is_empty() {
303        return None;
304    }
305    let t0 = topics[0]; // event sig
306    // Try to identify the event
307    let event = foundry_common::block_on(
308        identifier.identify_event_with_indexed_count(t0, topics.len().saturating_sub(1)),
309    )?;
310
311    // Check if event already has indexed information from signatures
312    let has_indexed_info = event.inputs.iter().any(|p| p.indexed);
313    // Only use get_indexed_event if the event doesn't have indexing info
314    let indexed_event = if has_indexed_info { event } else { get_indexed_event(event, log) };
315
316    // Try to decode the event
317    if let Ok(decoded) = indexed_event.decode_log(log) {
318        let params = reconstruct_event_params(&indexed_event, &decoded);
319
320        let decoded_params = params
321            .into_iter()
322            .zip(indexed_event.inputs.iter())
323            .map(|(param, input)| (input.name.clone(), format_token(&param)))
324            .collect();
325
326        return Some(DecodedCallLog {
327            name: Some(indexed_event.name),
328            params: Some(decoded_params),
329        });
330    }
331
332    None
333}
334
335/// Gets a detailed mismatch message for emit assertions
336pub(crate) fn get_emit_mismatch_message(
337    checks: [bool; 5],
338    expected: &RawLog,
339    actual: &RawLog,
340    is_anonymous: bool,
341    expected_decoded: Option<&DecodedCallLog>,
342    actual_decoded: Option<&DecodedCallLog>,
343) -> String {
344    // Early return for completely different events or incompatible structures
345
346    // 1. Different number of topics
347    if actual.topics().len() != expected.topics().len() {
348        let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
349        let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
350        let expected_topics = checked_topic_count(expected, is_anonymous);
351        let actual_topics = checked_topic_count(actual, is_anonymous);
352
353        if expected_name == actual_name {
354            return format!(
355                "{actual_name} indexed topic count mismatch: expected {expected_topics}, got {actual_topics}"
356            );
357        }
358
359        return name_mismatched_logs(expected_decoded, actual_decoded);
360    }
361
362    // 2. Different event signatures (for non-anonymous events)
363    if !is_anonymous
364        && checks[0]
365        && (!expected.topics().is_empty() && !actual.topics().is_empty())
366        && expected.topics()[0] != actual.topics()[0]
367    {
368        return name_mismatched_logs(expected_decoded, actual_decoded);
369    }
370
371    let expected_data = expected.data.as_ref();
372    let actual_data = actual.data.as_ref();
373
374    // 3. Check data
375    if checks[4] && expected_data != actual_data {
376        // Different lengths or not ABI-encoded
377        if expected_data.len() != actual_data.len()
378            || !expected_data.len().is_multiple_of(32)
379            || expected_data.is_empty()
380        {
381            return name_mismatched_logs(expected_decoded, actual_decoded);
382        }
383    }
384
385    // expected and actual events are the same, so check individual parameters
386    let mut mismatches = Vec::new();
387
388    // Check topics (indexed parameters)
389    for (i, (expected_topic, actual_topic)) in
390        expected.topics().iter().zip(actual.topics().iter()).enumerate()
391    {
392        // Skip topic[0] for non-anonymous events (already checked above)
393        if i == 0 && !is_anonymous {
394            continue;
395        }
396
397        // Only check if the corresponding check flag is set
398        if i < checks.len() && checks[i] && expected_topic != actual_topic {
399            let param_idx = if is_anonymous {
400                i // For anonymous events, topic[0] is param 0
401            } else {
402                i - 1 // For regular events, topic[0] is event signature, so topic[1] is param 0
403            };
404            mismatches
405                .push(format!("param {param_idx}: expected={expected_topic}, got={actual_topic}"));
406        }
407    }
408
409    // Check data (non-indexed parameters)
410    if checks[4] && expected_data != actual_data {
411        let num_indexed_params = if is_anonymous {
412            expected.topics().len()
413        } else {
414            expected.topics().len().saturating_sub(1)
415        };
416
417        for (i, (expected_chunk, actual_chunk)) in
418            expected_data.chunks(32).zip(actual_data.chunks(32)).enumerate()
419        {
420            if expected_chunk != actual_chunk {
421                let param_idx = num_indexed_params + i;
422                mismatches.push(format!(
423                    "param {}: expected={}, got={}",
424                    param_idx,
425                    hex::encode_prefixed(expected_chunk),
426                    hex::encode_prefixed(actual_chunk)
427                ));
428            }
429        }
430    }
431
432    if mismatches.is_empty() {
433        name_mismatched_logs(expected_decoded, actual_decoded)
434    } else {
435        // Build the error message with event names if available
436        let event_prefix = match (expected_decoded, actual_decoded) {
437            (Some(expected_dec), Some(actual_dec)) if expected_dec.name == actual_dec.name => {
438                format!(
439                    "{} param mismatch",
440                    expected_dec.name.as_ref().unwrap_or(&"log".to_string())
441                )
442            }
443            _ => {
444                if is_anonymous {
445                    "anonymous log mismatch".to_string()
446                } else {
447                    "log mismatch".to_string()
448                }
449            }
450        };
451
452        // Add parameter details if available from decoded events
453        let detailed_mismatches = if let (Some(expected_dec), Some(actual_dec)) =
454            (expected_decoded, actual_decoded)
455            && let (Some(expected_params), Some(actual_params)) =
456                (&expected_dec.params, &actual_dec.params)
457        {
458            mismatches
459                .into_iter()
460                .map(|basic_mismatch| {
461                    // Try to find the parameter name and decoded value
462                    if let Some(param_idx) = basic_mismatch
463                        .split(' ')
464                        .nth(1)
465                        .and_then(|s| s.trim_end_matches(':').parse::<usize>().ok())
466                        && param_idx < expected_params.len()
467                        && param_idx < actual_params.len()
468                    {
469                        let (expected_name, expected_value) = &expected_params[param_idx];
470                        let (_actual_name, actual_value) = &actual_params[param_idx];
471                        let param_name = if expected_name.is_empty() {
472                            &format!("param{param_idx}")
473                        } else {
474                            expected_name
475                        };
476                        return format!(
477                            "{param_name}: expected={expected_value}, got={actual_value}",
478                        );
479                    }
480                    basic_mismatch
481                })
482                .collect::<Vec<_>>()
483        } else {
484            mismatches
485        };
486
487        format!("{} at {}", event_prefix, detailed_mismatches.join(", "))
488    }
489}
490
491/// Formats the generic mismatch message: "log != expected log" to include event names if available
492fn name_mismatched_logs(
493    expected_decoded: Option<&DecodedCallLog>,
494    actual_decoded: Option<&DecodedCallLog>,
495) -> String {
496    let expected_name = expected_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
497    let actual_name = actual_decoded.and_then(|d| d.name.as_deref()).unwrap_or("log");
498    format!("{actual_name} != expected {expected_name}")
499}
500
501fn checked_topic_count(log: &RawLog, is_anonymous: bool) -> usize {
502    if is_anonymous { log.topics().len() } else { log.topics().len().saturating_sub(1) }
503}
504
505/// Why the tracked expected emits were not satisfied when a call ended.
506#[derive(Debug)]
507pub(crate) enum UnmetEmit {
508    /// An expected log was never matched.
509    Unmatched(ExpectedEmit),
510    /// A log was emitted a different number of times than expected.
511    Count { expected: u64, actual: u64 },
512    /// The call did not succeed while logs were expected.
513    Failed,
514}
515
516impl UnmetEmit {
517    /// Encodes the revert data, calling `identifier` only when event signatures are needed.
518    pub(crate) fn encode<'a>(
519        self,
520        identifier: impl FnOnce() -> Option<&'a SignaturesIdentifier>,
521    ) -> Bytes {
522        match self {
523            Self::Unmatched(expected) => {
524                let error_msg = expected
525                    .mismatch_error
526                    .as_ref()
527                    .map(|mismatch| {
528                        mismatch.to_error_msg(
529                            identifier,
530                            expected.checks,
531                            expected.log.as_ref(),
532                            expected.anonymous,
533                        )
534                    })
535                    .unwrap_or_else(|| "log != expected log".to_string());
536                error_msg.abi_encode().into()
537            }
538            Self::Count { expected, actual } => {
539                Error::encode(format!("log emitted {actual} times, expected {expected}"))
540            }
541            Self::Failed => Error::encode(
542                "expected an emit, but the call reverted instead. \
543                 ensure you're testing the happy path when using `expectEmit`",
544            ),
545        }
546    }
547}
548
549/// Checks the entire tracker when a non-static call ends at any tracked expectation's `depth`.
550///
551/// Clears the tracker only if all expectations are satisfied; otherwise leaves it untouched.
552/// Callers must skip already-reverted calls to preserve their original failure.
553pub(crate) fn check_call_emits(
554    tracker: &mut ExpectedEmitTracker,
555    depth: usize,
556    is_static: bool,
557    succeeded: bool,
558) -> Option<UnmetEmit> {
559    let should_check_emits =
560        tracker.iter().any(|(expected, _)| expected.depth == depth) && !is_static;
561    if !should_check_emits {
562        return None;
563    }
564
565    let expected_counts = tracker
566        .iter()
567        .filter_map(|(expected, count_map)| {
568            let count = match expected.address {
569                Some(emitter) => match count_map.get(&emitter) {
570                    Some(log_count) => expected
571                        .log
572                        .as_ref()
573                        .map(|l| log_count.count(l))
574                        .unwrap_or_else(|| log_count.count_unchecked()),
575                    None => 0,
576                },
577                None => match &expected.log {
578                    Some(log) => count_map.values().map(|logs| logs.count(log)).sum(),
579                    None => count_map.values().map(|logs| logs.count_unchecked()).sum(),
580                },
581            };
582
583            (count != expected.count).then_some((expected, count))
584        })
585        .collect::<Vec<_>>();
586
587    if let Some((expected, _)) =
588        tracker.iter().find(|(expected, _)| !expected.found && expected.count > 0)
589    {
590        return Some(UnmetEmit::Unmatched(expected.clone()));
591    }
592
593    if let Some((expected, count)) = expected_counts.first() {
594        return Some(if succeeded {
595            UnmetEmit::Count { expected: expected.count, actual: *count }
596        } else {
597            UnmetEmit::Failed
598        });
599    }
600
601    // Later calls need their own expectations.
602    tracker.clear();
603    None
604}
605
606/// Checks for leftover expected emits when the root call ends without reverting.
607///
608/// Returns the failure message if any expectation is still unmatched.
609/// Callers must skip already-reverted calls to preserve their original failure.
610pub(crate) fn first_unmet_root_emit(
611    tracker: &mut ExpectedEmitTracker,
612    succeeded: bool,
613) -> Option<&'static str> {
614    // A count=0 expectation is met when its log was never emitted.
615    for (expected, _) in tracker.iter_mut() {
616        if expected.count == 0 && !expected.found {
617            expected.found = true;
618        }
619    }
620    tracker.retain(|(expected, _)| !expected.found);
621    if tracker.is_empty() {
622        return None;
623    }
624    Some(if succeeded {
625        "expected an emit, but no logs were emitted afterwards. \
626         you might have mismatched events or not enough events were emitted"
627    } else {
628        "expected an emit, but the call reverted instead. \
629         ensure you're testing the happy path when using `expectEmit`"
630    })
631}
632
633#[cfg(test)]
634mod tests {
635    use super::*;
636    use alloy_primitives::{B256, address, b256};
637
638    const EMITTER: Address = address!("0x00000000000000000000000000000000000000aa");
639    const OTHER: Address = address!("0x00000000000000000000000000000000000000bb");
640    const TOPIC_A: B256 =
641        b256!("0x000000000000000000000000000000000000000000000000000000000000000a");
642    const TOPIC_B: B256 =
643        b256!("0x000000000000000000000000000000000000000000000000000000000000000b");
644    const ALL: [bool; 5] = [true; 5];
645
646    fn log(emitter: Address, topics: Vec<B256>, data: &'static [u8]) -> Log {
647        Log::new_unchecked(emitter, topics, Bytes::from_static(data))
648    }
649
650    fn expect(tracker: &mut ExpectedEmitTracker, expected: Option<&Log>, checks: [bool; 5]) {
651        expect_with(tracker, expected, checks, None, 1, false);
652    }
653
654    fn expect_with(
655        tracker: &mut ExpectedEmitTracker,
656        expected: Option<&Log>,
657        checks: [bool; 5],
658        address: Option<Address>,
659        count: u64,
660        anonymous: bool,
661    ) {
662        tracker.push_back((
663            ExpectedEmit {
664                depth: 0,
665                log: expected.map(|log| log.data.clone()),
666                checks,
667                address,
668                anonymous,
669                found: false,
670                count,
671                mismatch_error: None,
672            },
673            Default::default(),
674        ));
675    }
676
677    fn found(tracker: &ExpectedEmitTracker) -> Vec<bool> {
678        tracker.iter().map(|(expected, _)| expected.found).collect()
679    }
680
681    #[test]
682    fn matches_logs_in_order() {
683        let a = log(EMITTER, vec![TOPIC_A], b"a");
684        let b = log(EMITTER, vec![TOPIC_B], b"b");
685        let mut tracker = ExpectedEmitTracker::new();
686        expect(&mut tracker, Some(&a), ALL);
687        expect(&mut tracker, Some(&b), ALL);
688
689        assert_eq!(observe_log(&mut tracker, &a), None);
690        assert_eq!(observe_log(&mut tracker, &b), None);
691        assert_eq!(found(&tracker), [true, true]);
692    }
693
694    #[test]
695    fn out_of_order_logs_leave_an_expectation_unmatched() {
696        let a = log(EMITTER, vec![TOPIC_A], b"a");
697        let b = log(EMITTER, vec![TOPIC_B], b"b");
698        let mut tracker = ExpectedEmitTracker::new();
699        expect(&mut tracker, Some(&a), ALL);
700        expect(&mut tracker, Some(&b), ALL);
701
702        assert_eq!(observe_log(&mut tracker, &b), None);
703        assert_eq!(observe_log(&mut tracker, &a), None);
704        assert_eq!(found(&tracker), [false, true]);
705        assert_eq!(tracker[0].0.log.as_ref(), Some(&b.data));
706    }
707
708    #[test]
709    fn fills_an_unfilled_expectation_with_the_next_log() {
710        let a = log(EMITTER, vec![TOPIC_A], b"a");
711        let mut tracker = ExpectedEmitTracker::new();
712        expect(&mut tracker, None, ALL);
713
714        assert_eq!(observe_log(&mut tracker, &a), None);
715        assert_eq!(found(&tracker), [false]);
716        assert_eq!(tracker[0].0.log.as_ref(), Some(&a.data));
717
718        assert_eq!(observe_log(&mut tracker, &a), None);
719        assert_eq!(found(&tracker), [true]);
720    }
721
722    #[test]
723    fn fills_the_last_pending_expectation_before_found_ones() {
724        let a = log(EMITTER, vec![TOPIC_A], b"a");
725        let b = log(EMITTER, vec![TOPIC_B], b"b");
726        let mut tracker = ExpectedEmitTracker::new();
727        expect(&mut tracker, None, ALL);
728        expect(&mut tracker, None, ALL);
729        expect(&mut tracker, Some(&a), ALL);
730        tracker[2].0.found = true;
731
732        assert_eq!(observe_log(&mut tracker, &b), None);
733        assert_eq!(tracker[0].0.log, None);
734        assert_eq!(tracker[1].0.log.as_ref(), Some(&b.data));
735        assert_eq!(tracker[2].0.log.as_ref(), Some(&a.data));
736        assert_eq!(found(&tracker), [false, false, true]);
737    }
738
739    #[test]
740    fn counts_matching_logs() {
741        let a = log(EMITTER, vec![TOPIC_A], b"a");
742        let mut tracker = ExpectedEmitTracker::new();
743        expect_with(&mut tracker, Some(&a), ALL, None, 2, false);
744
745        assert_eq!(observe_log(&mut tracker, &a), None);
746        assert_eq!(found(&tracker), [false]);
747        assert_eq!(observe_log(&mut tracker, &a), None);
748        assert_eq!(found(&tracker), [true]);
749    }
750
751    #[test]
752    fn count_zero_fails_when_the_log_is_emitted() {
753        let a = log(EMITTER, vec![TOPIC_A], b"a");
754        let b = log(EMITTER, vec![TOPIC_B], b"b");
755        let mut tracker = ExpectedEmitTracker::new();
756        expect_with(&mut tracker, Some(&a), ALL, None, 0, false);
757
758        assert_eq!(observe_log(&mut tracker, &b), None);
759        assert_eq!(observe_log(&mut tracker, &a), Some("log emitted but expected 0 times"));
760        assert_eq!(found(&tracker), [false]);
761    }
762
763    #[test]
764    fn anonymous_logs_require_expect_emit_anonymous() {
765        let anonymous = log(EMITTER, vec![], b"a");
766
767        let mut tracker = ExpectedEmitTracker::new();
768        expect(&mut tracker, None, ALL);
769        assert_eq!(
770            observe_log(&mut tracker, &anonymous),
771            Some("use vm.expectEmitAnonymous to match anonymous events")
772        );
773        assert!(tracker.is_empty());
774
775        let mut tracker = ExpectedEmitTracker::new();
776        expect_with(&mut tracker, None, ALL, None, 1, true);
777        assert_eq!(observe_log(&mut tracker, &anonymous), None);
778        assert_eq!(tracker[0].0.log.as_ref(), Some(&anonymous.data));
779        assert_eq!(observe_log(&mut tracker, &anonymous), None);
780        assert_eq!(found(&tracker), [true]);
781    }
782
783    #[test]
784    fn filters_by_emitter_address() {
785        let a = log(EMITTER, vec![TOPIC_A], b"a");
786        let from_other = log(OTHER, vec![TOPIC_A], b"a");
787        let mut tracker = ExpectedEmitTracker::new();
788        expect_with(&mut tracker, Some(&a), ALL, Some(EMITTER), 1, false);
789
790        assert_eq!(observe_log(&mut tracker, &from_other), None);
791        assert_eq!(found(&tracker), [false]);
792        assert!(matches!(
793            tracker[0].0.mismatch_error,
794            Some(EmitMismatch::Emitter { expected: EMITTER, actual: OTHER })
795        ));
796
797        assert_eq!(observe_log(&mut tracker, &a), None);
798        assert_eq!(found(&tracker), [true]);
799    }
800
801    #[test]
802    fn applies_check_flags() {
803        const TOPICS: [B256; 4] = [TOPIC_A, TOPIC_A, TOPIC_A, TOPIC_A];
804        let expected = log(EMITTER, TOPICS.to_vec(), b"expected");
805
806        // Each flag only compares its own topic or the data.
807        for flag in 0..5 {
808            let actual = if flag < 4 {
809                let mut topics = TOPICS;
810                topics[flag] = TOPIC_B;
811                log(EMITTER, topics.to_vec(), b"expected")
812            } else {
813                log(EMITTER, TOPICS.to_vec(), b"other")
814            };
815
816            let mut unchecked = ALL;
817            unchecked[flag] = false;
818            let mut tracker = ExpectedEmitTracker::new();
819            expect(&mut tracker, Some(&expected), unchecked);
820            assert_eq!(observe_log(&mut tracker, &actual), None);
821            assert_eq!(found(&tracker), [true], "flag {flag} unchecked");
822
823            let mut tracker = ExpectedEmitTracker::new();
824            expect(&mut tracker, Some(&expected), ALL);
825            assert_eq!(observe_log(&mut tracker, &actual), None);
826            assert_eq!(found(&tracker), [false], "flag {flag} checked");
827            assert!(matches!(
828                &tracker[0].0.mismatch_error,
829                Some(EmitMismatch::Log { actual: mismatch }) if mismatch == &actual.data
830            ));
831        }
832    }
833
834    #[test]
835    fn mismatch_messages() {
836        let expected = log(EMITTER, vec![TOPIC_A], b"expected");
837        let actual = log(EMITTER, vec![TOPIC_A], b"actual");
838
839        let emitter = EmitMismatch::Emitter { expected: EMITTER, actual: OTHER };
840        assert_eq!(
841            emitter.to_error_msg(unused_identifier, ALL, Some(&expected.data), false),
842            "log emitter mismatch: expected=0x00000000000000000000000000000000000000aa, \
843             got=0x00000000000000000000000000000000000000bb"
844        );
845
846        let mismatch = EmitMismatch::Log { actual: actual.data };
847        assert_eq!(
848            mismatch.to_error_msg(unused_identifier, ALL, None, false),
849            "log != expected log"
850        );
851        assert_eq!(
852            mismatch.to_error_msg(unused_identifier, ALL, Some(&expected.data), true),
853            "log != expected log"
854        );
855        let mut resolved = false;
856        let resolve = || {
857            resolved = true;
858            None
859        };
860        assert_eq!(
861            mismatch.to_error_msg(resolve, ALL, Some(&expected.data), false),
862            "log != expected log"
863        );
864        assert!(resolved, "non-anonymous log mismatches resolve signatures");
865    }
866
867    /// Signatures are only resolved for non-anonymous log mismatches.
868    fn unused_identifier<'a>() -> Option<&'a SignaturesIdentifier> {
869        panic!("signatures identifier must not be resolved")
870    }
871
872    #[test]
873    fn call_end_reports_logs_counted_across_emitters() {
874        // Found after two logs from one emitter, with a third from another emitter.
875        let a = log(EMITTER, vec![TOPIC_A], b"a");
876        let mut tracker = ExpectedEmitTracker::new();
877        expect_with(&mut tracker, Some(&a), ALL, None, 2, false);
878        let (expected, count_map) = &mut tracker[0];
879        expected.found = true;
880        for (emitter, logs) in [(EMITTER, 2), (OTHER, 1)] {
881            let mut counts = LogCountMap::new(expected);
882            for _ in 0..logs {
883                counts.insert(&a.data);
884            }
885            count_map.insert(emitter, counts);
886        }
887
888        let before = format!("{tracker:?}");
889        for (succeeded, message) in [
890            (true, "log emitted 3 times, expected 2"),
891            (
892                false,
893                "expected an emit, but the call reverted instead. \
894                 ensure you're testing the happy path when using `expectEmit`",
895            ),
896        ] {
897            let unmet = check_call_emits(&mut tracker, 0, false, succeeded).unwrap();
898            assert_eq!(unmet.encode(unused_identifier), Error::encode(message));
899            assert_eq!(format!("{tracker:?}"), before);
900        }
901    }
902
903    #[test]
904    fn root_end_reports_leftover_expectations() {
905        let a = log(EMITTER, vec![TOPIC_A], b"a");
906        let mut tracker = ExpectedEmitTracker::new();
907        expect_with(&mut tracker, Some(&a), ALL, None, 0, false);
908        expect(&mut tracker, Some(&a), ALL);
909        tracker[1].0.found = true;
910
911        assert_eq!(first_unmet_root_emit(&mut tracker, true), None);
912        assert!(tracker.is_empty());
913
914        expect(&mut tracker, Some(&a), ALL);
915        assert_eq!(
916            first_unmet_root_emit(&mut tracker.clone(), true),
917            Some(
918                "expected an emit, but no logs were emitted afterwards. \
919                 you might have mismatched events or not enough events were emitted"
920            )
921        );
922        assert_eq!(
923            first_unmet_root_emit(&mut tracker, false),
924            Some(
925                "expected an emit, but the call reverted instead. \
926                 ensure you're testing the happy path when using `expectEmit`"
927            )
928        );
929    }
930
931    #[test]
932    fn call_end_skips_static_and_unrelated_calls_then_clears_tracker() {
933        let a = log(EMITTER, vec![TOPIC_A], b"a");
934        let b = log(OTHER, vec![TOPIC_B], b"b");
935        let mut tracker = ExpectedEmitTracker::new();
936        expect(&mut tracker, Some(&a), ALL);
937        expect(&mut tracker, Some(&b), ALL);
938        tracker[1].0.depth = 1;
939        assert_eq!(observe_log(&mut tracker, &b), None);
940
941        for satisfied in [false, true] {
942            if satisfied {
943                assert_eq!(observe_log(&mut tracker, &a), None);
944                assert_eq!(observe_log(&mut tracker, &b), None);
945            }
946            let before = format!("{tracker:?}");
947            for (depth, is_static) in [(0, true), (2, false)] {
948                assert!(check_call_emits(&mut tracker, depth, is_static, true).is_none());
949                assert_eq!(format!("{tracker:?}"), before);
950            }
951        }
952
953        assert!(check_call_emits(&mut tracker, 0, false, true).is_none());
954        assert!(tracker.is_empty());
955    }
956
957    #[test]
958    fn call_end_checks_counts_at_other_depths() {
959        let a = log(EMITTER, vec![TOPIC_A], b"a");
960        let b = log(OTHER, vec![TOPIC_B], b"b");
961        let mut tracker = ExpectedEmitTracker::new();
962        expect(&mut tracker, Some(&a), ALL);
963        expect_with(&mut tracker, Some(&b), ALL, Some(OTHER), 0, false);
964        tracker[1].0.depth = 1;
965        assert_eq!(observe_log(&mut tracker, &a), None);
966        let (expected, count_map) = &mut tracker[0];
967        assert_eq!(expected.depth, 1);
968        let mut counts = LogCountMap::new(expected);
969        assert!(counts.insert(&b.data));
970        count_map.insert(OTHER, counts);
971        let before = format!("{tracker:?}");
972
973        let unmet = check_call_emits(&mut tracker, 0, false, true).unwrap();
974        assert!(matches!(unmet, UnmetEmit::Count { expected: 0, actual: 1 }));
975        assert_eq!(format!("{tracker:?}"), before);
976    }
977
978    #[test]
979    fn call_end_unmatched_expectation_precedes_wrong_count_and_failed_call() {
980        let a = log(EMITTER, vec![TOPIC_A], b"a");
981        let b = log(OTHER, vec![TOPIC_B], b"b");
982        let mut tracker = ExpectedEmitTracker::new();
983        expect(&mut tracker, Some(&a), ALL);
984        tracker[0].0.found = true;
985        expect(&mut tracker, Some(&b), ALL);
986        tracker[1].0.depth = 1;
987        let before = format!("{tracker:?}");
988
989        for succeeded in [false, true] {
990            let unmet = check_call_emits(&mut tracker, 0, false, succeeded).unwrap();
991            assert!(matches!(&unmet, UnmetEmit::Unmatched(expected)
992                if expected.log.as_ref() == Some(&b.data)));
993            assert_eq!(
994                unmet.encode(unused_identifier),
995                Bytes::from("log != expected log".abi_encode())
996            );
997            assert_eq!(format!("{tracker:?}"), before);
998        }
999    }
1000
1001    #[test]
1002    fn unmatched_emit_uses_plain_abi_encoding_and_lazy_signature_resolution() {
1003        let a = log(EMITTER, vec![TOPIC_A], b"a");
1004        let actual = log(EMITTER, vec![TOPIC_A], b"actual");
1005        let mismatch = EmitMismatch::Log { actual: actual.data };
1006        for (expected_log, mismatch_error, anonymous, message) in [
1007            (None, Some(mismatch.clone()), false, "log != expected log"),
1008            (Some(&a), Some(mismatch.clone()), true, "log != expected log"),
1009            (
1010                Some(&a),
1011                Some(EmitMismatch::Emitter { expected: EMITTER, actual: OTHER }),
1012                false,
1013                "log emitter mismatch: expected=0x00000000000000000000000000000000000000aa, \
1014                 got=0x00000000000000000000000000000000000000bb",
1015            ),
1016        ] {
1017            let mut tracker = ExpectedEmitTracker::new();
1018            expect_with(&mut tracker, expected_log, ALL, None, 1, anonymous);
1019            tracker[0].0.mismatch_error = mismatch_error;
1020            let unmet = check_call_emits(&mut tracker, 0, false, true).unwrap();
1021            assert_eq!(unmet.encode(unused_identifier), Bytes::from(message.abi_encode()));
1022        }
1023
1024        let mut tracker = ExpectedEmitTracker::new();
1025        expect(&mut tracker, Some(&a), ALL);
1026        tracker[0].0.mismatch_error = Some(mismatch);
1027        let unmet = check_call_emits(&mut tracker, 0, false, true).unwrap();
1028        let mut resolved = false;
1029        let encoded = unmet.encode(|| {
1030            resolved = true;
1031            None
1032        });
1033        assert!(resolved, "non-anonymous log mismatches resolve signatures");
1034        assert_eq!(encoded, Bytes::from("log != expected log".abi_encode()));
1035    }
1036}