Sablier

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

Stream cancellation will be impossible if `sender` is blocklisted by underlying asset

Summary

A sender when cancelling his streams has no ways of specifying the address to refund the senderAmount to which can lead to cancellations being impossible if he's blocklisted by underlying asset as the refund cannot be processed.

Vulnerability Details

When the sender decides to cancel the stream, he calls the cancel function passing "only" the streamId, this is important to keep in mind as it means that further down the line, the amount to be refunded will be sent to the msg.sender which is established in the cancel function to be the sender of the stream. The internal _cancel function is then called.

function cancel(uint256 streamId) public override noDelegateCall notNull(streamId) {
..SNIP..
// Check: `msg.sender` is the stream's sender.
if (!_isCallerStreamSender(streamId)) {
revert Errors.SablierV2Lockup_Unauthorized(streamId, msg.sender);
}
// Checks, Effects and Interactions: cancel the stream.
_cancel(streamId);
}

The _cancel function performs the needed calculations, including calculating the senderAmount which is the amount to refund, retrieving the sender's address and also the asset. The function then attempts to transfer the senderAmount to the sender's address.

function _cancel(uint256 streamId) internal {
..SNIP..
// Effect: set the refunded amount.
_streams[streamId].amounts.refunded = senderAmount;
// Retrieve the sender and the recipient from storage.
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 });
..SNIP..
}

Now, from the readme, the protocol is compatible with any ERC20 tokens expect for the assumptions listed here

Compatibilities

Sablier protocol is compatible with the following:

Any network which is EVM compatible

Any ERC20 token

This means that the asset can be any ERC20 token, including tokens that have the blocklist functionality. Many commonly used token including USDC, USDT, ONDO have this functionality and can blocklist users from transfers. A typical blocklist token looks like this, or a variation meaning that a blocked sender's refund transaction will fail and the stream will be uncancellable.

function transferFrom(address src, address dst, uint wad) override public returns (bool) {
require(!blocked[src], "blocked");
require(!blocked[dst], "blocked");
return super.transferFrom(src, dst, wad);
}

This can also cause a temporary DOS when attempting to cancel multiple streams of which the asset's stream is among.

function cancelMultiple(uint256[] calldata streamIds) external override noDelegateCall {
// Iterate over the provided array of stream IDs and cancel each stream.
uint256 count = streamIds.length;
for (uint256 i = 0; i < count; ++i) {
// Effects and Interactions: cancel the stream.
cancel(streamIds[i]);
}
}

Impact

Sender will not be able to canel a stream.

Tools Used

Manual Review

Recommendations

Consider allowing the sender to be able to specify a to in the cancel function. The to will be refunded the senderAmount. If no to is provided, the refund will be made to the sender instead.

function _cancel(uint256 streamId, address to) internal {
..SNIP..
// Effect: set the refunded amount.
_streams[streamId].amounts.refunded = senderAmount;
// Retrieve the sender and the recipient from storage.
if (to == address(0)) {
address sender = _streams[streamId].sender;
} else {
address sender = to;
}
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 });
..SNIP..
}
Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

0xnevi Judge
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

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