Core Contracts

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

Inconsistent Timestamp Usage in `_updateReward` Function

Summary

The _updateReward function in the BaseGauge contract uses block.timestamp to update state.lastUpdateTime instead of using `lastTimeRewardApplicable()@ which is used for other reward time calculations.

Vulnerability Details

In the _updateReward function:

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; // @audit uses block.timestamp instead of lastTimeRewardApplicable()
emit RewardUpdated(account, state.rewards);
}
}

The problem is that in future calculations we will calculate getRewardPerToken inside the earned: this function calculates:

function getRewardPerToken() public view returns (uint256) {
if (totalSupply() == 0) {
return rewardPerTokenStored;
}
return rewardPerTokenStored + (
(lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18 / totalSupply()
);
}

and we have

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

This means after when block.timestamp > periodFinish() block.timestamp will be bigger than LastTimeRewardApplicable() which means the getRewardPerToken will underflow

Impact

  1. When reward period ends, the function will revert due to underflow

  2. Users can't claim rewards after period ends

  3. All functions using the updateReward modifier will be blocked

Tools Used

Manual Review

Recommendations

Use LastTimeRewardApplicable:

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 = lastTimeRewardApplicable(); // Fix: use lastTimeRewardApplicable
emit RewardUpdated(account, state.rewards);
}
}```
Updates

Lead Judging Commences

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

BaseGauge sets user's lastUpdateTime to uncapped block.timestamp while global lastUpdateTime uses capped lastTimeRewardApplicable(), generating reward calc inconsistencies after period ends

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

BaseGauge sets user's lastUpdateTime to uncapped block.timestamp while global lastUpdateTime uses capped lastTimeRewardApplicable(), generating reward calc inconsistencies after period ends

Support

FAQs

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