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

Hardhat测试ERC20合约转账报余额不足错误排查

问题描述

正在学习Solidity编写简易ERC20代币合约(包含转账、余额更新基础功能),运行转账功能测试用例时抛出如下报错:

npx hardhat test   
No need to generate any newer typings.


  MyERC20Contract
    when I transfer 10 tokens
      1) sould transfer tokens correctly


  0 passing (728ms)
  1 failing

  1) MyERC20Contract
       when I transfer 10 tokens
         sould transfer tokens correctly:
     Error: VM Exception while processing transaction: reverted with reason string 'ERC20: transfer amount exceeds balance'
    at ERC20._transfer (contracts/ERC20.sol:49)
    at ERC20.transfer (contracts/ERC20.sol:25)
    at async HardhatNode._mineBlockWithPendingTxs (node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:1773:23)
    at async HardhatNode.mineBlock (node_modules/hardhat/src/internal/hardhat-network/provider/node.ts:466:16)
    at async EthModule._sendTransactionAndReturnHash (node_modules/hardhat/src/internal/hardhat-network/provider/modules/eth.ts:1504:18)
    at async HardhatNetworkProvider.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:118:18)
    at async EthersProviderWrapper.send (node_modules/@nomiclabs/hardhat-ethers/src/internal/ethers-provider-wrapper.ts:13:20)

涉及代码

测试文件代码

import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { ethers } from "hardhat";
import { ERC20 } from "../typechain";

describe("MyERC20Contract", function() {

  let MyERC20Contract: ERC20;
  let someAddress: SignerWithAddress;
  let someOtherAddress: SignerWithAddress;

      beforeEach(async function() {
        const ERC20ContractFactory = await ethers.getContractFactory("ERC20");
        MyERC20Contract = await ERC20ContractFactory.deploy("Hello","SYM");
        await MyERC20Contract.deployed();
        someAddress = (await ethers.getSigners())[1];
        someOtherAddress = (await ethers.getSigners())[2];
      });

      describe("When I have 10 tokens", function() {
        beforeEach(async function() {
            await MyERC20Contract.transfer(someAddress.address, 10);
        });
      });

      describe("when I transfer 10 tokens",  function() {
        it("sould transfer tokens correctly", async function() {
          await MyERC20Contract
          .connect(someAddress)
          .transfer(someOtherAddress.address, 10);
          
          expect(
            await MyERC20Contract.balanceOf(someOtherAddress.address)
            ).to.equal(10);
        });
        
      });
    
});

Solidity合约代码

//SPDX-License-Identifier: Unlicense: MIT
pragma solidity ^0.8.0;

contract ERC20 {
    uint256 public totalSupply;
    string public name;
    string public symbol;
   

    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;


    constructor(string memory name_, string memory symbol_) {
        name = name_;
        symbol = symbol_;
        _mint(msg.sender, 100e18);
    } 

    function decimals() external pure returns (uint8) {
        return 18;
    }

    function transfer(address recipient, uint256 amount) external returns (bool) {
            return _transfer(msg.sender, recipient, amount);
            
    }

    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {

        uint256 currentAllowance = allowance[sender][msg.sender];

        require(currentAllowance >= amount, "ERC20: Transfer amount exceeds allowance" ) ;

        allowance[sender][msg.sender] = currentAllowance - amount;

        return _transfer(sender, recipient, amount);
            
    }

    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        return true;
    }

    function _transfer(address sender, address recipient, uint256 amount) private returns (bool) {
            require(recipient != address(0), "ERC20: transfer to zero address");
            uint256 senderBalance = balanceOf[sender];
            require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
            balanceOf[sender] = senderBalance - amount;
            balanceOf[recipient] += amount;

            return true;
            
    }

    function _mint(address to, uint256 amount) internal {
        require(recipient != address(0), "ERC20: transfer to zero address");
        totalSupply += amount;
        balanceOf[to] +=amount;
    }
 
}
问题根因

代码共存在3处直接导致测试失败的问题,另有2处规范/拼写问题:

  • 测试块结构逻辑错误:Mocha框架的beforeEach钩子仅对同块级、嵌套子块内的测试用例生效。你给someAddress转10代币的前置逻辑写在独立的describe("When I have 10 tokens")块下,和后续转账测试块为平级关系,转账测试运行前不会执行给someAddress转币的操作,someAddress初始余额为0,发起转账自然触发余额不足回滚。
  • 合约_mint函数语法错误:函数定义的接收地址参数为to,零地址校验时错误引用了不存在的变量recipient,代码无法正常编译。
  • 代币单位不匹配:合约设置小数位为18,部署时给部署者铸造的是100e18基础单位的代币(对应100个整币),但测试中转账、断言使用的金额都是无小数的10,仅对应10个最小单位(即0.000...001个整币),不符合转10个整币的测试预期。
  • 规范问题:SPDX许可证声明写法错误,正确写法应为// SPDX-License-Identifier: MIT。
  • 拼写问题:测试用例描述中sould为拼写错误,正确应为should。
修复方案

测试代码修复

  1. 调整测试块嵌套结构,将转账测试块放到"When I have 10 tokens"块内部,确保前置转币逻辑正常执行。
  2. 所有涉及代币金额的位置统一使用18位小数的标准单位,可直接调用ethers.utils.parseEther("10")表示10个整币。

修复后的测试核心代码如下:

describe("MyERC20Contract", function() {
  let MyERC20Contract: ERC20;
  let someAddress: SignerWithAddress;
  let someOtherAddress: SignerWithAddress;

  beforeEach(async function() {
    const ERC20ContractFactory = await ethers.getContractFactory("ERC20");
    MyERC20Contract = await ERC20ContractFactory.deploy("Hello","SYM");
    await MyERC20Contract.deployed();
    someAddress = (await ethers.getSigners())[1];
    someOtherAddress = (await ethers.getSigners())[2];
  });

  describe("When I have 10 tokens", function() {
    beforeEach(async function() {
      // 转10个标准单位的代币
      await MyERC20Contract.transfer(someAddress.address, ethers.utils.parseEther("10"));
    });

    // 嵌套转账测试块,继承前置逻辑
    describe("when I transfer 10 tokens",  function() {
      it("should transfer tokens correctly", async function() {
        await MyERC20Contract
        .connect(someAddress)
        .transfer(someOtherAddress.address, ethers.utils.parseEther("10"));
        
        expect(
          await MyERC20Contract.balanceOf(someOtherAddress.address)
          ).to.equal(ethers.utils.parseEther("10"));
      });
    });
  });
});

合约代码修复

  1. 修正SPDX头写法。
  2. 修改_mint函数的零地址校验逻辑,将错误的recipient变量替换为参数to,同时修正错误提示信息。

修复后的_mint函数代码如下:

// 修正SPDX头
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// ... 其他合约代码不变

function _mint(address to, uint256 amount) internal {
    require(to != address(0), "ERC20: mint to zero address");
    totalSupply += amount;
    balanceOf[to] += amount;
}

完成以上修改后重新运行测试即可正常通过。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 23:51:24