Core Contracts

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

Period end Mismanagement in BaseGauge

Summary

The BaseGauge contract is designed to distribute rewards over fixed periods. However, a critical flaw in how the contract calculates the period's end time results in an infinite extension of the period. This prevents proper period transitions and disrupts the intended reward distribution mechanism.


Issue Details

1. Flawed Period End Calculation

The periodFinish() function determines when a reward period ends:

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/BaseGauge.sol#L560

function periodFinish() public view returns (uint256) {
// @audit lastUpdateTime is updated dynamically, causing the period to never end
return lastUpdateTime + getPeriodDuration();
}

This function calculates the period's end time as lastUpdateTime + getPeriodDuration(). However, lastUpdateTime is updated frequently in the _updateReward() function, which means the period is continuously extended and never truly ends.


2. Constantly Extending Periods

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/BaseGauge.sol#L167

The _updateReward() function dynamically updates lastUpdateTime:

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

Since lastUpdateTime is updated frequently, periodFinish() always returns a time in the future, effectively preventing the period from ever ending.

  1. This mean that lastTimeRewardApplicable will in most case return block.timestamp

https://github.com/Cyfrin/2025-02-raac/blob/89ccb062e2b175374d40d824263a4c0b601bcb7f/contracts/core/governance/gauges/BaseGauge.sol#L552

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

Impact

  • No True Period End: The period is continuously extended, allowing new rewards to be injected indefinitely without transitioning to a new period.

  • Admin Dependency: Admins will manually start a new period using updatePeriod. If a period ends and the emission cap is exceeded before a new period begins, users may receive disproportionate rewards, and some users may lose rewards due to insufficient reward notification.

  • Reward Distribution Issues: Rewards are distributed based on periods, and the inability to properly end a period lead to inconsistencies in reward distribution.


Recommendation

To fix this issue, modify the periodFinish() function to use a fixed start time for the period instead of the dynamically updated lastUpdateTime. This ensures that each period has a definitive start and end.

Proposed Fix

Replace lastUpdateTime with a fixed periodStartTime that is set only once at the beginning of a new period:

function periodFinish() public view returns (uint256) {
return periodState.periodStartTime + getPeriodDuration();
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 month 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 about 1 month 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.