# Recursive _lzCompose() call can be leveraged to steal all generated USDO fees

Recursive _lzCompose hardcodes address(this) as srcChainSender, letting attacker remote-transfer-burn USDO fees and mint them to themselves cross-chain.

- Payout: Contest reward
- Severity: high
- Program: Tapioca
- Credited: 0xadrii
- Disclosed: 2024-02-14
- Write-up quality: 8/10
- Original: https://github.com/sherlock-audit/2024-02-tapioca-judging/issues/113
- Library page: https://bugbountylibrary.com/r/sherlock%3A2024-02-tapioca%3AH-11
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:chained technique:source-review vuln:access-control vuln:business-logic vuln:fee-bypass

**Target:** USDO omnichain receiver / _lzCompose compose handling
**Root cause:** When a compose message carries a nextMsg, _lzCompose recurses with srcChainSender hardcoded to address(this) (the USDO contract) instead of the real sender. In the recursive remote-transfer, owner==srcChainSender==USDO, so the allowance check in _internalTransferWithAllowance is skipped and USDO's own balance is burned.
**Steps:**
1. Craft initial A->B sendPacket with a compose msg whose nextMsg is a PT_REMOTE_TRANSFER with owner=address(bUsdo) and lzSendParam.to=attacker on chain A, amountLD=desired fees
2. Trigger lzReceive on chain B so the first compose executes (e.g. remote transfer of 0 amount)
3. lzCompose recurses into nextMsg with srcChainSender=address(bUsdo); _remoteTransferReceiver sees owner==srcChainSender, skips _spendAllowance, burns amountLD from bUsdo
4. The remote transfer sends a packet back to chain A; call lzReceive on aUsdo to mint the burned amount to attacker
5. Assert bUsdo.balanceOf(bUsdo)==0 and aUsdo.balanceOf(attacker)==100 ether
**How found:** Code review of _lzCompose recursion: noticed the recursive call passes address(this) as srcChainSender, then traced how _remoteTransferReceiver/_internalTransferWithAllowance treats owner==srcChainSender as an implicit self-approval, enabling burning the contract's own balance.
**Exploitation:** Attacker needs no capital or allowance: they only submit a cross-chain compose message with a nested nextMsg. The recursive call makes USDO the srcChainSender, so USDO's own fee balance is burned on chain B and re-minted to the attacker on chain A, extracting all accumulated flash-loan fees.
**Insight:** When a function recurses or re-enters with a synthesized caller identity, check whether downstream authorization compares caller to a user-supplied field (owner==srcChainSender) — a spoofed caller turns that equality check into a free self-approval.
