# Remaining collateral used by ERC721Pool is missed in Auctions take and bucketTake return structures

Ajna ERC721Pool take/bucketTake leave result.remainingCollateral uninitialized, so _rebalanceTokens moves all borrower NFT collateral into bucketTokenIds, permanently freezing it.

- Payout: Contest reward
- Severity: high
- Program: Ajna
- Credited: hyh
- Disclosed: 2022-12-21
- Write-up quality: 7/10
- Original: https://github.com/sherlock-audit/2023-01-ajna-judging/issues/103
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AH-8
- Tags: domain:web3 impact:dos impact:funds-loss kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** ERC721Pool take/bucketTake auction settlement in Ajna contracts
**Root cause:** Auctions.take() and bucketTake() never set result.remainingCollateral, so it defaults to zero; ERC721Pool._rebalanceTokens then computes noOfTokensToTransfer as the full pledged count and moves every borrower token id into bucketTokenIds.
**Steps:**
1. Borrower pledges ERC721 tokens in ERC721Pool and is liquidated into auction.
2. Call `ERC721Pool.take(borrower, collateral, callee, data)` or `bucketTake(borrower, depositTake, index)` such that the auction settles.
3. Auctions.take/bucketTake returns TakeResult/BucketTakeResult with remainingCollateral == 0 (never assigned).
4. ERC721Pool sees result.settledAuction and calls `_rebalanceTokens(borrower, 0)`.
5. In _rebalanceTokens, borrowerCollateral_ == 0 makes noOfTokensToTransfer = borrowerTokens.length, popping all ids into bucketTokenIds.
6. Borrower's collateral ids array is empty; any later operation returning collateral reverts, and LPs cannot withdraw the orphaned ids.
**How found:** Code review of ERC721Pool.take/bucketTake usage of result.remainingCollateral against Auctions.take/bucketTake return struct population; noticed the field is read but never assigned in the library functions.
**Exploitation:** Any taker settling an ERC721Pool auction via take() or bucketTake() triggers the bug automatically; no special capital needed beyond normal auction participation. The borrower's remaining NFT collateral is moved to bucketTokenIds without bucket accounting, permanently frozen.
**Insight:** When a library returns a struct consumed by a caller, audit every field the caller reads against every field the library actually assigns; uninitialized struct fields silently default to zero and can invert logic like 'transfer all' vs 'transfer remainder'.
