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

调用executeMultitrading触发Ethereum JSON-RPC执行回滚错误求助

多轮Uniswap交易合约报错排查

我尝试调用executeMultitrading函数执行两轮交易:用ETH买入TokenX,再卖出TokenX换回ETH。部署合约并存入ETH后,传入TokenX地址和tradeAmount调用函数时返回错误:Returned error: {"jsonrpc":"2.0","error":"execution reverted"}。以下是合约代码,请帮忙排查问题:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./contracts/token/ERC20/IERC20.sol";
import "./contracts/interfaces/IUniswapV2Router02.sol";

contract MultiTrading {

    address private constant UNISWAP_ROUTER_ADDRESS =
        0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;

    uint256 private constant TRADE_COUNT = 2; // Number of trades to execute

    IUniswapV2Router02 private uniswapRouter;
    IERC20 private token;
    address private owner;
    event ETHReceived(address sender, uint256 amount);

    modifier onlyOwner() {
        require(
            msg.sender == owner,
            "Only the contract owner can call this function"
        );
        _;
    }

    constructor() {
        uniswapRouter = IUniswapV2Router02(UNISWAP_ROUTER_ADDRESS);
        owner = msg.sender;
    }

    function executeMultitrading(address tokenAddress, uint256 tradeAmount) external onlyOwner {
        token = IERC20(tokenAddress);
        // Approve token for buy and sell trades
        _approveTokenForBuyTrade(tokenAddress, tradeAmount);
        // Check if the token approval was successful
        require(_checkTokenApproval(tokenAddress, address(uniswapRouter), tradeAmount), "Token approval failed");

        // Perform buy and sell trades on Uniswap
        for (uint256 i = 0; i < TRADE_COUNT; i++) {
            // Perform buy trade on Uniswap
            bool buyTradeSuccess = _performBuyTrade(tokenAddress, tradeAmount);
            require(buyTradeSuccess, "Buy trade failed");

            // Perform sell trade on Uniswap
            bool sellTradeSuccess = _performSellTrade(tokenAddress, tradeAmount);
            require(sellTradeSuccess, "Sell trade failed");
        }
    }


    function _approveTokenForBuyTrade(address tokenAddress, uint256 tradeAmount) private {
        IERC20(tokenAddress).approve(address(uniswapRouter), tradeAmount);
    }

    function _approveTokenForSellTrade(address tokenAddress, uint256 tradeAmount) private {
        IERC20(tokenAddress).approve(address(uniswapRouter), tradeAmount);
    }


    function _performBuyTrade(address tokenAddress, uint256 tradeAmount) private returns (bool) {
        // Calculate the amount of ETH required for the trade
        uint256[] memory amounts = uniswapRouter.getAmountsIn(
            tradeAmount,
            getPathForETHToToken(tokenAddress)
        );
        uint256 ethAmount = amounts[0];

        // Swap ETH for the token on Uniswap
        address[] memory path = getPathForETHToToken(tokenAddress);
        _swap(ethAmount, path);

        // Assuming the swap was successful, you can return true
        return true;
    }

    function _performSellTrade(address tokenAddress, uint256 tradeAmount) private returns (bool) {
        // Calculate the amount of token required for the trade
        uint256[] memory amounts = uniswapRouter.getAmountsOut(
            tradeAmount,
            getPathForTokenToETH(tokenAddress)
        );
        uint256 tokenAmount = amounts[1];

        // Swap the token for ETH on Uniswap
        address[] memory path = getPathForTokenToETH(tokenAddress);
        _swap(tokenAmount, path);
        return true;
    }


    function _swap(uint256 amountIn, address[] memory path) private {

        // Approve the spending of tokens on Uniswap
        IERC20(path[0]).approve(address(uniswapRouter), amountIn);

        uniswapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            amountIn,
            0,
            path,
            address(this),
            block.timestamp
        );
    }

    function getPathForETHToToken(address tokenAddress) private view returns (address[] memory) {
        address[] memory path = new address[](2);
        path[0] = uniswapRouter.WETH();
        path[1] = tokenAddress;
        return path;
    }

    function getPathForTokenToETH(address tokenAddress) private view returns (address[] memory) {
        address[] memory path = new address[](2);
        path[0] = tokenAddress;
        path[1] = uniswapRouter.WETH();
        return path;
    }

    function withdrawETH() external onlyOwner {
        uint256 balance = address(this).balance;
        payable(owner).transfer(balance);
    }

    receive() external payable {
        emit ETHReceived(msg.sender, msg.value);
    }

    function view_balance() external view returns (uint256) {
        return address(this).balance;
    }

   function _checkTokenApproval(address tokenAddress, address spender, uint256 tradeAmount) private view returns (bool) {
       IERC20 token = IERC20(tokenAddress);
       uint256 allowance = token.allowance(msg.sender, spender);
       return allowance >= tradeAmount;
    }

}

