Core Contracts

Regnum Aurum Acquisition Corp
HardhatReal World AssetsNFT
77,280 USDC
View results
Submission Details
Severity: medium
Valid

GaugeController lacks vote delay implementation despite documentation claims, enabling vote manipulation

Summary

The GaugeController contract fails to implement the documented vote delay mechanism, allowing users to vote multiple times without any time restriction between votes. This contradicts the documentation which states "Minimum vote delay for manipulation prevention" is used.

Vulnerability Details

The GaugeController's documentation explicitly states that a vote delay is used for manipulation prevention. However, the vote() function reveals no implementation of this delay mechanism, despite having defined constants VOTE_DELAY, MIN_VOTE_DELAY, and MAX_VOTE_DELAY.

This means users can vote multiple times in rapid succession or even within the same transaction, which breaks the intended security mechanism.

Impact

The absence of vote delay enables:

  • Vote manipulation through rapid successive votes

  • Unfair distribution of rewards through quick weight shifts between gauges

  • Undermining of the entire voting mechanism's integrity

This represents a HIGH severity issue as it:

  • Breaks a core security mechanism

  • Enables direct economic manipulation

  • Affects the main protocol functionality (reward distribution)

  • Could result in significant financial losses

Tools Used

Manual review

Proof of Concept

Add the following test case to the test/unit/core/governance/gauges/GaugeController.test.js file:

it("GaugeController Vote Delay Exploit", async () => {
await veRAACToken.mint(user1.address, ethers.parseEther("1000"));
// Get VOTE_DELAY
const VOTE_DELAY = await gaugeController.VOTE_DELAY();
const initialTime = await time.latest();
// Can vote multiple times without waiting for VOTE_DELAY
await gaugeController.connect(user1).vote(await rwaGauge.getAddress(), 5000);
await gaugeController.connect(user1).vote(await rwaGauge.getAddress(), 0);
await gaugeController.connect(user1).vote(await rwaGauge.getAddress(), 10000);
const finalTime = await time.latest();
const timeElapsed = finalTime - initialTime;
// User voted multiple times without waiting for VOTE_DELAY
expect(timeElapsed).to.be.lt(VOTE_DELAY);
const userVote = await gaugeController.userGaugeVotes(user1.address, await rwaGauge.getAddress());
expect(userVote).to.equal(10000);
});

Recommendations

Implement the vote delay check in 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 VoteDelayNotElapsed();
// ... existing code ...
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

GaugeController::vote never enforces VOTE_DELAY or updates lastVoteTime, allowing users to spam votes and manipulate gauge weights without waiting

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

GaugeController::vote never enforces VOTE_DELAY or updates lastVoteTime, allowing users to spam votes and manipulate gauge weights without waiting

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.