English 中文(简体)
Contract event listener is not firing when running hardhat tests with ethers js
原标题:

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)"
}
问题回答

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);

In case you need to do it only inside the tests, an alternative would be to use Hardhats Chai expect().to.emit() syntax. Docs here

import { expect } from "chai";
import { ethers } from "hardhat";

//...

expect(contractA.mint(123)).to.emit(contractA, "TokensMinted")
  .withArgs(123)

Keep in mind that you still want to use a timeout pooling if you need to fire another event in sequence in the same test. Something like this.

      expect(contractA.mint(123)).to.emit(contractA, "TokensMinted")
        .withArgs(123)

      // wait 5s due to hardhat pooling of 4s
      await new Promise(resolve => setTimeout(resolve, 5000));

      // if you remove the wait above, it will try to burn tokens with 0 balance
      await constractA.burn(123)
   
      // "wait" for the burn to happen
      await new Promise(resolve => setTimeout(resolve, 5000));
      
      // now we can check balances
      expect(contractA.getBalance()).to.be(0)




相关问题
ERC20 津贴不足

我非常经常地问这个问题。 我道歉,但我不理解许多答案。 我曾试图订立一项契约,但这一错误使我陷入了境地。 ......

Web3.js sendSignedTransaction Problem...... plz help me

i use ganache-cli for operate node in local environment. after that I wrote Smart contract solidity code in remix. And then, deployed in my ganache node. And deployed log was successful in my ganache. ...

unable to get data from blockchain using thirdweb

I am building a simple crowdfunding app using React and thirdweb , I was able to successfully create a Campaign mean data is pushed successfully , I have read docs everything works fine , but when i ...

false Transaction mined but execution failed in Remix IDE

I am getting false Transaction mined but execution failed error from the constructor function in the contract. I am just saving the address from which the contract is deployed in manager variable and ...

TypeError: (0 , ethers_1.getAddress) is not a function

I am getting this error while deploying my smart contract const main = async() => { const contractFactory = await ethers.getContractFactory( TwitterContract ); const contract = await ...

ETH sendSignedTransaction

I Try to use @ethereumjs/tx to sign Transaction. I want to send ETH from one address to second address. .... const legacy = require( @ethereumjs/tx/dist/legacyTransaction ); const utils = require( ...

热门标签