Core Contracts

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

Incorrect boost calculation divisor in `BaseGauge::applyBoost` leads to complete loss of user rewards

Summary

The BaseGauge::_applyBoost function divides by 1e18 instead of the WEIGHT_PRECISION (10000) basis points when applying the boost multiplier, causing user rewards to be reduced to zero. This effectively breaks the reward distribution mechanism for all users.

Vulnerability Details

The _applyBoost function incorrectly divides the boost calculation by 1e18 instead of using the basis point precision (10000):

function _applyBoost(address account, uint256 baseWeight) public view virtual returns (uint256) {
.
.
.
uint256 boost = BoostCalculator.calculateBoost(veBalance, totalVeSupply, params);
@> return (baseWeight * boost) / 1e18;
}

The boost value returned from `BoostCalculator::calculateBoost` is in basis points (10000 = 1x, 25000 = 2.5x), but dividing by 1e18 reduces any reasonable boosted value to 0.

The BaseGauge::earned function relies on the value returned by this function. As it returns 0, it will lead to complete loss of user earned rewards

POC

  • User has veToken balance worth the maximum boost

  • _applyBoost is called with user as account and baseWeight = 5000

  • BoostCalculator::calculateBoost returns boost = 25000 (2.5x)

  • Final calculation: (5000 * 25000) / 1e18 = 0

  • User receives no rewards

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 {RWAGauge} from "../../../../contracts/core/governance/gauges/RWAGauge.sol";
import {Test, console} from "forge-std/Test.sol";
contract TestSuite is Test {
FeeCollector feeCollector;
Treasury treasury;
RAACToken raacToken;
veRAACToken veRAACTok;
GaugeController gaugeController;
RAACGauge raacGauge;
RAACGauge raacGauge2;
RWAGauge rwaGauge;
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));
rwaGauge = new RWAGauge(rewardToken, address(veRAACTok), address(gaugeController));
vm.stopPrank();
}
function testApplyBoostReducesUserRewardsToZero() public {
address user = makeAddr("user");
uint256 mintAmount = 1e18;
uint256 maxDuration = veRAACTok.MAX_LOCK_DURATION();
uint256 baseWeight = 5000;
vm.startPrank(admin);
raacToken.mint(user, mintAmount);
vm.stopPrank();
//user mints veRaacTokens to have maximum boost
vm.startPrank(user);
raacToken.approve(address(veRAACTok), mintAmount);
veRAACTok.lock(mintAmount, maxDuration);
vm.stopPrank();
uint256 userBoostedWeight = raacGauge._applyBoost(user, baseWeight); // bug in this function prevents this from working. Change the minBoost in the constructor of BaseGauge to 10000
//user boosted weight is 0 when it should be 2.5x the baseWeight
assertLt(userBoostedWeight, baseWeight);
assertEq(userBoostedWeight, 0);
}
}

Impact

The incorrect divisor completely breaks the reward distribution mechanism as all users receive 0 rewards. The boost mechanism becomes non-functional and protocol incentives fail to work as intended

Tools Used

Manual review, foundry test suite

Recommendations

Change the divisor to match basis points:

- return (baseWeight * boost) / 1e18;
+ return (baseWeight * boost) / 10000; //i.e WEIGHT_PRECISION
Updates

Lead Judging Commences

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

BaseGauge reward calculations divide by 1e18 despite using 1e4 precision weights, causing all user weights to round down to zero and preventing reward distribution

Support

FAQs

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

Give us feedback!