All findings

Orvex ·Robinhood Chain

VoterV5 stale reward-index lets a late voter capture other gauges' skipped-epoch rewards

High

Verified with working PoC

Contract: 0xEacfE55197F35B03B40DCD5af5919eEF7cf1c3Bd

accountingreward indexgovernancevalue extraction

Summary

VoterV5._vote()/_reset() never refresh a gauge's reward-index checkpoint - unlike real Velodrome, which updates it on every vote action. The checkpoint only advances via distribute()/killGauge(), which is permissionless and unenforced per-epoch, so a gauge that goes unvoted for N epochs then receives even minimal weight captures the full N-epoch accumulated index delta.

Root cause

supplyIndex[gauge] stays frozen at its last distribute() call while the global index keeps advancing every epoch regardless of that gauge's participation - multiplying the full stale delta by only the most recent epoch's vote weight on the next distribute().

Verification

Fork PoC against live VoterV5 state: a gauge with zero vote weight for 9 real epochs, then 0.1% of total weight in the 10th, captured 10x its fair one-epoch entitlement when distribute() was finally called - quantified end to end, not just a broken-invariant claim.

Verified against real, live deployed contract state.

Receipt on GitHub