1use 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 pub depth: usize,
19 pub log: Option<RawLog>,
21 pub checks: [bool; 5],
28 pub address: Option<Address>,
30 pub anonymous: bool,
33 pub found: bool,
35 pub count: u64,
37 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 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
86pub(crate) fn observe_log(tracker: &mut ExpectedEmitTracker, log: &Log) -> Option<&'static str> {
90 if tracker.iter().all(|(expected, _)| expected.found) {
101 return None;
102 }
103
104 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 && (expected_emit.address.is_none_or(|address| address == log.address))
112 {
113 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 tracker.iter().position(|(emit, _)| emit.found).unwrap_or(tracker.len()).saturating_sub(1)
123 } else {
124 tracker.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0)
128 };
129
130 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 if event_to_fill_or_check.anonymous || !log.topics().is_empty() {
142 event_to_fill_or_check.log = Some(log.data.clone());
143 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 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 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 event_to_fill_or_check.found {
198 tracker.push_back((event_to_fill_or_check, count_map));
199 } else {
200 tracker.push_front((event_to_fill_or_check, count_map));
203 }
204
205 None
206}
207
208pub 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 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 fn insert(&mut self, log: &RawLog) -> bool {
237 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 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 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 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]; let event = foundry_common::block_on(
306 identifier.identify_event_with_indexed_count(t0, topics.len().saturating_sub(1)),
307 )?;
308
309 let has_indexed_info = event.inputs.iter().any(|p| p.indexed);
311 let indexed_event = if has_indexed_info { event } else { get_indexed_event(event, log) };
313
314 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(¶m)))
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
333fn 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
350pub(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 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 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 if checks[4] && expected_data != actual_data {
391 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 let mut mismatches = Vec::new();
402
403 for (i, (expected_topic, actual_topic)) in
405 expected.topics().iter().zip(actual.topics().iter()).enumerate()
406 {
407 if i == 0 && !is_anonymous {
409 continue;
410 }
411
412 if i < checks.len() && checks[i] && expected_topic != actual_topic {
414 let param_idx = if is_anonymous {
415 i } else {
417 i - 1 };
419 mismatches
420 .push(format!("param {param_idx}: expected={expected_topic}, got={actual_topic}"));
421 }
422 }
423
424 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 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 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 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
506fn 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#[derive(Debug)]
522pub(crate) enum UnmetEmit {
523 Unmatched(ExpectedEmit),
525 Count { expected: u64, actual: u64 },
527 Failed,
529}
530
531impl UnmetEmit {
532 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
564pub(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 tracker.clear();
618 None
619}
620
621pub(crate) fn first_unmet_root_emit(
626 tracker: &mut ExpectedEmitTracker,
627 succeeded: bool,
628) -> Option<&'static str> {
629 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 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 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 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}