Core Contracts

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

Wrong use of the `GaugeController::typeWeights` multiplier in `GaugeController::_calculateReward` causes a reduction in gauge rewards instead

Summary

Wrong use of the GaugeController::typeWeights multiplier in GaugeController::_calculateReward causes a reduction in gauge rewards instead

Vulnerability Details

In the code docs, typeWeights are specified as multipliers for the actual gauge weight.

/**
* @notice Type weights and periods
* @dev Tracking for gauge type weights and their time periods
@> * typeWeights: Weight multipliers for each gauge type
* typePeriods: Period data for each gauge type
*/
mapping(GaugeType => uint256) public typeWeights;

But the way it is used in `GaugeController::_calculateReward` , makes it a divisor instead.

function _calculateReward(address gauge) public view returns (uint256) {
.
.
.
@> uint256 typeShare = (typeWeights[g.gaugeType] * WEIGHT_PRECISION) / MAX_TYPE_WEIGHT;
// Calculate period emissions based on gauge type
uint256 periodEmission = g.gaugeType == GaugeType.RWA ? _calculateRWAEmission() : _calculateRAACEmission();
@> return (periodEmission * gaugeShare * typeShare) / (WEIGHT_PRECISION * WEIGHT_PRECISION);
}

This makes the final reward returned, significantly less than expected.

POC

To use foundry in the codebase, follow the hardhat guide here: Foundry-Hardhat hybrid integration by Nomic foundation

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {ERC20Mock} from "../../../../contracts/mocks/core/tokens/ERC20Mock.sol";
import {FeeCollector} from "../../../../contracts/core/collectors/FeeCollector.sol";
import {Treasury} from "../../../../contracts/core/collectors/Treasury.sol";
import {RAACToken} from "../../../../contracts/core/tokens/RAACToken.sol";
import {veRAACToken} from "../../../../contracts/core/tokens/veRAACToken.sol";
import {GaugeController, IGaugeController} from "../../../../contracts/core/governance/gauges/GaugeController.sol";
import {RAACGauge} from "../../../../contracts/core/governance/gauges/RAACGauge.sol";
import {Test, console} from "forge-std/Test.sol";
contract UnitTest is Test {
FeeCollector feeCollector;
Treasury treasury;
RAACToken raacToken;
veRAACToken veRAACTok;
GaugeController gaugeController;
RAACGauge raacGauge;
RAACGauge raacGauge2;
address repairFund;
address admin;
address rewardToken;
uint256 initialSwapTaxRate = 100; //1%
uint256 initialBurnTaxRate = 50; //0.5%
function setUp() public {
repairFund = makeAddr("repairFund");
admin = makeAddr("admin");
rewardToken = address(new ERC20Mock("Reward Token", "RWT"));
treasury = new Treasury(admin);
raacToken = new RAACToken(admin, initialSwapTaxRate, initialBurnTaxRate);
veRAACTok = new veRAACToken(address(raacToken));
feeCollector = new FeeCollector(address(raacToken), address(veRAACTok), address(treasury), repairFund, admin);
vm.startPrank(admin);
raacToken.setFeeCollector(address(feeCollector));
raacToken.setMinter(admin);
gaugeController = new GaugeController(address(veRAACTok));
raacGauge = new RAACGauge(rewardToken, address(veRAACTok), address(gaugeController));
raacGauge2 = new RAACGauge(rewardToken, address(veRAACTok), address(gaugeController));
vm.stopPrank();
}
function testTypeWeightMultiplierDecreasesRewardsInstead() public {
uint256 initialWeight = 5000; //50%
//admin creates and adds gauges
vm.startPrank(admin);
gaugeController.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, initialWeight);
gaugeController.addGauge(address(raacGauge2), IGaugeController.GaugeType.RAAC, initialWeight);
vm.stopPrank();
// At least 50% of the periodEmission should go to the gauge
uint256 periodEmission = gaugeController._calculateRAACEmission();
uint256 expectedMinimumReward = periodEmission / 2;
uint256 reward = gaugeController._calculateReward(address(raacGauge));
// reward is less than the expected minimum by 50%/5000 bips which is the value of typeshare
console.log("Period emission: ", periodEmission);
console.log("Reward: ", reward);
console.log("Expected minimum reward: ", expectedMinimumReward);
assertLt(reward, expectedMinimumReward);
}

Impact

Reward allocated is significantly reduced

Tools Used

Manual review, foundry test suite

Recommendations

function _calculateReward(address gauge) public view returns (uint256) {
.
.
.
- return (periodEmission * gaugeShare * typeShare) / (WEIGHT_PRECISION * WEIGHT_PRECISION);
+ return ((periodEmission * gaugeShare * typeShare) / (WEIGHT_PRECISION * WEIGHT_PRECISION)) + ((periodEmission * gaugeShare) / (WEIGHT_PRECISION));
}
Updates

Lead Judging Commences

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

GaugeController::_calculateReward uses combined weight of all gauge types instead of type-specific weights, causing incorrect reward distribution between RWA and RAAC gauges

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

GaugeController::_calculateReward uses combined weight of all gauge types instead of type-specific weights, causing incorrect reward distribution between RWA and RAAC gauges

Appeal created

uddercover Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

GaugeController::_calculateReward redundantly multiplies by typeShare causing reward reduction since periodEmission is already type-specific

Support

FAQs

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

Give us feedback!