Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Valid

Stream's sender may not able to cancel the stream

Summary

In some edge cases, a stream sender cannot cancel a stream because the _cancel() function attempts to transfer the asset to the sender.

Vulnerability Details

USDC can be paused, or users can get blacklisted. When USDC is paused or a user gets blacklisted, transferring USDC will revert.

Scenario:

  1. Stream Creation: Bob creates a stream with the following parameters:

    • Recipient: Alice

    • IsCancellable: true

    • Asset: USDC

  2. Cancelling the Stream: As the assets are being streamed, Bob wants to cancel the stream and retrieve the remaining balance.

  3. Blacklisting or Pausing: Let's say Bob gets blacklisted by USDC or USDC is paused.

  4. Cancellation Reversion: Since the _cancel() function uses the push method to transfer the remaining balance of the stream to the stream's sender (in this case, Bob), the transaction will revert because Bob is blacklisted or USDC is paused.

    address sender = _streams[streamId].sender;
    address recipient = _ownerOf(streamId);
    // Retrieve the ERC-20 asset from storage.
    IERC20 asset = _streams[streamId].asset;
    // Interaction: refund the sender.
    asset.safeTransfer({ to: sender, value: senderAmount });

    USDC.sol:

    function transfer(address to, uint256 value)
    external
    override
    whenNotPaused
    notBlacklisted(msg.sender)
    notBlacklisted(to)
    returns (bool)
    {
    _transfer(msg.sender, to, value);
    return true;
    }

Since there is no other option left for Bob to retrieve the assets, Alice continues to earn the assets over time. In the worst-case scenario, Bob will lose all his assets to Alice.

Impact

In the worst case stream's sender will loose all his assets.

Tools Used

Manual review

Recommendations

To address the issue, we can implement a pull method instead of pushing the assets to the stream's sender when canceling the stream. This approach ensures that the sender can cancel the stream without automatically transferring the asset in the _cancel() function. Since _streams[streamId].amounts.refunded already keeps track of the sender's refundable amount, we can introduce a new function. This function takes the streamId as input and retrieves the refundable amount for the sender, allowing the sender to pull the assets.

Here's how we can implement this:

  1. Modify the _cancel() function to remove the asset transfer operation.

  2. Implement a new function, let's call it claimRefund(), which allows the sender to retrieve their refundable amount.

Here's the updated pseudo-code:

function _cancel(uint256 streamId) internal {
// Additional logic here if needed
// Update stream status to cancelled
_streams[streamId].status = Status.Cancelled;
}
function claimRefund(uint256 streamId) external {
// Ensure the sender is the stream sender
require(msg.sender == _streams[streamId].sender, "Sender is not the stream sender");
// Retrieve the refundable amount
uint256 refundableAmount = _streams[streamId].amounts.refunded;
// Ensure there are funds to refund
require(refundableAmount > 0, "No refundable amount");
// Transfer the refundable amount to the sender
_streams[streamId].amounts.refunded = 0; // Reset the refunded amount
_streams[streamId].asset.safeTransfer(msg.sender, refundableAmount);
}

With this implementation, the sender can call the claimRefund() function to pull the refundable amount from the stream, thereby allowing them to cancel the stream without automatically transferring the assets.

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Push over pull for `_cancel` in context of Pausable tokens

0xnevi Judge
about 1 year ago
amow Auditor
about 1 year ago
0xg0p1 Submitter
about 1 year ago
0xspryon Auditor
about 1 year ago
ge6a Judge
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

Push over pull for `_cancel` in context of Pausable tokens

Support

FAQs

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