The BaseGauge contract operates on a period-based distribution where rewards accrue linearly. However, there is a mismanagement issue with how rewards are calculated and distributed within a given period, which can lead to inconsistent reward emissions.
The contract defines a period with a start timestamp and an end time, during which emissions occur linearly. When rewards are sent using notifyRewardAmount
, they are subject to an emission cap:
The function notifyReward
is used to calculate the reward rate:
The periodDuration function
The contract assumes that every reward distribution spans the entire periodDuration
(typically 7 days). This creates an issue when additional rewards are added mid-period:
Assume the period has an emission cap of 20,000.
The controller calls notifyRewardAmount(10,000)
, setting the rewardRate
as:
rewardRate = 10,000 / 7 days
After 3 days, the controller adds another reward of 10,000, assuming it will be distributed over the remaining 4 days.
However, the rewardRate
is recalculated as:
rewardRate = 10,000 / 7 days
, instead of 10,000 / 4 days
.
This leads to a misalignment between the intended and actual reward distributions.
The period is set to end in 4 days, but the newly added reward is incorrectly scheduled to be distributed over 7 days. As a result, the portion of the reward intended for the last 3 days (10,000 / 3) will not be properly allocated.
run in BaseGauge.test.js
In the second case where reward are split user get less than the first case
To clearly see the issue we should fix another issue in the BaseGauge contract
In the second test, the user receives less reward than in the first test because the rewardRate
is incorrectly calculated and split over 7 days in both calls to notifyReward
. Here's a more explicit explanation:
First call to notifyReward
with 500:
The rewardRate
is calculated as 500 / 7
, which means the reward is distributed evenly over 7 days.
So, rewardRate = 500 / 7 ≈ 71.428
per day.
Second call to notifyReward
with 500:
The rewardRate
is incorrectly updated by adding 500 / 7
again, instead of considering the remaining duration (which is now 3 days).
So, rewardRate += 500 / 7 ≈ 71.428
, resulting in a total rewardRate ≈ 142.856
per day.
Accumulated reward calculation:
For the first 4 days, the user earns 4 * (500 / 7) = 284
.
For the next 3 days, the user earns 3 * (500 / 7) + 3 * (500 / 7)≈ 426
.
The total reward becomes 500 + 426 ≈ 710
, which is less than the expected amount.
The rewardRate
should be adjusted based on the remaining duration when the second reward is added. Here's how it should work:
First call to notifyReward
with 500:
rewardRate = 500 / 7 ≈ 71.428
per day.
Second call to notifyReward
with 500:
The remaining duration is 3 days, so the rewardRate
for the new reward should be 500 / 3 ≈ 166.666
per day.
The total rewardRate
should now be 71.428 + 166.666 ≈ 238.094
per day.
Accumulated reward calculation:
For the first 7 days, the user earns 4 * (500 / 7) = 284
.
For the next 3 days, the user earns 3 * (500 / 3) + 3 * (500 / 7)≈ 713
.
The total reward becomes 284 + 713 ≈ 997
, which is the correct amount.
Unfair Reward Distribution: claimers may receive less rewards.
Modify the notifyReward
function to correctly account for the remaining period duration instead of assuming a fixed periodDuration
.
Instead of using getPeriodDuration()
, compute the actual remaining period dynamically:
This ensures that rewards added mid-period are distributed over the correct duration, avoiding misalignment.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.