# Counter never incremented or reset

Guards keyed on a counter that is never incremented, reset, or initialized correctly

_Area: DeFi accounting and math bugs. Seen in 8 disclosed reports; latest 2024-09-13. Updated 2026-10-23._

## How it works
A cap, index, or "already set" guard reads a stored counter, but the counter's lifecycle is broken: it is never incremented after the action it should track, it is reset to zero after state already exists in the new epoch, or it starts at a value that collides with a default. The guard then always passes, always fails, or points at the wrong entry, so the limit it was meant to enforce does not hold.

## Where it shows up
- A limit check reads `x.counter` but no code path writes `x.counter` after the limited action [E2](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-1.md).
- Epoch/generation rollover sets a count to 0 while items can already be written into the new epoch [E1](https://bugbountylibrary.com/r/c4%3A2024-07-traitforge%3AH-04.md).
- A mapping index starts at 0, which is also the default for "unset" [E4](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-2.md).
- An "already initialized" guard keys on a counter that only advances on a later lifecycle event [E5](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AM-3.md).
- A shared aggregate counter is decremented per-item while individual items can be zeroed independently [E3](https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AM-01.md).

## How to review
For every guard, find the counter it reads and trace every write. The invariant: after each action the guard limits, the counter must reflect that action, and its initial/reset value must be distinguishable from any valid state. Check:
- Does every path that should increment the counter actually write it? [E2](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-1.md)
- On rollover, can state enter the new epoch before the reset fires? [E1](https://bugbountylibrary.com/r/c4%3A2024-07-traitforge%3AH-04.md)
- Is the initial index distinct from the default/unset value? [E4](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-2.md)
- Does the "set" flag change on the same event that sets the state, not a later one? [E5](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AM-3.md)
- Can an external actor move the aggregate out of step with per-item values? [E3](https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AM-01.md)

## Variants seen
- Missing increment: cap check never trips, unlimited votes [E2](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-1.md).
- Reset drops pre-existing entries: per-epoch cap surpassed [E1](https://bugbountylibrary.com/r/c4%3A2024-07-traitforge%3AH-04.md).
- Zero-index collision: only first entry recorded, others read as first [E4](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-2.md).
- Guard keyed on late-advancing counter: state set repeatedly [E5](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AM-3.md).
- Aggregate underflow from independent per-item zeroing [E3](https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AM-01.md).

## Impact and severity
Caps bypassed (unlimited voting, over-cap minting), records lost or overwritten, and markets DOSed via underflow [E3](https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AM-01.md). Severity is high where the broken guard protects a hard supply or voting limit [E1](https://bugbountylibrary.com/r/c4%3A2024-07-traitforge%3AH-04.md)[E2](https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-1.md); worse when the counter is shared across users so one actor's action corrupts global accounting.

## Evidence
- E1: https://bugbountylibrary.com/r/c4%3A2024-07-traitforge%3AH-04.md
- E2: https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-1.md
- E3: https://bugbountylibrary.com/r/c4%3A2022-01-insure%3AM-01.md
- E4: https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-2.md
- E5: https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AM-3.md
