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

When a staked Sablier NFT is cancelled, the stream owner can claim more rewards than expected

Summary

Users have the ability to stake Sablier NFT, streaming Fjord tokens to earn rewards.

The effectively staked amount is the amount of tokens deposited in the stream minus the amount of tokens withdrawn by the user. The refundedAmount can be omitted as it will always equal 0 unless the stream is effectively cancelled.

https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordStaking.sol#L414

uint256 _amount = depositedAmount - (withdrawnAmount + refundedAmount);

Authorized Fjord multisig addresses have the ability to cancel a stream that is staked which will reduce the staked amount of the user by the amount of tokens that have not been streamed yet.

Cancelling a staked stream will trigger the onStreamCanceled() callback which in turn will trigger _unstakeVested() responsible for reducing the user's staked amount.

https://github.com/Cyfrin/2024-08-fjord/blob/main/src/FjordStaking.sol#L819-L840

/// @param senderAmount The amount of assets refunded to the stream's sender, denoted in units of the asset's
function onStreamCanceled(
uint256 streamId,
address sender,
uint128 senderAmount,
uint128 /*recipientAmount*/
) external override onlySablier checkEpochRollover {

Users have the ability to use claimRewards() to start a claiming process that will take at least 3 weeks (3 epochs).

This process will save the rewards owed to the user in the claimReceipts storage variable. The amount owed is calculated based upon the amount of tokens the user has staked.

Vulnerability Details

The issue is that a user has the ability to trigger the claiming process right before his staked stream is cancelled. Effectively claiming rewards as if he staked the entire stream while in reality he only partially staked his stream.

Let the following scenario :

  • user stakes a Sablier NFT that is streaming 1000 tokens

  • multiple epochs pass and the user has the ability to claim rewards but keeps waiting

  • an authorized Fjord multisig address attempts to cancel the stream when it has 500 tokens left to be streamed

  • the user frontruns this transaction and calls claimRewards() which will register the owed rewards for 1000 tokens

  • the stream is cancelled and Fjord retrieves his 500 tokens

  • after 3 epochs, the user can call completeClaimRequest() to get his rewards for staking 1000 tokens while he effectively only staked 500 of his tokens

Impact

A user has the ability to claim an amount of rewards that does not align with the amount of tokens that was effectively staked.

Tools Used

Manual review

Recommendations

When a Sablier stream is cancelled, make sure the _unstakeVested() function updates the claimReceipts variable representing the user's unclaimedRewards to account for the amount of tokens retrieved by Fjord.

Updates

Lead Judging Commences

inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

greed Submitter
10 months ago
inallhonesty Lead Judge
10 months ago
inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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