#Reference
WARNING
This package is in beta. While we don't expect major changes to its API, some details could change.
This is the reference for the Hardhat Chai Matchers plugin. This is a chai plugin that adds new assertion capabilities for testing smart contracts.
# Numbers
When @nomicfoundation/hardhat-chai-matchers
is used, equality comparisons of numbers will work even if the numbers are represented by different types. This means that assertions like this:
expect(await token.totalSupply()).to.equal(1_000_000);
will work. These assertions don't normally work because the value returned by totalSupply()
is an ethers BigNumber, and an instance of a BigNumber
will always be different than a plain number.
The supported types are:
- Plain javascript numbers
- BigInts
- Ethers BigNumbers
bn.js
instancesbignumber.js
instances
This also works when deep-equal comparisons of arrays or objects are performed:
expect(await contract.getRatio()).to.deep.equal([100, 55]);
# Reverted transactions
Several matchers are included to assert that a transaction reverted, and the reason of the revert.
#.reverted
Assert that a transaction reverted for any reason, without checking the cause of the revert:
await expect(token.transfer(address, 0)).to.be.reverted;
#.revertedWith
Assert that a transaction reverted with a specific reason string:
await expect(token.transfer(address, 0)).to.be.revertedWith(
"transfer value must be positive"
);
#.revertedWithCustomError
Assert that a transaction reverted with a specific custom error:
await expect(token.transfer(address, 0)).to.be.revertedWithCustomError(
token,
"InvalidTransferValue"
);
The first argument must be the contract that defines the error.
If the error has arguments, the .withArgs
matcher can be added:
await expect(token.transfer(address, 0))
.to.be.revertedWithCustomError(token, "InvalidTransferValue")
.withArgs(0);
See the .withArgs
matcher entry to learn more.
#.revertedWithPanic
Assert that a transaction reverted with a panic code:
await expect(token.transfer(address, 0)).to.be.revertedWithPanic();
An optional argument can be passed to assert that a specific panic code was thrown:
await expect(token.transfer(address, 0)).to.be.revertedWithPanic(0x12);
You can also import and use the PANIC_CODES
dictionary:
import { PANIC_CODES } from "@nomicfoundation/hardhat-chai-matchers/panic";
await expect(token.transfer(address, 0)).to.be.revertedWithPanic(
PANIC_CODES.DIVISION_BY_ZERO
);
#.revertedWithoutReason
Assert that a transaction reverted without returning a reason:
await expect(token.transfer(address, 0)).to.be.revertedWithoutReason();
This matcher differs from .reverted
in that it will fail if the transaction reverts with a reason string, custom error or panic code. Examples of Solidity expressions that revert without a reason are require(false)
(without the reason string) and assert(false)
before Solidity v0.8.0. This also happens for out-of-gas errors.
# Events
#.emit
Assert that a transaction emits a specific event:
await expect(token.transfer(address, 100)).to.emit(token, "Transfer");
The first argument must be the contract that emits the event.
If the event has arguments, the .withArgs
matcher can be added:
await expect(token.transfer(address, 0))
.to.be.revertedWithCustomError(token, "InvalidTransferValue")
.withArgs(100);
See the .withArgs
matcher entry to learn more.
# Balance change
These matchers can be used to assert how a given transaction affects the ether balance, or an ERC20 token balance, of a specific address.
All these matchers assume that the given transaction is the only transaction mined in its block.
#.changeEtherBalance
Assert that the ether balance of an address changed by a specific amount:
await expect(
sender.sendTransaction({ to: receiver, value: 1000 })
).to.changeEtherBalance(sender, -1000);
This matcher ignores the fees of the transaction, but you can include them with the includeFee
option:
await expect(
sender.sendTransaction({ to: receiver, value: 1000 })
).to.changeEtherBalance(sender, -22000, { includeFee: true });
#.changeTokenBalance
Assert that an ERC20 token balance of an address changed by a specific amount:
await expect(token.transfer(receiver, 1000)).to.changeTokenBalance(
token,
sender,
-1000
);
The first argument must be the contract of the token.
#.changeEtherBalances
Like .changeEtherBalance
, but allows checking multiple addresses at the same time:
await expect(
sender.sendTransaction({ to: receiver, value: 1000 })
).to.changeEtherBalances([sender, receiver], [-1000, 1000]);
#.changeTokenBalances
Like .changeTokenBalance
, but allows checking multiple addresses at the same time:
await expect(token.transfer(receiver, 1000)).to.changeTokenBalances(
token,
[sender, receiver],
[-1000, 1000]
);
# Other matchers
#.withArgs
Can be used after a .emit
or a .revertedWithCustomError
matcher to assert the values of the event/error's arguments:
// events
await expect(token.transfer(address, 0))
.to.be.revertedWithCustomError(token, "InvalidTransferValue")
.withArgs(100);
// errors
await expect(token.transfer(address, 0))
.to.be.revertedWithCustomError(token, "InvalidTransferValue")
.withArgs(0);
If you don't care about the value of one of the arguments, you can use the anyValue
predicate:
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
await expect(factory.create(9999))
.to.emit(factory, "Created")
.withArgs(anyValue, 9999);
Predicates are just function that return true if the value is correct, and return false if it isn't, so you can create your own predicates:
function isEven(x: BigNumber): boolean {
return x.mod(2).isZero();
}
await expect(token.transfer(100)).to.emit(token, "Transfer").withArgs(isEven);
#.properAddress
Assert that the given string is a proper address:
expect("0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266").to.be.properAddress;
#.properPrivateKey
Assert that the given string is a proper private key:
expect("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80").to
.be.properPrivateKey;
#.properHex
Assert that the given string is a proper hexadecimal string of a specific length:
expect("0x1234").to.be.properHex(4);
#.hexEqual
Assert that the given strings hexadecimal strings correspond to the same numerical value:
expect("0x00012AB").to.hexEqual("0x12ab");