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.