Skip to content

Commit

Permalink
fix: revert in withdraw if the balance is zero
Browse files Browse the repository at this point in the history
  • Loading branch information
andreivladbrg committed Apr 19, 2024
1 parent 59ae9f0 commit f470114
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 22 deletions.
5 changes: 5 additions & 0 deletions src/SablierV2OpenEnded.sol
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,11 @@ contract SablierV2OpenEnded is ISablierV2OpenEnded, NoDelegateCall, SablierV2Ope
revert Errors.SablierV2OpenEnded_WithdrawalTimeInTheFuture(time, block.timestamp);
}

// Check: the stream balance is not zero.
if (_streams[streamId].balance == 0) {
revert Errors.SablierV2OpenEnded_WithdrawBalanceZero(streamId);
}

// Calculate how much to withdraw based on the time reference.
uint128 withdrawAmount = _withdrawableAmountOf(streamId, time);

Expand Down
4 changes: 2 additions & 2 deletions src/interfaces/ISablierV2OpenEnded.sol
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,8 @@ interface ISablierV2OpenEnded is ISablierV2OpenEndedState {
/// - `streamId` must not reference a null or canceled stream.
/// - `to` must not be the zero address.
/// - `to` must be the recipient if `msg.sender` is not the stream's recipient.
/// - `time` must be greater than the stream's `lastTimeUpdate`.
/// - `time` must not be greater than the `block.timestamp`.
/// - `time` must be greater than the stream's `lastTimeUpdate` and must not be in the future.
/// - The stream balance must be greater than zero.
///
/// @param streamId The id of the stream to withdraw from.
/// @param to The address receiving the withdrawn assets.
Expand Down
3 changes: 3 additions & 0 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,7 @@ library Errors {

/// @notice Thrown when trying to withdraw to the zero address.
error SablierV2OpenEnded_WithdrawToZeroAddress();

/// @notice Thrown when trying to withdraw but the stream balance is zero.
error SablierV2OpenEnded_WithdrawBalanceZero(uint256 streamId);
}
18 changes: 18 additions & 0 deletions test/integration/withdraw/withdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,24 @@ contract Withdraw_Integration_Test is Integration_Test {
openEnded.withdraw({ streamId: defaultStreamId, to: users.recipient, time: futureTime });
}

function test_RevertWhen_BalanceZero()
external
whenNotDelegateCalled
givenNotNull
givenNotCanceled
whenToNonZeroAddress
whenWithdrawalAddressIsRecipient
whenWithdrawalTimeGreaterThanLastUpdate
whenWithdrawalTimeNotInTheFuture
{
vm.warp({ newTimestamp: WARP_ONE_MONTH - ONE_MONTH });
uint256 streamId = createDefaultStream();
vm.warp({ newTimestamp: WARP_ONE_MONTH });

vm.expectRevert(abi.encodeWithSelector(Errors.SablierV2OpenEnded_WithdrawBalanceZero.selector, streamId));
openEnded.withdraw({ streamId: streamId, to: users.recipient, time: WITHDRAW_TIME });
}

function test_Withdraw_CallerSender()
external
whenNotDelegateCalled
Expand Down
39 changes: 21 additions & 18 deletions test/integration/withdraw/withdraw.tree
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,24 @@ withdraw.t.sol
β”œβ”€β”€ when the withdrawal time is in the future
β”‚ └── it should revert
└── when the withdrawal time is not in the future
β”œβ”€β”€ when the caller is not the recipient
β”‚ β”œβ”€β”€ when the caller is the sender
β”‚ β”‚ └── it should make the withdrawal
β”‚ └── when the caller is unknown
β”‚ └── it should make the withdrawal
└── when the caller is the recipient
β”œβ”€β”€ given the asset does not have 18 decimals
β”‚ β”œβ”€β”€ it should make the withdrawal
β”‚ β”œβ”€β”€ it should update the time
β”‚ β”œβ”€β”€ it should update the stream balance
β”‚ β”œβ”€β”€ it should perform the ERC-20 transfer
β”‚ └── it should emit a {Transfer} and {WithdrawFromOpenEndedStream} event
└── given the asset has 18 decimals
β”œβ”€β”€ it should make the withdrawal
β”œβ”€β”€ it should update the time
β”œβ”€β”€ it should update the stream balance
β”œβ”€β”€ it should perform the ERC-20 transfer
└── it should emit a {Transfer} and {WithdrawFromOpenEndedStream} event
β”œβ”€β”€ when the balance is zero
β”‚ └── it should revert
└── when the balance is not zero
β”œβ”€β”€ when the caller is not the recipient
β”‚ β”œβ”€β”€ when the caller is the sender
β”‚ β”‚ └── it should make the withdrawal
β”‚ └── when the caller is unknown
β”‚ └── it should make the withdrawal
└── when the caller is the recipient
β”œβ”€β”€ given the asset does not have 18 decimals
β”‚ β”œβ”€β”€ it should make the withdrawal
β”‚ β”œβ”€β”€ it should update the time
β”‚ β”œβ”€β”€ it should update the stream balance
β”‚ β”œβ”€β”€ it should perform the ERC-20 transfer
β”‚ └── it should emit a {Transfer} and {WithdrawFromOpenEndedStream} event
└── given the asset has 18 decimals
β”œβ”€β”€ it should make the withdrawal
β”œβ”€β”€ it should update the time
β”œβ”€β”€ it should update the stream balance
β”œβ”€β”€ it should perform the ERC-20 transfer
└── it should emit a {Transfer} and {WithdrawFromOpenEndedStream} event
6 changes: 4 additions & 2 deletions test/invariant/handlers/OpenEndedHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,11 @@ contract OpenEndedHandler is BaseHandler {
}

function deposit(
uint256 timeJumpSeed,
uint256 streamIndexSeed,
uint128 depositAmount
)
external
instrument("deposit")
adjustTimestamp(timeJumpSeed)
useFuzzedStream(streamIndexSeed)
useFuzzedStreamSender
{
Expand Down Expand Up @@ -245,6 +243,10 @@ contract OpenEndedHandler is BaseHandler {
return;
}

if (openEnded.getBalance(currentStreamId) == 0) {
return;
}

// Bound the time so that it is between last time update and current time.
time = uint40(_bound(time, openEnded.getLastTimeUpdate(currentStreamId) + 1, block.timestamp));

Expand Down

0 comments on commit f470114

Please sign in to comment.