Skip to content

Commit

Permalink
feat: implement getDepletionTime() (#118)
Browse files Browse the repository at this point in the history
* feat: function depletionTimeOf()

* test: rename defaultDeposit to depositToDefaultStream

* refactor: use uint40 for the return value of depletionTimeOf

refactor: improve readability in depletionTimeOf

---------

Co-authored-by: andreivladbrg <[email protected]>
  • Loading branch information
smol-ninja and andreivladbrg authored May 24, 2024
1 parent ef8b7ce commit 0835718
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 16 deletions.
32 changes: 32 additions & 0 deletions src/SablierV2OpenEnded.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,38 @@ contract SablierV2OpenEnded is
CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc ISablierV2OpenEnded
function depletionTimeOf(uint256 streamId)
external
view
override
notNull(streamId)
notPaused(streamId)
returns (uint40 depletionTime)
{
uint128 balance = _streams[streamId].balance;

// If the stream balance is zero, return zero.
if (balance == 0) {
return 0;
}

// Calculate here the recipient amount for gas optimization.
uint128 recipientAmount =
_streams[streamId].remainingAmount + _streamedAmountOf(streamId, uint40(block.timestamp));

// If the stream has debt, return zero.
if (recipientAmount >= balance) {
return 0;
}

// Safe to unchecked because subtraction cannot underflow.
unchecked {
uint128 solvencyPeriod = (balance - recipientAmount) / _streams[streamId].ratePerSecond;
depletionTime = uint40(block.timestamp + solvencyPeriod);
}
}

/// @inheritdoc ISablierV2OpenEnded
function refundableAmountOf(uint256 streamId)
external
Expand Down
11 changes: 11 additions & 0 deletions src/interfaces/ISablierV2OpenEnded.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,17 @@ interface ISablierV2OpenEnded is
CONSTANT FUNCTIONS
//////////////////////////////////////////////////////////////////////////*/

/// @notice Returns the timestamp at which the stream depletes its balance and starts to accumulate debt.
/// @dev Reverts if `streamId` refers to a paused or a null stream.
///
/// Notes:
/// - If the stream has no debt, it returns the timestamp when the debt begins based on current balance and rps.
/// - If the stream has debt, it returns 0.
///
/// @param streamId The stream ID for the query.
/// @return depletionTime The UNIX timestamp.
function depletionTimeOf(uint256 streamId) external view returns (uint40 depletionTime);

/// @notice Calculates the amount that the sender can refund from stream, denoted in 18 decimals.
/// @dev Reverts if `streamId` references a null stream.
/// @param streamId The stream ID for the query.
Expand Down
8 changes: 2 additions & 6 deletions test/integration/Integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,8 @@ abstract contract Integration_Test is Base_Test {
});
}

function defaultDeposit() internal {
defaultDeposit(defaultStreamId);
}

