# Cap check ignores cumulative state

Caps compared to per-call or per-caller values instead of global accumulated totals or prior usage

_Area: DeFi accounting and math bugs. Seen in 3 disclosed reports; latest 2024-08-07. Updated 2026-10-25._

## How it works
A cap is meant to bound a global quantity, but the check compares against a local or post-decrement value. The invariant "total usage <= cap" is never actually enforced because the code measures the wrong side of the comparison. Either the check uses a per-caller balance instead of the target's total [E2](https://bugbountylibrary.com/r/sherlock%3A2024-08-sentiment-v2%3AM-8.md), applies a tolerance multiplier to a post-decrement value against a pre-decrement ceiling [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-19.md), or omits the cap comparison entirely before accumulating [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AM-9.md). In each case the arithmetic looks plausible locally but the global bound silently fails.

## Where it shows up
- A cap check that reads `getAssetsOf(id, address(this))` or a caller-specific balance rather than the target's total assets [E2](https://bugbountylibrary.com/r/sherlock%3A2024-08-sentiment-v2%3AM-8.md)
- A tolerance/factor (e.g. 1.2x) applied to a value after decrement, compared to a ceiling derived before decrement [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-19.md)
- An accumulator (`AddTokensMinted`) called without first comparing against `Remaining` [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AM-9.md)
- Subtraction of minted amount from a cap that can go negative
- Router/aggregator enforcing its own per-target cap while the target has its own global cap

## How to review
Identify every cap and the quantity it bounds. The check must compare the *global accumulated total* (or remaining capacity) against the requested amount, not a per-caller or post-decrement proxy. For tolerance-based ceilings, verify the invariant holds for the worst-case delta: solve `tolerance*(W-d) <= W` and confirm no repeatable partial exit passes. For accumulators, confirm the cap comparison precedes the mutation and that remaining capacity cannot go negative. Trace all writers to the capped resource, including direct depositors bypassing the router [E2](https://bugbountylibrary.com/r/sherlock%3A2024-08-sentiment-v2%3AM-8.md).

## Variants seen
- Per-caller balance vs target global cap in a deposit router [E2](https://bugbountylibrary.com/r/sherlock%3A2024-08-sentiment-v2%3AM-8.md)
- Tolerance multiplier on post-decrement weight vs pre-decrement ceiling, enabling repeatable partial unstake [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-19.md)
- Missing cap comparison before mint accumulation, causing negative remaining and block abort [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AM-9.md)

## Impact and severity
Led to bypass of slash-protection (over 90% unstakeable) [E1](https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-19.md), deposits reverting and free capacity skipped [E2](https://bugbountylibrary.com/r/sherlock%3A2024-08-sentiment-v2%3AM-8.md), and a deterministic per-epoch block abort compromising one block per month [E3](https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AM-9.md). Severity rises when the cap protects a safety invariant (slashing, supply) rather than mere liveness, and when the bypass is repeatable.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2023-12-ethereumcreditguild%3AM-19.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2024-08-sentiment-v2%3AM-8.md
- E3: https://bugbountylibrary.com/r/sherlock%3A2024-06-allora%3AM-9.md
