The first depositor receives all the rewards from the first epoch, even if they are not the only depositor or even if they haven't staked for it.
Depositor games the system.
The first depositor earns rewards from the first epoch even if they have staked in the second.
distributePoints will reward the first depositor with all the points from the first epoch due to this if check:
FjordPoints is called through FjordStaking and used as another form of rewards. To simplify the report, we avoid the mechanisms of the call from FjordStaking. The only important part to note is that on each stake/unstake, FjordStaking calls onStaked or onUnstaked in FjordPoints.
Note that FjordPoints will distribute rewards to users for the epochs after their join. This means that if User1 joins in epoch 4, they would start earning rewards for epoch 5 and onward. This is to avoid just-in-time liquidity and users earning rewards for quickly entering and leaving the pools. This is handled inside updatePendingPoints, where a user’s lastPointsPerToken is synced to the global pointsPerToken.
When we look into distributePoints, we can see two if checks verifying whether the first epoch has started and if there is any totalStaked amount. Even if we are in epoch N, but have 0 totalStaked, we won’t update any variables and just return.
This would enable the first user to stake in epoch 2 and earn all the rewards from epoch 1. While this might be rare, if it happens, this user would earn points from an epoch they were not staked for.
This happens because onStake will first sync the user and then update totalStaked, but since totalStaked == 0 during distributePoints (called in checkDistribution), the user will not be properly synced.
Example:
Epoch 1 passes without anyone staking.
User1 stakes in epoch 2 and gets their lastPointsPerToken synced to 0 since pointsPerToken hasn't been updated yet.
User2 stakes right after them.
User2 will trigger checkDistribution, updating pointsPerToken to its new value and syncing with them.
Since User2 is synced to pointsPerToken of epoch 2, they will not get any rewards.
User1 still has their lastPointsPerToken set to 0, so they can claim all of pointsPerToken, i.e., the rewards for epochs 1 and 2.
The if statement inside distributePoints.
The functions sync before the user increases totalStaked.
onStaked does not check for the first depositor depositing late.
Manual review.
If the first depositor triggers distributePoints after their deposit.
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.