# Inconsistent liquidation predicate across code paths

Compare every function that decides liquidatability, caps, or bands; diff operators, boundaries, and which paths enforce them.

_Area: DeFi oracles, liquidations and flash loans. Seen in 3 disclosed reports; latest 2024-02-22. Updated 2026-10-25._

## How it works
A position's health is defined in more than one place, and the definitions disagree. One function opens or guards a position under a bound (max debt per collateral, a price band, a safety ratio); another function decides whether it can be liquidated. When the two encode the condition differently — different comparison operator, different boundary, or a check present on one path and absent on the other — a position can be unsafe by one definition while the liquidation path treats it as safe. The invariant that should hold is: the predicate that permits liquidation is the same predicate, with the same boundary, that defines when a position is unsafe.

## Where it shows up
- A bound enforced at open (`_borrow` max borrow, `_checkPriceBand`) with no equivalent check in the liquidation function [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-07.md)[E2](https://bugbountylibrary.com/r/sherlock%3A2024-02-perpetual%3AM-5.md)
- Liquidation gated only on events (deprecation, missed repayment deadline) rather than on current health [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-07.md)
- Interest accrual or quote drift that can push a value past the open-time bound after opening [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-07.md)[E2](https://bugbountylibrary.com/r/sherlock%3A2024-02-perpetual%3AM-5.md)
- Two functions computing the same health condition with different operators (`<` vs `> 1e18`) [E3](https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-09.md)
- A settlement/price check applied in one settlement path but not in `liquidate()` [E2](https://bugbountylibrary.com/r/sherlock%3A2024-02-perpetual%3AM-5.md)

## How to review
List every function that answers "is this position liquidatable?" or "is this position within bounds?" and diff them line by line: same comparison operator, same equality handling at the exact threshold, same inputs (including accrued interest and current quote, not just stored values). Confirm the liquidation path re-derives health from current state rather than from open-time or event-based conditions. For any bound enforced at open, ask whether later accrual or price movement can breach it and whether that breach makes the position callable.

## Variants seen
- Open-time max-debt bound breachable by interest, with liquidation only on deprecation/deadline [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-07.md)
- Price band enforced on order settlement but not on liquidation, while the maker quote can drift outside the band [E2](https://bugbountylibrary.com/r/sherlock%3A2024-02-perpetual%3AM-5.md)
- Off-by-one at the exact safety threshold between `canLiquidate()` and `maxLiquidatableDebt()` [E3](https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-09.md)

## Impact and severity
Outcomes ranged from unliquidatable positions accruing bad debt [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-07.md), to forced liquidations users cannot avoid [E2](https://bugbountylibrary.com/r/sherlock%3A2024-02-perpetual%3AM-5.md), to boundary positions reported safe yet liquidatable [E3](https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-09.md). Severity rises when the gap is reachable through normal accrual or market drift rather than an edge case, and when no alternate path can close the position.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-07.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2024-02-perpetual%3AM-5.md
- E3: https://bugbountylibrary.com/r/c4%3A2023-03-polynomial%3AM-09.md
