The FjordPoints contract is designed to distribute points to users who lock their FJO tokens in the FjordStaking contract. This point system is intended to reward long-term stakers who commit to locking their tokens for extended periods.
The points (BjordBoint tokens) are distributed on an epoch basis, with each epoch lasting one week.
Meaning that users must hold their locked stakes from current epoch to the next in order to be eligible for point rewards.
See: FjordPoints.distributePoints
In the FjordStaking contract, the locking mechanism works as follows:
Users stake their FJO tokens in a particular epoch (let's call it epoch X).
The staked tokens become eligible for rewards and are considered locked in the next epoch (X+1).
Once locked, the tokens remain locked for 6 epochs.
This design ensures that users commit their tokens for a significant period to be eligible for point distribution and staking rewards. The onStaked
function in FjordPoints, called by FjordStaking when users stake, increments the eligible stake amount for distributed points:
See: FJordStaking.stake
However, there exists a vulnerability in the interaction between the FjordStaking and FjordPoints contracts due to potential epoch desynchronization. This desynchronization can occur if the FjordPoints contract is deployed before the FjordStaking contract, leading to a discrepancy in epoch start and end times between the two contracts.
The root of the problem lies in how the epoch start times are set in the constructors of both contracts:
FjordPoints.sol:
FjordStaking.sol:
This time discrepancy allows a malicious actor to exploit the system by:
Staking just before the end of a FjordPoints epoch
Claiming points after point distribution
Unstaking immediately, as the FjordStaking contract's epoch hasn't advanced yet
This exploit is possible because the unstaking function in FjordStaking allows immediate unstaking if the current epoch matches the staking epoch:
See: FjordStaking.unstake
FjordPoints deployed at timestamp 1000
FjordStaking deployed at timestamp 1010
At timestamp 605790 (just before FjordPoints epoch end at 87400):
Attacker stakes a large amount of FJO tokens
At timestamp 605800 (FjordPoints epoch ends, point distribution occurs)
At timestamp 605805:
Attacker claims points
Attacker unstakes all tokens (possible because FjordStaking epoch hasn't ended, it ends at 605810)
This situation where FjordPoint is deployed before FjordStaking already happened as we can see in the testnet.
The reason is the deployment of FjordStaking requires a deployed address of FjordPoint.
The following test demonstrates the exact scenario in Example Scenario.
Steps
Create a new file, timediscrepency.t.sol
, in 2024-08-fjord/test/unit/
and paste the following test.
Run forge t --match-contract TimeDiscrepency -vv
Observe that BOB can claim the point and unstake immediately
This exploit undermines the intended locking mechanism and allows users to receive points without actually committing their tokens for the designed lock period. It breaks the point system's core principle of rewarding long-term stakers and could lead to an unfair distribution of points.
Synchronize epoch timings between FjordStaking and FjordPoints contracts:
Implement a shared epoch start time that both contracts reference.
Consider using contract factory for deployment
Impact: High - Users are getting an unreasonable amount of points through exploiting a vulnerability Likelihood: Low - Most of the times, when using the script, all deployment tx will get processed in the same block. But, there is a small chance for them to be processed in different blocks.
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.