调用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
相关产品推荐
相关产品推荐

