Core Contracts

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

Inconsistent voting power calculation across different contracts

Summary

Different contracts in the system fetch and calculate voting power using the veRAACToken::getVotingPower() function. This approach is correctly followed by FeeCollector, BoostController, and Governance contracts. However, an issue was found in GaugeController::vote() and BaseGauge::voteDirection(), where voting power is calculated differently.

Vulnerability Details

Instead of using veRAACToken::getVotingPower(), GaugeController::vote() and BaseGauge::voteDirection() incorrectly calculate voting power based only on the user's**** veRAACToken balance.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/gauges/GaugeController.sol#L190-L203

function voteDirection(uint256 direction) public whenNotPaused updateReward(msg.sender) {
if (direction > 10000) revert InvalidWeight();
uint256 votingPower = IERC20(IGaugeController(controller).veRAACToken()).balanceOf(msg.sender);//@audit
if (votingPower == 0) revert NoVotingPower();
totalVotes = processVote(
userVotes[msg.sender],
direction,
votingPower,
totalVotes
);
emit DirectionVoted(msg.sender, direction, votingPower);
}

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/governance/gauges/BaseGauge.sol#L407-L420

function voteDirection(uint256 direction) public whenNotPaused updateReward(msg.sender) {
if (direction > 10000) revert InvalidWeight();

function voteDirection(uint256 direction) public whenNotPaused updateReward(msg.sender) {
if (direction > 10000) revert InvalidWeight();
uint256 votingPower = IERC20(IGaugeController(controller).veRAACToken()).balanceOf(msg.sender);//@audit
if (votingPower == 0) revert NoVotingPower();
totalVotes = processVote(
userVotes[msg.sender],
direction,
votingPower,
totalVotes
);
emit DirectionVoted(msg.sender, direction, votingPower);
}

This is not correct because getVotingPower() uses VotingPowerLib::getCurrentPower() to determine voting power. This function not only considers the token balance but also accounts for how long the tokens have been locked.

By relying solely on token balance, vote() and voteDirection() allow users to manipulate the system by accumulating a large number of tokens just before voting. This creates an unfair advantage, as new token holders can significantly influence voting outcomes, while long-term token holders lose their intended weight in governance decisions.

Impact

High. Attacker can unfairly influence votes by temporarily acquiring a large balance. Long-term token holders are disadvantaged, undermining the intended governance mechanism.

Tools Used

Recommendations

Update GaugeController::vote() and BaseGauge::voteDirection() to use getVotingPower() instead relying on balanceOf(user)

Updates

Lead Judging Commences

inallhonesty Lead Judge 3 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.