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

