Core Contracts

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

Gauge period cannot be updated

Summary

Gauge period cannot be updated.

Vulnerability Details

updatePeriod() in gauge is used to update the period and calculates new weights, and this function can only be called by gauge controller.

BaseGauge::updatePeriod()

function updatePeriod() external override onlyController {

However, updatePeriod() is not actually called anywhere in GaugeController, hence the gauage cannot be updated.

Impact

Each gauge period has a emission cap which determines the max reward amount can be distributed to the gauge, because gauage period cannot be updated, the reward distributed to a gauge is limited by the emission cap, and users's reward is also limited.

POC

Run forge test --mt testAudit_GaugeRewardCannotBeDistributedDueToPeriodNotUpdated.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Test, console, stdError} from "forge-std/Test.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "../contracts/libraries/math/WadRayMath.sol";
import "../contracts/core/pools/LendingPool/LendingPool.sol";
import "../contracts/core/pools/StabilityPool/StabilityPool.sol";
import "../contracts/mocks/core/tokens/crvUSDToken.sol";
import "../contracts/core/tokens/RToken.sol";
import "../contracts/core/tokens/DebtToken.sol";
import "../contracts/core/tokens/DeToken.sol";
import "../contracts/core/tokens/RAACToken.sol";
import "../contracts/core/tokens/RAACNFT.sol";
import "../contracts/core/tokens/veRAACToken.sol";
import "../contracts/core/primitives/RAACHousePrices.sol";
import "../contracts/core/minters/RAACMinter/RAACMinter.sol";
import "../contracts/core/collectors/FeeCollector.sol";
import "../contracts/core/collectors/Treasury.sol";
import "../contracts/core/governance/proposals/Governance.sol";
import "../contracts/core/governance/proposals/TimelockController.sol";
import "../contracts/core/governance/boost/BoostController.sol";
import "../contracts/core/governance/gauges/RAACGauge.sol";
import "../contracts/core/governance/gauges/GaugeController.sol";
import "../contracts/mocks/core/pools/MockPool.sol";
import "../contracts/mocks/core/tokens/MockToken.sol";
contract Audit is Test {
using WadRayMath for uint256;
using SafeCast for uint256;
address owner = makeAddr("Owner");
address repairFund = makeAddr("RepairFund");
LendingPool lendingPool;
StabilityPool stabilityPool;
RAACHousePrices raacHousePrices;
crvUSDToken crvUSD;
RToken rToken;
DebtToken debtToken;
DEToken deToken;
RAACToken raacToken;
RAACNFT raacNft;
veRAACToken veRaacToken;
RAACMinter raacMinter;
FeeCollector feeCollector;
Treasury treasury;
Governance governance;
TimelockController timelockController;
BoostController boostController;
GaugeController gaugeController;
RAACGauge raacGauge;
MockToken gaugeRewardToken;
MockToken gaugeStakingToken;
function setUp() public {
vm.warp(1740000000);
vm.startPrank(owner);
raacHousePrices = new RAACHousePrices(owner);
// Deploy tokens
raacToken = new RAACToken(owner, 100, 50);
veRaacToken = new veRAACToken(address(raacToken));
crvUSD = new crvUSDToken(owner);
rToken = new RToken("RToken", "RToken", owner, address(crvUSD));
debtToken = new DebtToken("DebtToken", "DT", owner);
raacNft = new RAACNFT(address(crvUSD), address(raacHousePrices), owner);
deToken = new DEToken("DEToken", "DEToken", owner, address(rToken));
// Deploy Treasury and FeeCollector
treasury = new Treasury(owner);
feeCollector = new FeeCollector(
address(raacToken),
address(veRaacToken),
address(treasury),
repairFund,
owner
);
// Deploy LendingPool
lendingPool = new LendingPool(
address(crvUSD),
address(rToken),
address(debtToken),
address(raacNft),
address(raacHousePrices),
0.1e27
);
// Deploy stabilityPool Proxy
bytes memory data = abi.encodeWithSelector(
StabilityPool.initialize.selector,
address(rToken),
address(deToken),
address(raacToken),
address(owner),
address(crvUSD),
address(lendingPool)
);
address stabilityPoolProxy = address(
new TransparentUpgradeableProxy(
address(new StabilityPool(owner)),
owner,
data
)
);
stabilityPool = StabilityPool(stabilityPoolProxy);
// RAACMinter
raacMinter = new RAACMinter(
address(raacToken),
address(stabilityPool),
address(lendingPool),
owner
);
// Governance
address[] memory proposers;
address[] memory executors;
timelockController = new TimelockController(2 days, proposers, executors, owner);
governance = new Governance(address(veRaacToken), address(timelockController));
// Boost
boostController = new BoostController(address(veRaacToken));
// Gauges
gaugeController = new GaugeController(address(veRaacToken));
gaugeRewardToken = new MockToken("Reward Token", "RWD", 18);
gaugeStakingToken = new MockToken("veRAAC Token", "veRAAC", 18);
raacGauge = new RAACGauge(address(gaugeRewardToken), address(gaugeStakingToken), address(gaugeController));
// Initialization
raacHousePrices.setOracle(owner);
rToken.setReservePool(address(lendingPool));
debtToken.setReservePool(address(lendingPool));
deToken.setStabilityPool(address(stabilityPool));
stabilityPool.setRAACMinter(address(raacMinter));
lendingPool.setStabilityPool(address(stabilityPool));
raacToken.setMinter(address(raacMinter));
raacToken.setFeeCollector(address(feeCollector));
raacToken.manageWhitelist(address(feeCollector), true);
raacToken.manageWhitelist(address(raacMinter), true);
raacToken.manageWhitelist(address(stabilityPool), true);
raacToken.manageWhitelist(address(veRaacToken), true);
timelockController.grantRole(keccak256("PROPOSER_ROLE"), address(governance));
timelockController.grantRole(keccak256("EXECUTOR_ROLE"), address(governance));
timelockController.grantRole(keccak256("CANCELLER_ROLE"), address(governance));
timelockController.grantRole(keccak256("EMERGENCY_ROLE"), address(governance));
vm.stopPrank();
vm.label(address(crvUSD), "crvUSD");
vm.label(address(rToken), "RToken");
vm.label(address(debtToken), "DebtToken");
vm.label(address(deToken), "DEToken");
vm.label(address(raacToken), "RAACToken");
vm.label(address(raacNft), "RAAC NFT");
vm.label(address(lendingPool), "LendingPool");
vm.label(address(stabilityPool), "StabilityPool");
vm.label(address(raacMinter), "RAACMinter");
vm.label(address(veRaacToken), "veRAAC");
vm.label(address(feeCollector), "FeeCollector");
vm.label(address(treasury), "Treasury");
vm.label(address(governance), "Governance");
vm.label(address(timelockController), "TimelockController");
vm.label(address(boostController), "BoostController");
vm.label(address(gaugeController), "GaugeController");
vm.label(address(raacGauge), "RAACGauge");
vm.label(address(gaugeRewardToken), "GaugeRewardToken");
vm.label(address(gaugeStakingToken), "GaugeStakingToken");
}
function testAudit_GaugeRewardCannotBeDistributedDueToPeriodNotUpdated() public {
vm.prank(owner);
gaugeController.addGauge(address(raacGauge), IGaugeController.GaugeType.RAAC, 10000);
(,,,uint256 periodStartTime) = raacGauge.periodState();
vm.warp(periodStartTime);
(,uint256 emission, ,) = raacGauge.periodState();
deal(address(gaugeRewardToken), address(raacGauge), emission + 1);
// Distribute rewards
vm.prank(address(gaugeController));
raacGauge.notifyRewardAmount(emission);
// Period ends
vm.warp(block.timestamp + raacGauge.getPeriodDuration() + 1);
// Cannot distribute rewards as period is not updated
vm.prank(address(gaugeController));
vm.expectRevert(IGauge.RewardCapExceeded.selector);
raacGauge.notifyRewardAmount(emission);
}
}

Tools Used

Manual Review

Recommendations

Call gauge's updatePeriod() from gauge controller to update the period.

Updates

Lead Judging Commences

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

GaugeController::updatePeriod doesn't call the gauge's updatePeriod function, preventing periodState.distributed from resetting and eventually causing distributeRewards to permanently fail

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

GaugeController::updatePeriod doesn't call the gauge's updatePeriod function, preventing periodState.distributed from resetting and eventually causing distributeRewards to permanently fail

Support

FAQs

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

Give us feedback!