There is no difference in rewards between users who complete their stake one second before the end of the current epoch and those who stake at the beginning of the current epoch. The calculation of rewards only begins in the next epoch after the stake()
function is called.A user aiming to maximize their rewards may choose to execute stake()
just before an epoch ends. However, due to the time difference between the call and the actual execution, there is a risk that the stake()
transaction could be completed after the epoch has flipped. In such a case, the user’s funds would be locked for nearly an entire epoch without receiving any reward.
The FjordStaking::_checkEpochRollover()
function is responsible for rolling over to the next epoch. The relevant code snippet is provided below, with the critical parts marked by @>
. This function calls FjordStaking::getEpoch()
to determine the current epoch. The epoch rollover relies on the calculation uint16((_timestamp - startTime) / epochDuration)
, which rounds down to the nearest integer multiple of the epoch duration. As a result, There is no difference in rewards between users who complete the pledge one second before the end of the current epoch and those who complete the pledge at the beginning of the current epoch.
Consider the following scenario:
Alice stakes her tokens at the beginning of an epoch.
Bob stakes his tokens just one second before the epoch ends.
Despite the vast difference in their staking durations, both Alice and Bob will be locked for 6 epochs and will receive the same rewards. Bob's effective staking time is 604,799 seconds less than Alice's, But there is no difference in rewards they receive.
Please add the code to test/unit/unstake.t.sol
and execute it:
output:
https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordStaking.sol#L691-L724
https://github.com/Cyfrin/2024-08-fjord/blob/0312fa9dca29fa7ed9fc432fdcd05545b736575d/src/FjordStaking.sol#L330-L333
Consider the following scenario:
A user aiming to maximize their rewards may choose to execute stake()
just before an epoch ends. However, due to the time difference between the call and the actual execution, there is a risk that the stake()
transaction could be completed after the epoch has flipped. In such a case, the user’s funds would be locked for nearly an entire epoch without receiving any reward. Network congestion could exacerbate this issue, cause users to lose funds in disguise. Should an epoch flip check be added? If the epoch has advanced beyond the user's expectation, the call could be aborted or rescheduled to avoid locking funds without rewards, ensuring the user's expectations are met.
Manual Review
Consider redesigning the staking reward system for the current epoch. If it is already determined that no rewards will be granted regardless of the staking time within the same epoch, it is recommended to add checks in all staking-related functions. For example:
When the user calls stake()
, include a logic check with a bool
parameter to confirm whether to continue execution if the current epoch has flipped. If the user sets the parameter to true
, the execution proceeds as normal. If set to false
and the epoch has flipped, the transaction could be aborted or rolled back to avoid unintended fund locking.
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.