3

I'm currently using chai with hardhat and ethereum-waffle to test my smart contracts:

const chai = require('chai')
const hre = require('hardhat')
const { solidity } = require('ethereum-waffle')

chai.use(solidity)

I have been finding it great to use.

However I have had some difficulty testing events. I'm currently using expect( ... ).to.emit( ... ).withArgs( ... ) which works for a lot of cases but not all.

Take the following example snippet from a test:

const destroyResult = await gameContract.attack([4, 0])

// Expect the game to end with the announced winner being owner
expect(destroyResult)
  .to.emit(gameContract, 'GameEnd')
  .withArgs(undefined, owner.address)

In this example, the GameEnd event has two arguments: the timestamp of the game ending and the address of the winner (owner.address above). In my test, I only care about testing the 2nd argument (who won the game).

I tried providing undefined for the timestamp, but this is not acceptable and causes the test to fail. What I need is a way to test only the 2nd argument of the event.

It would also be acceptable for me to have a function where I have access to the event object so that I can perform any test I like. However I haven't seen any way of doing this.

Any help would be appreciated, thank you!

2
  • I think you need to use extra libraries to do that. it is a general js problem. maybe those 2 links could help you: sinonjs.org/releases/latest/matchers stackoverflow.com/questions/26442849/…
    – Majd TL
    Sep 29, 2021 at 13:54
  • you can also extract that check to a new function as you said (probably much easier than using sinonjs. I could try that tomorrow but I think it is very simple. do console.log on "destroyResult" and you can probably see the values
    – Majd TL
    Sep 29, 2021 at 14:01

4 Answers 4

5

This is how I do that for the Transfer event of the ERC20 tokens:

usually :

await expect(tokenInstance.connect(<signer-account>).transfer(<addr-of-receipent>, <amount-BigNumber>))
.to.emit(tokenInstance, 'Transfer').withArgs(<addr-of-signer-account>, <addr-of-receipent>, <amount-BigNumber>);

And this is how you can get all the arguments and test all of them or any one of them:

...
const tx = await tokenInstance.connect(<signer-account>).transfer(<addr-of-receipent>, <amount-BigNumber>);
const receipt = await ethers.provider.getTransactionReceipt(tx.hash);
const interface = new ethers.utils.Interface(["event Transfer(address indexed from, address indexed to, uint256 amount)"]);
const data = receipt.logs[0].data;
const topics = receipt.logs[0].topics;
const event = interface.decodeEventLog("Transfer", data, topics);
expect(event.from).to.equal(<addr-of-signer-account>);
expect(event.to).to.equal(<addr-of-receipent>);
expect(event.amount.toString()).to.equal(<amount-BigNumber>.toString());
...

I assume here that only 1 event will be emitted and therefore I get the first element in the logs array logs[0]

0
3

You can accomplish this cleanly by using predicates with withArgs:

const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
await expect(contract.call())
  .to.emit(contract, "TwoUints")
  .withArgs(anyValue, 3);
2

Use the withNamedArgs method with the variable name of the event argument:

const destroyResult = await gameContract.attack([4, 0])

// Expect the game to end with the announced winner being owner
expect(destroyResult)
  .to.emit(gameContract, 'GameEnd')
  .withNamedArgs({winnerAddress: owner.address})
0

To get the 'GameEnd' event from your transaction, you can get the events from the tx receipt and filter for the event name like this:

const destroyResult = await gameContract.attack([4, 0]);
// get all events of the tx
const { events } = await destroyResult.wait();
// filter events for 'GameEnd' 
const event = events.find(x => x.event === "GameEnd");
// get second arg which is the address
const ownerAddress = event.args[1];
// check if address is equal
expect(ownerAddress).to.be.equal(owner.address);

Your Answer

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

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