Summary
BaseGauge::voteDirection
lacks a minimum vote interval check, allowing users to spam votes continuously inflating totalVotes
. While vote timestamps are stored, they are never checked to prevent spam voting.
Vulnerability Details
[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/BaseGauge.sol#L407-L420)
Users can repeatedly call BaseGauge::voteDirection
without any cooldown period, despite the contract storing vote timestamps:
function voteDirection(uint256 direction) public whenNotPaused updateReward(msg.sender) {
totalVotes = processVote(
userVotes[msg.sender],
direction,
votingPower,
totalVotes
);
}
The processVote
function updates and stores the vote timestamp but never checks it:
[](https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/BaseGauge.sol#L443)
function processVote(...) internal returns (uint256) {
vote.timestamp = block.timestamp;
return newTotalVotes;
}
Impact
This creates several issues:
Users can spam votes to inflate totalVotes through repeated transactions
Potential vote manipulation through rapid voting
No protection against vote thrashing (rapidly changing directions)
Tools Used
Foundry
Recommendations
Create a Minimum Vote Interval Time:
uint256 public constant MIN_VOTE_INTERVAL = 1 days;
Add a minimum vote interval check inside of voteDirection
function voteDirection(uint256 direction) public whenNotPaused updateReward(msg.sender) {
if (direction > 10000) revert InvalidWeight();
+ // Prevent vote spam
+ if (block.timestamp - userVotes[msg.sender].timestamp < MIN_VOTE_INTERVAL) {
+ revert VoteTooFrequent();
+ }
uint256 votingPower = IERC20(IGaugeController(controller).veRAACToken()).balanceOf(msg.sender);
if (votingPower == 0) revert NoVotingPower();
totalVotes = processVote(
userVotes[msg.sender],
direction,
votingPower,
totalVotes
);
emit DirectionVoted(msg.sender, direction, votingPower);
}