Skip to content

bug: expectEmit and expectCall don't only check the next call #1745

@mds1

Description

@mds1

Component

Forge

Have you ensured that all of these are up to date?

  • Foundry
  • Foundryup

What version of Foundry are you on?

forge 0.2.0 (4fcd7e0 2022-05-26T00:08:37.693306Z)

What command(s) is the bug in?

forge test

Operating System

No response

Describe the bug

My understanding is that the expected behavior of expectEmit is "look for event in the next call" but it seems to actually be "look for expected event before the end of the test". And the docs are contradictory here—one part says it must be the "next event" (which seems to imply it needs to be the first event in the next call), another part says "before the end of the current function". Similarly issue for expectCall behavior.

To ensure tests are clear, I think expectEmit and expectCall should only look in the next call. Otherwise this can result in bugs where your event was emitted eventually in a later portion of your test, but not emitted at the time you expected it, and the test still passes.

Reproduce with this test:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.13;

import "forge-std/Test.sol";

contract CallMe {
  function calling() public {}
}

contract MyContract {
  CallMe callMe;

  constructor(CallMe _callMe) {
    callMe = _callMe;
  }

  event MyEvent();
  event OtherEvent();

  function noEventOrCall() public {}

  function myEventAndCall() public {
    emit OtherEvent(); // Shows event order does not matter if only checking for MyEvent.
    emit MyEvent();
    callMe.calling();
  }
}

contract ExpectTest is Test {
  event MyEvent();
  CallMe callMe;
  MyContract myContract;

  function setUp() public {
    callMe = new CallMe();
    myContract = new MyContract(callMe);
  }

  // -------- expectEmit Tests --------

  function test_MyEvent() public {
    // Passes like expected.
    vm.expectEmit(true, true, true, true);
    emit MyEvent();
    myContract.myEventAndCall();
  }

  function test_NoEventOrCall() public {
    // Fails like expected.
    vm.expectEmit(true, true, true, true);
    emit MyEvent();
    myContract.noEventOrCall();
  }

  function test_EventuallyMyEvent() public {
    // Passes, but should fail, because expectEmit should only check the next call.
    vm.expectEmit(true, true, true, true);
    emit MyEvent();
    myContract.noEventOrCall();
    myContract.myEventAndCall();
  }

  // -------- expectCall Tests --------

  function test_Call() public {
    // Passes like expected.
    vm.expectCall(address(callMe), abi.encodeWithSelector(CallMe.calling.selector));
    myContract.myEventAndCall();
  }

  function test_NoCall() public {
    // Fails like expected.
    vm.expectCall(address(callMe), abi.encodeWithSelector(CallMe.calling.selector));
    myContract.noEventOrCall();
  }

  function test_EventuallyCall() public {
    // Passes, but should fail, because expectCall should only check the next call.
    vm.expectCall(address(callMe), abi.encodeWithSelector(CallMe.calling.selector));
    emit MyEvent();
    myContract.noEventOrCall();
    myContract.myEventAndCall();
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    Status

    Done

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions