Skip to content

Commit

Permalink
feat: round up protocol fee if it rounds down to 0
Browse files Browse the repository at this point in the history
  • Loading branch information
smol-ninja committed Nov 11, 2024
1 parent 8b32598 commit dd3c1ff
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 16 deletions.
4 changes: 3 additions & 1 deletion src/interfaces/ISablierFlow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,9 @@ interface ISablierFlow is
///
/// Notes:
/// - It sets the snapshot time to the `block.timestamp` if `amount` is greater than snapshot debt.
/// - A protocol fee may be charged on the withdrawn amount if the protocol fee is enabled for the streaming token.
/// - If the protocol fee is enabled for the streaming token:
/// - A percentage based protocol fee will be charged on the withdrawn amount.
/// - If withdrawn amount is so small that the protocol fee rounds down to zero, a fee of 1 token will be charged.
///
/// Requirements:
/// - Must not be delegate called.
Expand Down
13 changes: 11 additions & 2 deletions src/libraries/Helpers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ library Helpers {
// Calculate the fee amount based on the fee percentage.
feeAmount = ud(totalAmount).mul(fee).intoUint128();

// If `totalAmount` is so small that the `feeAmount` is rounded down to zero, set the fee to 1. This avoids
// exploiting the protocol fee by withdrawing very small amounts.
if (feeAmount == 0) {
feeAmount = 1;
}

// Calculate the net amount after subtracting the fee from the total amount.
netAmount = totalAmount - feeAmount;
}
Expand All @@ -46,8 +52,11 @@ library Helpers {
revert Errors.SablierFlow_BrokerAddressZero();
}

// Calculate the broker fee amount that is going to be transferred to the `broker.account`.
(brokerFeeAmount, depositAmount) = calculateAmountsFromFee(totalAmount, broker.fee);
// Calculate the broker fee amount.
brokerFeeAmount = ud(totalAmount).mul(broker.fee).intoUint128();

// Calculate the net amount after subtracting the broker fee from the total amount.
depositAmount = totalAmount - brokerFeeAmount;
}

/// @dev Descales the provided `amount` from 18 decimals fixed-point number to token's decimals number.
Expand Down
39 changes: 38 additions & 1 deletion tests/integration/concrete/withdraw/withdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ contract Withdraw_Integration_Concrete_Test is Integration_Test {
});
}

function test_GivenProtocolFeeNotZero()
function test_WhenFeeAmountRoundsDownToZero()
external
whenNoDelegateCall
givenNotNull
Expand All @@ -188,6 +188,43 @@ contract Withdraw_Integration_Concrete_Test is Integration_Test {
whenWithdrawalAddressOwner
whenAmountNotOverdraw
whenAmountEqualTotalDebt
givenProtocolFeeNotZero
{
// Go back to the starting point.
vm.warp({ newTimestamp: OCT_1_2024 });

resetPrank({ msgSender: users.sender });

// Create the stream and make a deposit.
uint256 streamId = createDefaultStream(tokenWithProtocolFee);
deposit(streamId, DEPOSIT_AMOUNT_6D);

// Simulate the one month of streaming.
vm.warp({ newTimestamp: WARP_ONE_MONTH });

// Make recipient the caller test.
resetPrank({ msgSender: users.recipient });

// It should deduct 1 token as protocol fee
_test_Withdraw({
streamId: streamId,
to: users.recipient,
depositAmount: DEPOSIT_AMOUNT_6D,
protocolFeeAmount: 1,
withdrawAmount: 99
});
}

function test_WhenFeeAmountNotRoundDownToZero()
external
whenNoDelegateCall
givenNotNull
whenAmountNotZero
whenWithdrawalAddressNotZero
whenWithdrawalAddressOwner
whenAmountNotOverdraw
whenAmountEqualTotalDebt
givenProtocolFeeNotZero
{
// Go back to the starting point.
vm.warp({ newTimestamp: OCT_1_2024 });
Expand Down
28 changes: 16 additions & 12 deletions tests/integration/concrete/withdraw/withdraw.tree
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,20 @@ Withdraw_Integration_Concrete_Test
β”‚ └── it should make the withdrawal
└── when amount exceed snapshot debt
β”œβ”€β”€ given protocol fee not zero
β”‚ β”œβ”€β”€ it should update the protocol revenue
β”‚ └── it should withdraw the net amount
β”‚ β”œβ”€β”€ when fee amount rounds down to zero
β”‚ β”‚ β”œβ”€β”€ it should update the protocol revenue by 1
β”‚ β”‚ └── it should make the withdrawal
β”‚ └── when fee amount not round down to zero
β”‚ β”œβ”€β”€ it should update the protocol revenue
β”‚ └── it should withdraw the net amount
└── given protocol fee zero
β”œβ”€β”€ given token has 18 decimals
β”‚ └── it should make the withdrawal
└── given token not have 18 decimals
β”œβ”€β”€ it should withdraw the withdrawable amount
β”œβ”€β”€ it should reduce the stream balance by the withdrawn amount
β”œβ”€β”€ it should reduce the aggregate amount by the withdrawn amount
β”œβ”€β”€ it should update snapshot debt to zero
β”œβ”€β”€ it should update snapshot time
β”œβ”€β”€ it should emit 1 {Transfer}, 1 {WithdrawFromFlowStream} and 1 {MetadataUpdated} events
└── it should return the withdrawn amount
β”œβ”€β”€ given token has 18 decimals
β”‚ └── it should make the withdrawal
└── given token not have 18 decimals
β”œβ”€β”€ it should withdraw the withdrawable amount
β”œβ”€β”€ it should reduce the stream balance by the withdrawn amount
β”œβ”€β”€ it should reduce the aggregate amount by the withdrawn amount
β”œβ”€β”€ it should update snapshot debt to zero
β”œβ”€β”€ it should update snapshot time
β”œβ”€β”€ it should emit 1 {Transfer}, 1 {WithdrawFromFlowStream} and 1 {MetadataUpdated} events
└── it should return the withdrawn amount
1 change: 1 addition & 0 deletions tests/integration/fuzz/withdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ contract Withdraw_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test {
vars.expectedProtocolRevenue = flow.protocolRevenue(token);
if (flow.protocolFee(token) > ZERO) {
vars.protocolFeeAmount = ud(withdrawAmount).mul(flow.protocolFee(token)).intoUint128();
if (vars.protocolFeeAmount == 0) vars.protocolFeeAmount = 1;
vars.expectedProtocolRevenue += vars.protocolFeeAmount;
}

Expand Down
4 changes: 4 additions & 0 deletions tests/utils/Modifiers.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@ abstract contract Modifiers {
WITHDRAW
//////////////////////////////////////////////////////////////////////////*/

modifier givenProtocolFeeNotZero() {
_;
}

modifier givenProtocolFeeZero() {
_;
}
Expand Down

0 comments on commit dd3c1ff

Please sign in to comment.