Core Contracts

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

GaugeController.sol uses incorrect voting power metric leading to compromised voting weight

Summary

The GaugeController.sol::vote() function uses veRAACToken.balanceOf() to determine a user's voting power instead of veRAACToken.getVotingPower(). This bypasses the core vote-escrow mechanism where voting power should be weighted by both the amount of tokens locked and the lock duration. Additionally, user whose locks have expired can continue to vote on key protocol decision despite having zero voting power.

Vulnerability Details

In the GaugeController contract, the vote() function determines voting influence using:

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);
}

However, this only retrieves the raw balance of locked tokens without considering the lock duration. The protocol implements a vote-escrow model similar to Curve's veCRV where voting power should be a function of both:

  • Amount of tokens locked

  • Duration of the lock (longer locks = more voting power)

The correct voting power should be obtained through veRAACToken.getVotingPower() which properly calculates the time-weighted voting power using the VotingPowerLib:

function getVotingPower(address account) public view returns (uint256) {
return _votingState.getCurrentPower(account, block.timestamp);
}

Additionally, users can keep voting once their locks have expired as the balance indicates they have voting power when they should have zero.

uint256 votingPower = veRAACToken.balanceOf(msg.sender);
-> if (votingPower == 0) revert NoVotingPower();

Impact

High:

  • Users receive voting weight solely based on their locked token amount, ignoring lock duration

  • This breaks the vote-escrow incentive mechanism where longer lock commitments should grant greater voting influence

  • Users who have zero voting power can keep impacting the protocol once their locks have expired

Likelihood

High - Happens on every vote, not preconditions required.

Recommendations

Replace veRAACToken.balanceOf(msg.sender) with veRAACToken.getVotingPower(msg.sender) in the vote() function:

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);
+ uint256 votingPower = veRAACToken.getVotingPower(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);
}
Updates

Lead Judging Commences

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

BaseGauge::_applyBoost, GaugeController::vote, BoostController::calculateBoost use balanceOf() instead of getVotingPower() for vote-escrow tokens, negating time-decay mechanism

Support

FAQs

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

Give us feedback!