基于ERC20的QNN代币购买触发VM回滚异常求助
问题排查:ERC20代币售卖合约交易回滚异常
问题描述
我基于ERC20标准开发了QNN(Quinn)代币及对应的售卖合约QuinnSale,在Truffle控制台执行代币购买操作时触发「VM Exception while processing transaction: revert」异常。具体操作流程为:部署Quinn与QuinnSale合约后,获取账户列表,调用tokenSale.buyTokens(1,{from:acc[1],value:1000000000000000,gas:500000})。已知代币价格为0.001 ETH,acc[1]账户ETH充足,售卖合约已获得授权,但仍触发回滚。
合约代码
Quinn.sol
// SPDX-License-Identifier:GPL-3.0 pragma solidity ^0.8.19; contract Quinn { string public name = "Quinn"; string public symbol = "QNN"; string public standard = "QNN v1.0"; uint256 public totalSupply; // transfer event event Transfer(address indexed _from, address indexed _to, uint256 _value); // approve event 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) { totalSupply = _initialSupply; // 1 million balanceOf[msg.sender] = _initialSupply; } //transfer //Exception if account does not have enough //return a boolen //transfer event function transfer( address _to, uint256 _value ) public returns (bool success) { require(balanceOf[msg.sender] >= _value); balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; // trigger the transfer event emit Transfer(msg.sender, _to, _value); return true; } //delegated transfer // approve transfer function approve( address _spender, uint256 _value ) public returns (bool success) { // allowance allowance[msg.sender][_spender] = _value; // approve event emit Approval(msg.sender, _spender, _value); return true; } // transfer from function transferFrom( address _from, address _to, uint256 _value ) public returns (bool success) { // require _from has enough tokens require(_value <= balanceOf[_from]); // require allowance is big enough require(_value <= allowance[_from][msg.sender]); // transfer event emit Transfer(_from, _to, _value); // change the balanceOf address balanceOf[_from] -= _value; // update the balanceOf address balanceOf[_to] += _value; // update the allowance of address allowance[_from][msg.sender] -= _value; return true; } }
QuinnSale.sol
// SPDX-License-Identifier:GPL-3.0 pragma solidity ^0.8.19; import "./Quinn.sol"; contract QuinnSale { // DO NOT expose the address of admin address admin; Quinn public tokenContract; uint256 public tokenPrice; uint256 public tokensSold; event Sell(address _buyer, uint256 _amount); constructor(Quinn _tokenContract, uint256 _tokenPrice) { // Assign an admin admin = msg.sender; tokenContract = _tokenContract; tokenPrice = _tokenPrice; } // mulitply //pure means not create transactions, not read or write data to blockchain function multiply(uint256 x, uint256 y) internal pure returns (uint256 z) { // ensuring that after multiplication, returned value is not getting overflowed. require(y == 0 || (z = x * y) / y == x); } // Buy tokens function buyTokens(uint256 _numberOfTokens) public payable { // require that value is equal to tokens require(msg.value == multiply(_numberOfTokens, tokenPrice)); // require that the contract has enough tokens require(tokenContract.balanceOf(admin) >= _numberOfTokens); // msg.sender here is the buyer // in transfer function, msg.sender is QuinnSale contract require(tokenContract.transfer(msg.sender, _numberOfTokens)); tokensSold += _numberOfTokens; emit Sell(msg.sender, _numberOfTokens); } }
问题原因
- 代币转移逻辑错误:
QuinnSale的buyTokens函数中调用了tokenContract.transfer(msg.sender, _numberOfTokens),此时transfer函数的msg.sender是售卖合约本身,但售卖合约并没有持有任何代币(代币全部在admin账户),所以transfer函数里的require(balanceOf[msg.sender] >= _value)会直接触发回滚。 - 授权未被实际使用:虽然你提到售卖合约已获得授权,但当前代码完全没用到
transferFrom来利用授权额度从admin账户转移代币,属于逻辑误用。
解决办法
修改QuinnSale合约的buyTokens函数,改用transferFrom实现代币转移,并增加授权额度检查:
// Buy tokens function buyTokens(uint256 _numberOfTokens) public payable { // 验证支付金额与代币数量匹配 require(msg.value == multiply(_numberOfTokens, tokenPrice)); // 验证admin账户有足够代币 require(tokenContract.balanceOf(admin) >= _numberOfTokens); // 验证售卖合约拥有足够的授权额度 require(tokenContract.allowance(admin, address(this)) >= _numberOfTokens); // 从admin账户转移代币给买家 require(tokenContract.transferFrom(admin, msg.sender, _numberOfTokens)); tokensSold += _numberOfTokens; emit Sell(msg.sender, _numberOfTokens); }
额外操作步骤
部署完成后,必须由admin账户执行以下操作:
调用Quinn合约的approve函数,授权售卖合约转移指定数量的代币,示例:token.approve(tokenSale.address, 1000000)(这里的1000000是你允许售卖的最大代币数量)
内容的提问来源于stack exchange,提问作者Mizanur Rahman Sohan
相关产品推荐
相关产品推荐

