All ETH can be stolen during rebalancing for `mTOFTs` that hold native
Rebalancing of ETH transfers the ETH to the destination `mTOFT` without calling `sgRecieve` which leaves the ETH hanging inside the `mTOFT` contract. This can be exploited to steal all the ETH.\\nRebalancing of `mTOFTs` that hold native tokens is done through the `routerETH` contract inside the `Balancer.sol` contract. Here is the code snippet for the `routerETH` contract:\\n```\\n## Balancer.sol\\n\\nif (address(this).balance < _amount) revert ExceedsBalance();\\n        uint256 valueAmount = msg.value + _amount;\\n        routerETH.swapETH{value: valueAmount}(\\n            _dstChainId,\\n            payable(this),\\n            abi.encodePacked(connectedOFTs[_oft][_dstChainId].dstOft),\\n            _amount,\\n            _computeMinAmount(_amount, _slippage)\\n        );\\n```\\n\\nThe expected behaviour is ETH being received on the destination chain whereby `sgReceive` is called and ETH is deposited inside the `TOFTVault`.\\n```\\n## mTOFT.sol\\n\\n    function sgReceive(uint16, bytes memory, uint256, address, uint256 amountLD, bytes memory) external payable {\\n        if (msg.sender != _stargateRouter) revert mTOFT_NotAuthorized();\\n\\n        if (erc20 == address(0)) {\\n            vault.depositNative{value: amountLD}();\\n        } else {\\n            IERC20(erc20).safeTransfer(address(vault), amountLD);\\n        }\\n    }\\n```\\n\\nBy taking a closer look at the logic inside the `routerETH` contract we can see that the transfer is called with an empty payload:\\n```\\n    // compose stargate to swap ETH on the source to ETH on the destination\\n    function swapETH(\\n        uint16 _dstChainId,                         // destination Stargate chainId\\n        address payable _refundAddress,             // refund additional messageFee to this address\\n        bytes calldata _toAddress,                  // the receiver of the destination ETH\\n        uint256 _amountLD,                          // the amount, in Local Decimals, to be swapped\\n        uint256 _minAmountLD                        // the minimum amount accepted out on destination\\n    ) external payable {\\n        require(msg.value > _amountLD, "Stargate: msg.value must be > _amountLD");\\n\\n        // wrap the ETH into WETH\\n        IStargateEthVault(stargateEthVault).deposit{value: _amountLD}();\\n        IStargateEthVault(stargateEthVault).approve(address(stargateRouter), _amountLD);\\n\\n        // messageFee is the remainder of the msg.value after wrap\\n        uint256 messageFee = msg.value - _amountLD;\\n\\n        // compose a stargate swap() using the WETH that was just wrapped\\n        stargateRouter.swap{value: messageFee}(\\n            _dstChainId,                        // destination Stargate chainId\\n            poolId,                             // WETH Stargate poolId on source\\n            poolId,                             // WETH Stargate poolId on destination\\n            _refundAddress,                     // message refund address if overpaid\\n            _amountLD,                          // the amount in Local Decimals to swap()\\n            _minAmountLD,                       // the minimum amount swap()er would allow to get out (ie: slippage)\\n            IStargateRouter.lzTxObj(0, 0, "0x"),\\n            _toAddress,                         // address on destination to send to\\n      bytes("")                           // empty payload, since sending to EOA\\n        );\\n    }\\n```\\n\\nNotice the comment:\\nempty payload, since sending to EOA\\nSo `routerETH` after depositing ETH in `StargateEthVault` calls the regular `StargateRouter` but with an empty payload.\\nNext, let's see how the receiving logic works.\\nAs Stargate is just another application built on top of LayerZero the receiving starts inside the `Bridge::lzReceive` function. As the type of transfer is `TYPE_SWAP_REMOTE` the `router::swapRemote` is called:\\n```\\nfunction lzReceive(\\n    uint16 _srcChainId,\\n    bytes memory _srcAddress,\\n    uint64 _nonce,\\n    bytes memory _payload\\n) external override {\\n\\n\\n    if (functionType == TYPE_SWAP_REMOTE) {\\n        (\\n            ,\\n            uint256 srcPoolId,\\n            uint256 dstPoolId,\\n            uint256 dstGasForCall,\\n            Pool.CreditObj memory c,\\n            Pool.SwapObj memory s,\\n            bytes memory to,\\n            bytes memory payload\\n        ) = abi.decode(_payload, (uint8, uint256, uint256, uint256, Pool.CreditObj, Pool.SwapObj, bytes, bytes));\\n        address toAddress;\\n        assembly {\\n            toAddress := mload(add(to, 20))\\n        }\\n        router.creditChainPath(_srcChainId, srcPoolId, dstPoolId, c);\\n  router.swapRemote(_srcChainId, _srcAddress, _nonce, srcPoolId, dstPoolId, dstGasForCall, toAddress, s, payload);\\n```\\n\\n`Router:swapRemote` has two responsibilities:\\nFirst it calls `pool::swapRemote` that transfers the actual tokens to the destination address. In this case this is the `mTOFT` contract.\\nSecond it will call `IStargateReceiver(mTOFTAddress)::sgReceive` but only if the payload is not empty.\\n```\\n function _swapRemote(\\n    uint16 _srcChainId,\\n    bytes memory _srcAddress,\\n    uint256 _nonce,\\n    uint256 _srcPoolId,\\n    uint256 _dstPoolId,\\n    uint256 _dstGasForCall,\\n    address _to,\\n    Pool.SwapObj memory _s,\\n    bytes memory _payload\\n) internal {\\n    Pool pool = _getPool(_dstPoolId);\\n    // first try catch the swap remote\\n    try pool.swapRemote(_srcChainId, _srcPoolId, _to, _s) returns (uint256 amountLD) {\\n   if (_payload.length > 0) {\\n            // then try catch the external contract call\\n      try IStargateReceiver(_to).sgReceive{gas: _dstGasForCall}(_srcChainId, _srcAddress, _nonce, pool.token(), amountLD, _payload) {\\n                // do nothing\\n            } catch (bytes memory reason) {\\n                cachedSwapLookup[_srcChainId][_srcAddress][_nonce] = CachedSwap(pool.token(), amountLD, _to, _payload);\\n                emit CachedSwapSaved(_srcChainId, _srcAddress, _nonce, pool.token(), amountLD, _to, _payload, reason);\\n            }\\n        }\\n    } catch {\\n        revertLookup[_srcChainId][_srcAddress][_nonce] = abi.encode(\\n            TYPE_SWAP_REMOTE_RETRY,\\n            _srcPoolId,\\n            _dstPoolId,\\n            _dstGasForCall,\\n            _to,\\n            _s,\\n            _payload\\n        );\\n        emit Revert(TYPE_SWAP_REMOTE_RETRY, _srcChainId, _srcAddress, _nonce);\\n    }\\n}\\n```\\n\\nAs payload is empty in case of using the `routerETH` contract the `sgReceive` function is never called. This means that the ETH is left sitting inside the `mTOFT` contract.\\n```\\n## TapiocaOmnichainSender.sol\\n\\n function sendPacket(LZSendParam calldata _lzSendParam, bytes calldata _composeMsg)\\n        external\\n        payable\\n        returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt)\\n    {\\n        // @dev Applies the token transfers regarding this send() operation.\\n        // - amountDebitedLD is the amount in local decimals that was ACTUALLY debited from the sender.\\n        // - amountToCreditLD is the amount in local decimals that will be credited to the recipient on the remote OFT instance.\\n        (uint256 amountDebitedLD, uint256 amountToCreditLD) =\\n            _debit(_lzSendParam.sendParam.amountLD, _lzSendParam.sendParam.minAmountLD, _lzSendParam.sendParam.dstEid);\\n\\n        // @dev Builds the options and OFT message to quote in the endpoint.\\n        (bytes memory message, bytes memory options) =\\n            _buildOFTMsgAndOptions(_lzSendParam.sendParam, _lzSendParam.extraOptions, _composeMsg, amountToCreditLD);\\n\\n        // @dev Sends the message to the LayerZero endpoint and returns the LayerZero msg receipt.\\n        msgReceipt =\\n            _lzSend(_lzSendParam.sendParam.dstEid, message, options, _lzSendParam.fee, _lzSendParam.refundAddress);\\n        // @dev Formulate the OFT receipt.\\n        oftReceipt = OFTReceipt(amountDebitedLD, amountToCreditLD);\\n\\n        emit OFTSent(msgReceipt.guid, _lzSendParam.sendParam.dstEid, msg.sender, amountDebitedLD);\\n    }\\n```\\n\\nAll he has to do is specify the option type `lzNativeDrop` inside the `_lsSendParams.extraOptions` and the cost of calling `_lzSend` plus the airdrop amount will be paid out from the balance of `mTOFT`.\\nAs this is a complete theft of the rebalanced amount I'm rating this as a critical vulnerability.
```\\nfunction swapETHAndCall(\\n        uint16 _dstChainId, // destination Stargate chainId\\n        address payable _refundAddress, // refund additional messageFee to this address\\n        bytes calldata _toAddress, // the receiver of the destination ETH\\n        SwapAmount memory _swapAmount, // the amount and the minimum swap amount\\n        IStargateRouter.lzTxObj memory _lzTxParams, // the LZ tx params\\n        bytes calldata _payload // the payload to send to the destination\\n    ) external payable {\\n```\\n
All ETH can be stolen during rebalancing for mTOFTs that hold native tokens.
```\\n## Balancer.sol\\n\\nif (address(this).balance < _amount) revert ExceedsBalance();\\n        uint256 valueAmount = msg.value + _amount;\\n        routerETH.swapETH{value: valueAmount}(\\n            _dstChainId,\\n            payable(this),\\n            abi.encodePacked(connectedOFTs[_oft][_dstChainId].dstOft),\\n            _amount,\\n            _computeMinAmount(_amount, _slippage)\\n        );\\n```\\n