Event

Setup

Target contract:

pragma solidity 0.8.18;

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

    function transfer(address from, address to, uint256 amount) external {
        emit Transfer(from, to, amount);
    }

    function transferMany(address from, address[] calldata to, uint256[] calldata amounts) external {
        for (uint256 i = 0; i < to.length; i++) {
            emit Transfer(from, to[i], amounts[i]);
        }
    }
}

Test file:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;

import "forge-std/Test.sol";
import {Event} from "../src/Event.sol";

// forge test --match-path test/Event.t.sol -vvvv

contract EventTest is Test {
    Event public e;

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

    function setUp() public {
        e = new Event();
    }

    function testEmitTransferEvent() public {
        // function expectEmit(
        //     bool checkTopic1,
        //     bool checkTopic2,
        //     bool checkTopic3,
        //     bool checkData
        // ) external;

        // 1. Tell Foundry which data to check
        // Check index 1, index 2 and data
        vm.expectEmit(true, true, false, true);
        // 2. Emit the expected event
        emit Transfer(address(this), address(123), 456);
        // 3. Call the function that should emit the event
        e.transfer(address(this), address(123), 456);

        // Check only index 1
        vm.expectEmit(true, false, false, false);
        emit Transfer(address(this), address(123), 456);
        // NOTE: index 2 and data (amount) doesn't match
        //       but the test will still pass
        e.transfer(address(this), address(111), 222);
    }

    function testEmitManyTransferEvent() public {
        address[] memory to = new address[](2);
        to[0] = address(111);
        to[1] = address(222);

        uint256[] memory amounts = new uint[](2);
        amounts[0] = 1;
        amounts[1] = 2;

        for (uint256 i = 0; i < to.length; i++) {
            // 1. Tell Foundry which data to check
            vm.expectEmit(true, true, false, true);
            // 2. Emit the expected event
            emit Transfer(address(this), to[i], amounts[i]);
        }

        // 3. Call the function that should emit the event
        e.transferMany(address(this), to, amounts);
    }
}

Test event example 1

To test event, follow these 3 steps:

  1. Tell Foundry which data to check

  2. Emit the expected event

  3. Call the function that should emit the event

In step 1, we call vm.expectEmit(). Here is its definition:

function expectEmit(
    bool checkTopic1, // check index1?
    bool checkTopic2, // check index2?
    bool checkTopic3, // check index3?
    bool checkData    // check data?
) external;

The checked event must be indexed. For example, our Transfer event has two indexed parameters:

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

Writing a test case:

function testEmitTransferEvent() public {
    // function expectEmit(
    //     bool checkTopic1,
    //     bool checkTopic2,
    //     bool checkTopic3,
    //     bool checkData
    // ) external;

    // 1. Tell Foundry which data to check
    // Check index 1, index 2 and data
    vm.expectEmit(true, true, false, true);
    // 2. Emit the expected event
    emit Transfer(address(this), address(123), 456);
    // 3. Call the function that should emit the event
    e.transfer(address(this), address(123), 456);
}

Test event example 2

We can choose to only test some of the parameters:

function testEmitTransferEvent() public {
    // Check only index 1
    vm.expectEmit(true, false, false, false);
    emit Transfer(address(this), address(123), 456);
    // NOTE: index 2 and data (amount) doesn't match
    //       but the test will still pass
    e.transfer(address(this), address(111), 222);
}

In this case, if index 1 matches then the test will pass.

Test multiple events

To test multiple events emitted by a single function, we put step 1 and step 2 into a for loop, and then call the function outside the for loop:

function testEmitManyTransferEvent() public {
    address[] memory to = new address[](2);
    to[0] = address(111);
    to[1] = address(222);

    uint256[] memory amounts = new uint[](2);
    amounts[0] = 1;
    amounts[1] = 2;

    for (uint256 i = 0; i < to.length; i++) {
        // 1. Tell Foundry which data to check
        vm.expectEmit(true, true, false, true);
        // 2. Emit the expected event
        emit Transfer(address(this), to[i], amounts[i]);
    }

    // 3. Call the function that should emit the event
    e.transferMany(address(this), to, amounts);
}

Last updated