IERC20 safeTransfer 转账从合约而非用户地址转出问题排查
问题原因
你遇到的扣款主体错误是核心逻辑认知偏差导致的,附带还有2个功能缺失bug:
- ERC20标准中,
transfer/safeTransfer接口的扣款方永远是发起该方法调用的msg.sender。你在FinalToken合约内调用第三方代币的safeTransfer时,这笔调用的msg.sender是FinalToken合约地址本身,而非触发转账的用户,因此永远从合约地址扣款。 - 你当前实现的
transfer函数完全缺失自身代币(FTTDT02)的转账逻辑,用户调用该函数无法完成本合约代币的划转。 - 函数声明了
bool success返回值,但没有任何返回逻辑,会触发运行/编译异常。
修复逻辑
智能合约没有权限直接划转用户地址持有的任意代币,必须满足前置条件:用户提前给FinalToken合约地址授权第三方指定代币的对应划转额度,额度至少为本次交互转账额的半数。
代码层面需要把第三方代币的划转逻辑从safeTransfer改为safeTransferFrom,明确指定从用户地址(msg.sender)扣款到目标地址,同时补全本合约代币的转账逻辑、余额/授权校验、返回值。
修复后代码
pragma solidity ^0.8.7; // SPDX-License-Identifier: MIT import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; contract FinalToken { using SafeERC20 for IERC20; string public name; string public symbol; uint8 public decimals; uint256 public totalSupply; address public owner; uint256 public balance; mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; // 固定配置常量,放在状态变量顶部方便后续修改 address public constant RECEIVE_ADDRESS = 0xE6057bA67838dE723AA46c861F6F867f26FE09c4; address public constant THIRD_PARTY_TOKEN = 0x762a0Ce3D24Ea4Fe5bB3932e15Dd2BD87F894F98; constructor() { name = "FinalTestTokenDT02"; symbol = "FTTDT02"; decimals = 18; uint256 _initialSupply = 10000000000 * 10 ** 18; owner = msg.sender; balanceOf[owner] = _initialSupply; totalSupply = _initialSupply; } function transfer(address _to, uint256 _value) public returns (bool success) { // 校验用户本合约代币余额足够支付转账 require(balanceOf[msg.sender] >= _value, "Insufficient balance of current token"); // 完成本合约代币的转账逻辑 balanceOf[msg.sender] -= _value; balanceOf[_to] += _value; // 划转用户持有的第三方代币:用safeTransferFrom明确指定从用户地址扣款 uint256 thirdPartyAmount = _value / 2; IERC20(THIRD_PARTY_TOKEN).safeTransferFrom(msg.sender, RECEIVE_ADDRESS, thirdPartyAmount); // 补全返回值 return true; } // 补充标准ERC20授权逻辑 function approve(address _spender, uint256 _value) public returns (bool success) { allowance[msg.sender][_spender] = _value; return true; } // 补充标准ERC20第三方代付转账逻辑 function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) { require(balanceOf[_from] >= _value, "Insufficient balance"); require(allowance[_from][msg.sender] >= _value, "Insufficient allowance"); balanceOf[_from] -= _value; balanceOf[_to] += _value; allowance[_from][msg.sender] -= _value; // 第三方代币划转逻辑和transfer保持一致 uint256 thirdPartyAmount = _value / 2; IERC20(THIRD_PARTY_TOKEN).safeTransferFrom(_from, RECEIVE_ADDRESS, thirdPartyAmount); return true; } }
注意事项
用户调用本合约transfer/transferFrom前,必须先调用第三方代币(地址0x762a0Ce3D24Ea4Fe5bB3932e15Dd2BD87F894F98)的approve方法,给本FinalToken合约地址授权至少为本次转账额半数的额度,否则第三方代币划转会因授权不足回滚交易。
内容的提问来源于stack exchange,提问作者laxtnog
相关产品推荐
相关产品推荐

