Skip to content

Commit 1f7f460

Browse files
fix(cheatcodes): expectEmit(count: 0) should not fail on a different log (#11663)
* fix(cheatcodes): expectEmit(count: 0) should not fail on a different log * nit * nit --------- Co-authored-by: grandizzy <[email protected]>
1 parent ea1cdd3 commit 1f7f460

File tree

5 files changed

+73
-11
lines changed

5 files changed

+73
-11
lines changed

crates/cheatcodes/src/inspector.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1539,7 +1539,13 @@ impl Inspector<EthEvmContext<&mut dyn DatabaseExt>> for Cheatcodes {
15391539

15401540
// Check if we have any leftover expected emits
15411541
// First, if any emits were found at the root call, then we its ok and we remove them.
1542-
self.expected_emits.retain(|(expected, _)| expected.count > 0 && !expected.found);
1542+
// For count=0 expectations, NOT being found is success, so mark them as found
1543+
for (expected, _) in &mut self.expected_emits {
1544+
if expected.count == 0 && !expected.found {
1545+
expected.found = true;
1546+
}
1547+
}
1548+
self.expected_emits.retain(|(expected, _)| !expected.found);
15431549
// If not empty, we got mismatched emits
15441550
if !self.expected_emits.is_empty() {
15451551
let msg = if outcome.result.is_ok() {

crates/cheatcodes/src/test/expect.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,26 @@ pub(crate) fn handle_expect_emit(
809809
return;
810810
}
811811

812+
// Check count=0 expectations against this log - fail immediately if violated
813+
for (expected_emit, _) in &state.expected_emits {
814+
if expected_emit.count == 0
815+
&& !expected_emit.found
816+
&& let Some(expected_log) = &expected_emit.log
817+
&& checks_topics_and_data(expected_emit.checks, expected_log, log)
818+
// Check revert address
819+
&& (expected_emit.address.is_none() || expected_emit.address == Some(log.address))
820+
{
821+
// This event was emitted but we expected it NOT to be (count=0)
822+
// Fail immediately
823+
interpreter.bytecode.set_action(InterpreterAction::new_return(
824+
InstructionResult::Revert,
825+
Error::encode("log emitted 1 time, expected 0"),
826+
interpreter.gas,
827+
));
828+
return;
829+
}
830+
}
831+
812832
let should_fill_logs = state.expected_emits.iter().any(|(expected, _)| expected.log.is_none());
813833
let index_to_fill_or_check = if should_fill_logs {
814834
// If there's anything to fill, we start with the last event to match in the queue
@@ -820,11 +840,19 @@ pub(crate) fn handle_expect_emit(
820840
.unwrap_or(state.expected_emits.len())
821841
.saturating_sub(1)
822842
} else {
823-
// Otherwise, if all expected logs are filled, we start to check any unmatched event
843+
// if all expected logs are filled, check any unmatched event
824844
// in the declared order, so we start from the front (like a queue).
825-
0
845+
// Skip count=0 expectations as they are handled separately above
846+
state.expected_emits.iter().position(|(emit, _)| !emit.found && emit.count > 0).unwrap_or(0)
826847
};
827848

849+
// If there are only count=0 expectations left, we can return early
850+
if !should_fill_logs
851+
&& state.expected_emits.iter().all(|(emit, _)| emit.found || emit.count == 0)
852+
{
853+
return;
854+
}
855+
828856
let (mut event_to_fill_or_check, mut count_map) = state
829857
.expected_emits
830858
.remove(index_to_fill_or_check)
@@ -852,18 +880,13 @@ pub(crate) fn handle_expect_emit(
852880
// Increment/set `count` for `log.address` and `log.data`
853881
match count_map.entry(log.address) {
854882
Entry::Occupied(mut entry) => {
855-
// Checks and inserts the log into the map.
856-
// If the log doesn't pass the checks, it is ignored and `count` is not incremented.
857883
let log_count_map = entry.get_mut();
858884
log_count_map.insert(&log.data);
859885
}
860886
Entry::Vacant(entry) => {
861887
let mut log_count_map = LogCountMap::new(&event_to_fill_or_check);
862-
863888
if log_count_map.satisfies_checks(&log.data) {
864889
log_count_map.insert(&log.data);
865-
866-
// Entry is only inserted if it satisfies the checks.
867890
entry.insert(log_count_map);
868891
}
869892
}
@@ -910,7 +933,6 @@ pub(crate) fn handle_expect_emit(
910933
}
911934

912935
let expected_count = event_to_fill_or_check.count;
913-
914936
match event_to_fill_or_check.address {
915937
Some(emitter) => count_map
916938
.get(&emitter)

crates/forge/tests/cli/failure_assertions.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,8 @@ Suite result: FAILED. 0 passed; 15 failed; 0 skipped; [ELAPSED]
222222
[FAIL: log != expected log] testShouldFailCountEmitsFromAddress() ([GAS])
223223
[FAIL: log != expected log] testShouldFailCountLessEmits() ([GAS])
224224
[FAIL: log != expected Something] testShouldFailEmitSomethingElse() ([GAS])
225-
[FAIL: log emitted 1 times, expected 0] testShouldFailNoEmit() ([GAS])
226-
[FAIL: log emitted 1 times, expected 0] testShouldFailNoEmitFromAddress() ([GAS])
225+
[FAIL: log emitted 1 time, expected 0] testShouldFailNoEmit() ([GAS])
226+
[FAIL: log emitted 1 time, expected 0] testShouldFailNoEmitFromAddress() ([GAS])
227227
Suite result: FAILED. 0 passed; 5 failed; 0 skipped; [ELAPSED]
228228
...
229229
"#,

crates/forge/tests/it/repros.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,3 +422,6 @@ test_repro!(
422422

423423
// https://github.com/foundry-rs/foundry/issues/11353
424424
test_repro!(11353);
425+
426+
// https://github.com/foundry-rs/foundry/issues/11616
427+
test_repro!(11616);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
pragma solidity ^0.8.24;
3+
4+
import "ds-test/test.sol";
5+
import "cheats/Vm.sol";
6+
7+
contract Emit {
8+
event A();
9+
event B();
10+
11+
function emitB() public {
12+
emit B();
13+
}
14+
}
15+
16+
contract Issue11616Test is DSTest {
17+
Vm constant vm = Vm(HEVM_ADDRESS);
18+
Emit public e;
19+
20+
function setUp() public {
21+
e = new Emit();
22+
}
23+
24+
function test_emitNotOk() public {
25+
vm.expectEmit({count: 0});
26+
emit Emit.A();
27+
vm.expectEmit();
28+
emit Emit.B();
29+
e.emitB();
30+
}
31+
}

0 commit comments

Comments
 (0)