# Hack Analysis: Omni Protocol, July 2022

Omni NFT money market lacked reentrancy guards; safeTransferFrom hooks in withdrawERC721 and liquidationERC721 let attacker double-reenter, borrow against collateral, then withdraw it while debt was forgotten, stealing $1.4M.

- Payout: Real-world hack
- Severity: critical
- Program: Omni Protocol Pool
- Credited: Immunefi
- Disclosed: 2022-12-20
- Write-up quality: 9/10
- Original: https://immunefi.com/blog/bug-fix-reviews/hack-analysis-omni-protocol-july-2022/
- Library page: https://bugbountylibrary.com/r/blog%3Aimmunefi-com-blog-bug-fix-reviews-hack-analysis-omni-protocol-july-2022
- Tags: domain:web3 impact:funds-loss kind:incident platform:immunefi sev:critical tech:evm tech:solidity technique:chained technique:source-review vuln:flash-loan vuln:reentrancy

**Target:** Omni Pool smart contract (executeWithdrawERC721 / executeERC721LiquidationCall)
**Root cause:** executeWithdrawERC721 and executeERC721LiquidationCall call NToken.burn (which invokes ERC721 safeTransferFrom to the receiver) before setting userConfig.setUsingAsCollateral(reserve.id, false), violating checks-effects-interactions and lacking reentrancy guards.
**Steps:**
1. Flashloan 1000 WETH from Balancer; in receiveFlashLoan flashloan 4 vTokens from Doodles NFTX vault.
2. Swap ~0.3 vTokens worth of WETH on SushiSwap, redeem 4 Doodles NFTs from NFTX vault.
3. Deploy DebtTaker; transfer 4 NFTs to it; DebtTaker supplies 3 NFTs as collateral via `supplyERC721` and borrows max WETH via `borrow`.
4. DebtTaker calls `withdrawERC721(doodles, [id1,id2], liquidator)`; on second NFT's `onERC721Received`, Liquidator reenters `liquidationERC721(doodles, WETH, debtTaker, id3, 100 ether, false)`.
5. Inside liquidation's safeTransferFrom callback, Liquidator sends 3 NFTs back to DebtTaker, which calls `borrowALot()`: supplies all 4 NFTs as collateral and borrows max WETH again.
6. Liquidation finishes setting collateral config false; withdrawERC721 finishes without debt check; DebtTaker calls `withdrawERC721` for all tokenIds to Liquidator and transfers WETH out.
7. Repay NFTX flashloan by minting vTokens back; repay 1000 WETH to Balancer; keep profit.
**How found:** Code review of Pool contract: noticed NToken.burn calls ERC721 safeTransferFrom (external call to receiver) before userConfig.setUsingAsCollateral(false), and that both withdrawERC721 and liquidationERC721 share this ordering, suggesting a double-reentrancy where the market forgets collateral while debt remains.
**Exploitation:** Attacker flashloaned WETH (Balancer) and Doodles vTokens (NFTX), redeemed real Doodles NFTs, supplied them as collateral to Omni to borrow WETH, then used the ERC721 safeTransferFrom callback during withdrawERC721 to trigger a liquidation whose own safeTransferFrom callback re-supplied collateral and borrowed again; both functions then set the collateral-usage flag false, so the final withdrawERC721 skipped the debt check and let the attacker walk away with all NFTs plus borrowed WETH.
**Escalation:** Chained two separate reentrancy points (withdraw and liquidation) via a state-machine onERC721Received handler and two contracts (Liquidator/DebtTaker); scaled by flashloaning 20 Doodles in the real attack to borrow against maximum collateral.
**Insight:** ERC721/ERC1155 safeTransferFrom and burn-to-receiver paths hand execution to attacker contracts mid-function; look for state flags (like useAsCollateral) written after the transfer, and check whether a second function (liquidation) shares the same ordering so two hooks can be chained to erase debt accounting.
