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

基于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);
    }
}

问题原因

  1. 代币转移逻辑错误:QuinnSale的buyTokens函数中调用了tokenContract.transfer(msg.sender, _numberOfTokens),此时transfer函数的msg.sender是售卖合约本身,但售卖合约并没有持有任何代币(代币全部在admin账户),所以transfer函数里的require(balanceOf[msg.sender] >= _value)会直接触发回滚。
  2. 授权未被实际使用:虽然你提到售卖合约已获得授权,但当前代码完全没用到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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 09:33:18