Core Contracts

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

Missing Reward Transfer in `distributeRewards()` Causes Reverts

Summary

The distributeRewards() function in GaugeController fails to transfer reward tokens to the gauge contract before notifying it of the reward amount. This results in a guaranteed revert in BaseGauge.notifyRewardAmount() due to insufficient balance, making reward distribution non-functional in its current implementation.

Vulnerability Details

Issue in distributeRewards() Function:

GaugeController.distributeRewards() distributes rewards to a gauge if reward != 0:

GaugeController.sol#L329-L332

uint256 reward = _calculateReward(gauge);
if (reward == 0) return;
IGauge(gauge).notifyRewardAmount(reward);

However, it does not transfer reward tokens to the gauge before this call. This leads to a failure in BaseGauge.notifyRewardAmount(), specifically at:

BaseGauge.sol#L360-L363

uint256 balance = rewardToken.balanceOf(address(this));
if (rewardRate * getPeriodDuration() > balance) {
revert InsufficientRewardBalance();
}

Since balance (the gauge’s token balance) is zero or insufficient, the function will always revert.

Expected Behavior:

  1. GaugeController should hold reward tokens.

  2. Before calling notifyRewardAmount(), it should transfer the calculated reward amount to the gauge.

  3. The gauge contract can then properly update its reward rate without reversion.

Note: The similar issue is also evident in GaugeController._distributeToGauges() when distributing rewards to gauges of a specific type.

Impact

  • Rewards will not be sent, affecting incentives.

  • Transactions will fail due to missing funds.

  • Requires manual funding, making the process inefficient.

  • The system relies on an unverified external funding process.

Tools Used

Manual

Recommendations

1. Fix distributeRewards() to Transfer Rewards First

Modify the function to first transfer reward tokens to the gauge before notifying it.

Fixed Code:

function distributeRewards(address gauge) external override nonReentrant whenNotPaused {
if (!isGauge(gauge)) revert GaugeNotFound();
if (!gauges[gauge].isActive) revert GaugeNotActive();
uint256 reward = _calculateReward(gauge);
if (reward == 0) return;
// **Ensure Gauge is funded before notifying**
IERC20(rewardToken).safeTransfer(gauge, reward);
IGauge(gauge).notifyRewardAmount(reward);
emit RewardDistributed(gauge, msg.sender, reward);
}

This ensures that:

  • The gauge has the required balance before notifyRewardAmount().

  • rewardRate can be properly set without reversion.

2. Implement a Pre-Funding Check (Alternative Approach)

If funding is meant to be handled separately, the contract should:

  • Require a minimum balance before calling notifyRewardAmount().

  • Implement an explicit funding function to deposit rewards in advance.

  • Emit an event for missing funds instead of causing a revert.

Example Pre-Funding Check:

if (IERC20(rewardToken).balanceOf(address(this)) < reward) {
emit InsufficientFunding(gauge, reward);
return;
}

This prevents unnecessary transaction failures and allows governance to track missing funding.

Updates

Lead Judging Commences

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

GaugeController notifies gauges of rewards without transferring tokens in both distributeRewards and _distributeToGauges functions, breaking reward distribution

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

GaugeController notifies gauges of rewards without transferring tokens in both distributeRewards and _distributeToGauges functions, breaking reward distribution

Support

FAQs

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