核心问题排查

1. 买入交易用错了Swap函数

买入是ETH换Token,但当前_swap调用的是swapExactTokensForTokensSupportingFeeOnTransferTokens——这是Token转Token的专用函数。ETH转Token必须用swapExactETHForTokensSupportingFeeOnTransferTokens,而且这个函数需要带payable修饰,还要确保合约有足够ETH转入Router。

2. 授权逻辑全错

  • _approveTokenForBuyTrade是让合约给Router授权Token,但买入操作是用ETH换Token,此时合约还没有Token,这个授权完全多余。
  • _checkTokenApproval检查的是**合约所有者(msg.sender)对Router的授权,而实际需要的是合约自身(address(this))**对Router的授权(因为是合约在操作自己的Token),这个检查直接无效,会触发Token approval failed的回滚。

3. 卖出交易的数量计算错误

_performSellTrade里调用getAmountsOut(tradeAmount, path)时,path是Token→WETH:

  • amounts[0]是需要卖出的Token数量
  • amounts[1]是预计得到的ETH数量
    但代码把amounts[1]当成要卖出的Token数量,会导致卖出数量错误,大概率因为合约Token余额不足触发回滚。

4. 重复授权的潜在问题

_swap里每次都调用approve,部分ERC20代币不允许重复设置相同授权值(比如当前allowance已经是amountIn,再次调用approve(amountIn)会失败)。正确做法是先调用approve(0)再设置新授权,或者用safeApprove。

5. 缺少余额检查

  • 买入前没检查合约ETH余额是否≥计算出的ethAmount,余额不足会直接回滚。
  • 卖出前没检查合约Token余额是否≥要卖出的数量,同样会导致swap失败。

关键修复示例

// 修复买入交易逻辑
function _performBuyTrade(address tokenAddress, uint256 tradeAmount) private returns (bool) {
    uint256[] memory amounts = uniswapRouter.getAmountsIn(tradeAmount, getPathForETHToToken(tokenAddress));
    uint256 ethAmount = amounts[0];
    
    // 检查合约ETH余额
    require(address(this).balance >= ethAmount, "Insufficient ETH balance");
    
    address[] memory path = getPathForETHToToken(tokenAddress);
    uniswapRouter.swapExactETHForTokensSupportingFeeOnTransferTokens{value: ethAmount}(
        0,
        path,
        address(this),
        block.timestamp
    );
    return true;
}

// 修复卖出交易逻辑
function _performSellTrade(address tokenAddress, uint256 tradeAmount) private returns (bool) {
    IERC20 token = IERC20(tokenAddress);
    // 计算需要卖出的Token数量
    uint256[] memory amounts = uniswapRouter.getAmountsIn(tradeAmount, getPathForETHToToken(tokenAddress));
    uint256 tokenAmount = amounts[1];
    
    // 检查合约Token余额
    require(token.balanceOf(address(this)) >= tokenAmount, "Insufficient Token balance");
    
    // 授权Router使用Token(先清零再授权)
    token.approve(address(uniswapRouter), 0);
    token.approve(address(uniswapRouter), tokenAmount);
    
    address[] memory path = getPathForTokenToETH(tokenAddress);
    uniswapRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
        tokenAmount,
        0,
        path,
        address(this),
        block.timestamp
    );
    return true;
}

// 修复授权检查函数
function _checkTokenApproval(address tokenAddress, address spender, uint256 tradeAmount) private view returns (bool) {
    IERC20 token = IERC20(tokenAddress);
    // 检查合约自身对Router的授权
    uint256 allowance = token.allowance(address(this), spender);
    return allowance >= tradeAmount;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 13:09:56