DeFiFoundry
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Incorrect Sablier integration in the `onStreamWithdrawn()` callback function can lead to user fund loss.

Summary

In Sablier version 1.1.2, utilized by the Fjord protocol, the stream sender has the ability to forcefully withdraw streams they initiated. When this happens, the onStreamWithdrawn() function callback is activated. However, in the FjordStaking contract, the onStreamWithdrawn() function lacks the necessary handling for this situation, potentially resulting in the loss of user funds.

Vulnerability Details

The current implementation permits users to stake streams solely from authorizedSablierSenders, yet it appears to assume that stream cancellation is the only possible action by an authorized stream sender.

However, Sablier's code also permits a stream sender to execute a force withdrawal:

In SablierV2Lockup.sol:

function withdraw(
uint256 streamId,
address to,
uint128 amount
)
public
override
noDelegateCall
updateMetadata(streamId)
{
...
// Checks: if `msg.sender` is the stream's sender, the withdrawal address must be the recipient.
if (isCallerStreamSender && to != recipient) {
revert Errors.SablierV2Lockup_InvalidSenderWithdrawal(streamId, msg.sender, to);
}
...

If a partially trusted authorizedSablierSender withdraws all stream IDs deposited into the FjordStaking contract, all transferred FJO tokens might be mistakenly considered as rewards, despite these tokens actually belonging to individual users.
This issue arises because the onStreamWithdrawn() function fails to adequately handle such situations.

Impact

Improper handling of stream withdrawals can lead to incorrect staking calculations, potentially resulting in honest users losing a portion of their rewards.

Tools Used

Manual Review

Recommendations

As we can't fully prevent the force withdrawal of a stream, making it crucial to properly manage the onStreamWithdrawn() callback. When the streamer calls on onStreamWithdrawn(), identify the stream owner and the vesting stake epoch for the given streamID. Adjust the accounting in the relevant user's DepositReceipt, transferring the corresponding amount from vestedStaked to staked. Additionally, reduce the global totalVestedStaked by the amount specified in the onStreamWithdrawn() function.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.