Vulnerability: Residual ETH not sent back when `batchBalanceAndTradeAction` executed
Severity: high

Description:
Residual ETH was not sent back when `batchBalanceAndTradeAction` function was executed, resulting in a loss of assets.\\nPer the comment at Line 122 below, when there is residual ETH, native ETH will be sent from Notional V3 to the wrapper contract. In addition, per the comment at Line 109, it is often the case to have an excess amount to be refunded to the users.\\n```\\nFile: wfCashLogic.sol\\n    function _lendLegacy(\\nFile: wfCashLogic.sol\\n        // If deposit amount external is in excess of the cost to purchase fCash amount (often the case),\\n        // then we need to return the difference between postTradeCash - preTradeCash. This is done because\\n        // the encoded trade does not automatically withdraw the entire cash balance in case the wrapper\\n        // is holding a cash balance.\\n        uint256 preTradeCash = getCashBalance();\\n\\n        BalanceActionWithTrades[] memory action = EncodeDecode.encodeLegacyLendTrade(\\n            currencyId,\\n            getMarketIndex(),\\n            depositAmountExternal,\\n            fCashAmount,\\n            minImpliedRate\\n        );\\n        // Notional will return any residual ETH as the native token. When we _sendTokensToReceiver those\\n        // native ETH tokens will be wrapped back to WETH.\\n        NotionalV2.batchBalanceAndTradeAction{value: msgValue}(address(this), action); \\n\\n        uint256 postTradeCash = getCashBalance(); \\n\\n        if (preTradeCash != postTradeCash) { \\n            // If ETH, then redeem to WETH (redeemToUnderlying == false)\\n            NotionalV2.withdraw(currencyId, _safeUint88(postTradeCash - preTradeCash), !isETH);\\n        }\\n    }\\n```\\n\\nThis is due to how the `depositUnderlyingExternal` function within Notional V3 is implemented. The `batchBalanceAndTradeAction` will trigger the `depositUnderlyingExternal` function. Within the `depositUnderlyingExternal` function at Line 196, excess ETH will be transferred back to the account (wrapper address) in Native ETH term.\\nNote that for other ERC20 tokens, such as DAI or USDC, the excess will be added to the wrapper's cash balance, and this issue will not occur.\\n```\\nFile: TokenHandler.sol\\n    function depositUnderlyingExternal(\\n        address account,\\n        uint16 currencyId,\\n        int256 _underlyingExternalDeposit,\\n        PrimeRate memory primeRate,\\n        bool returnNativeTokenWrapped\\n    ) internal returns (int256 actualTransferExternal, int256 netPrimeSupplyChange) {\\n        uint256 underlyingExternalDeposit = _underlyingExternalDeposit.toUint();\\n        if (underlyingExternalDeposit == 0) return (0, 0);\\n\\n        Token memory underlying = getUnderlyingToken(currencyId);\\n        if (underlying.tokenType == TokenType.Ether) {\\n            // Underflow checked above\\n            if (underlyingExternalDeposit < msg.value) {\\n                // Transfer any excess ETH back to the account\\n                GenericToken.transferNativeTokenOut(\\n                    account, msg.value - underlyingExternalDeposit, returnNativeTokenWrapped\\n                );\\n            } else {\\n                require(underlyingExternalDeposit == msg.value, "ETH Balance");\\n            }\\n\\n            actualTransferExternal = _underlyingExternalDeposit;\\n```\\n\\nIn the comment, it mentioned that any residual ETH in native token will be wrapped back to WETH by the `_sendTokensToReceiver`.\\n```\\nFile: wfCashLogic.sol\\n    function _lendLegacy(\\n..SNIP..\\n        // Notional will return any residual ETH as the native token. When we _sendTokensToReceiver those\\n        // native ETH tokens will be wrapped back to WETH.\\n```\\n\\nHowever, the current implementation of the `_sendTokensToReceiver`, as shown below, does not wrap the Native ETH to WETH. Thus, the residual ETH will not be sent back to the users and stuck in the contract.\\n```\\nFile: wfCashLogic.sol\\n    function _sendTokensToReceiver( \\n        IERC20 token,\\n        address receiver,\\n        bool isETH,\\n        uint256 balanceBefore\\n    ) private returns (uint256 tokensTransferred) {\\n        uint256 balanceAfter = isETH ? WETH.balanceOf(address(this)) : token.balanceOf(address(this)); \\n        tokensTransferred = balanceAfter - balanceBefore; \\n\\n        if (isETH) {\\n            // No need to use safeTransfer for WETH since it is known to be compatible\\n            IERC20(address(WETH)).transfer(receiver, tokensTransferred); \\n        } else if (tokensTransferred > 0) { \\n            token.safeTransfer(receiver, tokensTransferred); \\n        }\\n    }\\n```\\n

Impact:
Loss of assets as the residual ETH is not sent to the users.

Recommended mitigation:
If the underlying is ETH, measure the Native ETH balance before and after the `batchBalanceAndTradeAction` is executed. Forward any residual Native ETH to the users, if any.