Core Contracts

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

Function need to `updateEmissionRate` before they get triggered

Summary

Vulnerability Details

updateEmissionRate needs to be called before doing any of those interactions:

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/minters/RAACMinter/RAACMinter.sol#L281

function updateBenchmarkRate(uint256 _newRate) external onlyRole(UPDATER_ROLE) {
if (_newRate == 0 || _newRate > MAX_BENCHMARK_RATE) revert InvalidBenchmarkRate();
uint256 oldRate = benchmarkRate;
benchmarkRate = _newRate;
emit BenchmarkRateUpdated(oldRate, _newRate);
}
function setMinEmissionRate(uint256 _minEmissionRate) external onlyRole(UPDATER_ROLE) {
if (_minEmissionRate >= maxEmissionRate) revert InvalidMinEmissionRate();
uint256 oldRate = minEmissionRate;
minEmissionRate = _minEmissionRate;
emit MinEmissionRateUpdated(oldRate, _minEmissionRate);
}
function setMaxEmissionRate(uint256 _maxEmissionRate) external onlyRole(UPDATER_ROLE) {
if (_maxEmissionRate <= minEmissionRate) revert InvalidMaxEmissionRate();
uint256 oldRate = maxEmissionRate;
maxEmissionRate = _maxEmissionRate;
emit MaxEmissionRateUpdated(oldRate, _maxEmissionRate);
}
// and the rest...

The reason we need it is because all of those function tamper with emission math, meaning that any change to one of those variables will change the emisions for a time that has already passed.

https://github.com/Cyfrin/2025-02-raac/blob/main/contracts/core/minters/RAACMinter/RAACMinter.sol#L220

function calculateNewEmissionRate() internal view returns (uint256) {
uint256 utilizationRate = getUtilizationRate();
// emissionRate * 5%
uint256 adjustment = (emissionRate * adjustmentFactor) / 100;
if (utilizationRate > utilizationTarget) {
// emissionRate + emissionRate * 5%
uint256 increasedRate = emissionRate + adjustment;
// max(increasedRate || benchmarkRate)
uint256 maxRate = increasedRate > benchmarkRate
? increasedRate
: benchmarkRate;
// min ( max((emissionRate + emissionRate * 5%) || benchmarkRate) || maxEmissionRate)
return maxRate < maxEmissionRate
? maxRate
: maxEmissionRate;

Example:

  1. There are no interactions for a day, current utilization is at 65%

  2. setUtilizationTarget is called moving the target from 60% to 70%

  3. Now all of those rewards for that day are in the lower bracket of our emission, meaning that all of these rewards gets reduced

These function might be called only by admins, however if they are called this issue will occure and we would either lower the rewards or generate more for a *time that has already passed. That is staking 101

Impact

Rewards are changed for time that is already passed

Tools Used

Manual review

Recommendations

Add updateEmissionRate to the bellow function and the rest.

function updateBenchmarkRate(uint256 _newRate) external onlyRole(UPDATER_ROLE) {
if (_newRate == 0 || _newRate > MAX_BENCHMARK_RATE) revert InvalidBenchmarkRate();
+ updateEmissionRate();
uint256 oldRate = benchmarkRate;
benchmarkRate = _newRate;
emit BenchmarkRateUpdated(oldRate, _newRate);
}
Updates

Lead Judging Commences

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

RAACMinter parameter update functions modify emission variables without calling updateEmissionRate() first, retroactively changing rewards for time periods that have already passed

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

RAACMinter parameter update functions modify emission variables without calling updateEmissionRate() first, retroactively changing rewards for time periods that have already passed

Support

FAQs

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