Flow

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

Fee on Transfer tokens would affect internal accounting

Description

The protocol allows for any type of token to be used. In a situation where a FOT token is used, the amount actually been received in a stream deposit would be lesser than what the protocol accounts for, which would lead to the balance of that token in the protocol been overstated and also affecting the accounting in the protocol, as stream balance was used a reasonable amounts of time in the protocol to determine sensitive values in the protocol.

POC

Below is proof that the balance variable of a FOT token in a particular stream would be overstated.

// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.8.22;
import { ud, UD60x18 } from "@prb/math/src/UD60x18.sol";
import { UD21x18 } from "@prb/math/src/UD21x18.sol";
import { Test, console } from "forge-std/src/Test.sol";
import { FlowNFTDescriptor } from "src/FlowNFTDescriptor.sol";
import { SablierFlow } from "src/SablierFlow.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20WithFee is ERC20 {
uint private constant FEE_BPS = 100;
uint private constant BPS = 10000;
constructor(
string memory _name,
string memory _symbol,
uint _totalSupply
) ERC20(_name, _symbol) {
_mint(msg.sender, _totalSupply);
}
function getReceivedAmount(
address, /*_from */
address, /*_to*/
uint _sentAmount
) public pure returns (uint receivedAmount, uint feeAmount) {
receivedAmount = (_sentAmount * (BPS - FEE_BPS)) / BPS;
feeAmount = _sentAmount - receivedAmount;
}
function transfer(
address to,
uint value
) public override returns(bool) {
(uint transferAmount, uint burnAmount) = getReceivedAmount(msg.sender, to, value);
_burn(msg.sender, burnAmount);
super._transfer(msg.sender ,to, transferAmount);
return true;
}
function transferFrom(address from, address to, uint256 value) public override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
(uint transferAmount, uint burnAmount) = getReceivedAmount(spender, to, value);
_burn(from, burnAmount);
super._transfer(from ,to, transferAmount);
return true;
}
}
contract POC is Test {
SablierFlow flow;
FlowNFTDescriptor nftDescriptor;
MockERC20WithFee mock;
address admin = makeAddr("admin");
address sender = makeAddr("sender");
address recipient = makeAddr("recipient");
function setUp() public {
vm.startPrank(admin);
nftDescriptor = new FlowNFTDescriptor();
flow = new SablierFlow(admin, nftDescriptor );
mock = new MockERC20WithFee("MockERC20WithFee", "MOCK", 100 ether);
mock.transfer(sender, 12 ether);
vm.stopPrank();
}
function test_poc() public {
vm.startPrank(sender);
UD21x18 ratePerSecond = UD21x18.wrap(0.001e18);
flow.create(sender, recipient, ratePerSecond, mock, true);
mock.approve(address(flow), 10 ether);
flow.deposit(1, 10 ether, sender, recipient);
assert(mock.balanceOf(address(flow)) < 10 ether);
}
}

Impact

Inaccurate accounting.

Tool Used

Manual Review

Recommendation

Use before and after balance to accurately reflect the true amount received/A token whitelist system

Updates

Lead Judging Commences

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

Support

FAQs

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