合约调用ERC20 transfer/transferFrom触发revert错误求助
ERC20转账触发revert错误排查与修复
问题现象
每次调用合约的bet函数尝试转账ERC20代币时,都会触发如下revert错误:Note: The called function should be payable if you send value and the value you send should be less than your current balance.
合约代码
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; 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); } contract Hilo { uint public fee; uint256 public maxBet; uint256 public minBet; address public owner; IERC20 public immutable token; constructor(address _tradeToken, uint initialFee, uint256 initalMaxBet, uint256 initalMinBet) { fee = initialFee; maxBet = initalMaxBet; minBet = initalMinBet; owner = msg.sender; token = IERC20(_tradeToken); } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } function changeFee(uint newFee) public onlyOwner { fee = newFee; } function changeMaxBet(uint newMaxBet) public onlyOwner { maxBet = newMaxBet; } function changeMinBet(uint newMinBet) public onlyOwner { maxBet = newMinBet; } function bet(uint card, string memory choice, uint256 amount, uint256 multiplier) external returns (uint nextCard) { require(amount <= maxBet, "amount is greater than the maxBet"); require(amount >= minBet, "amount is smaller than minBet"); uint256 newAmount = amount * (fee / 1000); require(token.balanceOf(msg.sender) >= newAmount, "Token transfer failed."); token.transferFrom(msg.sender, address(this), newAmount); uint next = uint(keccak256(abi.encodePacked(block.timestamp, block.difficulty, msg.sender))) % 13 + 1; if (keccak256(abi.encodePacked(choice)) == keccak256(abi.encodePacked('L'))) { if (card > next) { token.transfer(msg.sender, amount * (multiplier / 100)); return next; } } else if (keccak256(abi.encodePacked(choice)) == keccak256(abi.encodePacked('H'))) { if (card < next) { token.transfer(msg.sender, amount * (multiplier / 100)); return next; } } return next; } }
环境说明
使用Remix虚拟机环境,ERC20代币已成功部署。移除合约中的transfer或transferFrom调用后代码可正常运行;直接调用ERC20合约的transferFrom方法(完成授权后)能正常工作,排除单纯的授权错误(授权错误有专属提示)。
问题根源
- 整数除法精度丢失:
代码中uint256 newAmount = amount * (fee / 1000);的计算顺序错误,fee通常是小于1000的数值(比如5代表0.5%手续费),fee / 1000会因Solidity整数除法向下取整得到0,最终newAmount为0。部分ERC20合约会拒绝0金额转账,触发异常。 - 缺失授权额度检查:
transferFrom执行的前提是用户已授权合约足够额度,当前代码仅检查用户余额,未验证allowance(msg.sender, address(this)) >= newAmount,若授权不足会导致转账失败。 changeMinBet函数笔误:
该函数内错误地修改了maxBet变量而非minBet,导致最小投注额配置失效,可能引发后续逻辑异常。
修复方案
- 修正手续费计算逻辑:
调整计算顺序为先乘后除,避免精度丢失:uint256 newAmount = amount * fee / 1000; - 添加授权检查:
在transferFrom调用前增加授权验证:require(token.allowance(msg.sender, address(this)) >= newAmount, "Insufficient allowance"); - 修复
changeMinBet函数:
将函数内的maxBet = newMinBet;改为:minBet = newMinBet; - 显式检查转账返回值:
部分ERC20合约转账失败时返回false而非revert,建议显式检查返回值:require(token.transferFrom(msg.sender, address(this), newAmount), "TransferFrom failed"); require(token.transfer(msg.sender, amount * multiplier / 100), "Transfer failed");
内容的提问来源于stack exchange,提问作者fwara07
相关产品推荐
相关产品推荐

