Core Contracts

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

Users are unable to vote to update gauge weight

Summary

Function GaugeController::vote() allows an user to vote for gauge and update the gauge weight based on user's current veToken balance. However, users can be unable to vote to update gauge weight because of arithmetic underflow.

Vulnerability Details

The function GaugeController::vote() allows an user to vote for a gauge many times. In case, the user already voted for that gauge, the next vote will consider the old vote weight with user's current voting power. The gauge's total weight is recalculated with user's old voted weight, new voted weight and the current voting power, handled by function _updateGaugeWeight(). This function computes the gauge's new total weight: uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION) + (newWeight * votingPower / WEIGHT_PRECISION). This operation can be failed when oldGaugeWeight < (oldWeight * votingPower / WEIGHT_PRECISION), causing arithmetic underflow. Hence, the function vote() reverts.

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);
}
function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 votingPower
) internal {
Gauge storage g = gauges[gauge];
uint256 oldGaugeWeight = g.weight;
@> uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ (newWeight * votingPower / WEIGHT_PRECISION);
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}

PoC

Add the test to test/unit/core/governance/gauges/GaugeController.test.js

describe("Period Management", () => {
// ...
it.only("vote reverts", async () => {
// @audit PoC Users are unable vote to increase gauge weight
await gaugeController.connect(user1).vote(await raacGauge.getAddress(), 5000);
// user1 locks more token -> veToken balance increases
await veRAACToken.mint(user1.address, ethers.parseEther("1000"));
await gaugeController.connect(user1).vote(await raacGauge.getAddress(), 7000);
});

Run the test and console shows

GaugeController
Period Management
1) vote reverts
0 passing (2s)
1 failing
1) GaugeController
Period Management
vote reverts:
Error: VM Exception while processing transaction: reverted with panic code 0x11 (Arithmetic operation overflowed outside of an unchecked block)
at GaugeController._updateGaugeWeight (contracts/core/governance/gauges/GaugeController.sol:235)
at GaugeController.vote (contracts/core/governance/gauges/GaugeController.sol:210)

Impact

  • Unable to vote, specially updating a existed vote for a gauge. It can happens when gauge total weight is less than the user's (old_weight * current_voting_power)

Tools Used

Manual

Recommendations

function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 votingPower
) internal {
Gauge storage g = gauges[gauge];
uint256 oldGaugeWeight = g.weight;
- uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
- + (newWeight * votingPower / WEIGHT_PRECISION);
+ int256 newGaugeWeight;
+ unchecked {
+ newGaugeWeight = int(oldGaugeWeight - (oldWeight * votingPower / WEIGHT_PRECISION)
+ + (newWeight * votingPower / WEIGHT_PRECISION));
+ }
- g.weight = newGaugeWeight;
+ g.weight = newGaugeWeight < 0 ? 0 : uint(newGaugeWeight);
g.lastUpdateTime = block.timestamp;
}
Updates

Lead Judging Commences

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

GaugeController::_updateGaugeWeight uses current voting power for both old and new vote calculations, causing underflows when voting power increases and incorrect gauge weights

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

GaugeController::_updateGaugeWeight uses current voting power for both old and new vote calculations, causing underflows when voting power increases and incorrect gauge weights

Support

FAQs

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

Give us feedback!