In Sablier version 1.1.2, used by the Fjord protocol, the stream sender can force withdrawal of streams they created. When this occurs, the onStreamWithdrawn()
function callback is triggered. However, the onStreamWithdrawn()
function in the FjordStaking
contract does not contain code to handle this scenario, which can lead to user funds being lost.
Similar issue was identified in the Cyfrin audit (and was incorrectly "fixed" by removing the code within the onStreamWithdrawn()
function).
Current implementation allows users to stake streams only from authorizedSablierSenders
, but the code seems to assume that stream cancellation is the only operation that an authorized stream sender might perform.
Unfortunately, Sablier's code allows for a force withdrawal by the stream's sender:
If a partially trusted authorizedSablierSender
initiates a withdrawal for all stream IDs deposited into the FjordStaking
contract, all transferred FJO
tokens might be treated as rewards, even though these tokens actually belong to individual users.
This mismanagement occurs because the onStreamWithdrawn()
function does not properly account for such scenarios.
Proper Sablier integration in the onStreamWithdrawn()
function is necessary to prevent this issue.
Loss of user funds.
https://github.com/sablier-labs/v2-core/blob/v1.1.2/src/abstracts/SablierV2Lockup.sol#L260-L262
https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordStaking.sol#L792-L799
Manual review.
The force withdrawal from stream can't be prevented. It is essential to properly handle onStreamWithdrawn()
callback.
In the event that the onStreamWithdrawn()
callback is triggered, retrieve the stream owner and the epoch of the vesting stake of the streamID. Adjust the accounting in the appropriate user DepositReceipt
and move the corresponding amount from vestedStaked
to staked
. Additionally, decrease the global totalVestedStaked
by the amount
parameter from the onStreamWithdrawn()
function.
This approach ensures that the vested portion of the stake is correctly transferred to the staked portion, corresponding to the amount of tokens withdrawn through a force withdrawal.
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.