Summary
GaugeController::vote
function is missing implementation of the intended vote delay mechanism, allowing users to vote multiple times in rapid succession and potentially manipulate gauge weights.
Vulnerability Details
[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/GaugeController.sol#L190-L200)
[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/GaugeController.sol#L84-L88)
[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/GaugeController.sol#L82)
In GaugeController
, there are constants defined for vote delay restrictions:
uint256 public constant VOTE_DELAY = 10 days;
uint256 public constant MIN_VOTE_DELAY = 1 days;
uint256 public constant MAX_VOTE_DELAY = 10 days;
However, GaugeController::vote
function neither checks nor updates the lastVoteTime mapping:
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);
....
Impact
-
Users can rapidly change their votes to manipulate gauge weights in a short timeframe with no cooldown, despite the protocol clearly intending a delay between votes (as shown by the VOTE_DELAY
and MIN_VOTE_DELAY
constants)
-
This breaks core governance assumptions where users should be locked into their voting decisions for a minimum time period
-
The system's intended vote delay mechanism (MIN_VOTE_DELAY
= 1 days, VOTE_DELAY
= 10 days) is completely unused, allowing vote changes that should be rate-limited
Tools Used
Foundry
Recommendations
In GaugeController::vote
add the following with whatever vote delay you want:
function vote(address gauge, uint256 weight) external override whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (weight > WEIGHT_PRECISION) revert InvalidWeight();
+ // Ensure minimum delay between votes has passed
+ if(block.timestamp < lastVoteTime[msg.sender] + MIN_VOTE_DELAY) revert VotingDelayNotMet();
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);
....