Core Contracts

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

Past reward rate is not consider when calculating new reward rate

Summary

New rewardRate calculation in notifyReward function in BaseGauge is base only on new amount to distribute and it skips past reward rate.

Vulnerability Details

When GaugeController calls notifyRewardAmount function the new rewardRate is calculated inside notifyReward function. The calculations are based on new amount parameter and distribution period. This new rewardRate calculation skips previous rate value which is key part of the new rate as it includes information about previous rate that the users used to claim their rewards.

function notifyReward(
PeriodState storage state,
uint256 amount,
uint256 maxEmission,
uint256 periodDuration
) internal view returns (uint256) {
if (amount > maxEmission) revert RewardCapExceeded();
if (amount + state.distributed > state.emission) {
revert RewardCapExceeded();
}
uint256 rewardRate = amount / periodDuration;
if (rewardRate == 0) revert ZeroRewardRate();
return rewardRate;
}

When we look at Synthetix staking contract, that the logic is used in BaseGauge, we can see that notifyRewardAmount calculates new reward rate based on previous rate. The same thing should be done in BaseGauge so that past rewards can be still claimed by the users.

function notifyRewardAmount(uint256 reward) external onlyRewardsDistribution updateReward(address(0)) {
if (block.timestamp >= periodFinish) {
rewardRate = reward.div(rewardsDuration);
} else {
uint256 remaining = periodFinish.sub(block.timestamp);
uint256 leftover = remaining.mul(rewardRate);
rewardRate = reward.add(leftover).div(rewardsDuration);
}
// Ensure the provided reward amount is not more than the balance in the contract.
// This keeps the reward rate in the right range, preventing overflows due to
// very high values of rewardRate in the earned and rewardsPerToken functions;
// Reward + leftover must be less than 2^256 / 10^18 to avoid overflow.
uint balance = rewardsToken.balanceOf(address(this));
require(rewardRate <= balance.div(rewardsDuration), "Provided reward too high");
lastUpdateTime = block.timestamp;
periodFinish = block.timestamp.add(rewardsDuration);
emit RewardAdded(reward);
}

Impact

To calculate new rewardRate the past rewardRate is not used which leads to incorrect rewardRate calculation and prevents users from claiming their REAL rewards.

Tools Used

Manual Review, Hardhat

Recommendations

Consider modifying new rewardRate calculations so that it is also based on past rewardRate to ensure correct calculations.

Updates

Lead Judging Commences

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

BaseGauge's notifyRewardAmount overwrites reward rates without accounting for undistributed rewards, allowing attackers to reset admin-distributed rewards

Support

FAQs

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