Vulnerability: Not considering fees when wrapping mtOFTs leads to DoS in leverage executors
Severity: medium

Description:
When wrapping mtOFTs in leverage executors, fees are not considered, making calls always revert because the obtained assets amount is always smaller than expected.\\nTapioca will allow tOFTs and mtOFTs to act as collateral in some of Tapioca's markets, as described by the documentation. Although regular tOFTs don't hardcode fees to 0, meta-tOFTs (mtOFTs) could incur a fee when wrapping, as shown in the following code snippet, where `_checkAndExtractFees()` is used to calculate a fee considering the wrapped _amount:\\n```\\n// mTOFT.sol\\n\\nfunction wrap(address _fromAddress, address _toAddress, uint256 _amount)\\n        external\\n        payable \\n        whenNotPaused\\n        nonReentrant\\n        returns (uint256 minted)\\n    {\\n        // rest of code\\n     \\n        uint256 feeAmount = _checkAndExtractFees(_amount);\\n        if (erc20 == address(0)) {\\n            _wrapNative(_toAddress, _amount, feeAmount);\\n        } else { \\n            if (msg.value > 0) revert mTOFT_NotNative();\\n            _wrap(_fromAddress, _toAddress, _amount, feeAmount);\\n        }\\n\\n        return _amount - feeAmount;\\n    } \\n```\\n\\nWhen fees are applied, the amount of `mtOFTs` minted to the caller won't be the full `_amount`, but the `_amount - feeAmount`.\\nTapioca's leverage executors are required to wrap/unwrap assets when tOFTs are used as collateral in order to properly perform their logic. The problem is that leverage executors don't consider the fact that if collateral is an `mtOFT`, then a fee could be applied.\\nLet's consider the `BaseLeverageExecutor` ****contract (who whas the `_swapAndTransferToSender()` function, called by all leverage executors):\\n```\\n// BaseLeverageExecutor.sol\\n\\nfunction _swapAndTransferToSender( \\n        bool sendBack, \\n        address tokenIn,\\n        address tokenOut,\\n        uint256 amountIn, \\n        bytes memory data\\n    ) internal returns (uint256 amountOut) {\\n        SLeverageSwapData memory swapData = abi.decode(data, (SLeverageSwapData)); \\n \\n     // rest of code\\n  \\n        // If the tokenOut is a tOFT, wrap it. Handles ETH and ERC20.\\n        // If `sendBack` is true, wrap the `amountOut to` the sender. else, wrap it to this contract.\\n        if (swapData.toftInfo.isTokenOutToft) {  \\n            _handleToftWrapToSender(sendBack, tokenOut, amountOut);\\n        } else if (sendBack == true) {\\n            // If the token wasn't sent by the wrap OP, send it as a transfer.\\n            IERC20(tokenOut).safeTransfer(msg.sender, amountOut);\\n        } \\n    } \\n```\\n\\nAs we can see in the code snippet, if the user requires to wrap the obtained swapped assets by setting `swapData.toftInfo.isTokenOutToft` to `true`, then the internal `_handleToftWrapToSender()` function will be called. This function will wrap the tOFT (or mtOFT) and send it to `msg.sender` or `address(this)`, depending on the user's `sendBack` input:\\n```\\n// BaseLeverageExecutor.sol\\n\\nfunction _handleToftWrapToSender(bool sendBack, address tokenOut, uint256 amountOut) internal {\\n        address toftErc20 = ITOFT(tokenOut).erc20();\\n        address wrapsTo = sendBack == true ? msg.sender : address(this);\\n\\n        if (toftErc20 == address(0)) {\\n            // If the tOFT is for ETH, withdraw from WETH and wrap it.\\n            weth.withdraw(amountOut);\\n            ITOFT(tokenOut).wrap{value: amountOut}(address(this), wrapsTo, amountOut);\\n        } else {\\n            // If the tOFT is for an ERC20, wrap it.\\n            toftErc20.safeApprove(tokenOut, amountOut);\\n            ITOFT(tokenOut).wrap(address(this), wrapsTo, amountOut);\\n            toftErc20.safeApprove(tokenOut, 0);\\n        }\\n    }\\n```\\n\\nThe problem here is that if `tokenOut` is an mtOFT, then a fee might be applied when wrapping. However, this function does not consider the `wrap()` function return value (which as shown in the first code snippet in this report, whill return the actual minted amount, which is always `_amount - feeAmount` ).\\nThis leads to a vulnerability where contracts performing this wraps will believe they have more funds than the intended, leading to a Denial of Service and making the leverage executors never work with mtOFTs.\\nLet's say a user wants to lever up by calling BBLeverage.sol's `buyCollateral()` function:\\n```\\n// BBLeverage.sol\\n\\nfunction buyCollateral(address from, uint256 borrowAmount, uint256 supplyAmount, bytes calldata data) \\n        external\\n        optionNotPaused(PauseType.LeverageBuy)\\n        solvent(from, false)\\n        notSelf(from)  \\n        returns (uint256 amountOut) \\n    { \\n        \\n\\n        // rest of code\\n        \\n        { \\n            amountOut = leverageExecutor.getCollateral( \\n                collateralId, \\n                address(asset),\\n                address(collateral),\\n                memoryData.supplyShareToAmount + memoryData.borrowShareToAmount,\\n                calldata_.from,\\n                calldata_.data\\n            );\\n        }\\n        uint256 collateralShare = yieldBox.toShare(collateralId, amountOut, false);\\n        address(asset).safeApprove(address(yieldBox), type(uint256).max); \\n  \\n        \\n        yieldBox.depositAsset(collateralId, address(this), address(this), 0, collateralShare); \\n        address(asset).safeApprove(address(yieldBox), 0); \\n \\n        // rest of code\\n    } \\n```\\n\\nAs we can see, the contract will call `leverageExecutor.getCollateral()` in order to perform the swap. Notice how the value returned by `getCollateral()` will be stored in the amountOut variable, which will later be converted to `collateralShare` and deposited into the `yieldBox`.\\nLet's say the `leverageExecutor` in this case is the `SimpleLeverageExecutor.sol` contract. When `getCollateral()` is called, `SimpleLeverageExecutor` will directly return the value returned by the internal `_swapAndTransferToSender()` function:\\n`// `SimpleLeverageExecutor.sol`\\n\\nfunction getCollateral(  \\n        address assetAddress,\\n        address collateralAddress,\\n        uint256 assetAmountIn,\\n        bytes calldata swapperData \\n    ) external payable override returns (uint256 collateralAmountOut) {\\n        // Should be called only by approved SGL/BB markets.\\n        if (!cluster.isWhitelisted(0, msg.sender)) revert SenderNotValid();\\n        return _swapAndTransferToSender(true, assetAddress, collateralAddress, assetAmountIn, swapperData);\\n    }  `\\nAs seen in the report, `_swapAndTransferToSender()` won't return the amount swapped and wrapped, and will instead only return the amount obtained when swapping, assuming that wraps will always mint the same amount:\\n`// BaseLeverageExecutor.sol\\n\\nfunction _swapAndTransferToSender( \\n        bool sendBack, \\n        address tokenIn,\\n        address tokenOut,\\n        uint256 amountIn, \\n        bytes memory data\\n    ) internal returns (uint256 amountOut) {\\n    \\n        ...\\n        \\n        amountOut = swapper.swap(swapperData, amountIn, swapData.minAmountOut);\\n        \\n        ...\\n        if (swapData.toftInfo.isTokenOutToft) {  \\n            _handleToftWrapToSender(sendBack, tokenOut, amountOut);\\n        } else if (sendBack == true) {\\n            // If the token wasn't sent by the wrap OP, send it as a transfer.\\n            IERC20(tokenOut).safeTransfer(msg.sender, amountOut);\\n        } \\n    } `\\nIf the tokenOut is an mtOFT, the actual obtained amount will be smaller than the `amountOut` stored due to the fees that might be applied.\\nThis makes the `yieldBox.depositAsset()` in `BBLeverage.sol` inevitably always fail due to not having enough funds to deposit into the YieldBox effectively causing a Denial of Service

Impact:
High. The core functionality of leverage won't work if the tokens are mtOFT tokens.

Recommended mitigation:
Consider the fees applied when wrapping assets by following OFT's API, and store the returned value by `wrap()`. For example, `_handleToftWrapToSender()` could return an integer with the actual amount obtained after wrapping:\\n```\\n// BaseLeverageExecutor.sol\\n\\nfunction _handleToftWrapToSender(bool sendBack, address tokenOut, uint256 amountOut) internal returns(uint256 _amountOut) {\\n        address toftErc20 = ITOFT(tokenOut).erc20();\\n        address wrapsTo = sendBack == true ? msg.sender : address(this);\\n\\n        if (toftErc20 == address(0)) {\\n            // If the tOFT is for ETH, withdraw from WETH and wrap it.\\n            weth.withdraw(amountOut);\\n// Remove the line below\\n            ITOFT(tokenOut).wrap{value: amountOut}(address(this), wrapsTo, amountOut);\\n// Add the line below\\n     _amountOut = ITOFT(tokenOut).wrap{value: amountOut}(address(this), wrapsTo, amountOut);\\n        } else {\\n            // If the tOFT is for an ERC20, wrap it.\\n            toftErc20.safeApprove(tokenOut, amountOut);\\n// Remove the line below\\n           _amountOut = ITOFT(tokenOut).wrap(address(this), wrapsTo, amountOut);\\n// Add the line below\\n           ITOFT(tokenOut).wrap(address(this), wrapsTo, amountOut);\\n            toftErc20.safeApprove(tokenOut, 0);\\n        }\\n    }\\n```\\n\\nAnd this value should be the one stored in _swapAndTransferToSender()'s amountOut:\\n```\\nfunction _swapAndTransferToSender( \\n        bool sendBack, \\n        address tokenIn,\\n        address tokenOut,\\n        uint256 amountIn, \\n        bytes memory data\\n    ) internal returns (uint256 amountOut) {\\n        SLeverageSwapData memory swapData = abi.decode(data, (SLeverageSwapData)); \\n \\n     // rest of code\\n  \\n        // If the tokenOut is a tOFT, wrap it. Handles ETH and ERC20.\\n        // If `sendBack` is true, wrap the `amountOut to` the sender. else, wrap it to this contract.\\n        if (swapData.toftInfo.isTokenOutToft) {  \\n// Remove the line below\\n            _handleToftWrapToSender(sendBack, tokenOut, amountOut);\\n// Add the line below\\n      amountOut = _handleToftWrapToSender(sendBack, tokenOut, amountOut);\\n        } else if (sendBack == true) {\\n            // If the token wasn't sent by the wrap OP, send it as a transfer.\\n            IERC20(tokenOut).safeTransfer(msg.sender, amountOut);\\n        } \\n    } \\n```\\n