The introduction of vesting periods in the staking algorithm can lead to unallocated and undistributed reward tokens that will be stuck in the contract and cause loss of protocol funds.
The TempleGoldStaking contract follows the classic staking algorithm, popularized by Synthetix's implementation. In the algorithm, an accumulator named rewardPerTokenStored
tracks the amount of reward tokens corresponding to a single unit of the staking token. This is given by the _rewardPerToken()
function.
Basically this is the delta in time since last updated, multiplied by rate per second, and divided by the total supply. Important to note here that total supply is the total amount of staking tokens deposited, without any notion of vesting.
Users then have another accumulator in the userRewardPerTokenPaid
mapping. Pending rewards are then calculated as the difference between rewardPerTokenStored
and userRewardPerTokenPaid
.
A fundamental difference from the classic algorithm is the vesting rate. The user's reward per token here is multiplied by the rate. When the stake is fully matured, the algorithm behaves the same. However, when the rate is less than 1e18
the reward per token will be a percentage of the expected reward per token at that specific point in time.
Recall from before that rewardPerTokenStored
is calculated using the total stake supply. This means that if a user doesn't wait until their stake is fully matured, there will be a portion of the allocated reward tokens that won't be distributed to anyone.
Of course this isn't a problem in the classic version since there is no notion of vestion. There, whenever a user unstakes, the rewardPerTokenStored
variable will be adjusted accordingly to distribute the pending rewards to the other stakers. The key difference in the Temple implementation, and the core of the problem, is that while the global userRewardPerTokenPaid
is calculated using the totalSupply
, the same doesn't always apply to each specific user's userRewardPerTokenPaid
.
The following test illustrates the issue. While Alice has her stake fully matured along the reward period, Bob withdraws at the middle of the vesting period. The end result is a portion of the rewards are left unallocated and locked in the staking contract.
Place this test in the TempleGoldStaking.t.sol file.
Reward tokens associated with unrealized stakes are not redistributed to other stakers when users exit their position before their vesting period is fully matured. Undistributed reward tokens will be left stuck in the contract, unallocated and unrecoverable, and cause loss of protocol funds.
Note that the issue becomes critical since these undistributed rewards not only are left unallocated, but cannot be eventually re-claimed by the user. As stakes are dependent on a unique index determined at deposit time, the user cannot re-stake under the same index again.
None.
When a user withdraws their stake before it is fully matured, the implementation should distribute the rewards associated with the inflicted penalty (the 1e18 - vestingRate
percentage) to other stakers. This could be potentially done by re-adjusting the reward rate to account for the undistributed tokens.
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.