如何成功运行Solidity智能合约测试:ERC20众筹合约buyTokens测试回退排查
我一直在学习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错误:
请问我哪里的写法有问题?
问题解答
你代码存在两个核心错误:
转账调用误用
.call()方法
Truffle中调用合约写操作时加.call()只会做本地模拟执行,不会真的修改链上状态。你这里的token.transfer.call看起来执行成功,但众筹合约的代币余额实际没有变化,还是0。执行buyTokens时会触发require(tokenContract.balanceOf(address(this)) >= _numberOfTokens)校验失败直接revert。
解决方法:删掉.call,直接写await token.transfer(tokenSale.address, tokensAvailable, {from: adminAccount}),转账才会真的生效。买家余额断言逻辑错误
你断言买之前的众筹合约余额等于numberOfTokens,逻辑完全错误,应该断言买家的实际余额等于numberOfTokens,修改为assert.equal(buyerBalance, numberOfTokens, 'Buyer Balance updated')即可。
修改后测试即可正常通过,符合预期业务逻辑。
内容的提问来源于stack exchange,提问作者AllJs

