Core Contracts

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

Period finish implementation in base gauge is incorrect

Summary

Period finish function always increases the period end timestamp even after the period has ended which leads to incorrect distribution of the rewards.

Vulnerability Details

Following is what period finish returns

Error is how lastUpdateTime is used here because lastUpdateTime is always increased whenver update reward call is made for any user

function _updateReward(address account) internal {
rewardPerTokenStored = getRewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
UserState storage state = userStates[account];
state.rewards = earned(account);
state.rewardPerTokenPaid = rewardPerTokenStored;
state.lastUpdateTime = block.timestamp;
emit RewardUpdated(account, state.rewards);
}
}
function periodFinish() public view returns (uint256) {
return lastUpdateTime + getPeriodDuration();
}

last time reward applicable call is made in order to check if the period has ended or not so that if period is ended there cannot be more distriution of the rewards.

function lastTimeRewardApplicable() public view returns (uint256) {
return block.timestamp < periodFinish() ? block.timestamp : periodFinish();
}

Suppose rewards are notified

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

It is clear that amount is divided by period duration therefore when time enough time has passed then reward distribution should stop. Precisely when period duration is over then reward distribution should stop.

Suppose period duration is 7 days.

Current day is t=0 Days.

Rewards are notified.

reward rate is amount/7 (days)

Now what period finish function does is even if current timestamp is greater than the t+7 days it will not indicate that the period has finished because it will add period duration to the current timestamp and it will appera that still 7 days are left and will allow distribution of the rewards. So essentially amount/7 (days) can even get mulitplied by 14 days (just for example) so 2*amount will be distributed even when there is only amount tokens in the gauge.

last update time is always updated whenver rewards are updated for any user

function _updateReward(address account) internal {
rewardPerTokenStored = getRewardPerToken();
lastUpdateTime = lastTimeRewardApplicable();
if (account != address(0)) {
UserState storage state = userStates[account];
state.rewards = earned(account);
state.rewardPerTokenPaid = rewardPerTokenStored;
state.lastUpdateTime = block.timestamp;
emit RewardUpdated(account, state.rewards);
}
}

Due to this last update time can be greater than t=0 days due to which last update time + period duration will give period finish time to be greater than t=7 days.

Impact

Wrong distribution of the rewards

Tools Used

Manual

Recommendations

Do not use lastupdatetime in period finish function instead intialize a variable when rewarda were notified.

Updates

Lead Judging Commences

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

BaseGauge period end time miscalculation creates circular dependency between periodFinish() and lastUpdateTime, preventing periods from naturally ending and disrupting reward distribution

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

BaseGauge period end time miscalculation creates circular dependency between periodFinish() and lastUpdateTime, preventing periods from naturally ending and disrupting reward distribution

Support

FAQs

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

Give us feedback!