Core Contracts

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

Concurrent reward distribution mechanisms leads to reward rate override

Relevant Context

The system has two mechanisms for distributing rewards to gauges:

  1. GaugeController#distributeRewards - Distributes rewards based on gauge weights (callable by anyone)

  2. GaugeController#distributeRevenue - Distributes revenue between veToken holders and gauges (callable by emergency admin)

Both mechanisms ultimately call BaseGauge#notifyRewardAmount to update reward rates.

Finding Description

The GaugeController#distributeRevenue function can override the current reward emission rate set by GaugeController#distributeRewards in the gauge. This occurs because both functions use BaseGauge#notifyRewardAmount to update the reward rate, but do not coordinate their timing or amounts.

When notifyRewardAmount is called, it completely overwrites the previous rewardRate with a new calculation based on the latest amount:

rewardRate = notifyReward(periodState, amount, periodState.emission, getPeriodDuration());

This means if distributeRevenue is called shortly after distributeRewards, the second call will override the reward rate from the first call, potentially leading to incorrect reward distributions. The issue is particularly severe because distributeRewards can be called by any address.

Impact Explanation

High. Since distributeRewards can be called by any address, this creates a potential denial of service vector where malicious actors could repeatedly override legitimate reward distributions. This could lead to significant disruption of the reward distribution mechanism.

Likelihood Explanation

High. Given that distributeRewards is publicly callable and distributeRevenue is a core protocol function, the likelihood of reward rate overrides is high, whether malicious or accidental.

Proof of Concept

  1. Time t=0: Admin calls distributeRewards(gaugeA) with 1000 tokens

    • gaugeA.rewardRate is set to 1000/period

  2. Time t=1: Emergency admin calls distributeRevenue(gaugeType, 2000)

    • For gaugeA, notifyRewardAmount is called with its share of the revenue

    • gaugeA.rewardRate is overwritten with new rate based only on the revenue share

  3. The original reward rate from distributeRewards is lost, and users will earn rewards only based on the revenue distribution

Alternatively, a malicious actor could:

  1. Monitor for distributeRevenue transactions in the mempool

  2. Front-run with a call to distributeRewards

  3. Cause the revenue distribution to fail or result in incorrect reward rates

Recommendation

  1. Implement reward rate accumulation instead of overwriting in BaseGauge#notifyRewardAmount

  2. Add an admin function to reset reward rates when needed

// ... existing code ...
function notifyRewardAmount(uint256 amount) external override onlyController updateReward(address(0)) {
if (amount > periodState.emission) revert RewardCapExceeded();
// Add to existing reward rate instead of overwriting
uint256 newRate = notifyReward(periodState, amount, periodState.emission, getPeriodDuration());
rewardRate += newRate;
periodState.distributed += amount;
uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}
lastUpdateTime = block.timestamp;
emit RewardNotified(amount);
}
/// @notice Resets the reward rate to zero
/// @dev Only callable by controller
function resetRewardRate() external onlyController updateReward(address(0)) {
rewardRate = 0;
emit RewardRateReset();
}
// ... existing code ...

Also consider adding access control to distributeRewards or implementing rate limiting mechanisms to prevent abuse.

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!