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)); } }
关键修复说明
- 钱包类型修正:将
wallet改为address payable,确保可以执行ETH转账操作。 - 转账结果校验:在代币和ETH转账后都添加
require校验,确保操作失败时回滚所有状态更新。 - ETH转账方式优化:使用
call替代transfer,避免transfer的2300 gas限制导致转账失败(比如目标钱包是合约且需要更多gas处理)。 - 余额查询函数修正:
getWalletEthBalance:直接返回目标钱包的ETH余额。getContractTokenBalance:返回当前ICO合约持有的代币余额,用于确认合约是否有足够代币发放。
- 冗余代码移除:删除SafeMath相关导入和使用,改用原生算术运算。
内容的提问来源于stack exchange,提问作者Santos
相关产品推荐
相关产品推荐

