Skip to content

Commit 8a51b89

Browse files
authored
fix(cheatcodes): do not account already matched emit events when fill or check (#8824)
* fix(6643): do not account already matched events when fill or check * Move test as repro test
1 parent 872e2f3 commit 8a51b89

File tree

3 files changed

+99
-18
lines changed

3 files changed

+99
-18
lines changed

crates/cheatcodes/src/test/expect.rs

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -464,14 +464,16 @@ fn expect_emit(
464464
address: Option<Address>,
465465
anonymous: bool,
466466
) -> Result {
467-
state.expected_emits.push_back(ExpectedEmit {
468-
depth,
469-
checks,
470-
address,
471-
found: false,
472-
log: None,
473-
anonymous,
474-
});
467+
let expected_emit = ExpectedEmit { depth, checks, address, found: false, log: None, anonymous };
468+
if let Some(found_emit_pos) = state.expected_emits.iter().position(|emit| emit.found) {
469+
// The order of emits already found (back of queue) should not be modified, hence push any
470+
// new emit before first found emit.
471+
state.expected_emits.insert(found_emit_pos, expected_emit);
472+
} else {
473+
// If no expected emits then push new one at the back of queue.
474+
state.expected_emits.push_back(expected_emit);
475+
}
476+
475477
Ok(Default::default())
476478
}
477479

@@ -494,23 +496,34 @@ pub(crate) fn handle_expect_emit(
494496
return
495497
}
496498

497-
// If there's anything to fill, we need to pop back.
498-
// Otherwise, if there are any events that are unmatched, we try to match to match them
499-
// in the order declared, so we start popping from the front (like a queue).
500-
let mut event_to_fill_or_check =
501-
if state.expected_emits.iter().any(|expected| expected.log.is_none()) {
502-
state.expected_emits.pop_back()
503-
} else {
504-
state.expected_emits.pop_front()
505-
}
499+
let should_fill_logs = state.expected_emits.iter().any(|expected| expected.log.is_none());
500+
let index_to_fill_or_check = if should_fill_logs {
501+
// If there's anything to fill, we start with the last event to match in the queue
502+
// (without taking into account events already matched).
503+
state
504+
.expected_emits
505+
.iter()
506+
.position(|emit| emit.found)
507+
.unwrap_or(state.expected_emits.len())
508+
.saturating_sub(1)
509+
} else {
510+
// Otherwise, if all expected logs are filled, we start to check any unmatched event
511+
// in the declared order, so we start from the front (like a queue).
512+
0
513+
};
514+
515+
let mut event_to_fill_or_check = state
516+
.expected_emits
517+
.remove(index_to_fill_or_check)
506518
.expect("we should have an emit to fill or check");
507519

508520
let Some(expected) = &event_to_fill_or_check.log else {
509521
// Unless the caller is trying to match an anonymous event, the first topic must be
510522
// filled.
511523
if event_to_fill_or_check.anonymous || log.topics().first().is_some() {
512524
event_to_fill_or_check.log = Some(log.data.clone());
513-
state.expected_emits.push_back(event_to_fill_or_check);
525+
// If we only filled the expected log then we put it back at the same position.
526+
state.expected_emits.insert(index_to_fill_or_check, event_to_fill_or_check);
514527
} else {
515528
interpreter.instruction_result = InstructionResult::Revert;
516529
interpreter.next_action = InterpreterAction::Return {

crates/forge/tests/it/repros.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,3 +378,6 @@ test_repro!(8383, false, None, |res| {
378378

379379
// https://github.com/foundry-rs/foundry/issues/1543
380380
test_repro!(1543);
381+
382+
// https://github.com/foundry-rs/foundry/issues/6643
383+
test_repro!(6643);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
2+
pragma solidity 0.8.18;
3+
4+
import "ds-test/test.sol";
5+
import "cheats/Vm.sol";
6+
7+
contract Counter {
8+
event TestEvent(uint256 n);
9+
event AnotherTestEvent(uint256 n);
10+
11+
constructor() {
12+
emit TestEvent(1);
13+
}
14+
15+
function f() external {
16+
emit TestEvent(2);
17+
}
18+
19+
function g() external {
20+
emit AnotherTestEvent(1);
21+
this.f();
22+
emit AnotherTestEvent(2);
23+
}
24+
}
25+
26+
// https://github.com/foundry-rs/foundry/issues/6643
27+
contract Issue6643Test is DSTest {
28+
Vm constant vm = Vm(HEVM_ADDRESS);
29+
Counter public counter;
30+
31+
event TestEvent(uint256 n);
32+
event AnotherTestEvent(uint256 n);
33+
34+
function setUp() public {
35+
counter = new Counter();
36+
}
37+
38+
function test_Bug1() public {
39+
// part1
40+
vm.expectEmit();
41+
emit TestEvent(1);
42+
new Counter();
43+
// part2
44+
vm.expectEmit();
45+
emit TestEvent(2);
46+
counter.f();
47+
// part3
48+
vm.expectEmit();
49+
emit AnotherTestEvent(1);
50+
vm.expectEmit();
51+
emit TestEvent(2);
52+
vm.expectEmit();
53+
emit AnotherTestEvent(2);
54+
counter.g();
55+
}
56+
57+
function test_Bug2() public {
58+
vm.expectEmit();
59+
emit TestEvent(1);
60+
new Counter();
61+
vm.expectEmit();
62+
emit TestEvent(1);
63+
new Counter();
64+
}
65+
}

0 commit comments

Comments
 (0)