The Fjord Protocol allows users to stake linear stream vested tokens from Sablier. It calculates the staking token amount when the vesting tokens are initially added to the protocol. However, the protocol does not implement a withdraw hook from Sablier, so it assumes the vested token amount in Sablier remains unchanged. Consequently, rewards are distributed to the NFT owner based on the initially calculated amount, even if tokens are later withdrawn from Sablier. Keep in mind that the sender of the stream in Sablier can call the withdraw function. Refer to the subsequent section for more details.
Let have a Look at the withdraw function of Sbailer@V1.1.2 :
In the code snippet, at @1>
, the code checks if the caller is the stream sender. Then, at @2>
, it validates whether the caller is either the sender, recipient, or an approved recipient. If this validation passes, it is considered a valid call. The withdraw function than send the token to the recipient which in our case will be staking contract.
The issue is that the Fjord staking contract does not implement the onStreamWithdrawn
hook, which falsely assumes that the stream still holds the exact same amount of tokens as it did initially.
Let break down this case in following steps :
Bob has a stream with an available amount = 100 ether
and NFT = 1
.
Bob calls stakeVested
with streamId = 1
.
Fjord calculates the available amount and creates deposits
with 100 ether for the currentEpoch
.
From this point onward, the rewards for this staking will be calculated based on _amount = 100 ether
, regardless of whether the sender calls the withdraw function on Sablier.
The sender withdraws 50 tokens from the stream on Sablier. However, due to the absence of an onStreamWithdrawn
hook implementation, Fjord will still assume that the stream holds 100 tokens. In reality, the stream now contains fewer tokens, which can result in a loss for the Fjord staking contract. This is because Fjord relies on the stream holding the full 100 tokens and accumulates rewards based on this assumption.
I suggest that if Fjord decides to use Sablier's latest version (1.2.2), anyone can call the withdraw function as long as the recipient is valid. Therefore, if the team plans to use the latest version in the future, they must implement the onStreamWithdrawn
hook. Version@1.2.2.
Fjord Staking will calculate incorrect rewards for vested stakes if tokens are withdrawn from the stream on Sablier.
Manual review
Implement the onStreamWithdrawn
hook with the following considerations:
Update the user's staked amount based on the received amount in the hook. Subtract this amount from the user's totalStaked
.
If the remaining amount after subtraction is zero, delete the staking record and transfer the NFT back to the owner.
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.