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

代币跨合约转移问题求助:从合约A转至合约B失败排查

代币合约转账问题排查与修复

问题1:构造函数设置initialSupply能否解决合约A的代币持有问题?

不能。当前合约A的构造函数是将initialSupply分配给部署合约的外部地址(msg.sender),而非合约A自身地址。因此合约A的代币余额为0,无法直接向合约B转移代币。

问题2:「发送者没有足够代币」错误原因及修复

错误根源

  1. 合约A初始分配错误:代币被发给了部署者,合约A自身无代币余额。
  2. 合约B转账逻辑错误:transferTokenFromAtoB函数中调用transferFrom(address(this), to, amount),试图从合约B自身地址转出代币,但合约B余额为0,触发余额不足报错。
  3. 前端授权逻辑错误:当前的approve操作是部署者给合约B授权,和合约A的代币持有、转账需求完全无关。

修复方案

1. 修正合约A的代币初始分配与接口兼容性

修改合约A的构造函数,将初始代币分配给合约A自身,并补全ERC20标准缺失的函数和事件:

// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.28;

contract token {
    string public tokenName = "ABC Coin";
    string public tokenSymbol = "$abc";
    uint8 public decimals = 18;
    uint256 public totalSupply = 1000e27; // 与初始供应量保持一致
    uint256 public initialSupply = 1000e27;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value); // 补全Approval事件

    mapping(address => uint256) public balances;
    mapping(address => mapping(address => uint256)) public allowed;

    constructor() {
        balances[address(this)] = initialSupply; // 代币分配给合约A自身
        emit Transfer(address(0), address(this), initialSupply); // 标准mint事件
    }

    // 补全标准transfer函数
    function transfer(address to, uint256 value) public returns (bool success) {
        require(value <= balances[msg.sender], "Oops! Sender does not have enough token!");
        require(to != address(0), "Transfer to zero address");

        balances[msg.sender] -= value;
        balances[to] += value;
        emit Transfer(msg.sender, to, value);
        return true;
    }

    function transferFrom(address from, address to, uint256 value)
    public returns (bool success)
    {
        require(value <= balances[from], "Oops! Sender does not have enough token!");
        require(value <= allowed[from][msg.sender], "Oops! Sender is not allowed to send this amount of tokens!");
        require(to != address(0), "Transfer to zero address");

        allowed[from][msg.sender] -= value;
        balances[from] -= value;
        balances[to] += value;
        emit Transfer(from, to, value);
        return true;
    }

    function approve(address spender, uint256 amount)
    public returns (bool success)
    {
        require(msg.sender != address(0), "Approve from the zero address");
        require(spender != address(0), "Approve to the zero address"); // 修复原代码的重复判断错误

        allowed[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    // 添加让合约A授权给其他地址的函数(可添加onlyOwner修饰符做权限控制)
    function approveSpender(address spender, uint256 amount) public {
        allowed[address(this)][spender] = amount;
        emit Approval(address(this), spender, amount);
    }
}

2. 修正合约B的转账逻辑

修改合约B的函数,实现从合约A向合约B转账的逻辑:

// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.28;

// 导入依赖(根据实际项目调整)
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract BContract is Pausable, Ownable(msg.sender) {
    using SafeERC20 for IERC20;

    IERC20 public immutable ABCToken;

    mapping(address => uint256) public balances;

    constructor() {
        address _token = 0x4..8; // 替换为实际合约A地址
        ABCToken = IERC20(_token);
    }

    // 从合约A转账代币到合约B(需合约A先授权)
    function pullTokenFromA(uint256 amount) public onlyOwner {
        ABCToken.safeTransferFrom(address(ABCToken), address(this), amount);
    }

    function getSmartContractBalance() external view returns(uint256) {
        return ABCToken.balanceOf(address(this));
    }
}

3. 修正前端操作流程

调整前端代码,先完成授权再执行转账:

let AAddress = "0xf...d"; // 修正后的合约A地址
let BAddress = "0x4...E"; 

const AContract = new ethers.Contract(AAddress, AAbi, signer); 
const BContract = new ethers.Contract(BAddress, BAbi, signer);

try {
    // 1. 让合约A授权合约B可以转出它的代币
    console.log("A approve B: ", await AContract.approveSpender(BAddress, 100));
    
    // 2. 查看合约B初始余额
    console.log("Initial balance: ", await BContract.getSmartContractBalance());

    // 3. 从合约A转账到合约B
    console.log("Transfer Token from A to B: ", await BContract.pullTokenFromA(100));

    // 4. 查看合约B当前余额
    console.log("Current balance: ", await BContract.getSmartContractBalance());
} catch (error) {
    console.error(error);
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 15:19:50