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);
}
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();
uint256 adjustment = (emissionRate * adjustmentFactor) / 100;
if (utilizationRate > utilizationTarget) {
uint256 increasedRate = emissionRate + adjustment;
uint256 maxRate = increasedRate > benchmarkRate
? increasedRate
: benchmarkRate;
return maxRate < maxEmissionRate
? maxRate
: maxEmissionRate;
Example:
There are no interactions for a day, current utilization is at 65%
setUtilizationTarget
is called moving the target from 60% to 70%
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);
}