# RoyalRoyalties: zero-amount ERC1155 batch inflates tier balance

Zero-amount ERC1155 batch transfers inflated a receiver's tier balance 100x, letting attacker claim 100x deposit from Royalties and drain 261k USDC.

- Payout: Real-world hack
- Severity: critical
- Program: RoyalRoyalties
- Credited: DeFiHackLabs
- Disclosed: 2026-06-23
- Write-up quality: 8/10
- Original: https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2026-06/RoyalRoyalties_exp.sol
- Library page: https://bugbountylibrary.com/r/dhl%3A20260623-royalroyalties
- Tags: domain:web3 impact:funds-loss kind:incident platform:defihacklabs sev:critical tech:evm tech:solidity technique:chained technique:source-review vuln:accounting-error vuln:business-logic vuln:flash-loan

**Target:** Royal1155LDA ERC1155 contract and Royalties deposit/claim contract on Polygon
**Root cause:** Royal1155LDA increments per-tier balance bookkeeping for every ERC1155 batch item even when amounts[i]==0, and owned-token backfill was incomplete, so a zero-balance sender can inflate a receiver's tierBalanceOf. Royalties then uses that inflated balance for pro-rata claim calculation against a tier supply of 1.
**Steps:**
1. Flash-borrow USDC from QuickSwap V2 pair via `swap(0, borrowAmount, attacker, data)`.
2. Call `safeBatchTransferFrom(attacker, receiver, ids[100], amounts[100]=0, "")` on Royal1155LDA with ids = `(42<<128)|424242` to inflate receiver tierBalanceOf to 100.
3. Approve Royalties and call `deposit(attacker, 42, borrowAmount)` with borrowed USDC.
4. Call `claim(receiver, [42], attacker)` from receiver; claimed = borrowAmount * 100.
5. Repay QuickSwap flash swap `(borrowAmount*1000)/997 + 1` and keep profit.
**How found:** Researcher likely noticed that Royal1155LDA's batch transfer hook updates tier balance bookkeeping unconditionally, and that getIsOwnedTokensBackfillComplete() was false, meaning zero-balance senders could still trigger balance increments. Testing zero-amount batch transfers confirmed the inflation.
**Exploitation:** Attacker flash-borrows USDC sized as royaltyFloat/99, inflates a fresh receiver's tier balance to 100 via 100 zero-amount ERC1155 batch transfers, deposits the borrowed USDC into tier 42, then claims through the inflated receiver. The claim pays 100x the deposit, funded by pre-existing royalties in the contract, netting 261,162.93 USDC after repaying the flash loan.
**Insight:** When a contract tracks per-tier or per-user balances in a transfer hook, check whether zero-amount transfers still mutate state; combined with an incomplete backfill flag, this lets anyone mint apparent ownership without holding tokens. Also check pro-rata claim math against total supply for mismatch.
