Link to Affected Code:
https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/GaugeController.sol#L190-L203
function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
userGaugeVotes[msg.sender][gauge] = weight;
_updateGaugeWeight(gauge, oldWeight, weight, votingPower);
emit WeightUpdated(gauge, oldWeight, weight);
}
Description:
The GaugeController contract defines a VOTE_DELAY
constant of 10 days and the doc state and maintains a lastVoteTime
mapping to track user votes, but fails to enforce this delay in the vote()
function. This allows users to vote multiple times in rapid succession, bypassing the intended 10-day cooldown period.
The docs stated Minimum Vote Delay is 10 used for manipulation preventions
Impact:
Users can vote multiple times within the 10-day period
Gauge weights can be manipulated through rapid voting
Protocol's vote delay mechanism is completely bypassed
Breaks core voting mechanics and fairness guarantees
Proof of Concept:
function attackVoting() external {
gaugeController.vote(targetGauge, 5000);
gaugeController.vote(targetGauge, 7500);
gaugeController.vote(targetGauge, 10000);
}
Recommended Mitigation:
Add vote delay enforcement to the vote() function:
function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
if (block.timestamp < lastVoteTime[msg.sender] + VOTE_DELAY) {
revert VotingDelayNotElapsed();
}
uint256 votingPower = veRAACToken.balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
uint256 oldWeight = userGaugeVotes[msg.sender][gauge];
userGaugeVotes[msg.sender][gauge] = weight;
lastVoteTime[msg.sender] = block.timestamp;
_updateGaugeWeight(gauge, oldWeight, weight, votingPower);
emit WeightUpdated(gauge, oldWeight, weight);
}
error VotingDelayNotElapsed();