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