1use 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 pub depth: usize,
21 pub log: Option<RawLog>,
23 pub checks: [bool; 5],
30 pub address: Option<Address>,
32 pub anonymous: bool,
35 pub found: bool,
37 pub count: u64,
39 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 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
88pub(crate) fn observe_log(tracker: &mut ExpectedEmitTracker, log: &Log) -> Option<&'static str> {
92 if tracker.iter().all(|(expected, _)| expected.found) {
103 return None;
104 }
105
106 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 && (expected_emit.address.is_none_or(|address| address == log.address))
114 {
115 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 tracker.iter().position(|(emit, _)| emit.found).unwrap_or(tracker.len()).saturating_sub(1)
125 } else {
126 tracker.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0)
130 };
131
132 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 if event_to_fill_or_check.anonymous || !log.topics().is_empty() {
144 event_to_fill_or_check.log = Some(log.data.clone());
145 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 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 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 event_to_fill_or_check.found {
200 tracker.push_back((event_to_fill_or_check, count_map));
201 } else {
202 tracker.push_front((event_to_fill_or_check, count_map));
205 }
206
207 None
208}
209
210pub 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 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 fn insert(&mut self, log: &RawLog) -> bool {
239 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 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 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 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]; let event = foundry_common::block_on(
308 identifier.identify_event_with_indexed_count(t0, topics.len().saturating_sub(1)),
309 )?;
310
311 let has_indexed_info = event.inputs.iter().any(|p| p.indexed);
313 let indexed_event = if has_indexed_info { event } else { get_indexed_event(event, log) };
315
316 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(¶m)))
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
335pub(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 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 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 if checks[4] && expected_data != actual_data {
376 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 let mut mismatches = Vec::new();
387
388 for (i, (expected_topic, actual_topic)) in
390 expected.topics().iter().zip(actual.topics().iter()).enumerate()
391 {
392 if i == 0 && !is_anonymous {
394 continue;
395 }
396
397 if i < checks.len() && checks[i] && expected_topic != actual_topic {
399 let param_idx = if is_anonymous {
400 i } else {
402 i - 1 };
404 mismatches
405 .push(format!("param {param_idx}: expected={expected_topic}, got={actual_topic}"));
406 }
407 }
408
409 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 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 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 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
491fn 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#[derive(Debug)]
507pub(crate) enum UnmetEmit {
508 Unmatched(ExpectedEmit),
510 Count { expected: u64, actual: u64 },
512 Failed,
514}
515
516impl UnmetEmit {
517 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
549pub(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 tracker.clear();
603 None
604}
605
606pub(crate) fn first_unmet_root_emit(
611 tracker: &mut ExpectedEmitTracker,
612 succeeded: bool,
613) -> Option<&'static str> {
614 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 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 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 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}