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