Core Contracts

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

Permissionless `distributeRewards` allows a malicious user to overwrite previous `rewardRate`. Users may receive less rewards

Summary

A malicious user can reduce the rewardRate by calling GaugeController::distributeRewards without sending any actual rewards. As a result, gauge stakers will receive fewer rewards.

Vulnerability Details

notifyRewardAmount is called each time when rewards are sent to gauge to calculate the new rewardRate. The reward rate is calculated as amount / periodDuration, reflecting only the latest amount of rewards to be distributed to stakers.

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);
}

The problem is each new notifyRewardAmount call resets the old rewardRate, inflicting potential loses for stakers who didn't claimed rewards right before the new reward rate is set.
GaugeController calls notifyRewardAmount from 2 functions distributeRewards which is permissionless and from _distributeToGauges
.

Having two notification rewards entry points, one of which is not gated by an onlyAdmin modifier, leads to several potential scenarios:

  • admin calls distributeRevenue (not reward function) setting rewardRate for gaugeA to 1000;

  • in the next block a malicious user calls distributeRewards(gaugeA). Due to the rewards allocated to this gauge based on type and weight are less than the revenue from distributeRevenue, the rewardRate gets reset to a lower value, 700.

A second scenario it may happen is :

  • admin send funds to gaugeA and calls distributeRewards to notify it.

  • in the next block, a malicious user remove voting power from gaugeA and calls distributeRewards(gaugeA). Due to smaller gaugeA's weight, _calculateReward alocate less rewards and gaugeA's rewardRate is reduced.

Another scenario is a malicious user can block future reward distribution for the same period by calling distributeRewards immediately after rewards have been distributed. Same amount distributed multiple times is added to periodState.distributed until RewardCapExceeded error is fired.
Future reward distribution are DOSed.

Impact

A malicious user may reduce the reward rate for gauges, stakers receiving less rewards.

Tools Used

Recommendations

Allow only a selected role to call distributeRewards.
Consider merging distributeRewards and distributeRevenue to avoid overwriting the rewardRate when rewards/revenue are distributed for the same period.

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!