Core Contracts

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

Incorrect calculations and potential arithmetic overflow in _updateGaugeWeight

Summary

The vote function in the GaugeController contract uses the latest veRAACToken.balanceOf(msg.sender) as votingPower to call _updateGaugeWeight. However, in _updateGaugeWeight, this votingPower is incorrectly used to calculate the old weight contribution. If the user's votingPower has increased since their last vote, this can lead to incorrect calculations and potential arithmetic overflow. This issue arises because the old weight contribution should be calculated using the votingPower at the time of the previous vote, not the current votingPower.

Vulnerability Details

The vote function retrieves the user's current veRAACToken balance as votingPower and passes it to _updateGaugeWeight:

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

In _updateGaugeWeight, the votingPower is used to calculate both the old and new weight contributions:

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

The votingPower passed to _updateGaugeWeight is the user's current veRAACToken balance. This votingPower is used to calculate both the old and new weight contributions, which is incorrect because the old weight contribution should be calculated using the votingPower at the time of the previous vote. If the user's votingPower has increased since their last vote, the calculation of the old weight contribution can result in a negative value, causing an underflow in Solidity's uint256.

Example Scenario

  1. First Vote:

    • votingPower = 10000

    • weight = 100

    • newGaugeWeight = 0 - 0 + (10000 * 100 / 10000) = 100

  2. Second Vote:

    • votingPower = 20000 (increased from 10000)

    • weight = 100

    • newGaugeWeight = 100 - (100 * 20000 / 10000) + (100 * 20000 / 10000)

    • The subtraction 100 - (100 * 20000 / 10000) results in 100 - 200 = -100, which causes an underflow in Solidity's uint256.

POC

add following test case in GaugeController.test.js

it("vote should not revert after the balance of veRAACToken increase", async () => {
//user1 has 1000 veRAACToken now and vote will succeed
await gaugeController.connect(user1).vote(await rwaGauge.getAddress(), 5000);
//user1 has 2000 veRAACToken now
await veRAACToken.mint(user1.address, ethers.parseEther("1000"));
//vote again, it should succeed but revert
await gaugeController.connect(user1).vote(await rwaGauge.getAddress(), 5000);
});

run npx hardhat test --grep "vote should not revert"

1) GaugeController
Weight Management
vote should not revert:
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:224)
at GaugeController.vote (contracts/core/governance/gauges/GaugeController.sol:200)
at EdrProviderWrapper.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:444:41)
at HardhatEthersSigner.sendTransaction (node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:125:18)
at send (node_modules/ethers/src.ts/contract/contract.ts:313:20)

The vote transaction will revert due to Arithmetic operation overflowed error.

Impact

The gauge weight calculation is totally incorrect. The impact is High, the likelihood is High, so the severity is High.

Tools Used

Manual Review

Recommendations

To fix this issue, we need to track the votingPower used in each vote and use it to calculate the old weight contribution in _updateGaugeWeight.

Add a mapping to store the votingPower used in each vote:

mapping(address => mapping(address => uint256)) public userVotingPower;

Store the votingPower used in the current vote:

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];
uint256 oldVotingPower = userVotingPower[msg.sender][gauge]; // Get old voting power
userGaugeVotes[msg.sender][gauge] = weight;
userVotingPower[msg.sender][gauge] = votingPower; // Store new voting power
_updateGaugeWeight(gauge, oldWeight, weight, oldVotingPower, votingPower);
emit WeightUpdated(gauge, oldWeight, weight);
}

Use the old votingPower to calculate the old weight contribution and the new votingPower to calculate the new weight contribution:

function _updateGaugeWeight(
address gauge,
uint256 oldWeight,
uint256 newWeight,
uint256 oldVotingPower,
uint256 newVotingPower
) internal {
Gauge storage g = gauges[gauge];
uint256 oldGaugeWeight = g.weight;
uint256 newGaugeWeight = oldGaugeWeight - (oldWeight * oldVotingPower / WEIGHT_PRECISION)
+ (newWeight * newVotingPower / WEIGHT_PRECISION);
g.weight = newGaugeWeight;
g.lastUpdateTime = block.timestamp;
}
Updates

Lead Judging Commences

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