Summary
The GaugeController
contract defines time control constants and state variables for vote delays but fails to implement these controls in the actual voting mechanism (GaugeController::vote
). This allows users to vote repeatedly without any time restrictions, leading to vote manipulation and reward distribution exploitation.
Vulnerability Details
@> mapping(address => uint256) public lastVoteTime;
@> uint256 public constant VOTE_DELAY = 10 days;
@> uint256 public constant MIN_VOTE_DELAY = 1 days;
@> uint256 public constant MAX_VOTE_DELAY = 10 days;
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);
}
Impact
The GaugeController::vote
fails to implement constant and variable controls (lastVoteTime
, VOTE_DELAY
, MIN_VOTE_DELAY
, MAX_VOTE_DELAY
) allowing users to vote repeatedly without any time restriction. This can lead users to vote multiple times in rapid succession (no cooldown period between votes). Allowing rapid weight changes to manipulate reward distribution and maximize rewards through rapid voting.
Tools Used
Manual review
Recommendations
Implement vote delay modifier and add it to the GaugeController::vote
function.
+ modifier enforceVoteDelay() {
+ require(
+ lastVoteTime[msg.sender] == 0 ||
+ block.timestamp >= lastVoteTime[msg.sender] + VOTE_DELAY,
+ "Vote delay not elapsed"
+ );
+ _;
+ lastVoteTime[msg.sender] = block.timestamp;
+}
- function vote(address gauge, uint256 weight) external override whenNotPaused {
+ function vote(address gauge, uint256 weight) external override whenNotPaused enforceVoteDelay{
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);
}