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

以太坊主网套利合约执行触发CALL_EXCEPTION错误求助

DyDx闪贷套利合约调用executeTrade触发CALL_EXCEPTION错误排查求助

调用合约的executeTrade函数时触发如下错误:

Error: transaction failed [ CALL_EXCEPTION ]

完整合约代码

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

import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "hardhat/console.sol";

contract Arbitrage is DyDxFlashLoan {
    IUniswapV2Router02 public immutable uRouter;
    IUniswapV2Router02 public immutable sRouter;

    address public owner;

    constructor(address _uRouter, address _sRouter) {
        uRouter = IUniswapV2Router02(_uRouter);
        sRouter = IUniswapV2Router02(_sRouter);
        owner = msg.sender;
    }

    function executeTrade(
        address _tokenA,
        address _tokenB,
        uint256 _flashAmount,
        bool _startOnUniswap
    ) external {
        console.log("Balance before", IERC20(_tokenA).balanceOf(address(this)));
        uint balanceBefore = IERC20(_tokenA).balanceOf(address(this));
        bytes memory data = abi.encode(
            _startOnUniswap,
            _tokenA,
            _tokenB,
            _flashAmount,
            balanceBefore
        );
        flashloan(_tokenA, _flashAmount, data);
    }

    function callFunction(
        address,
        Info calldata,
        bytes calldata data
    ) external onlyPool {
        (
            bool _startOnUniswap,
            address _tokenA,
            address _tokenB,
            uint256 flashAmount,
            uint256 balanceBefore
        ) = abi.decode(data, (bool, address, address, uint256, uint256));

        console.log("Balance after:", IERC20(_tokenA).balanceOf(address(this)));
        uint balanceAfter = IERC20(_tokenA).balanceOf(address(this));

        require(
            balanceAfter - balanceBefore == flashAmount,
            "Didn't receive flash loan"
        );

        address[] memory tokens = new address[](2);

        tokens[0] = _tokenA;
        tokens[1] = _tokenB;

        if (_startOnUniswap == true) {
            swapOnUniswap(flashAmount, 0, tokens);

            tokens[0] = _tokenB;
            tokens[1] = _tokenA;

            swapOnSushiswap(
                IERC20(tokens[0]).balanceOf(address(this)),
                0,
                tokens
            );
        } else {
            // console.log("Ho 10 WETH e li swappo per x COMPOUND");
            console.log(
                "Balance of WETH before swap:",
                IERC20(tokens[0]).balanceOf(address(this))
            );
            swapOnSushiswap(flashAmount, 0, tokens);
            tokens[0] = _tokenB;
            tokens[1] = _tokenA;

            // console.log("Ho x COMPOUND e li swappo per >10 WETH");
            swapOnUniswap(
                IERC20(tokens[0]).balanceOf(address(this)),
                0,
                tokens
            );
            console.log(
                "Balance of WETH after swap:",
                IERC20(tokens[1]).balanceOf(address(this))
            );
        }
    }

    function swapOnUniswap(
        uint _amountIn,
        uint _amountOut,
        address[] memory _path
    ) internal {
        require(
            IERC20(_path[0]).approve(address(uRouter), _amountIn),
            "Uniswap failed the approval"
        );

        uint[] memory amounts = uRouter.swapExactTokensForTokens(
            _amountIn,
            _amountOut,
            _path,
            address(this),
            (block.timestamp + 1200)
        );

        console.log(
            "UNISWAP: Input tokens (COMP):",
            amounts[0],
            "Output tokens (ETH):",
            amounts[1]
        );
    }

    function swapOnSushiswap(
        uint _amountIn,
        uint _amountOut,
        address[] memory _path
    ) internal {
        require(
            IERC20(_path[0]).approve(address(sRouter), _amountIn),
            "Sushiswap failed the approval"
        );

        uint[] memory amounts = sRouter.swapExactTokensForTokens(
            _amountIn,
            _amountOut,
            _path,
            address(this),
            (block.timestamp + 1200)
        );

        console.log(
            "SUSHISWAP: Input tokens (ETH):",
            amounts[0],
            "Output tokens (COMP):",
            amounts[1]
        );
    }

    function withdraw(address _tokenAddress) external {
        console.log("Owner balance:", IERC20(_tokenAddress).balanceOf(owner));
        require(owner == msg.sender, "Only owner can withdraw");
        ERC20(_tokenAddress).transfer(
            owner,
            IERC20(_tokenAddress).balanceOf(address(this))
        );
        console.log("Owner balance", IERC20(_tokenAddress).balanceOf(owner));
    }
}

触发交易的代码

const address = new ethers.Wallet(process.env.PRIVATE_KEY, mainnetProvider);
  if (process.env.IS_DEPLOYED === true) {
    const options = {
      gasLimit: 2500000,
      maxFeePerGas: 40000000000,
    };
    const trade = await arbitrageContract
      .connect(address)
      .executeTrade(
        process.env.TOKEN_WETH_MAINNET,
        process.env.TOKEN_COMPOUND_MAINNET,
        ethers.BigNumber.from("10000000000000000000"),
        startOnUniswap,
        options
      );

    receipt = await trade.wait();
    console.log("sending eth to contract...");
    const withdraw = await arbitrageContract
      .connect(address)
      .withdraw(process.env.TOKEN_WETH_MAINNET);
    await withdraw.wait();
    console.log("sent eth to owner successfully");
  } else {
    console.log("CURRENTLY ON TEST MODE");
  }

已完成的排查

  • 合约已部署至以太坊主网(地址:0x7221a612Fc346fa218F9f64B4B4283782F639ea0),未验证
  • 调用的合约代码正确
  • 代码未在require语句处回滚
  • 未使用错误的ABI

排查建议

  1. 检查DyDxFlashLoan基类实现:确认flashloan函数的参数传递、权限控制是否符合DyDx要求,onlyPool修饰符逻辑是否正确,是否遗漏基类必须实现的方法或校验。
  2. 补全闪贷还款逻辑:DyDx要求在callFunction结束前归还闪贷本金+手续费,当前合约仅执行套利交换未处理还款,这会直接触发交易回滚。
  3. 校验交易路径与滑点:确认WETH/COMP在两大交易所的流动性充足,swapExactTokensForTokens的_amountOut设为0可能因滑点超限失败,建议设置合理的最小输出值。
  4. 查看交易详细失败信息:通过区块浏览器查看失败交易的回滚原因或调用栈,定位具体失败指令。
  5. 测试小额度闪贷:先尝试0.1 WETH这类小额交易,排除大额流动性不足的问题。
  6. 确认接口签名兼容性:检查DyDxFlashLoan的callFunction签名是否与官方要求完全一致,参数顺序、类型是否匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 16:48:10