# `QVSimpleStrategy` never updates `allocator.voiceCredits`.

QVSimpleStrategy never increments allocator.voiceCredits, so the per-allocator credit cap check always passes and allocators can cast unlimited votes.

- Payout: Contest reward
- Severity: high
- Program: Gitcoin
- Credited: 0x00ffDa
- Disclosed: 2023-09-01
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-09-Gitcoin-judging/issues/150
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-09-Gitcoin%3AH-1
- Tags: domain:web3 impact:integrity kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** Allo v2 QVSimpleStrategy quadratic voting strategy contract
**Root cause:** _allocate checks _hasVoiceCreditsLeft against allocator.voiceCredits but never increments allocator.voiceCredits after allocation, so the stored value stays zero and the cap is never reached.
**Steps:**
1. Deploy/observe QVSimpleStrategy with maxVoiceCreditsPerAllocator set.
2. Register as allocator and call allocate() with voiceCreditsToAllocate up to the max.
3. Observe allocator.voiceCredits remains 0.
4. Call allocate() repeatedly with max credits each time; _hasVoiceCreditsLeft always returns true.
5. Accumulate unbounded voice credits and skew the quadratic vote tally.
**How found:** Code reading of _allocate and _hasVoiceCreditsLeft revealed the check reads allocator.voiceCredits but no code path writes it, making the cap ineffective.
**Exploitation:** A registered allocator repeatedly calls allocate() with voiceCreditsToAllocate equal to maxVoiceCreditsPerAllocator; since the stored credit counter is never updated, each call passes the check, letting the allocator dominate the QV tally and direct pool funding.
**Insight:** When a limit check reads a stored counter, verify every code path that should increment that counter actually does; a read-only check with no corresponding write is a silent cap bypass.
