# User rewards stop accruing after any _writeCheckpoint calling action

Off-by-one index in Gauge._writeCheckpoint copies voted=false into every new checkpoint, so any deposit/withdraw/getReward halts a voted user's reward accrual until next vote.

- Payout: Contest reward
- Severity: high
- Program: Velodrome Finance
- Credited: hyh
- Disclosed: 2022-08-08
- Write-up quality: 7/10
- Original: https://github.com/code-423n4/2022-05-velodrome-findings/issues/59
- Library page: https://bugbountylibrary.com/r/c4%3A2022-05-velodrome%3AH-03
- Tags: domain:web3 impact:funds-loss kind:contest platform:code4rena sev:high tech:solidity technique:source-review vuln:accounting-error vuln:reward-distribution

**Target:** Gauge.sol reward checkpointing contract
**Root cause:** _writeCheckpoint reads checkpoints[account][_nCheckPoints] (the not-yet-created slot) instead of [_nCheckPoints-1], so prevVoteStatus is always false; earned() only accrues for checkpoints with voted==true.
**Steps:**
1. User votes, setting voted=true on their latest checkpoint.
2. User calls deposit/withdraw/withdrawToken/getReward, invoking _writeCheckpoint.
3. New checkpoint is written with voted=false due to `checkpoints[account][_nCheckPoints].voted` reading an empty slot.
4. earned() skips all checkpoints with voted==false, so rewards stop accruing until the user votes again.
5. Fix: read `checkpoints[account][_nCheckPoints - 1].voted`.
**How found:** Code review of _writeCheckpoint's checkpoint-copy logic; noticed the mapping index used the new (empty) slot rather than the previous checkpoint, then traced earned()'s dependence on the voted flag.
**Escalation:** Systematic across all gauges and all users: every balance-changing action by any voted user silently zeroes future reward accrual.
**Insight:** When copying state forward into a new array/mapping slot, verify the source index is the last written entry (n-1), not the slot being created; mappings silently return defaults instead of reverting, masking the off-by-one.
