Vulnerability: Composing approval with other messages is subject to DoS
Severity: medium

Description:
`TOFT::sendPacket` function allows the caller to specify multiple messages that are executed on the destination chain. On the receiving side the `lzCompose` function in `TOFT` contract can be DoS-ed by front-running the approval message and causing the `lzCompose` to revert. As `lzCompose` is supposed to process several messages, this results in lost fee paid on the sending chain for executing the subsequent messages and any value or gas airdropped to the contract.\\n`TOFT::sendPacket` allows the caller to specify arbitrary `_composeMsg`. It can be a single message or multiple composed messages.\\n```\\n    function sendPacket(LZSendParam calldata _lzSendParam, bytes calldata _composeMsg)\\n        public\\n        payable\\n        whenNotPaused // @audit Pausing is not implemented yet.\\n        returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt)\\n    {\\n        (msgReceipt, oftReceipt) = abi.decode(\\n            _executeModule(\\n                uint8(ITOFT.Module.TOFTSender),\\n               abi.encodeCall(TapiocaOmnichainSender.sendPacket, (_lzSendParam, _composeMsg)),\\n                false\\n            ),\\n            (MessagingReceipt, OFTReceipt)\\n        );\\n    }\\n```\\n\\nIf we observe the logic inside the lzCompose:\\n```\\n    function _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\\nAt the beginning of the function bytes memory `tapComposeMsg_` is the message being processed, while `bytes memory nextMsg_` are all the other messages. `lzCompose` will process all the messages until `nextMsg_` is empty.\\nA user might want to have his first message to grant approval, e.g. `_extExec` function call, while his second message might execute `BaseTOFTReceiver::_toeComposeReceiver` with `_msgType == MSG_YB_SEND_SGL_BORROW`.\\nThis is a problem as there is a clear DoS attack vector on granting any approvals. A griever can observe the permit message from the user and front-run the `lzCompose` call and submit the approval on the user's behalf.\\nAs permits use nonce it can't be replayed, which means if anyone front-runs the permit, the original permit will revert. This means that `lzCompose` always reverts and all the gas and value to process the `BaseTOFTReceiver::_toeComposeReceiver` with `_msgType == MSG_YB_SEND_SGL_BORROW` is lost for the user.

Impact:
When user is granting approvals and wants to execute any other message in the same `lzCompose` call, the attacker can deny the user from executing the other message by front-running the approval message and causing the `lzCompose` to revert. The impact is lost fee paid on the sending chain for executing the subsequent messages and any value or gas airdropped to the contract. This is especially severe when the user wants to withdraw funds to another chain, as he needs to pay for that fee on the sending chain.

Recommended mitigation:
`TOFT::sendPacket` should do extra checks to ensure if the message contains approvals, it should not allow packing several messages.