The FjordStaking
contract fails to accurately track the dynamic nature of Sablier stream vesting, potentially allowing users to manipulate token accounting and gain unfair advantages in staking and rewards.
The FjordStaking
contract interacts with Sablier streams to allow users to stake vested tokens. However, the contract's implementation does not account for the continuous vesting nature of Sablier streams, leading to potential issues.
The stakeVested()
function captures the vested amount at a single point in time:
This static capture fails to account for tokens that vest after the initial stake. The _unstakeVested()
function similarly relies on this static amount:
The root cause of the issue is the failure to update the vested amount over time. This static approach leads to several critical issues:
Inaccurate vested amounts: The contract does not reflect the true vested balance as it changes over time.
Potential for double-spending: Users could stake more tokens than they actually have vested by manipulating the timing of their actions.
Loss of rewards: Tokens vesting after the initial stake are not accounted for in reward calculations.
Inconsistency with Sablier: The contract's view of vested tokens becomes out of sync with the actual state in the Sablier contract.
Users can manipulate the staking process to gain unfair advantages, leading to an imbalance in reward distribution and potential economic losses for the protocol and its users.
Alice has a Sablier stream of 1000 FJORD tokens vesting over 10 days.
On Day 1, 100 tokens have vested. Alice calls stakeVested()
with her stream ID.
The contract records that Alice has staked 100 tokens.
By Day 5, an additional 400 tokens have vested (total 500 vested).
Alice calls _unstakeVested()
to unstake 100 tokens.
Alice immediately calls stakeVested()
again.
The contract now records that Alice has staked 500 tokens, even though she only unstaked 100.
Alice has effectively "created" 400 tokens in the contract's accounting.
Manual review
To address this issue, the contract should implement a dynamic tracking system for vested tokens. Here's a high-level approach:
Instead of storing a static amount, store the stream ID and the last update time.
Implement a function to calculate the current vested amount based on the Sablier stream's current state.
Update the vested amount calculation in all relevant functions (staking, unstaking, reward calculations).
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.