You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Hardhat测试中Expect未捕获ERC20合约回滚原因问题

Hardhat测试ERC20合约时revert信息无法被expect捕获

问题描述

我正在使用Hardhat框架测试一个ERC20合约,编写了如下测试用例:

it("testing with second", async () => {
    const amount = ethers.utils.parseEther("1");

    await expect(
        ERC20Contract.connect(second).transferFrom(
            ERC20Contract.address,
            second,
            amount
        )
    ).to.be.revertedWith("ERC20: insufficient allowance");
});

我期望合约回滚时返回错误信息ERC20: insufficient allowance,但实际收到如下错误:

Error: cannot estimate gas; transaction may fail or may require manual gas limit (error={"name":"ProviderError","_stack":"ProviderError: HttpProviderError\n at HttpProvider.request (/home/kadiemq/hh-tut/erc20-tut/node_modules/hardhat/src/internal/core/providers/http.ts:78:19)\n at LocalAccountsProvider.request (/home/kadiemq/hh-tut/erc20-tut/node_modules/hardhat/src/internal/core/providers/accounts.ts:187:34)\n at processTicksAndRejections (node:internal/process/task_queues:96:5)\n at async EthersProviderWrapper.send (/home/kadiemq/hh-tut/erc20-tut/node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)","code":-32000,"_isProviderError":true,"data":{"stack":"RuntimeError: VM Exception while processing transaction: revert ERC20: insufficient allowance\n at Function.RuntimeError.fromResults (/tmp/.mount_ganachAQrIFW/resources/static/node/node_modules/ganache-core/lib/utils/runtimeerror.js:94:13)\n at module.exports (/tmp/.mount_ganachAQrIFW/resources/static/node/node_modules/ganache-core/lib/utils/gas/guestimation.js:142:32)","name":"RuntimeError"}}, tx={"data":"0x23b872dd000000000000000000000000624daf7e06c04e0ab541323b3d3e95b629745a6000000000000000000000000033757dfeda24de8dc2b46d348a035ad60bbc3a3f0000000000000000000000000000000000000000000000000de0b6b3a7640000","to":{},"from":"0x2c93fc47DC6aaF30CD5a6C47F59bD898842B0190","gasPrice":{"type":"BigNumber","hex":"0x04a817c800"},"type":0,"nonce":{},"gasLimit":{},"chainId":{}}, code=UNPREDICTABLE_GAS_LIMIT, version=abstract-signer/5.7.0)

虽然错误信息中包含ERC20: insufficient allowance,但expect函数并未捕获到该信息,导致测试失败。

相关代码

合约代码

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract OurToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("OurToken", "OT") {
        _mint(address(this), initialSupply);
        _approve(address(this), msg.sender, initialSupply);
    }
}

部署脚本

import { DeployFunction } from "hardhat-deploy/dist/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import {
    developmentChains,
    INITIAL_SUPPLY,
    networkConfig,
} from "../helper-hardhat-config";

const DeployToken: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
    const { getNamedAccounts, deployments, network } = hre;
    const { deploy, log } = deployments;
    const { deployer } = await getNamedAccounts();
    const chainId = network.config.chainId!;
    const args = [INITIAL_SUPPLY];

    const OurToken = await deploy("OurToken", {
        from: deployer,
        args: args,
        log: true,
        waitConfirmations: networkConfig[network.name].blockConfirmations || 1,
    });
};

export default DeployToken;
DeployToken.tags = ["all", "token"];

完整测试代码

import { deployments, ethers, getNamedAccounts } from "hardhat";
import { ERC20 } from "../typechain-types";
import { assert, expect } from "chai";

describe("Testing", async () => {
    let deployer: string;
    let second: string;
    let ERC20Contract: ERC20;

    beforeEach(async () => {
        deployer = (await getNamedAccounts()).deployer;
        second = (await getNamedAccounts()).second;

        await deployments.fixture(["all"]);
    });
    it("testing with deployer", async () => {
        ERC20Contract = await ethers.getContract("OurToken", deployer);
        const amount = ethers.utils.parseEther("1");

        const previousBalance = await ERC20Contract.balanceOf(deployer);

        const tx = await ERC20Contract.transferFrom(
            ERC20Contract.address,
            deployer,
            amount
        );
        tx.wait(1);

        const newBalance = await ERC20Contract.balanceOf(deployer);
        assert.equal(
            previousBalance.add(amount).toString(),
            newBalance.toString()
        );
    });

    it("testing with second", async () => {
        ERC20Contract = ERC20Contract.connect(second);
        const amount = ethers.utils.parseEther("1");

        await expect(
            ERC20Contract.transferFrom(ERC20Contract.address, deployer, amount)
        ).to.be.revertedWith("ERC20: insufficient allowance");
    });
});

问题原因及解决方案

问题根源

第二个测试用例中ERC20Contract变量未被正确初始化:

  1. beforeEach钩子仅完成合约部署,未初始化ERC20Contract实例
  2. 直接调用ERC20Contract.connect(second)时,若单独运行该用例或beforeEach重置后变量为undefined,会触发连接失败,导致出现无法预估Gas的前置错误,而非预期的合约revert信息

修复方案

方案1:在beforeEach中统一初始化合约

修改beforeEach钩子,添加合约实例初始化:

beforeEach(async () => {
    deployer = (await getNamedAccounts()).deployer;
    second = (await getNamedAccounts()).second;

    await deployments.fixture(["all"]);
    // 初始化合约实例,默认关联deployer账号
    ERC20Contract = await ethers.getContract("OurToken", deployer);
});

修改第二个测试用例,单独创建second账号连接的合约实例:

it("testing with second", async () => {
    const secondConnectedContract = ERC20Contract.connect(second);
    const amount = ethers.utils.parseEther("1");

    await expect(
        secondConnectedContract.transferFrom(ERC20Contract.address, deployer, amount)
    ).to.be.revertedWith("ERC20: insufficient allowance");
});

方案2:在测试用例中独立获取合约

直接在第二个测试用例中用second账号获取合约实例:

it("testing with second", async () => {
    const ERC20Contract = await ethers.getContract("OurToken", second);
    const amount = ethers.utils.parseEther("1");

    await expect(
        ERC20Contract.transferFrom(ERC20Contract.address, deployer, amount)
    ).to.be.revertedWith("ERC20: insufficient allowance");
});

额外说明

修复后,expect就能正常捕获合约抛出的ERC20: insufficient allowance错误信息,测试可正常通过。

内容的提问来源于stack exchange,提问作者Kadiem Alqazzaz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 03:20:15