TempleGold

TempleDAO
Foundry
25,000 USDC
View results
Submission Details
Severity: low
Invalid

Reward tokens of `TempleGoldStaking` may be locked forever if `totalSupply` is zero during reward distribution period.

Summary

while totalSupply of TempleGoldStaking is zero, reward tokens are not distributed to stakers.
This undistributed reward tokens are never swept and are locked forever.

Vulnerability Details

TempleGoldStaking.sol#updateReward modifier is the following.

modifier updateReward(address _account, uint256 _index) {
{
// stack too deep
rewardData.rewardPerTokenStored = uint216(_rewardPerToken());
593: rewardData.lastUpdateTime = uint40(_lastTimeRewardApplicable(rewardData.periodFinish));
......
}
_;
}

In the above, _lastTimeRewardApplicable(rewardData.periodFinish) is equal to Math.min(rewardData.periodFinish, block.timestamp).
And TempleGoldStaking.sol#_rewardPerToken function is the following.

function _rewardPerToken() internal view returns (uint256) {
if (totalSupply == 0) {
504: return rewardData.rewardPerTokenStored;
}
return
rewardData.rewardPerTokenStored +
(((_lastTimeRewardApplicable(rewardData.periodFinish) -
rewardData.lastUpdateTime) *
rewardData.rewardRate * 1e18)
/ totalSupply);
}

As can be seen, if totalSupply is zero, the rewardData.rewardPerTokenStored is not updated.
However, the rewardData.lastUpdateTime is updated in L593.
So the rewards for the corresponding period [rewardData.lastUpdateTime, _lastTimeRewardApplicable(rewardData.periodFinish)] of L593 are not distributed to the stakers and are locked forever.

Impact

Reward tokens of TempleGoldStaking may be not distributed to stakers and locked forever if totalSupply is zero during reward distribution period.

Tools Used

Manual Review

Recommendations

Modify the TempleGoldStaking.sol#updateReward modifier as follows.

modifier updateReward(address _account, uint256 _index) {
{
// stack too deep
rewardData.rewardPerTokenStored = uint216(_rewardPerToken());
++ uint256 lastUpdateTimeCache = rewardData.lastUpdateTime;
rewardData.lastUpdateTime = uint40(_lastTimeRewardApplicable(rewardData.periodFinish));
++ if (totalSupply == 0) {
++ nextRewardAmount += rewardData.rewardRate * (rewardData.lastUpdateTime - lastUpdateTimeCache);
++ }
if (_account != address(0)) {
StakeInfo memory _stakeInfo = _stakeInfos[_account][_index];
uint256 vestingRate = _getVestingRate(_stakeInfo);
claimableRewards[_account][_index] = _earned(_stakeInfo, _account, _index);
userRewardPerTokenPaid[_account][_index] = vestingRate * uint256(rewardData.rewardPerTokenStored) / 1e18;
}
}
_;
}

That is, refund the undistributed rewards to the nextRewardAmount.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

At the first time a account is called in that index, `userRewardPerTokenPaid[_account][_index]`will equal to 0

Support

FAQs

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