Flow

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

Withdraw Function Vulnerable to Fee-on-Transfer and Rebasing Tokens

Description:

The withdraw function assumes a normal ERC20 transfer will be used, but does not properly handle tokens with transfer fees or rebasing mechanics. This can cause accounting discrepancies, locked funds, and unexpected behavior when such tokens are streamed.

Summary:

Fee-on-transfer and rebasing tokens can lead to inconsistencies between the withdraw function's amount parameter and the actual tokens received by the to address. This breaks internal bookkeeping and can result in lost funds or incorrect balances. The function's assumptions about token behavior create vulnerabilities when used with non-standard ERC20s.

Vulnerability Details:

The key issue is in the withdraw function in SablierFlow.sol:

function _withdraw(uint256 streamId, address to, uint128 amount) internal returns (uint128 withdrawnAmount, uint128 protocolFeeAmount) {
// Update stream balance
_streams[streamId].balance -= amount;
// Transfer tokens to recipient
token.safeTransfer(to, amount);
// Protocol Invariant: the difference in total debt should be equal to the difference in the stream balance
assert(totalDebt - _totalDebtOf(streamId) == balance - _streams[streamId].balance);
}

For fee-on-transfer tokens, the amount deducted from _streams[streamId].balance will be greater than the tokens actually received by to, since a fee is taken out. This means the stream balance will be decremented by more than the true amount withdrawn.

For rebasing tokens, the token contract balance can change between the safeTransfer and the balance update. If the token rebases up, the stream's balance will end up lower than expected. If it rebases down, the stream balance will be higher than the contract actually holds.

In both cases, the invariant at the end will fail, reverting the withdraw. Over time, locked funds can accumulate that cannot be withdrawn due to this.

Impact:

Medium. When used with fee-on-transfer or rebasing tokens, withdraw will routinely fail due to the broken invariant. User funds can become stuck in the contract. While the admin could allow withdrawals by temporarily removing the invariant check, this requires a high degree of active management and trust in the admin.

Tools Used:

  • Manual code review

Recommendations:

  1. For fee-on-transfer tokens, calculate the actual amount received and use that to update internal accounting, rather than the original amount.

  2. For rebasing tokens, take a balance snapshot before and after the transfer to determine the change, and use this "realized amount" to update the stream balance.

  3. Consider adding warnings or restrictions when creating streams with these token types to prevent users accidentally using incompatible tokens.

  4. Longer-term, explore designs that isolate each stream's balance to limit the impact of individual token issues.

Updates

Lead Judging Commences

inallhonesty Lead Judge
8 months ago
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Known issue
inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Known issue

Support

FAQs

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