Core Contracts

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

If notifyRewardAmount is called multiple times, state.distributed may not accurately reflect the true amount of rewards distributed

Summary

The notifyRewardAmount function in BaseGauge contract is used to adjust the rewardRate by distributing a specified amount of rewards. However, there is a discrepancy between the actual rewards distributed and the value stored in state.distributed. This inconsistency arises because state.distributed is incremented by the amount passed to notifyRewardAmount, but the actual rewards distributed depend on the rewardRate and the duration of the period. If notifyRewardAmount is called multiple times, state.distributed may not accurately reflect the true amount of rewards distributed, leading to potential issues in reward accounting and distribution.

Vulnerability Details

The notifyRewardAmount function updates the rewardRate and increments state.distributed by the amount provided. Here is the relevant code:

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;
...
...

The notifyReward function calculates the rewardRate based on the amount and the period duration. Here is the relevant code:

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

Every time notifyRewardAmount is called, state.distributed is incremented by the amount passed to the function. This assumes that the entire amount will be distributed immediately, which is not the case. However, the actual rewards distributed depend on the rewardRate and the duration of the period. The rewardRate is calculated as amount / periodDuration, meaning the rewards are distributed gradually over the period. If notifyRewardAmount is called multiple times within a single period, the rewardRate is updated, but the actual rewards distributed may not match the sum of the amounts passed to notifyRewardAmount.

Example Scenario

  1. First Call to notifyRewardAmount:

    • amount = 1000

    • periodDuration = 7 days

    • rewardRate = 1000 / 7 days ≈ 142.857 per day

    • state.distributed = 1000

  2. Second Call to notifyRewardAmount (after 3 days):

    • amount = 1000

    • rewardRate = 1000 / 7 days ≈ 142.857 per day

    • state.distributed = 1000 + 1000 = 2000

  3. Actual Rewards Distributed:

    • After 3 days, the first rewardRate would have distributed 142.857 * 3 ≈ 428.571.

    • The second rewardRate would distribute 142.857 * 4 ≈ 571.428 over the remaining 4 days.

    • Total rewards distributed: 428.571 + 571.428 ≈ 1000

    • However, state.distributed is 2000, which is double the actual rewards distributed.

Impact

  • Inaccurate Reward Accounting:

    • state.distributed does not reflect the actual rewards distributed, leading to discrepancies in the protocol's accounting.

  • Potential Overestimation of Rewards:

    • If notifyRewardAmount is called multiple times, state.distributed may exceed the actual rewards distributed, causing the protocol to overestimate the rewards.

  • Reward Distribution Issues:

    • The discrepancy between state.distributed and the actual rewards distributed can lead to incorrect reward calculations, potentially causing users to receive fewer rewards than expected.

The impact is Medium, the likelihood is Medium, so the severity is Medium.

Tools Used

Manual Review

Recommendations

Instead of incrementing state.distributed by the amount passed to notifyRewardAmount, track the actual rewards distributed based on the rewardRate and the time elapsed since the last update.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

BaseGauge::notifyRewardAmount increments periodState.distributed with full amount regardless of actual distribution timing, causing reward accounting discrepancies

inallhonesty Lead Judge about 1 month ago
Submission Judgement Published
Validated
Assigned finding tags:

BaseGauge::notifyRewardAmount increments periodState.distributed with full amount regardless of actual distribution timing, causing reward accounting discrepancies

Support

FAQs

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