Ancient Denim Horse
Medium
As the staking contract strongly inherits the mechanism of Synthetix, it also contains it's bug where a portion of the rewards from the initial period remain undistributed until a new cycle is started. This bug will occur not just on the initial period, but on every instance of a fresh reward period where the earning power starts from 0, possibly from user withdraws or users moving to a delegatee that is yet to become eligible.
Let's consider that the contract starts off fresh, it notifies the very first rewards and therefore has no deposits yet so the totalEarningPower
is 0 and the checkpoint is at the current timestamp X.
We have our first depositor some time after the notification of rewards at timestamp Y, which tries to update the global index but since all of the values are fresh, there are 0 rewards accumulated.
The period finishes and the user withdraws and claims his funds. rewardPerTokenAccumulated()
will calculate the reward as the rate * (period_end - Y) / totalEarningPower and _checkpointReward()
will use that reward per token to calculate our reward against our earning power.
However as the reward is only updated for the period of (end - Y), this means that the rewards allocated for the time (Y - X) do not get distributed.
This is a bug that will definitely occur on the initial period and every time that the earning power is reset to 0 which can be a result of both withdrawals between periods and changing of delegatees to ones that are yet to be eligible.
Those rewards will not get transferred over to the next period as remainingRewards
, since the contract logic thinks they have been distributed, they will sit inside the contract balance, since only verified contracts can notify rewards and they cannot notify new rewards without doing a transfer.
Rewards need to be re-notified or otherwise get left undistributed inside of the contract.
https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/GovernanceStaker.sol#L430-L461 https://github.com/sherlock-audit/2024-11-tally/blob/main/staker/src/GovernanceStaker.sol#L303-L308
Manual Review
As the following Synthetix write-up recommends https://0xmacro.com/blog/synthetix-staking-rewards-issue-inefficient-reward-distribution/ - consider defining rewardEndTime
in the first stake() that is done after notifyRewardAmount()
, when total earning power is 0.