Core Contracts

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

Loss of Already Distributed Rewards in notifyRewardAmount Function

Summary

The notifyRewardAmount function in the rewards distribution contract incorrectly overrides the rewardRate without considering its previous value. This leads to a loss of already distributed rewards, as the ongoing reward distribution is disrupted, potentially preventing users from claiming their entitled rewards.

Vulnerability Details

Affected Function:

function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
if (amount > periodState.emission) revert RewardCapExceeded();
rewardRate = notifyReward(periodState, amount, periodState.emission, getPeriodDuration());
periodState.distributed += amount;
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}
lastUpdateTime = block.timestamp;
emit RewardNotified(amount);
}

Root Cause

  • The function directly sets rewardRate using notifyReward, which calculates rewardRate as:

    uint256 rewardRate = amount / periodDuration;
  • This calculation completely overrides the existing rewardRate, without accumulating previous values.

  • The contract does not track or distribute rewards that were already in progress, leading to loss of unclaimed rewards.

Impact

  • Users may lose a portion of their entitled rewards when a new reward amount is notified.

  • If rewards are meant to be continuously distributed over time, resetting rewardRate disrupts the expected distribution.

Proof of Concept

  1. Assume rewardRate is set for an initial reward amount of 1000 over 10 seconds (rewardRate = 100 per second).

  2. Users accumulate rewards at 100 per second for 5 seconds.

  3. The controller calls notifyRewardAmount(500), which resets rewardRate instead of accumulating it.

  4. The previous accumulated rewards for the first 5 seconds are lost, leading to user loss.

Recommended Fix

Modify notifyRewardAmount to accumulate the previous rewardRate before updating it:

function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
if (amount > periodState.emission) revert RewardCapExceeded();
uint256 remainingTime = getPeriodDuration() - (block.timestamp - lastUpdateTime);
uint256 accruedReward = rewardRate * remainingTime;
rewardRate = notifyReward(periodState, amount + accruedReward, periodState.emission, getPeriodDuration());
periodState.distributed += amount;
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}
lastUpdateTime = block.timestamp;
emit RewardNotified(amount);
}

This ensures that rewards already in progress are carried forward into the new reward calculation.

Severity: High

  • Loss of funds: Users lose rewards due to incorrect calculations.

Suggested Mitigation

  • Implement cumulative reward calculations.

Conclusion

This issue results in significant user losses and disrupts the intended reward distribution mechanism.

Updates

Lead Judging Commences

inallhonesty Lead Judge 7 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.

Give us feedback!