Learn how to efficiently retrieve and decode event logs emitted within a transaction receipt using Hardhat, a popular Ethereum development framework.
When working with smart contracts in a Hardhat development environment, it's often crucial to interact with events emitted during transactions. This guide demonstrates how to retrieve and analyze events from transaction receipts using ethers.js, a popular Ethereum JavaScript library. We'll cover two approaches: directly accessing the events array in the transaction receipt and utilizing the convenience of OpenZeppelin's test helpers for streamlined event assertion in your tests.
To get events from a transaction receipt in Hardhat using ethers.js:
Wait for the transaction to be mined:
const tx = await contract.someFunction();
const receipt = await tx.wait();
Access the events array in the receipt:
console.log(receipt.events);
Filter for specific events by name:
const transferEvents = receipt.events.filter((x) => x.event === 'Transfer');
Access event arguments:
const from = transferEvents[0].args.from;
const to = transferEvents[0].args.to;
const value = transferEvents[0].args.value;
Using OpenZeppelin test helpers:
Install the package:
npm install @openzeppelin/test-helpers
Import and use expectEvent
in your test:
const { expectEvent } = require('@openzeppelin/test-helpers');
const receipt = await contract.someFunction();
expectEvent(receipt, 'Transfer', { from: user1.address, to: user2.address });
This approach simplifies event assertion and provides a cleaner way to test your contract's events.
This code defines a simple ERC20 token contract named "MyToken" and a corresponding test suite. The contract initializes with an initial supply of tokens minted to the deployer. The test suite verifies the token transfer functionality by transferring tokens between accounts and then demonstrates two methods for accessing and verifying events emitted during the transfer: manual filtering of transaction receipt events and using the expectEvent
helper from OpenZeppelin Test Helpers.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;
import "hardhat/console.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender, 1000 * 10 ** decimals());
}
}
// test/MyToken.js
const { expect } = require("chai");
const { ethers } = require("hardhat");
const { expectEvent } = require("@openzeppelin/test-helpers");
describe("MyToken", function () {
let token;
let owner;
let addr1;
let addr2;
beforeEach(async function () {
[owner, addr1, addr2] = await ethers.getSigners();
const Token = await ethers.getContractFactory("MyToken");
token = await Token.deploy();
await token.deployed();
});
it("Should transfer tokens between accounts", async function () {
// Transfer 50 tokens from owner to addr1
const amount = 50;
const tx = await token.transfer(addr1.address, amount);
const receipt = await tx.wait();
// Access and log all events
console.log("All events in the receipt:", receipt.events);
// Filter for Transfer events
const transferEvents = receipt.events.filter(
(x) => x.event === "Transfer"
);
console.log("Transfer events:", transferEvents);
// Access event arguments
const from = transferEvents[0].args.from;
const to = transferEvents[0].args.to;
const value = transferEvents[0].args.value;
expect(from).to.equal(owner.address);
expect(to).to.equal(addr1.address);
expect(value).to.equal(amount);
// Using expectEvent from OpenZeppelin test helpers
await expectEvent(
token.transfer(addr2.address, amount),
"Transfer",
{ from: owner.address, to: addr2.address }
);
});
});
Explanation:
MyToken
contract is defined, inheriting from OpenZeppelin's ERC20
contract.chai
for assertions, ethers
for interacting with the Ethereum network, and expectEvent
from @openzeppelin/test-helpers
.beforeEach
hook:
MyToken
contract.it("Should transfer tokens between accounts", ...)
: This test case checks the token transfer functionality.token.transfer(addr1.address, amount)
: Transfer amount
tokens from the owner to addr1
.tx.wait()
: Wait for the transaction to be mined and get the transaction receipt.console.log(receipt.events)
: Log all events emitted in the transaction.Transfer
events and log them.from
, to
, and value
from the first Transfer
event.expect
assertions to verify the correctness of the event arguments.expectEvent
:
expectEvent(token.transfer(...), "Transfer", ...)
: This line demonstrates how to use expectEvent
to assert that a Transfer
event was emitted with the expected arguments.This example provides a comprehensive overview of how to get events from a transaction receipt in Hardhat using both manual filtering and the expectEvent
helper function.
General:
Directly Accessing Events:
events
array.Using OpenZeppelin Test Helpers:
expectEvent
provides a more readable and concise way to assert event emissions compared to manually filtering and checking event arguments.expectEvent
allows for more complex matching criteria, such as partial argument matching or using regular expressions. Refer to the OpenZeppelin documentation for more details.Beyond the Basics:
ethers.js
event listener functionality. This allows you to subscribe to specific events and execute code when they are emitted.By understanding these concepts and techniques, you can effectively work with events in your Hardhat projects and build powerful decentralized applications.
This document outlines two methods for accessing and verifying events emitted by smart contracts during testing with Hardhat and ethers.js:
Method 1: Direct Access from Transaction Receipt
tx.wait()
.receipt
) contains an events
array holding all emitted events.filter()
to select specific events based on their event
property (e.g., 'Transfer').event.args.from
).Method 2: Simplified Assertion with OpenZeppelin Test Helpers
@openzeppelin/test-helpers
package.expectEvent
: Import the expectEvent
function and provide the transaction receipt, expected event name, and expected arguments. This simplifies event assertion and provides cleaner test code.Benefits of OpenZeppelin Test Helpers:
In conclusion, understanding how to retrieve and analyze events emitted from your smart contracts is crucial for building interactive and transparent decentralized applications. By utilizing the methods outlined in this guide, developers can effectively work with events in their Hardhat projects, ensuring the proper functioning and auditability of their smart contracts. Whether you choose to directly access events from the transaction receipt or leverage the convenience of OpenZeppelin's test helpers, mastering event handling will undoubtedly prove invaluable throughout your blockchain development journey.