Ethereum: Hardhat does not recognize custom errors from legacy contracts
Here is an article based on the provided information:
Ethereum Hardhat doesn’t recognize custom errors from inherited Contracts
When building and testing smart contracts on the Ethereum network using Hardhat, it’s not uncommon to encounter issues with custom error handling. In this case, we’ll explore a common problem that arises when trying to test the TimelockController
contract from OpenZeppelin.
The issue: Custom errors in inherited Contracts
To use custom errors in your contracts, you need to import them from an external module (in this case, Address.sol
) and then extend their functionality. However, Hardhat has a limitation when it comes to recognizing these custom errors.
Specifically, the TimelockController
contract imports FailedCall
, which is defined in the Address.sol
module. Unfortunately, Hardhat does not recognize these custom errors out-of-the-box.
The solution: Create a separate error module
To solve this issue, we can create a separate module that defines the custom error called FailedCall
. We’ll then import and extend it from our TimelockController
contract.
Here’s an example of how you could do this:
// failedcall.sol(new module)
error FailedCall {
msg = "Failed to call function";
}
Then, in your TimelockController
contract:
// timelockcontroller.sol (updated contract)
import { Address } from "@openzeppelin/contracts/token/ERC20/SafeToken.sol";
contract TimelockController is SafeToken {
// existing code...
function timelock() public onlyOwner {
try {
// attempt to call the timelock function
_timelock();
} catch (error: FailedCall) {
// handle the custom error
console.log("Failed to call timelock: ", error.msg);
}
}
function _timelock() public onlyOwner {
// implementation of timelock logic...
}
}
Hard Testing
Now that we’ve defined our failedcall.sol
module, we can import it and test the custom error in our TimelockController
contract using Hardhat.
Here’s an example of how you could do this:
// Hardhat configuration
const hre = require("hard");
const TimelockContract = await hre.getContractFactory("TimelockController");
const timelockContract = await TimelockContract.deploy();
async function main() {
// deploy the contract
const timelockAddress = await timelockContract.address;
try {
// call the timelock function to test the custom error
await timelockContract.timelock();
} catch (error: FailedCall) {
console.log("Failed to call timelock: ", error.msg);
}
}
By creating a separate error module and extending it from our TimelockController
contract, we’ve been able to use custom errors in our test. This should help you overcome the issue with Hardhat not recognizing these errors.
Hope this helps! Let me know if you have any questions or need further assistance.