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

ERC20合约测试难题:如何覆盖零地址发起Transfer与Approve场景

解决ERC20合约零地址场景的测试覆盖率问题

我用Hardhat和OpenZeppelin开发ERC20代币项目,测试覆盖率接近100%,但以下两个内部函数的零地址校验分支始终无法覆盖:

  • _transfer函数中from为零地址的场景
  • _approve函数中owner为零地址的场景

这两个都是极端边缘场景,但为了达成100%覆盖率,需要通过编写测试合约暴露内部函数调用入口的方式触发分支,具体方案如下:


1. 覆盖_transfer中from为零地址的分支

_transfer是内部函数,外部的transfer/transferFrom方法无法直接传入零地址作为from(因为调用者本身是非零地址)。需要编写一个测试合约继承你的ERC20合约,新增公开方法直接调用_transfer:

测试合约示例(Solidity)

// test/helpers/TestERC20.sol
pragma solidity ^0.8.0;

import "../contracts/YourERC20.sol"; // 替换为你的ERC20合约实际路径

contract TestERC20 is YourERC20 {
    // 公开方法,直接调用内部的_transfer
    function testTransferFromZeroAddress(address to, uint256 amount) external {
        _transfer(address(0), to, amount);
    }
}

测试用例(JavaScript)

it('reverts when transfer from the zero address', async () => {
  const TestERC20 = await ethers.getContractFactory('TestERC20');
  const testContract = await TestERC20.deploy(); // 按你的ERC20构造函数要求传入参数
  await testContract.deployed();
  
  const { AddressZero } = ethers.constants;
  const recipient = ethers.Wallet.createRandom().address;
  const amount = 1;

  await expect(testContract.testTransferFromZeroAddress(recipient, amount))
    .to.be.revertedWith("ERC20: transfer from the zero address");
});

2. 覆盖_approve中owner为零地址的分支

同理,外部approve方法的owner是调用者本身(非零地址),需要通过测试合约暴露方法直接调用_approve:

测试合约示例(Solidity)

// test/helpers/TestERC20.sol
pragma solidity ^0.8.0;

import "../contracts/YourERC20.sol";

contract TestERC20 is YourERC20 {
    // 新增方法,直接调用内部的_approve
    function testApproveFromZeroAddress(address spender, uint256 amount) external {
        _approve(address(0), spender, amount);
    }
}

测试用例(JavaScript)

it('reverts when approve from the zero address', async () => {
  const TestERC20 = await ethers.getContractFactory('TestERC20');
  const testContract = await TestERC20.deploy(); // 按你的ERC20构造函数要求传入参数
  await testContract.deployed();
  
  const { AddressZero } = ethers.constants;
  const spender = ethers.Wallet.createRandom().address;
  const amount = 1;

  await expect(testContract.testApproveFromZeroAddress(spender, amount))
    .to.be.revertedWith("ERC20: approve from the zero address");
});

补充说明

  • 测试合约仅用于测试环境部署,无需上链;
  • 如果你的ERC20合约使用了OpenZeppelin的自定义错误(如ERC20InvalidSender),请将断言中的revertedWith替换为revertedWithCustomError,和你现有测试风格保持一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 14:27:37