The onStreamCanceled
function is triggered by the Sablier contract to unstake a stream owner's vested FJORD tokens. It begins by calling _redeem
to update the streamOwner’s rewards, then proceeds with _unstakeVested
to partially or fully unstake the vested FJORD tokens.
Here's the _unstakeVested
where msg.sender
is used instead of streamOwner
.
As the onStreamCanceled
function is always called by the Sablier, it's wrong to use msg.sender
in the _unstakeVested
function.
Hence, the onUnstaked
function incorrectly assigns the Sablier address to the user variable, causing the function to revert because the original staking points were correctly assigned to the streamOwner
.
Since the onStreamCanceled
function is executed within a try-catch block, this revert leads to the points.onUnstaked
call being bypassed. Consequently, the user's totalStaked in the FjordPoints contract remains unchanged, enabling them to continue earning points on the unstaked amount and receiving more rewards than they should.
The explicit streamOwner
will gain more rewards than they should, and this can also be used to exploit the system to earn extra rewards.
Manual Review
To resolve this issue, ensure that the streamOwner
is passed to the points.onUnstaked
function instead of msg.sender
. This will correctly attribute the unstaking to the streamOwner
and adjust their points accordingly.
Indeed the `points.onUnstaked` should use the streamOwner instead of msg.sender as an input parameter. Impact: high - The vested stakers who got their streams canceled will keep on receiving rewards (points included) for the previously staked stream. Likelihood: low - whenever a Sablier stream sender decides to `cancel()` a recipient's stream
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.