Issue when handling native ETH trade and WETH trade in DODO RouterProxy#externalSwap
Lack of logic to wrap the native ETH to WETH in function externalSwap\\nThe function exeternalSwap can handle external swaps with 0x, 1inch and paraswap or other external resources.\\n```\\n    function externalSwap(\\n        address fromToken,\\n        address toToken,\\n        address approveTarget,\\n        address swapTarget,\\n        uint256 fromTokenAmount,\\n        uint256 minReturnAmount,\\n        bytes memory feeData,\\n        bytes memory callDataConcat,\\n        uint256 deadLine\\n    ) external payable judgeExpired(deadLine) returns (uint256 receiveAmount) {      \\n        require(isWhiteListedContract[swapTarget], "DODORouteProxy: Not Whitelist Contract");  \\n        require(isApproveWhiteListedContract[approveTarget], "DODORouteProxy: Not Whitelist Appprove Contract");  \\n\\n        // transfer in fromToken\\n        if (fromToken != _ETH_ADDRESS_) {\\n            // approve if needed\\n            if (approveTarget != address(0)) {\\n                IERC20(fromToken).universalApproveMax(approveTarget, fromTokenAmount);\\n            }\\n\\n            IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(\\n                fromToken,\\n                msg.sender,\\n                address(this),\\n                fromTokenAmount\\n            );\\n        }\\n\\n        // swap\\n        uint256 toTokenOriginBalance;\\n        if(toToken != _ETH_ADDRESS_) {\\n            toTokenOriginBalance = IERC20(toToken).universalBalanceOf(address(this));\\n        } else {\\n            toTokenOriginBalance = IERC20(_WETH_).universalBalanceOf(address(this));\\n        }\\n```\\n\\nnote the code above, if the fromToken is set to _ETH_ADDRESS, indicating the user wants to trade with native ETH pair. the function does has payable modifier and user can send ETH along when calling this function.\\nHowever, the toTokenOriginBalance is check the only WETH balance instead of ETH balance.\\n```\\n  if(toToken != _ETH_ADDRESS_) {\\n      toTokenOriginBalance = IERC20(toToken).universalBalanceOf(address(this));\\n  } else {\\n      toTokenOriginBalance = IERC20(_WETH_).universalBalanceOf(address(this));\\n  }\\n```\\n\\nThen we do the swap:\\n```\\n(bool success, bytes memory result) = swapTarget.call{\\n    value: fromToken == _ETH_ADDRESS_ ? fromTokenAmount : 0\\n}(callDataConcat);\\n```\\n\\nIf the fromToken is _ETH_ADDRESS, we send the user supplied fromTokenAmount without verifying that the fromTokenAmount.\\nFinally, we use the before and after balance to get the amount with received.\\n```\\n// calculate toToken amount\\n  if(toToken != _ETH_ADDRESS_) {\\n      receiveAmount = IERC20(toToken).universalBalanceOf(address(this)) - (\\n          toTokenOriginBalance\\n      );\\n  } else {\\n      receiveAmount = IERC20(_WETH_).universalBalanceOf(address(this)) - (\\n          toTokenOriginBalance\\n      );\\n  }\\n```\\n\\nWe are checking the WETH amount instead of ETH amount again.\\nThe issue is that some trades may settle the trade in native ETH, for example\\nwe can look into the Paraswap contract\\nIf we click the implementation contract and see the method swapOnUniswapV2Fork\\nCode line 927 - 944, which calls the function\\n```\\nfunction swapOnUniswapV2Fork(\\n address tokenIn,\\n uint256 amountIn,\\n uint256 amountOutMin,\\n address weth,\\n uint256[] calldata pools\\n)\\n external\\n payable\\n{\\n _swap(\\n  tokenIn,\\n  amountIn,\\n  amountOutMin,\\n  weth,\\n  pools\\n );\\n}\\n```\\n\\nwhich calls:\\n```\\n  function _swap(\\n        address tokenIn,\\n        uint256 amountIn,\\n        uint256 amountOutMin,\\n        address weth,\\n        uint256[] memory pools\\n    )\\n        private\\n        returns (uint256 tokensBought)\\n    {\\n        uint256 pairs = pools.length;\\n\\n        require(pairs != 0, "At least one pool required");\\n\\n        bool tokensBoughtEth;\\n\\n        if (tokenIn == ETH_IDENTIFIER) {\\n            require(amountIn == msg.value, "Incorrect msg.value");\\n            IWETH(weth).deposit{value: msg.value}();\\n            require(IWETH(weth).transfer(address(pools[0]), msg.value));\\n        } else {\\n            require(msg.value == 0, "Incorrect msg.value");\\n            transferTokens(tokenIn, msg.sender, address(pools[0]), amountIn);\\n            tokensBoughtEth = weth != address(0);\\n        }\\n\\n        tokensBought = amountIn;\\n\\n        for (uint256 i = 0; i < pairs; ++i) {\\n            uint256 p = pools[i];\\n            address pool = address(p);\\n            bool direction = p & DIRECTION_FLAG == 0;\\n\\n            tokensBought = NewUniswapV2Lib.getAmountOut(\\n                tokensBought, pool, direction, p  FEE_OFFSET\\n            );\\n            (uint256 amount0Out, uint256 amount1Out) = direction\\n                ? (uint256(0), tokensBought) : (tokensBought, uint256(0));\\n            IUniswapV2Pair(pool).swap(\\n                amount0Out,\\n                amount1Out,\\n                i + 1 == pairs\\n                    ? (tokensBoughtEth ? address(this) : msg.sender)\\n                    : address(pools[i + 1]),\\n                ""\\n            );\\n        }\\n\\n        if (tokensBoughtEth) {\\n            IWETH(weth).withdraw(tokensBought);\\n            TransferHelper.safeTransferETH(msg.sender, tokensBought);\\n        }\\n\\n        require(tokensBought >= amountOutMin, "UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT");\\n    }\\n```\\n\\nas can clearly see, the code first receive ETH, wrap ETH to WETH, then instead end, unwrap the WETH to ETH and the send the ETH back to complete the trade.\\n```\\nif (tokensBoughtEth) {\\n IWETH(weth).withdraw(tokensBought);\\n TransferHelper.safeTransferETH(msg.sender, tokensBought);\\n}\\n```\\n\\nIn DODORouterProxy.sol#ExternalSwap however, we are using WETH balance before and after to check the received amount,\\nbut if we call swapOnUniswapV2Fork on Paraswap router, the balance change for WETH would be 0\\nbecause as we see above, the method on paraswap side wrap ETH to WETH but in the end unwrap WETH and send ETH back.\\nThere is also a lack of a method to wrap the ETH to WETH before the trade. making the ETH-related order not tradeable.
We recommend the project change from\\n```\\n  // swap\\n  uint256 toTokenOriginBalance;\\n  if(toToken != _ETH_ADDRESS_) {\\n      toTokenOriginBalance = IERC20(toToken).universalBalanceOf(address(this));\\n  } else {\\n      toTokenOriginBalance = IERC20(_WETH_).universalBalanceOf(address(this));\\n  }\\n```\\n\\n```\\n  // swap\\n  uint256 toTokenOriginBalance;\\n  if(toToken != _ETH_ADDRESS_) {\\n      toTokenOriginBalance = IERC20(toToken).universalBalanceOf(address(this));\\n  } else {\\n      toTokenOriginBalance = IERC20(_ETH_ADDRESS).universalBalanceOf(address(this));\\n  }\\n```\\n\\nIf we want to use WETH to do the balance check, we can help the user wrap the ETH to WETH by calling before do the balance check.\\n```\\nIWETH(_WETH_).deposit(receiveAmount);\\n```\\n\\nIf we want to use WETH as the reference to trade, we also need to approve external contract to spend our WETH.\\nWe can add\\n```\\nif(fromToken == _ETH_ADDRESS) {\\n   IERC20(_WETH_).universalApproveMax(approveTarget, fromTokenAmount);\\n}\\n```\\n\\nWe also need to verify the fromTokenAmount for\\n```\\n(bool success, bytes memory result) = swapTarget.call{\\n    value: fromToken == _ETH_ADDRESS_ ? fromTokenAmount : 0\\n}(callDataConcat);\\n```\\n\\nwe can add the check:\\n```\\nrequire(msg.value == fromTokenAmount, "invalid ETH amount");\\n```\\n
A lot of method that does not use WETH to settle the trade will not be callable.
```\\n    function externalSwap(\\n        address fromToken,\\n        address toToken,\\n        address approveTarget,\\n        address swapTarget,\\n        uint256 fromTokenAmount,\\n        uint256 minReturnAmount,\\n        bytes memory feeData,\\n        bytes memory callDataConcat,\\n        uint256 deadLine\\n    ) external payable judgeExpired(deadLine) returns (uint256 receiveAmount) {      \\n        require(isWhiteListedContract[swapTarget], "DODORouteProxy: Not Whitelist Contract");  \\n        require(isApproveWhiteListedContract[approveTarget], "DODORouteProxy: Not Whitelist Appprove Contract");  \\n\\n        // transfer in fromToken\\n        if (fromToken != _ETH_ADDRESS_) {\\n            // approve if needed\\n            if (approveTarget != address(0)) {\\n                IERC20(fromToken).universalApproveMax(approveTarget, fromTokenAmount);\\n            }\\n\\n            IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(\\n                fromToken,\\n                msg.sender,\\n                address(this),\\n                fromTokenAmount\\n            );\\n        }\\n\\n        // swap\\n        uint256 toTokenOriginBalance;\\n        if(toToken != _ETH_ADDRESS_) {\\n            toTokenOriginBalance = IERC20(toToken).universalBalanceOf(address(this));\\n        } else {\\n            toTokenOriginBalance = IERC20(_WETH_).universalBalanceOf(address(this));\\n        }\\n```\\n