# Stale balance in share and health math

Share, NAV or health math reading a stored balance that accrual or burn paths never update

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2025-07-15. Updated 2026-11-01._

## How it works
Accounting math assumes the balance it reads is current. When a stored balance is only refreshed on a separate accrual, burn or redeem path, any code that reads it before that path runs computes shares, payouts or health from a number that no longer matches reality. The invariant that breaks: the balance used in the numerator/denominator must equal the balance the protocol would settle right now.

## Where it shows up
- Health or liquidation checks calling a `*Stored`/last-accrual getter instead of an interest-previewed one [E1](https://bugbountylibrary.com/r/sherlock%3A2023-10-aloe%3AH-3.md)
- Payout or share formulas using an initial pool balance that is never decremented after withdrawal [E2](https://bugbountylibrary.com/r/c4%3A2022-07-fractional%3AH-08.md)
- A burn or transfer helper that bypasses the canonical redeem path, skipping a global counter decrement [E3](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.11.md)
- Conditional branches where a direct token transfer flips which path executes [E3](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.11.md)
- Denominators (totalSupply, totalSyntheticsIssued) that shrink while the numerator stays fixed [E2](https://bugbountylibrary.com/r/c4%3A2022-07-fractional%3AH-08.md)[E3](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.11.md)

## How to review
For every stored balance, list all writers and all readers. Confirm each reader either triggers accrual first or uses a preview function. Check that every burn/mint/transfer path updates the same counters the math depends on. The property: for any user action, the balance feeding share, NAV or health math equals the value after all pending accrual and settlement. Trace whether an external transfer can change a branch condition without updating counters.

## Variants seen
- Stale borrow balance in health checks, enabling one-transaction bad debt [E1](https://bugbountylibrary.com/r/sherlock%3A2023-10-aloe%3AH-3.md)
- Constant numerator with shrinking denominator in cash-out payouts, draining Ether [E2](https://bugbountylibrary.com/r/c4%3A2022-07-fractional%3AH-08.md)
- Skipped counter decrement when a conditional redeem path is bypassed, inflating debt and cutting payouts [E3](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.11.md)

## Impact and severity
Led to bad debt creation in one transaction [E1](https://bugbountylibrary.com/r/sherlock%3A2023-10-aloe%3AH-3.md), full drain of contract Ether [E2](https://bugbountylibrary.com/r/c4%3A2022-07-fractional%3AH-08.md), and reduced payouts via inflated bad-debt ratio [E3](https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.11.md). Severity is high: errors compound across users, and single-transaction paths make them cheap to trigger. Worse when the stale value also gates a branch, so an attacker controls which path runs.

## Why fixes fail
Evidence does not show regressed fixes; the common incomplete fix is patching one reader while other paths still read the stale value or skip the counter update.

## Evidence
- E1: https://bugbountylibrary.com/r/sherlock%3A2023-10-aloe%3AH-3.md
- E2: https://bugbountylibrary.com/r/c4%3A2022-07-fractional%3AH-08.md
- E3: https://bugbountylibrary.com/r/cantina%3Aalchemix_may2025%3A3.1.11.md
