# Last collateral check is not safe

purchaseLiquidationAuctionNFT infers last-collateral from cached oracle value; a zero oracle price zeroes remaining debt, letting owner withdraw other NFTs free.

- Payout: Contest reward
- Severity: medium
- Program: Backed Protocol
- Credited: hansfriese
- Disclosed: 2023-01-31
- Write-up quality: 6/10
- Original: https://github.com/code-423n4/2022-12-backed-findings/issues/216
- Library page: https://bugbountylibrary.com/r/c4%3A2022-12-backed%3AM-07
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:medium tech:solidity technique:source-review vuln:accounting-error vuln:liquidation vuln:oracle-manipulation

**Target:** PaprController.purchaseLiquidationAuctionNFT liquidation auction
**Root cause:** isLastCollateral is derived from collateralValueCached == 0 (oracle price * count) instead of the actual NFT count, so a zero oracle return is misread as last collateral.
**Steps:**
1. Vault owner holds multiple NFTs of same contract with debt.
2. Reservoir oracle returns 0 for the collection (e.g. 0x495f947276749Ce646f68AC8c248420045cb7b5e).
3. Liquidator calls `purchaseLiquidationAuctionNFT(auction, maxPrice, sendTo, oracleInfo)`; collateralValueCached = 0, isLastCollateral = true.
4. remaining debt is set to 0 via `_reduceDebtWithoutBurn`.
5. Owner withdraws remaining NFTs for free.
**How found:** Code review of purchaseLiquidationAuctionNFT noticed isLastCollateral derived from cached oracle value rather than count; verified via Reservoir test suite that oracle can return zero.
**Exploitation:** Trigger a liquidation auction purchase while oracle price is zero; protocol zeroes the vault debt, allowing owner to withdraw remaining collateral unbacked.
**Insight:** When a protocol infers state (e.g. 'last collateral') from an external oracle-derived value, check whether the oracle can return zero/empty and whether that collapses the state check.
