# Vote power accounting desync from lock state

Accumulators re-derived from cached timestamps or caller-supplied splits that diverge from live lock state

_Area: Smart contract access control and control flow. Seen in 2 disclosed reports; latest 2024-03-30. Updated 2026-10-23._

## How it works
Vote and lock accounting assumes the cached timestamp and the live clock agree, and that every path enforcing "is this lock active" uses the same cutoff. When an accumulator re-derives totals from scratch each call, a guard on a cached timestamp can silently skip the summation, leaving the total at its initial zero while sibling mappings still carry prior values. Separately, when the same conceptual invariant (lock eligibility) is checked in several places with different reference timestamps or comparison operators, boundary cases pass one check and fail another, so votes are counted for locks the guard considers inactive, or vice versa.

## Where it shows up
- Accumulator functions that reset a total to zero and re-sum in a loop, guarded by a cached `lastUpdate`/timestamp comparison.
- Multiple conditionals in one function testing `unlockTime` against different references (`block.timestamp` vs `nextTimestamp`) or with `>` vs `>=`.
- Claim/mint functions accepting a caller-supplied array that determines how a balance is split into tokens.
- Functions callable for another user without a `msg.sender == owner` restriction.

## How to review
For each accumulator, confirm the guard on the cached timestamp cannot skip summation when the cache is ahead of the clock; the invariant is that the re-derived total equals the sum of the per-entity mappings for all live state. Diff every lock-eligibility conditional in a function: same reference timestamp, same operator, so a lock is either active in all checks or none. For claim/mint, check whether array length and per-element sizing are attacker-controlled and whether the caller must be the beneficiary.

## Variants seen
- Future `lastUpdate` skips the summation loop, returning 0 and undercounting totalVotes after new gauges are added [E1](https://bugbountylibrary.com/r/cantina%3Aopal_feb2024%3A3.2.14.md).
- Locks unlocking exactly at `nextTimestamp` pass the NoActiveLocks guard but are excluded from vote summation and unlock tracking [E3](https://bugbountylibrary.com/r/cantina%3Aopal_feb2024%3A3.3.14.md).
- Anyone can call `claimMultiple` for a contributor with a caller-chosen split, fragmenting voting power into many low-power NFTs and inflating future gas costs [E2](https://bugbountylibrary.com/r/c4%3A2023-04-party%3AM-04.md).

## Impact and severity
Undercounted or corrupted vote totals and griefing of large depositors; severity depends on whether the desync is reachable by unprivileged callers and whether it persists across calls. Worse when the divergence is silent (no revert) and when boundary timestamps are attacker-influenced.

## Evidence
- E1: https://bugbountylibrary.com/r/cantina%3Aopal_feb2024%3A3.2.14.md
- E2: https://bugbountylibrary.com/r/c4%3A2023-04-party%3AM-04.md
- E3: https://bugbountylibrary.com/r/cantina%3Aopal_feb2024%3A3.3.14.md