function defaultDeposit(uint256 streamId) internal {
openEnded.deposit(streamId, DEPOSIT_AMOUNT);
function depositToDefaultStream() internal {
openEnded.deposit(defaultStreamId, DEPOSIT_AMOUNT);
}

/*//////////////////////////////////////////////////////////////////////////
Expand Down
42 changes: 42 additions & 0 deletions test/integration/depletion-time-of/depletionTimeOf.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity >=0.8.22;

import { Integration_Test } from "../Integration.t.sol";

contract DepletionTimeOf_Integration_Test is Integration_Test {
function test_RevertGiven_Null() external {
// it should revert
expectRevertNull();
openEnded.depletionTimeOf(nullStreamId);
}

function test_RevertGiven_Paused() external givenNotNull {
// it should revert
expectRevertPaused();
openEnded.depletionTimeOf(defaultStreamId);
}

function test_WhenBalanceIsZero() external view givenNotNull givenNotPaused {
// it should return 0
uint40 depletionTime = openEnded.depletionTimeOf(defaultStreamId);
assertEq(depletionTime, 0, "depletion time");
}

modifier whenBalanceIsNotZero() {
depositToDefaultStream();
_;
}

function test_WhenStreamHasDebt() external givenNotNull givenNotPaused whenBalanceIsNotZero {
vm.warp({ newTimestamp: block.timestamp + SOLVENCY_PERIOD });
// it should return 0
uint40 depletionTime = openEnded.depletionTimeOf(defaultStreamId);
assertEq(depletionTime, 0, "depletion time");
}

function test_WhenStreamHasNoDebt() external givenNotNull givenNotPaused whenBalanceIsNotZero {
// it should return the time at which the stream depletes its balance
uint40 depletionTime = openEnded.depletionTimeOf(defaultStreamId);
assertEq(depletionTime, block.timestamp + SOLVENCY_PERIOD, "depletion time");
}
}
14 changes: 14 additions & 0 deletions test/integration/depletion-time-of/depletionTimeOf.tree
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
DepletionTimeOf_Integration_Test
├── given null
│ └── it should revert
└── given not null
├── given paused
│ └── it should revert
└── given not paused
├── when balance is zero
│ └── it should return 0
└── when balance is not zero
├── when stream has debt
│ └── it should return 0
└── when stream has no debt
└── it should return the time at which the stream depletes its balance
2 changes: 1 addition & 1 deletion test/integration/pause/pause.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ contract Pause_Integration_Test is Integration_Test {
givenWithdrawableAmountNotZero
givenRefundableAmountNotZero
{
defaultDeposit();
depositToDefaultStream();

uint128 withdrawableAmount = openEnded.withdrawableAmountOf(defaultStreamId);

Expand Down
2 changes: 1 addition & 1 deletion test/integration/refund-from-stream/refundFromStream.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract RefundFromStream_Integration_Test is Integration_Test {
function setUp() public override {
Integration_Test.setUp();

defaultDeposit();
depositToDefaultStream();

vm.warp({ newTimestamp: WARP_ONE_MONTH });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ contract RefundableAmountOf_Integration_Test is Integration_Test {
}

function test_RefundableAmountOf_Paused() external givenNotNull {
defaultDeposit();
depositToDefaultStream();
openEnded.refundableAmountOf(defaultStreamId);

vm.warp({ newTimestamp: WARP_ONE_MONTH });
Expand All @@ -39,7 +39,7 @@ contract RefundableAmountOf_Integration_Test is Integration_Test {
}

function test_RefundableAmountOf() external givenNotNull givenNotPaused {
defaultDeposit();
depositToDefaultStream();

vm.warp({ newTimestamp: WARP_ONE_MONTH });
uint128 refundableAmount = openEnded.refundableAmountOf(defaultStreamId);
Expand Down
4 changes: 2 additions & 2 deletions test/integration/stream-debt-of/streamDebtOf.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ contract StreamDebtOf_Integration_Test is Integration_Test {
}

function test_RevertGiven_BalanceNotLessThanRemainingAmount() external givenNotNull givenPaused {
defaultDeposit();
depositToDefaultStream();
vm.warp({ newTimestamp: WARP_ONE_MONTH });

openEnded.pause(defaultStreamId);
Expand All @@ -37,7 +37,7 @@ contract StreamDebtOf_Integration_Test is Integration_Test {
}

function test_StreamDebtOf_BalanceNotLessThanSum() external givenNotNull givenNotPaused {
defaultDeposit();
depositToDefaultStream();
vm.warp({ newTimestamp: WARP_ONE_MONTH });

uint128 actualDebt = openEnded.streamDebtOf(defaultStreamId);
Expand Down
2 changes: 1 addition & 1 deletion test/integration/withdraw-at/withdrawAt.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ contract WithdrawAt_Integration_Test is Integration_Test {
function setUp() public override {
Integration_Test.setUp();

defaultDeposit();
depositToDefaultStream();

vm.warp({ newTimestamp: WARP_ONE_MONTH });
}
Expand Down
2 changes: 1 addition & 1 deletion test/integration/withdraw-max/withdrawMax.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ contract WithdrawMax_Integration_Concrete_Test is Integration_Test {
function setUp() public override {
Integration_Test.setUp();

defaultDeposit();
depositToDefaultStream();

vm.warp({ newTimestamp: WARP_ONE_MONTH });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ contract WithdrawableAmountOf_Integration_Test is Integration_Test {

function test_WithdrawableAmountOf_StreamPaused() external givenNotNull givenBalanceNotZero givenPaused {
// Deposit enough funds.
defaultDeposit();
depositToDefaultStream();

// Simulate passage of time.
vm.warp({ newTimestamp: WARP_ONE_MONTH });
Expand Down Expand Up @@ -91,7 +91,7 @@ contract WithdrawableAmountOf_Integration_Test is Integration_Test {
// Simulate passage of time.
vm.warp({ newTimestamp: WARP_ONE_MONTH });

defaultDeposit();
depositToDefaultStream();

uint128 newRatePerSecond = RATE_PER_SECOND * 2;

Expand Down
1 change: 1 addition & 0 deletions test/utils/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ abstract contract Constants {
uint128 public constant ONE_MONTH_REFUNDABLE_AMOUNT = DEPOSIT_AMOUNT - ONE_MONTH_STREAMED_AMOUNT;
uint128 public constant RATE_PER_SECOND = 0.001e18; // 86.4 daily
uint128 public constant REFUND_AMOUNT = 10_000e18;
uint128 public constant SOLVENCY_PERIOD = DEPOSIT_AMOUNT / RATE_PER_SECOND;
uint40 public immutable WARP_ONE_MONTH = MAY_1_2024 + ONE_MONTH;
uint128 public constant WITHDRAW_AMOUNT = 2500e18;
uint40 public immutable WITHDRAW_TIME = MAY_1_2024 + 2_500_000;
Expand Down

0 comments on commit 0835718

Please sign in to comment.