The SablierFlow
contract does not correctly handle ERC20 tokens that implement transfer fees (i.e., tokens that deduct a fee upon transfer). When users interact with such tokens, the contract's balance calculations become inaccurate, leading to potential loss of funds, incorrect stream balances, and unexpected behaviors.
The SablierFlow
contract is designed to handle token streams by transferring tokens from the sender to the recipient over time. However, it assumes that the amount of tokens transferred equals the amount specified in the deposit
function. This assumption fails with ERC20 tokens that implement a transfer fee (also known as deflationary tokens), where a percentage of the transferred amount is deducted as a fee.
In the provided test case, a MockTransferFeeToken
with a 2% transfer fee is used. When the sender attempts to deposit 100 tokens into the stream, only 98 tokens are actually transferred to the SablierFlow
contract due to the 2% fee. The contract, unaware of this discrepancy, still believes it has received 100 tokens, leading to incorrect balance calculations.
First create the mock file: tests/mocks/MockTransferFeeToken.sol
with this content:
Create the main test file: tests/SablierFlowTransferFeeTest.t.sol
with the following content and then run the test with command: forge test --mt testDepositWithTransferFee -vvvv
Deploy Contracts and Setup
Deploy the MockTransferFeeToken
with a 2% transfer fee.
Deploy the SablierFlow
contract.
Mint and Approve Tokens
Mint 1,000 tokens to the sender
.
Approve the SablierFlow
contract to spend the sender
's tokens.
Create a Stream
The sender
creates a stream to the recipient
with a rate of 1 token per second.
Deposit Tokens
The sender
deposits 100 tokens into the stream.
Due to the 2% transfer fee, only 98 tokens are actually transferred to the SablierFlow
contract.
Check Stream Balance
Retrieve the balance of the stream using getBalance(streamId)
.
Observe that the balance is 98 tokens instead of the expected 100 tokens.
The SablierFlow
contract does not account for the possibility that the amount of tokens transferred might be less than the amount specified due to transfer fees. It assumes a 1:1 correspondence between the intended transfer amount and the actual amount received. This assumption fails with tokens that implement a transfer fee, leading to incorrect balance tracking and potential loss of funds.
Incorrect Balance Calculations: The contract overestimates the tokens it holds, leading to mismanagement of the token streams.
Loss of Funds: Recipients may receive fewer tokens than expected, and senders might be charged for tokens that are not properly allocated.
Contract Malfunction: Functions that rely on accurate balance calculations may fail or behave unpredictably.
Security Risks: Attackers might exploit this discrepancy to manipulate stream balances or drain funds.
Foundry: A smart contract development and testing framework.
Solidity Compiler: Version 0.8.22.
Mock Contracts: MockTransferFeeToken
to simulate a token with a transfer fee.
Forge Test Suite: To write and execute the test case demonstrating the vulnerability.
Implement Safe Transfer Mechanisms
Use OpenZeppelin's SafeERC20
library, specifically the safeTransferFrom
function, which checks the actual amount of tokens transferred.
Adjust Balance Calculations
Calculate the difference between the contract's balance before and after the transfer to determine the actual amount received.
Update internal accounting to use the actual received amount.
Validate Token Compatibility
Implement checks to ensure that only tokens without transfer fees are allowed, or
Maintain a whitelist of supported tokens known to be compatible.
User Notifications
Inform users attempting to use tokens with transfer fees about potential issues.
Provide clear error messages when unsupported tokens are used.
Comprehensive Testing
Include test cases with tokens that have transfer fees, deflationary tokens, or tokens with other non-standard behaviors.
Use mock tokens to simulate various ERC20 token behaviors during testing.
Audit Third-Party Tokens
Before accepting a token into the platform, audit its transfer behavior to ensure compatibility.
Fallback Mechanisms
Implement fallback logic to handle cases where the transferred amount is less than expected, possibly reverting the transaction with an informative error message.
By implementing these recommendations, the SablierFlow
contract can safely interact with a wider range of ERC20 tokens, including those with transfer fees, and prevent potential loss of funds or incorrect stream balances.
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.