Since Solidity v0.8.4, custom errors are supported, also in ethers.js.
How can I use them in a test? Here is an example:
pragma solidity 0.8.10;
contract Contract {
uint8 value;
error ComparisionFailed(uint8 expected, uint8 actual);
function setValue(uint8 _value) external {
value = _value;
}
function compare(uint8 _value) external view {
if (_value != value)
revert ComparisionFailed({expected : value, actual : _value});
}
}
The test below works, but this is a bad solution.
import {expect} from 'chai';
import {ethers} from 'hardhat';
describe('Contract', function () {
let contract: any;
beforeEach(async function () {
let Contract = await ethers.getContractFactory('Contract');
contract = await Contract.deploy();
});
it('Should revert if value is wrong', async function () {
const expected = 123;
await contract.setValue(expected);
let actual = 123;
await expect(
contract.compare(actual)
).to.be.ok;
actual = 1
await expect(
contract.compare(actual)
).to.be.revertedWith("custom error 'ComparisionFailed(123, 1)'"); // bad solution
});
});
What's the proper way to handle this and check for the error ComparisionFailed
from the abi and the values 123
and 1
?
Best Answer
You can handle custom errors from your Solidity smart contract in your application using ethers-decode-error quite easily. This utility also allows you to extract and use the parameters from custom errors if needed.
Consider a scenario where you have a contract method called
swap
, and you want to catch a specific custom error calledInvalidSwapToken
. You can do this using the following code:The example above threw an
InvalidSwapToken
custom error which has a parameter of the token address. The value of this parameter can be accessed from theargs
variable when handling the error. If your customer error doesn't have any parameters, you can ignore theargs
variable.You can refer to the docs for more examples.
Here's how the ABI of the
InvalidSwapToken
custom error with an input parameter might have looked like:It will be much easier if you're using Typechain as you can pass the contract interface as ABI: