ERC20代币存取款智能合约transfer/transferFrom调用报错如何解决?
问题根因与解决方案
一、depositToken 报错原因与修复
你写的IERC20(_token).approve(address(this), _amount)逻辑完全不生效:
- ERC20的
approve方法的调用者必须是代币所有者本人,作用是所有者给指定地址(spender)授权可划转的代币额度 - 你在合约内部调用approve,本质是用合约地址作为调用方,给合约自身授权花用户的代币,权限完全不对,自然会触发
transfer amount exceeds allowance报错。
修复步骤:
- 删掉
depositToken方法里的IERC20(_token).approve(address(this), _amount);这行无效代码 - 用户调用
depositToken之前,必须先主动调用对应ERC20代币合约的approve方法,给你的TestFunding合约地址授权不低于_amount的额度,再调用depositToken即可成功。
二、depositToken2 报错原因与修复
ERC20的transfer方法的调用者是代币所有者,作用是把调用者自己持有的代币转给接收方:
- 你在合约内部调用
transfer(address(this), _amount),本质是让合约把自己持有的代币转给自身,和「用户存币到合约」的需求完全不符 - 如果你要靠
transfer实现用户存币,应该让用户直接调用ERC20代币合约的transfer方法,接收方填你的TestFunding合约地址即可,不需要调用合约的depositToken2方法,这个方法本身逻辑错误可以直接删除。
三、额外优化:withdrawToken 冗余逻辑修复
你当前的withdrawToken用了approve+transferFrom的组合,属于冗余操作:
- 合约本身就是自身持有代币的所有者,直接调用
transfer方法转给用户即可,不需要先授权再划转。 - 原有记账逻辑仅用单层级mapping,不区分不同代币的用户余额,存在严重逻辑漏洞,需要调整为双层mapping按用户+代币维度记账。
修正后完整合约代码
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.10; interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } contract TestFunding{ // 调整为双层mapping,按用户+代币维度记账 mapping(address => mapping(address => uint)) public userTokenBalances; address public owner; uint public minimumDeposit; uint public totalDeposit; uint public noOfUsers; constructor(){ owner = msg.sender; } function depositToken(address _token,uint _amount) public { require(IERC20(_token).transferFrom(msg.sender,address(this),_amount), "Deposit failed"); // 新增用户统计逻辑 if(userTokenBalances[msg.sender][_token] == 0) { noOfUsers += 1; } userTokenBalances[msg.sender][_token] += _amount; totalDeposit += _amount; } function getUserBalance(address _token) public view returns(uint) { return userTokenBalances[msg.sender][_token]; } function getCurrentBalance(address _token) public view returns(uint) { return IERC20(_token).balanceOf(address(this)) ; } function getTokenBalance(address _token,address _account) public view returns(uint) { return IERC20(_token).balanceOf(_account) ; } function withdrawToken(address _token,uint _amount) public { require(userTokenBalances[msg.sender][_token] >= _amount, "Insufficient balance"); userTokenBalances[msg.sender][_token] -= _amount; totalDeposit -= _amount; require(IERC20(_token).transfer(msg.sender, _amount), "Withdraw failed"); } }
存币标准操作流程
- 用户调用对应ERC20代币的
approve方法,spender参数填TestFunding合约地址,amount参数填要存入的数量 - 用户调用
TestFunding合约的depositToken方法,传入代币地址和存入数量,即可完成存币。
内容的提问来源于stack exchange,提问作者Harish Manthurthi
相关产品推荐
相关产品推荐

