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

ICO合约功能异常求助:ETH未转发、代币未到账问题排查

ICO合约ETH未转发、代币未发放问题分析与修复

核心错误点

  • 钱包类型误用:将接收ETH的目标钱包声明为IERC20接口类型,调用wallet.transfer时实际是尝试执行ERC20代币转账,而非ETH转账,导致ETH滞留在合约中。
  • 代币转账未校验结果:token.transfer调用后未验证返回值,即使代币余额不足或转账失败,合约状态仍会更新(weiRaised增加),但买家收不到代币。
  • receive函数缺失:Solidity 0.8.x中,纯ETH转账优先触发receive函数,当前仅靠fallback处理存在潜在风险。
  • SafeMath冗余:0.8.x版本已内置溢出检查,无需额外导入SafeMath。
  • 余额查询函数错误:getWallet和getTokenBalance的逻辑完全错误,无法正确查询目标钱包ETH余额和合约持有的代币余额。

修复后的完整合约代码

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

interface IERC20 {
    function totalSupply() external view returns (uint);
    function balanceOf(address account) external view returns (uint);
    function transfer(address recipient, uint amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint);
    function approve(address spender, uint amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);
    function burn(uint256 _value) external returns (bool success);
}

contract Crowdsale {
    // The token being sold
    IERC20 public token;

    // Address where funds are collected (payable for ETH transfers)
    address payable public wallet;

    // How many token units a buyer gets per wei
    uint256 public rate;

    // Amount of wei raised
    uint256 public weiRaised;

    event TokenPurchase(
        address indexed purchaser,
        address indexed beneficiary,
        uint256 value,
        uint256 amount
    );

    constructor(uint256 _rate, address _wallet, address _token) {
        require(_rate > 0, "Rate must be greater than 0");
        require(_wallet != address(0), "Wallet cannot be zero address");
        require(_token != address(0), "Token cannot be zero address");

        rate = _rate;
        wallet = payable(_wallet);
        token = IERC20(_token);
    }

    // Receive ETH directly and trigger token purchase
    receive() external payable {
        buyTokens(msg.sender);
    }

    // Fallback for cases where receive isn't triggered (e.g., data included with ETH)
    fallback() external payable {
        buyTokens(msg.sender);
    }

    function buyTokens(address _beneficiary) public payable {
        uint256 weiAmount = msg.value;
        _preValidatePurchase(_beneficiary, weiAmount);

        uint256 tokens = _getTokenAmount(weiAmount);
        weiRaised += weiAmount;

        _processPurchase(_beneficiary, tokens);
        emit TokenPurchase(msg.sender, _beneficiary, weiAmount, tokens);

        _forwardFunds();
    }

    function _preValidatePurchase(address _beneficiary, uint256 _weiAmount) internal pure {
        require(_beneficiary != address(0), "Beneficiary cannot be zero address");
        require(_weiAmount != 0, "Wei amount cannot be zero");
    }

    function _deliverTokens(address _beneficiary, uint256 _tokenAmount) internal {
        bool success = token.transfer(_beneficiary, _tokenAmount);
        require(success, "Token transfer failed");
    }

    function _processPurchase(address _beneficiary, uint256 _tokenAmount) internal {
        _deliverTokens(_beneficiary, _tokenAmount);
    }

    function _getTokenAmount(uint256 _weiAmount) internal view returns (uint256) {
        return _weiAmount * rate;
    }

    function _forwardFunds() internal {
        (bool success, ) = wallet.call{value: msg.value}("");
        require(success, "ETH transfer to wallet failed");
    }

    // Get ETH balance of the target wallet
    function getWalletEthBalance() public view returns (uint) {
        return wallet.balance;
    }

    // Get token balance held by this crowdsale contract
    function getContractTokenBalance() public view returns (uint) {
        return token.balanceOf(address(this));
    }
}

关键修复说明

  1. 钱包类型修正:将wallet改为address payable,确保可以执行ETH转账操作。
  2. 转账结果校验:在代币和ETH转账后都添加require校验,确保操作失败时回滚所有状态更新。
  3. ETH转账方式优化:使用call替代transfer,避免transfer的2300 gas限制导致转账失败(比如目标钱包是合约且需要更多gas处理)。
  4. 余额查询函数修正:
    • getWalletEthBalance:直接返回目标钱包的ETH余额。
    • getContractTokenBalance:返回当前ICO合约持有的代币余额,用于确认合约是否有足够代币发放。
  5. 冗余代码移除:删除SafeMath相关导入和使用,改用原生算术运算。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 00:20:55