In TempleGoldStaking, when a user first stakes, their userRewardPerTokenPaid is set to 0 since stakeInfo is not initialized as updateReward is being called first. Later, in the _earned function, when the user has passed the vestingPeriod, _perTokenReward would equal _rewardPerToken(). And the final reward calculation _stakeInfo.amount * (_perTokenReward - userRewardPerTokenPaid[_account][_index]) / 1e18 suggests that the current reward is _stakeInfo.amount * _rewardPerToken() / 1e18, which means the reward for amount is calculated for the whole staking period, far exceeding the actual staking time.
The updateReward modifier is used to update the rewardPerTokenStored and userRewardPerTokenPaid[_account][_index].
When a user first stakes, _applyStake is called, triggering updateReward.
In the calculation:
The vestingRate is calculated as _getVestingRate(_stakeInfo), but since _stakeInfo is not initialized (_stakeInfo.stakeTime == 0), vestingRate is 0.
claimableRewards[_account][_index] is 0 because vestingRate is 0.
As a result, userRewardPerTokenPaid[_account][_index] = vestingRate * uint256(rewardData.rewardPerTokenStored) / 1e18 is also 0.
Later, when the user’s vesting period has passed, the user calls getReward, invoking updateReward again.
The vestingRate is now 1e18 because the condition block.timestamp > _stakeInfo.fullyVestedAt is true.
In the _earned function, _perTokenReward is equal to _rewardPerToken() as vestingRate == 1e18.
The final earned calculation in _earned is (_stakeInfo.amount * (_perTokenReward - userRewardPerTokenPaid[_account][_index])) / 1e18 + claimableRewards[_account][_index], which simplifies to _stakeInfo.amount * _rewardPerToken() / 1e18. This results in the reward being calculated based on rewardData.rewardPerTokenStored for the entire staking period, exceeding the user’s actual staking time.
This leads to claimableRewards[_account][_index] being updated to a higher amount, allowing the user to claim more rewards than they should.
The reward calculation is incorrect, allowing users to receive more rewards than they are entitled to.
Manual
Reconsider the design of the reward calculation. userRewardPerTokenPaid should always record the rewardPerTokenStored without any further modification to ensure accurate reward calculation based on the actual staking time.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.