5

I tried running tests like this

function testEvent() public{
vm.expectEmit(true, true, false, false);
emit IContract.EventName(expectedValue, expectedValue, expectedValue);

IContract(_contractAddress).FunctionThatEmitsEvent(args);
}

And found that the EventName was coming back as undeclared, even though the contract interface was imported.

To fix this, I just inserted the events inside my test contract above the testEvent() function. But I don't see that as the way it's being done in the documentation. Is this the best way or am I missing something?

2 Answers 2

4

It is the way it's been done in the docs. See: https://book.getfoundry.sh/cheatcodes/expect-emit

You will have to declare locally in your tests the events and do

event Transfer(address indexed from, address indexed to, uint256 amount);

function testERC20EmitsTransfer() public {
  vm.expectEmit(true, true, false, true);
  emit MyToken.Transfer(address(this), address(1), 10);
}

Not the most practical, that's for sure

3
  • 1
    What do the true and fasle values in vm.expectEmit(true, true, false, true); refer to? Mar 7, 2023 at 2:12
  • That is the topics you want Foundry to check the parameters are (topic1, topic2, topic3, eventData). This way you can not just check that the event you want is emmited, but that it is emmited with the Topics and/or data that you expect! You can learn more about events on the Solidity docs: docs.soliditylang.org/en/v0.8.15/…
    – Marxian
    Mar 11 at 19:52
  • It is worth mentioning that the order of events matters if you expects multiple events from a function.
    – Peter
    Mar 25 at 7:50
4

I documented this in an article here if you're interested.

An approach I've been taking to have it be a bit cleaner is to inherit an interface of events only. The downside to this approach is that you have to make sure any updates to your events are reflected in your test interface files.

interface IERC20Events {
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract ERC20TestContract is IERC20Events {
    function testERC20EmitsTransfer() public {
        // We check that the token is the event emitter...
        vm.expectEmit(true, true, false, true, address(myToken));
        emit Transfer(address(this), address(1), 10);
        
        // We perform the call.
        myToken.transfer(address(1), 10);
    }
}

The fifth entry (address(myToken)) will check the emitting address.

1
  • 1
    Regarding the true/false input arguments for the vm.expectEmit cheatcode, the leftmost three indicate to Foundry whether you care about the value of the "topics" (i.e. indexed event fields), which will be returned as bytes32 values. The rightmost boolean input argument indicates to Foundry whether you care about the "data" field (i.e. non-indexed event fields), which will be returned as a bytes array. Worth note is that topic 0 will always be compared. It is the event signature. In this case, it is keccak256("Transfer(address,address,uint256)"). Jun 29, 2023 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.