Contract event listener is not firing when running hardhat tests with ethers js

Viewed 4771

Here is a very small repo to show the issue: https://github.com/adamdry/ethers-event-issue

But I'll explain it here too. This is my contract:

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

contract ContractA {

    event TokensMinted(uint amount);

    function mint(uint amount) public {
        emit TokensMinted(amount);
    }

}

And this is my test code:

import * as chai from 'chai'
import { BigNumber, ContractTransaction } from 'ethers'
import { ethers } from 'hardhat'
import { ContractA, ContractAFactory } from '../typechain'

const expect = chai.expect

describe("Example test", function () {
    it("should fire the event", async function () {
        const [owner] = await ethers.getSigners();

        const contractAFactory = (await ethers.getContractFactory(
            'ContractA',
            owner,
        )) as ContractAFactory

        const contractA: ContractA = await contractAFactory.deploy()

        contractA.on('TokensMinted', (amount: BigNumber) => {
            // THIS LINE NEVER GETS HIT
            console.log('###########')
        })

        const contractTx: ContractTransaction = await contractA.mint(123)
        const contractReceipt: ContractReceipt = await contractTx.wait()

        for (const event of contractReceipt.events!) {
            console.log(JSON.stringify(event))
        }
    });
});

I was expecting the ########### to get printed to the console however it doesn't so the listener function isn't being executed for some reason.

If I dig into the ContractReceipt the correct event data is there:

{
  "transactionIndex": 0,
  "blockNumber": 2,
  "transactionHash": "0x55d118548c8200e5e6c19759d9aab56cb2e6a274186a92643de776d617d51e1a",
  "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3",
  "topics": [
    "0x772f66a00a405709c30e7f18feadcc8f123b20c09c7260165d3eec36c9f21372"
  ],
  "data": "0x000000000000000000000000000000000000000000000000000000000000007b",
  "logIndex": 0,
  "blockHash": "0x808e6949118509b5a9e482e84cf47921a2fcffbcd943ebbd8ce4f6671469ee01",
  "args": [
    {
      "type": "BigNumber",
      "hex": "0x7b"
    }
  ],
  "event": "TokensMinted",
  "eventSignature": "TokensMinted(uint256)"
}
1 Answers

The full answer is here: https://github.com/nomiclabs/hardhat/issues/1692#issuecomment-905674692

But to summarise, the reason this doesn't work is that ethers.js, by default, uses polling to get events, and the polling interval is 4 seconds. If you add this at the end of your test:

await new Promise(res => setTimeout(() => res(null), 5000));

the event should fire.

However! You can also adjust the polling interval of a given contract like this:

// at the time of this writing, ethers' default polling interval is
// 4000 ms. here we turn it down in order to speed up this test.
// see also
// https://github.com/ethers-io/ethers.js/issues/615#issuecomment-848991047
const provider = greeter.provider as EthersProviderWrapper;
provider.pollingInterval = 100;

As seen here: https://github.com/nomiclabs/hardhat/blob/master/packages/hardhat-ethers/test/index.ts#L642

However! (again) If you want to get the results from an event, the below method requires no changes to the polling or any other "time" based solutions, which in my experience can cause flakey tests:

it('testcase', async() => {
  const tx = await contract.transfer(...args); // 100ms
  const rc = await tx.wait(); // 0ms, as tx is already confirmed
  const event = rc.events.find(event => event.event === 'Transfer');
  const [from, to, value] = event.args;
  console.log(from, to, value);
})

Here is my TypeScriptyfied version (on my own contract with a slightly different event and args):

const contractTx: ContractTransaction = await tokenA.mint(owner.address, 500)
const contractReceipt: ContractReceipt = await contractTx.wait()
const event = contractReceipt.events?.find(event => event.event === 'TokensMinted')
const amountMintedFromEvent: BigNumber = event?.args!['amount']

This is the event declaration in solidity that goes with the above:

event TokensMinted(uint amount);
Related