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

如何成功运行Solidity智能合约测试:ERC20众筹合约buyTokens测试回退排查

ERC20代币众筹合约buyTokens函数转账执行revert问题排查

我一直在学习Dapp University的Gregory发布的公开教程,实现自定义ERC20代币及众筹智能合约。截至目前流程都很顺利,但在调试buyTokens()函数实现代币从众筹合约地址到买家地址的转账功能时遇到了问题。本次展示的测试代码使用了async/await语法,和视频教程中的写法不同。以下是相关代码:

  • 我的ERC20代币合约
  • 我的众筹合约及其测试脚本(尤其是报错的片段)
  • 部署脚本
  • 以及运行测试时遇到的报错信息

感谢大家的帮助。

开发环境

Truffle v5.4.18 (core: 5.4.18)
Solidity - 0.8.9 (solc-js)
Node v16.1.0
Web3.js v1.5.3

众筹合约

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0;

import "./NzouaToken.sol";

contract NzouaTokenSale {

    address admin;
    NzouaToken public tokenContract;
    uint256 public tokenPrice;
    uint256 public tokensSold;

    event Sell(address _buyer, uint256 _amount);

    constructor(NzouaToken _tokenContract, uint256 _tokenPrice) {
        admin = msg.sender;
        tokenContract = _tokenContract;
        tokenPrice = _tokenPrice;
    }

    function multiply(uint x, uint y) internal pure returns(uint z){
        require(y == 0 || (z = x * y) / y == x);
    }

    function buyTokens(uint256 _numberOfTokens) public payable{
        require(msg.value == multiply(_numberOfTokens, tokenPrice));
        require(tokenContract.balanceOf(address(this)) >= _numberOfTokens);
        require(tokenContract.transfer(msg.sender, _numberOfTokens));

        tokensSold += _numberOfTokens;
        emit Sell(msg.sender, _numberOfTokens);
    }
}

ERC20代币合约

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0;

contract NzouaToken {
    string public name = "Nzouat Token";
    string public symbol = "NZT";
    uint256 public totalSupply;

    event Transfer(
        address indexed _from,
        address indexed _to,
        uint256 _value
    );

    event Approval(
      address indexed _owner, 
      address indexed _spender,
      uint256 _value
    );

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

    constructor(uint256 _initialSupply) {
        balanceOf[msg.sender] = _initialSupply;
        totalSupply = _initialSupply;
    }

    function transfer(address _to, uint256 _value) public returns(bool success){
        require(balanceOf[msg.sender] >= _value, 'The account has low funds');
        balanceOf[msg.sender] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(msg.sender, _to, _value);
        return true;
    }

    function approve(address _spender, uint256 _value) public returns(bool success) {
        allowance[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success){
        require(_value <= balanceOf[_from]);
        require(_value <= allowance[_from][msg.sender]);
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        allowance[_from][msg.sender] -= _value;
        emit Transfer(_from, _to, _value);
        return true;
    }
}

部署脚本

const NzouaToken = artifacts.require("NzouaToken");
const NzouaTokenSale = artifacts.require("NzouaTokenSale");

module.exports = async function (deployer, network, accounts) {
    await deployer.deploy(NzouaToken, 1000000); // 1000000 NZT tokens
    await deployer.deploy(NzouaTokenSale, NzouaToken.address, 1000000000000000);
};

Mocha单元测试(众筹合约测试报错的部分)

var NzouaToken = artifacts.require('./NzouaToken');
var NzouaTokenSale = artifacts.require('./NzouaTokenSale');

contract('NzouaTokenSale', async (accounts) => {
    let tokenSale;
    let token;
    let tokenPrice = 1000000000000000; // in wei
    const adminAccount = accounts[0];
    const buyerAccount = accounts[1];

    describe('Facilitates Token Buying', async () => {
        it('Required a successful Transfer of Tokens', async () => {
            token = await NzouaToken.deployed();
            tokenSale = await NzouaTokenSale.deployed();
            let tokenSaleBalance;
            let buyerBalance;

            const numberOfTokens = 10;
            const tokensAvailable = 750000;

            // try {
            let receipt = await token.transfer.call(tokenSale.address, tokensAvailable, {
                from: adminAccount
            })

            tokenSaleBalance = await token.balanceOf(tokenSale.address);
            assert.equal(tokenSaleBalance.toNumber(), tokensAvailable, 'Contract received funds.')

            await tokenSale.buyTokens(numberOfTokens, {
                from: buyerAccount,
                value: numberOfTokens * tokenPrice
            });

            // Grab the new balance of the buyerAccount
            buyerBalance = await token.balanceOf(buyerAccount);
            buyerBalance = buyerBalance.toNumber()
            assert.equal(tokenSaleBalance, numberOfTokens, 'Buyer Balance updated');

            // Grab the new balance of the Token Sale Contract
            tokenSaleBalance = await token.balanceOf(tokenSale.address);
            tokenSaleBalance = tokenSaleBalance.toNumber()
            assert.equal(tokenSaleBalance, tokensAvailable - numberOfTokens, 'Token Sale Balance updated');

            // } catch (error) {
            //     // console.log(error.message)
            //     assert(error.message.indexOf('revert') >= 0, 'Something went wrong while making the transfer');
            // }
        });
    });
});

如果我取消try{}catch{}代码段的注释,测试会通过,但不符合我预期的业务逻辑。
我在truffle控制台中运行truffle test执行测试时,其余所有测试都通过,仅该用例失败,返回
revert
错误:
Truffle控制台报错信息
请问我哪里的写法有问题?


问题解答

你代码存在两个核心错误:

  1. 转账调用误用.call()方法
    Truffle中调用合约写操作时加.call()只会做本地模拟执行,不会真的修改链上状态。你这里的token.transfer.call看起来执行成功,但众筹合约的代币余额实际没有变化,还是0。执行buyTokens时会触发require(tokenContract.balanceOf(address(this)) >= _numberOfTokens)校验失败直接revert。
    解决方法:删掉.call,直接写await token.transfer(tokenSale.address, tokensAvailable, {from: adminAccount}),转账才会真的生效。

  2. 买家余额断言逻辑错误
    你断言买之前的众筹合约余额等于numberOfTokens,逻辑完全错误,应该断言买家的实际余额等于numberOfTokens,修改为assert.equal(buyerBalance, numberOfTokens, 'Buyer Balance updated')即可。

修改后测试即可正常通过,符合预期业务逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 04:24:02