TempleGold

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

suboptimal reward implementation

Summary

distributeRewards() distributes TempleGold rewards to stakers and starts a new epoch, potentially elongating the periodFinish. In some scenarios, this can decrease the reward rate, resulting in suboptimal rewards for stakers.

Vulnerability Details

function distributeRewards() external updateReward(address(0), 0) {
if (
distributionStarter != address(0) && msg.sender != distributionStarter
) {
revert CommonEventsAndErrors.InvalidAccess();
}
if (totalSupply == 0) {
revert NoStaker();
}
// Mint and distribute TGLD if no cooldown set
if (
lastRewardNotificationTimestamp + rewardDistributionCoolDown >
block.timestamp
) {
revert CannotDistribute();
}
_distributeGold();
uint256 rewardAmount = nextRewardAmount;
// revert if next reward is 0 or less than reward duration (final dust amounts)
if (rewardAmount < rewardDuration) {
revert CommonEventsAndErrors.ExpectedNonZero();
}
nextRewardAmount = 0;
_notifyReward(rewardAmount);
lastRewardNotificationTimestamp = uint32(block.timestamp);
}

distributeRewards() calls _notifyReward to update the reward rate and other parameters used in computing TempleGold rewards:

function _notifyReward(uint256 amount) private {
if (block.timestamp >= rewardData.periodFinish) {
rewardData.rewardRate = uint216(amount / rewardDuration);
// collect dust
nextRewardAmount = amount - (rewardData.rewardRate * rewardDuration);
} else {
uint256 remaining = uint256(rewardData.periodFinish) - block.timestamp;
uint256 leftover = remaining * rewardData.rewardRate;
rewardData.rewardRate = uint216((amount + leftover) / rewardDuration);
// collect dust
nextRewardAmount =
(amount + leftover) -
(rewardData.rewardRate * rewardDuration);
}
rewardData.lastUpdateTime = uint40(block.timestamp);
rewardData.periodFinish = uint40(block.timestamp + rewardDuration);
}

Suppose the previous reward rate is r, remaining time is t, and reward duration is T. The new reward rate will be calculated as (amount + r*t)/T, which can be less than the previous rate r. In such cases, the resulting rate will be lower, and stakers will receive fewer rewards than they should

The issue arises Since instead of vesting the notified amount for the next rewardDuration, it streams the leftover + amount for the next rewardDuration. If distributionStarter is set to the zero address, anyone can call distributeRewards(), leading to the to above issue.

Example:

  • TGLD rewards = 100,000

  • Duration for rewards = 1,000 seconds

At t = 0:

  • First _notifyReward():

  • r = 100,000 / 1,000 = 100

At t = 900:

  • Second _notifyReward() adds 80,000 rewards:

  • leftover = 100 * 100 = 10,000

  • r = (80,000 + 10,000) / 1,000 = 90

A staker who expected to receive rewards at a rate of 100 will now receive rewards at a rate of 90

Impact

Suboptimal rewardrate implementation

Tools Used

Manual

Recommendations

Reward rate should not decrease after adding rewards.

Updates

Lead Judging Commences

inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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