Orvex ·Robinhood Chain
VoterV5 stale reward-index lets a late voter capture other gauges' skipped-epoch rewards
Verified with working PoC
Contract: 0xEacfE55197F35B03B40DCD5af5919eEF7cf1c3Bd
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