# Partial close strands residual collateral

Close/settle paths that zero one dimension while residual collateral or debt stays unaccounted

_Area: DeFi accounting and math bugs. Seen in 2 disclosed reports; latest 2023-08-01. Updated 2026-10-25._

## How it works
A position has two dimensions: debt (or short size) and collateral. Close, repay, and settle paths often gate their safety checks and their bookkeeping on one dimension. When that dimension reaches zero, the check becomes vacuous and the bookkeeping branch is skipped, so the other dimension is left stranded: collateral that is no longer attached to any live position, or debt that no longer has collateral behind it. The invariant that breaks is that every unit of collateral and debt must remain owned by exactly one live account or be explicitly returned.

## Where it shows up
- Close/settle functions where a `min`/health check scales with the dimension being zeroed (e.g. `minCollateral` derived from `totalShortAmount`) [E3](https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-17.md).
- Bookkeeping (rebalancing, re-registration) gated on `debtRemaining == 0` while collateral can change with debt still outstanding [E4](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AM-11.md).
- Partial fills that split a quote: new quote re-registered for one party but not the other, and locked balances adjusted asymmetrically [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AH-6.md).
- Separate debt buckets (e.g. earmarked debt) mutated in a different order than the debt they gate [E2](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.16.md).
- Any path that burns/zeroes a position token while a balance field is still non-zero.

## How to review
For each close/repay/settle/partial-fill path, enumerate every per-party accounting structure (pending lists, locked balances, collateral ids, debt buckets) and confirm each is updated symmetrically for both parties and both dimensions. Check the boundary case where one dimension hits zero: is the remaining dimension's check still meaningful, and is residual value returned or re-registered? The property to hold: after any close/settle, `sum(collateral held) == sum(collateral owed to live positions)` and no locked/debt field is left inflated or orphaned.

## Variants seen
- Partial fill inflates PartyB pending locked balance, locking funds [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AH-6.md).
- Repay drains earmarked debt first, skewing redemption and making CDPs uncloseable [E2](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.16.md).
- Full short close with zero collateral amount burns NFT, stranding collateral [E3](https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-17.md).
- Settle skips collateral rebalancing when debt remains, locking ids from lenders [E4](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AM-11.md).

## Impact and severity
Funds permanently locked or positions left undercollateralized and unliquidatable [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AH-6.md)[E2](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.16.md)[E3](https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-17.md)[E4](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AM-11.md). Severity is high because the stranded value is unrecoverable without upgrade; worse when the vacuous check also removes the liquidation path.

## Why fixes fail
Gating bookkeeping on full debt clearance rather than on the collateral change itself leaves the partial case broken [E4](https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AM-11.md); fixes must key on the actual mutation, not on a proxy condition.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AH-6.md
- E2: https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.16.md
- E3: https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-17.md
- E4: https://bugbountylibrary.com/r/sherlock%3A2023-01-ajna%3AM-11.md
