# Stale-state liquidation eligibility checks

Health or solvency checked before collateral retrieval, price refresh, or locked-value rescale in the same flow

_Area: DeFi oracles, liquidations and flash loans. Seen in 3 disclosed reports; latest 2025-08-11. Updated 2026-10-25._

## How it works
Liquidation and solvency logic assumes the state it reads is the state that will hold when the action completes. When eligibility is computed before collateral is retrieved, prices refreshed, or locked values rescaled, the check validates a stale snapshot. The invariant "the account is unhealthy under the same state the liquidation executes against" silently breaks, so healthy accounts become liquidatable, or the same unhealthy state can be re-processed for extra payouts.

## Where it shows up
- A `require(isLiquidatable(...))` or solvency check placed before a `_retrieveCollateral()`, price-sync, or state-rescale call in the same function [E2](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.6.md)[E3](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-12.md)
- Guard blocks that pin only some fields (e.g. upnl, totalUnrealizedLoss) while leaving prices or position counts mutable across partial steps [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-21.md)
- Functions that validate health and then mutate collateral/locked-value state afterward [E3](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-12.md)
- Accepted-risk notes saying "changes tracked only on retrieval" — audit every read of that value for ordering [E2](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.6.md)

## How to review
Trace the order of operations in the liquidation/solvency path. The property that must hold: every value feeding the eligibility decision is read after all retrieval, price refresh, and rescaling that the same transaction performs, and no unguarded field can change between partial steps. For setters guarded by equality on a subset of fields, confirm the unguarded fields cannot be refreshed mid-flow [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-21.md).

## Variants seen
- Partial-liquidation re-entry refreshing symbol prices between steps to double-collect CVA [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-21.md)
- Eligibility check before collateral retrieval, liquidating healthy positions [E2](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.6.md)
- Solvency check before locked-value rescale, enabling immediate post-open liquidation [E3](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-12.md)

## Why fixes fail
Guarding only a subset of fields (upnl, totalUnrealizedLoss) leaves prices mutable, so the same check passes again after a refresh [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-21.md).

## Impact and severity
Protocol funds drained via repeated CVA collection [E1](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-21.md), wrongful liquidation of healthy positions [E2](https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.6.md), and users losing CVA plus liquidation fee right after opening [E3](https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-12.md). Severity rises when the stale read is reachable repeatedly within one flow, amplifying a single stale check into a larger loss.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-21.md
- E2: https://bugbountylibrary.com/r/cantina%3Ajigsaw_june2025%3A3.1.6.md
- E3: https://bugbountylibrary.com/r/sherlock%3A2023-06-symmetrical%3AM-12.md
