diff --git a/TECHNICAL-DOC.md b/TECHNICAL-DOC.md index 98426a07..ecbcafa1 100644 --- a/TECHNICAL-DOC.md +++ b/TECHNICAL-DOC.md @@ -13,9 +13,10 @@ pause it or void it at a later date. A stream is represented by a struct, which can be found in [`DataTypes.sol`](https://github.com/sablier-labs/flow/blob/ba1c9ba64907200c82ccfaeaa6ab91f6229c433d/src/types/DataTypes.sol#L41-L76). -The debt is tracked using `snapshotDebt` and `snapshotTime`. At snapshot, the following events are taking place: +The debt is tracked using `snapshotDebtScaled` and `snapshotTime`. At snapshot, the following events are taking place: -1. `snapshotDebt` is incremented by `ongoingDebt` where `ongoingDebt = rps * (block.timestamp - snapshotTime)`. +1. `snapshotDebtScaled` is incremented by `ongoingDebtScaled` where + `ongoingDebtScaled = rps * (block.timestamp - snapshotTime)`. 2. `snapshotTime` is updated to `block.timestamp`. The recipient can withdraw the streamed amount at any point. However, if there aren't sufficient funds, the recipient diff --git a/src/SablierFlow.sol b/src/SablierFlow.sol index 648763d1..7f134321 100644 --- a/src/SablierFlow.sol +++ b/src/SablierFlow.sol @@ -72,10 +72,10 @@ contract SablierFlow is uint8 tokenDecimals = _streams[streamId].tokenDecimals; uint256 scaledBalance = Helpers.scaleAmount({ amount: balance, decimals: tokenDecimals }); - uint256 snapshotDebt = _streams[streamId].snapshotDebt; + uint256 snapshotDebtScaled = _streams[streamId].snapshotDebtScaled; // If the stream has uncovered debt, return zero. - if (snapshotDebt + _scaledOngoingDebtOf(streamId) > scaledBalance) { + if (snapshotDebtScaled + _ongoingDebtScaledOf(streamId) > scaledBalance) { return 0; } @@ -85,7 +85,7 @@ contract SablierFlow is // Safe to use unchecked because the calculations cannot overflow or underflow. unchecked { uint256 solvencyAmount = - scaledBalance - snapshotDebt + Helpers.scaleAmount({ amount: 1, decimals: tokenDecimals }); + scaledBalance - snapshotDebtScaled + Helpers.scaleAmount({ amount: 1, decimals: tokenDecimals }); uint256 solvencyPeriod = solvencyAmount / _streams[streamId].ratePerSecond.unwrap(); depletionTime = _streams[streamId].snapshotTime + solvencyPeriod; @@ -93,11 +93,14 @@ contract SablierFlow is } /// @inheritdoc ISablierFlow - function ongoingDebtOf(uint256 streamId) external view override notNull(streamId) returns (uint256 ongoingDebt) { - ongoingDebt = Helpers.descaleAmount({ - amount: _scaledOngoingDebtOf(streamId), - decimals: _streams[streamId].tokenDecimals - }); + function ongoingDebtScaledOf(uint256 streamId) + external + view + override + notNull(streamId) + returns (uint256 ongoingDebtScaled) + { + ongoingDebtScaled = _ongoingDebtScaledOf(streamId); } /// @inheritdoc ISablierFlow @@ -451,7 +454,7 @@ contract SablierFlow is /// @dev Calculates the ongoing debt, as a 18-decimals fixed point number, accrued since last snapshot. Return 0 if /// the stream is paused or `block.timestamp` is less than or equal to snapshot time. - function _scaledOngoingDebtOf(uint256 streamId) internal view returns (uint256) { + function _ongoingDebtScaledOf(uint256 streamId) internal view returns (uint256) { uint256 blockTimestamp = block.timestamp; uint256 snapshotTime = _streams[streamId].snapshotTime; @@ -483,7 +486,7 @@ contract SablierFlow is /// @dev The total debt is the sum of the snapshot debt and the ongoing debt. This value is independent of the /// stream's balance. function _totalDebtOf(uint256 streamId) internal view returns (uint256) { - uint256 scaledTotalDebt = _scaledOngoingDebtOf(streamId) + _streams[streamId].snapshotDebt; + uint256 scaledTotalDebt = _ongoingDebtScaledOf(streamId) + _streams[streamId].snapshotDebtScaled; return Helpers.descaleAmount({ amount: scaledTotalDebt, decimals: _streams[streamId].tokenDecimals }); } @@ -511,12 +514,12 @@ contract SablierFlow is revert Errors.SablierFlow_RatePerSecondNotDifferent(streamId, newRatePerSecond); } - uint256 scaledOngoingDebt = _scaledOngoingDebtOf(streamId); + uint256 scaledOngoingDebt = _ongoingDebtScaledOf(streamId); // Update the snapshot debt only if the stream has ongoing debt. if (scaledOngoingDebt > 0) { // Effect: update the snapshot debt. - _streams[streamId].snapshotDebt += scaledOngoingDebt; + _streams[streamId].snapshotDebtScaled += scaledOngoingDebt; } // Effect: update the snapshot time. @@ -560,7 +563,7 @@ contract SablierFlow is isVoided: false, ratePerSecond: ratePerSecond, sender: sender, - snapshotDebt: 0, + snapshotDebtScaled: 0, snapshotTime: uint40(block.timestamp), token: token, tokenDecimals: tokenDecimals @@ -701,16 +704,16 @@ contract SablierFlow is // If the stream is solvent, update the total debt normally. if (debtToWriteOff == 0) { - uint256 scaledOngoingDebt = _scaledOngoingDebtOf(streamId); + uint256 scaledOngoingDebt = _ongoingDebtScaledOf(streamId); if (scaledOngoingDebt > 0) { // Effect: Update the snapshot debt by adding the ongoing debt. - _streams[streamId].snapshotDebt += scaledOngoingDebt; + _streams[streamId].snapshotDebtScaled += scaledOngoingDebt; } } // If the stream is insolvent, write off the uncovered debt. else { // Effect: update the total debt by setting snapshot debt to the stream balance. - _streams[streamId].snapshotDebt = + _streams[streamId].snapshotDebtScaled = Helpers.scaleAmount({ amount: _streams[streamId].balance, decimals: _streams[streamId].tokenDecimals }); } @@ -762,7 +765,7 @@ contract SablierFlow is uint8 tokenDecimals = _streams[streamId].tokenDecimals; // Calculate the total debt. - uint256 scaledTotalDebt = _scaledOngoingDebtOf(streamId) + _streams[streamId].snapshotDebt; + uint256 scaledTotalDebt = _ongoingDebtScaledOf(streamId) + _streams[streamId].snapshotDebtScaled; uint256 totalDebt = Helpers.descaleAmount(scaledTotalDebt, tokenDecimals); // Calculate the withdrawable amount. @@ -789,13 +792,13 @@ contract SablierFlow is unchecked { // If the amount is less than the snapshot debt, reduce it from the snapshot debt and leave the snapshot // time unchanged. - if (scaledAmount <= _streams[streamId].snapshotDebt) { - _streams[streamId].snapshotDebt -= scaledAmount; + if (scaledAmount <= _streams[streamId].snapshotDebtScaled) { + _streams[streamId].snapshotDebtScaled -= scaledAmount; } // Else reduce the amount from the ongoing debt by setting snapshot time to `block.timestamp` and set the // snapshot debt to the remaining total debt. else { - _streams[streamId].snapshotDebt = scaledTotalDebt - scaledAmount; + _streams[streamId].snapshotDebtScaled = scaledTotalDebt - scaledAmount; // Effect: update the stream time. _streams[streamId].snapshotTime = uint40(block.timestamp); diff --git a/src/abstracts/SablierFlowBase.sol b/src/abstracts/SablierFlowBase.sol index 3a52c7cf..31cae3e3 100644 --- a/src/abstracts/SablierFlowBase.sol +++ b/src/abstracts/SablierFlowBase.sol @@ -134,14 +134,14 @@ abstract contract SablierFlowBase is } /// @inheritdoc ISablierFlowBase - function getSnapshotDebt(uint256 streamId) + function getSnapshotDebtScaled(uint256 streamId) external view override notNull(streamId) - returns (uint256 snapshotDebt) + returns (uint256 snapshotDebtScaled) { - snapshotDebt = _streams[streamId].snapshotDebt; + snapshotDebtScaled = _streams[streamId].snapshotDebtScaled; } /// @inheritdoc ISablierFlowBase diff --git a/src/interfaces/ISablierFlow.sol b/src/interfaces/ISablierFlow.sol index 35859c38..8abc3e1b 100644 --- a/src/interfaces/ISablierFlow.sol +++ b/src/interfaces/ISablierFlow.sol @@ -124,7 +124,7 @@ interface ISablierFlow is /// @notice Returns the amount of debt accrued since the snapshot time until now, denoted in token's decimals. /// @dev Reverts if `streamId` references a null stream. /// @param streamId The stream ID for the query. - function ongoingDebtOf(uint256 streamId) external view returns (uint256 ongoingDebt); + function ongoingDebtScaledOf(uint256 streamId) external view returns (uint256 ongoingDebtScaled); /// @notice Returns the amount that the sender can be refunded from the stream, denoted in token's decimals. /// @dev Reverts if `streamId` references a null stream. diff --git a/src/interfaces/ISablierFlowBase.sol b/src/interfaces/ISablierFlowBase.sol index f017378e..4d17f1c7 100644 --- a/src/interfaces/ISablierFlowBase.sol +++ b/src/interfaces/ISablierFlowBase.sol @@ -87,7 +87,7 @@ interface ISablierFlowBase is /// @notice Retrieves the snapshot debt of the stream, denoted as a fixed-point number where 1e18 is 1 token. /// @dev Reverts if `streamId` references a null stream. /// @param streamId The stream ID for the query. - function getSnapshotDebt(uint256 streamId) external view returns (uint256 snapshotDebt); + function getSnapshotDebtScaled(uint256 streamId) external view returns (uint256 snapshotDebtScaled); /// @notice Retrieves the snapshot time of the stream, which is a Unix timestamp. /// @dev Reverts if `streamId` references a null stream. diff --git a/src/types/DataTypes.sol b/src/types/DataTypes.sol index cb2dd7ff..8f68ee9d 100644 --- a/src/types/DataTypes.sol +++ b/src/types/DataTypes.sol @@ -55,9 +55,9 @@ library Flow { /// be restarted. Voiding an insolvent stream sets its uncovered debt to zero. /// @param token The contract address of the ERC-20 token to stream. /// @param tokenDecimals The decimals of the ERC-20 token to stream. - /// @param snapshotDebt The amount of tokens that the sender owed to the recipient at snapshot time, denoted as a - /// 18-decimals fixed-point number. This, along with the ongoing debt, can be used to calculate the total debt at - /// any given point in time. + /// @param snapshotDebtScaled The amount of tokens that the sender owed to the recipient at snapshot time, denoted + /// as a 18-decimals fixed-point number. This, along with the ongoing debt, can be used to calculate the total debt + /// at any given point in time. struct Stream { // slot 0 uint128 balance; @@ -72,6 +72,6 @@ library Flow { IERC20 token; uint8 tokenDecimals; // slot 3 - uint256 snapshotDebt; + uint256 snapshotDebtScaled; } } diff --git a/tests/fork/Flow.t.sol b/tests/fork/Flow.t.sol index 45f7368f..b935f94b 100644 --- a/tests/fork/Flow.t.sol +++ b/tests/fork/Flow.t.sol @@ -227,14 +227,13 @@ contract Flow_Fork_Test is Fork_Test { newRatePerSecond = ud21x18(newRatePerSecond.unwrap() + 1); } - uint256 beforeSnapshotAmount = flow.getSnapshotDebt(streamId); + uint256 beforeSnapshotAmount = flow.getSnapshotDebtScaled(streamId); uint256 totalDebt = flow.totalDebtOf(streamId); // Compute the snapshot time that will be stored post withdraw. vars.expectedSnapshotTime = getBlockTimestamp(); - uint256 scaledOngoingDebt = - calculateScaledOngoingDebt(flow.getRatePerSecond(streamId).unwrap(), flow.getSnapshotTime(streamId)); + uint256 ongoingDebtScaled = flow.ongoingDebtScaledOf(streamId); // It should emit 1 {AdjustFlowStream}, 1 {MetadataUpdate} events. vm.expectEmit({ emitter: address(flow) }); @@ -251,8 +250,8 @@ contract Flow_Fork_Test is Fork_Test { flow.adjustRatePerSecond({ streamId: streamId, newRatePerSecond: newRatePerSecond }); // It should update snapshot debt. - vars.actualSnapshotDebt = flow.getSnapshotDebt(streamId); - vars.expectedSnapshotDebt = scaledOngoingDebt + beforeSnapshotAmount; + vars.actualSnapshotDebt = flow.getSnapshotDebtScaled(streamId); + vars.expectedSnapshotDebt = ongoingDebtScaled + beforeSnapshotAmount; assertEq(vars.actualSnapshotDebt, vars.expectedSnapshotDebt, "AdjustRatePerSecond: snapshot debt"); // It should set the new rate per second @@ -304,7 +303,7 @@ contract Flow_Fork_Test is Fork_Test { isTransferable: transferable, snapshotTime: getBlockTimestamp(), ratePerSecond: ratePerSecond, - snapshotDebt: 0, + snapshotDebtScaled: 0, sender: sender, token: token, tokenDecimals: IERC20Metadata(address(token)).decimals() @@ -564,7 +563,7 @@ contract Flow_Fork_Test is Fork_Test { uint256 totalDebt = flow.totalDebtOf(streamId); vars.expectedSnapshotTime = withdrawAmount - <= getDescaledAmount(flow.getSnapshotDebt(streamId), flow.getTokenDecimals(streamId)) + <= getDescaledAmount(flow.getSnapshotDebtScaled(streamId), flow.getTokenDecimals(streamId)) ? flow.getSnapshotTime(streamId) : getBlockTimestamp(); diff --git a/tests/integration/Integration.t.sol b/tests/integration/Integration.t.sol index 74008979..5a3d5175 100644 --- a/tests/integration/Integration.t.sol +++ b/tests/integration/Integration.t.sol @@ -94,7 +94,7 @@ abstract contract Integration_Test is Base_Test { isTransferable: TRANSFERABLE, isVoided: false, ratePerSecond: RATE_PER_SECOND, - snapshotDebt: 0, + snapshotDebtScaled: 0, sender: users.sender, token: usdc, tokenDecimals: DECIMALS diff --git a/tests/integration/concrete/adjust-rate-per-second/adjustRatePerSecond.t.sol b/tests/integration/concrete/adjust-rate-per-second/adjustRatePerSecond.t.sol index bffbf075..dbd5f58d 100644 --- a/tests/integration/concrete/adjust-rate-per-second/adjustRatePerSecond.t.sol +++ b/tests/integration/concrete/adjust-rate-per-second/adjustRatePerSecond.t.sol @@ -95,7 +95,7 @@ contract AdjustRatePerSecond_Integration_Concrete_Test is Integration_Test { uint40 expectedSnapshotTime = getBlockTimestamp() - ONE_MONTH; assertEq(actualSnapshotTime, expectedSnapshotTime, "snapshot time"); - uint256 actualSnapshotDebt = flow.getSnapshotDebt(defaultStreamId); + uint256 actualSnapshotDebt = flow.getSnapshotDebtScaled(defaultStreamId); uint128 expectedSnapshotDebt = 0; assertEq(actualSnapshotDebt, expectedSnapshotDebt, "snapshot debt"); @@ -118,7 +118,7 @@ contract AdjustRatePerSecond_Integration_Concrete_Test is Integration_Test { assertEq(uint8(flow.statusOf(defaultStreamId)), uint8(Flow.Status.STREAMING_SOLVENT), "status not streaming"); // It should update snapshot debt. - actualSnapshotDebt = flow.getSnapshotDebt(defaultStreamId); + actualSnapshotDebt = flow.getSnapshotDebtScaled(defaultStreamId); expectedSnapshotDebt = ONE_MONTH_DEBT_18D; assertEq(actualSnapshotDebt, expectedSnapshotDebt, "snapshot debt"); diff --git a/tests/integration/concrete/deposit-and-pause/depositAndPause.t.sol b/tests/integration/concrete/deposit-and-pause/depositAndPause.t.sol index e7396464..e4812346 100644 --- a/tests/integration/concrete/deposit-and-pause/depositAndPause.t.sol +++ b/tests/integration/concrete/deposit-and-pause/depositAndPause.t.sol @@ -56,8 +56,7 @@ contract DepositAndPause_Integration_Concrete_Test is Integration_Test { function test_WhenCallerSender() external whenNoDelegateCall givenNotNull givenNotPaused { uint128 previousStreamBalance = flow.getBalance(defaultStreamId); - uint256 expectedSnapshotDebt = - calculateScaledOngoingDebt(RATE_PER_SECOND_U128, flow.getSnapshotTime(defaultStreamId)); + uint256 expectedSnapshotDebt = flow.ongoingDebtScaledOf(defaultStreamId); // It should emit 1 {Transfer}, 1 {DepositFlowStream}, 1 {PauseFlowStream}, 1 {MetadataUpdate} events vm.expectEmit({ emitter: address(usdc) }); @@ -99,7 +98,7 @@ contract DepositAndPause_Integration_Concrete_Test is Integration_Test { assertEq(actualRatePerSecond, 0, "rate per second"); // It should update the snapshot debt - uint256 actualSnapshotDebt = flow.getSnapshotDebt(defaultStreamId); + uint256 actualSnapshotDebt = flow.getSnapshotDebtScaled(defaultStreamId); assertEq(actualSnapshotDebt, expectedSnapshotDebt, "snapshot debt"); } } diff --git a/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.t.sol b/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.t.sol index c5c25a92..fca5f92b 100644 --- a/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.t.sol +++ b/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.t.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.22; import { Integration_Test } from "../../Integration.t.sol"; -contract OngoingDebtOf_Integration_Concrete_Test is Integration_Test { +contract OngoingDebtScaledOf_Integration_Concrete_Test is Integration_Test { function test_RevertGiven_Null() external { - bytes memory callData = abi.encodeCall(flow.ongoingDebtOf, nullStreamId); + bytes memory callData = abi.encodeCall(flow.ongoingDebtScaledOf, nullStreamId); expectRevert_Null(callData); } @@ -13,8 +13,8 @@ contract OngoingDebtOf_Integration_Concrete_Test is Integration_Test { flow.pause(defaultStreamId); // It should return zero. - uint256 ongoingDebt = flow.ongoingDebtOf(defaultStreamId); - assertEq(ongoingDebt, 0, "ongoing debt"); + uint256 ongoingDebtScaled = flow.ongoingDebtScaledOf(defaultStreamId); + assertEq(ongoingDebtScaled, 0, "ongoing debt"); } function test_WhenSnapshotTimeInPresent() external givenNotNull givenNotPaused { @@ -22,13 +22,13 @@ contract OngoingDebtOf_Integration_Concrete_Test is Integration_Test { updateSnapshotTimeAndWarp(defaultStreamId); // It should return zero. - uint256 ongoingDebt = flow.ongoingDebtOf(defaultStreamId); - assertEq(ongoingDebt, 0, "ongoing debt"); + uint256 ongoingDebtScaled = flow.ongoingDebtScaledOf(defaultStreamId); + assertEq(ongoingDebtScaled, 0, "ongoing debt"); } function test_WhenSnapshotTimeInPast() external view givenNotNull givenNotPaused { // It should return the correct ongoing debt. - uint256 ongoingDebt = flow.ongoingDebtOf(defaultStreamId); - assertEq(ongoingDebt, ONE_MONTH_DEBT_6D, "ongoing debt"); + uint256 ongoingDebtScaled = flow.ongoingDebtScaledOf(defaultStreamId); + assertEq(ongoingDebtScaled, ONE_MONTH_DEBT_18D, "ongoing debt"); } } diff --git a/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.tree b/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.tree index 6873b0d3..607e6052 100644 --- a/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.tree +++ b/tests/integration/concrete/ongoing-debt-of/ongoingDebtOf.tree @@ -1,4 +1,4 @@ -OngoingDebtOf_Integration_Concrete_Test +OngoingDebtScaledOf_Integration_Concrete_Test ├── given null │ └── it should revert └── given not null diff --git a/tests/integration/concrete/pause/pause.t.sol b/tests/integration/concrete/pause/pause.t.sol index 8f8c16e0..431b53f2 100644 --- a/tests/integration/concrete/pause/pause.t.sol +++ b/tests/integration/concrete/pause/pause.t.sol @@ -66,8 +66,7 @@ contract Pause_Integration_Concrete_Test is Integration_Test { } function _test_Pause() private { - uint256 expectedSnapshotDebt = - calculateScaledOngoingDebt(RATE_PER_SECOND_U128, flow.getSnapshotTime(defaultStreamId)); + uint256 expectedSnapshotDebt = flow.ongoingDebtScaledOf(defaultStreamId); // It should emit 1 {PauseFlowStream}, 1 {MetadataUpdate} events. vm.expectEmit({ emitter: address(flow) }); @@ -91,7 +90,7 @@ contract Pause_Integration_Concrete_Test is Integration_Test { assertEq(actualRatePerSecond, 0, "rate per second"); // It should update the snapshot debt. - uint256 actualSnapshotDebt = flow.getSnapshotDebt(defaultStreamId); + uint256 actualSnapshotDebt = flow.getSnapshotDebtScaled(defaultStreamId); assertEq(actualSnapshotDebt, expectedSnapshotDebt, "snapshot debt"); } } diff --git a/tests/integration/concrete/refund-and-pause/refundAndPause.t.sol b/tests/integration/concrete/refund-and-pause/refundAndPause.t.sol index 21f79cac..bd8fb078 100644 --- a/tests/integration/concrete/refund-and-pause/refundAndPause.t.sol +++ b/tests/integration/concrete/refund-and-pause/refundAndPause.t.sol @@ -54,8 +54,7 @@ contract RefundAndPause_Integration_Concrete_Test is Integration_Test { } function test_WhenCallerSender() external whenNoDelegateCall givenNotNull givenNotPaused { - uint256 expectedSnapshotDebt = - calculateScaledOngoingDebt(RATE_PER_SECOND_U128, flow.getSnapshotTime(defaultStreamId)); + uint256 expectedSnapshotDebt = flow.ongoingDebtScaledOf(defaultStreamId); // It should emit 1 {Transfer}, 1 {RefundFromFlowStream}, 1 {PauseFlowStream}, 1 {MetadataUpdate} events vm.expectEmit({ emitter: address(usdc) }); @@ -97,7 +96,7 @@ contract RefundAndPause_Integration_Concrete_Test is Integration_Test { assertEq(actualRatePerSecond, 0, "rate per second"); // It should update the snapshot debt - uint256 actualSnapshotDebt = flow.getSnapshotDebt(defaultStreamId); + uint256 actualSnapshotDebt = flow.getSnapshotDebtScaled(defaultStreamId); assertEq(actualSnapshotDebt, expectedSnapshotDebt, "snapshot debt"); } } diff --git a/tests/integration/concrete/total-debt-of/totalDebtOf.t.sol b/tests/integration/concrete/total-debt-of/totalDebtOf.t.sol index a2bfd20c..fa0f7943 100644 --- a/tests/integration/concrete/total-debt-of/totalDebtOf.t.sol +++ b/tests/integration/concrete/total-debt-of/totalDebtOf.t.sol @@ -14,25 +14,24 @@ contract TotalDebtOf_Integration_Concrete_Test is Integration_Test { function test_GivenPaused() external givenNotNull { flow.pause(defaultStreamId); - uint256 snapshotDebt = flow.getSnapshotDebt(defaultStreamId); + uint256 snapshotDebtScaled = flow.getSnapshotDebtScaled(defaultStreamId); - assertEq(ONE_MONTH_DEBT_18D, snapshotDebt, "total debt"); + assertEq(ONE_MONTH_DEBT_18D, snapshotDebtScaled, "total debt"); } function test_WhenCurrentTimeEqualsSnapshotTime() external givenNotNull givenNotPaused { // Set the snapshot time to the current time by changing rate per second. flow.adjustRatePerSecond(defaultStreamId, ud21x18(RATE_PER_SECOND_U128 * 2)); - uint256 snapshotDebt = flow.getSnapshotDebt(defaultStreamId); + uint256 snapshotDebtScaled = flow.getSnapshotDebtScaled(defaultStreamId); - assertEq(ONE_MONTH_DEBT_18D, snapshotDebt, "total debt"); + assertEq(ONE_MONTH_DEBT_18D, snapshotDebtScaled, "total debt"); } function test_WhenCurrentTimeGreaterThanSnapshotTime() external view givenNotNull givenNotPaused { - uint256 snapshotDebt = flow.getSnapshotDebt(defaultStreamId); - uint256 scaledOngoingDebt = - calculateScaledOngoingDebt(RATE_PER_SECOND_U128, flow.getSnapshotTime(defaultStreamId)); + uint256 snapshotDebtScaled = flow.getSnapshotDebtScaled(defaultStreamId); + uint256 ongoingDebtScaled = flow.ongoingDebtScaledOf(defaultStreamId); - assertEq(snapshotDebt + scaledOngoingDebt, ONE_MONTH_DEBT_18D, "total debt"); + assertEq(snapshotDebtScaled + ongoingDebtScaled, ONE_MONTH_DEBT_18D, "total debt"); } } diff --git a/tests/integration/concrete/withdraw-delay/withdrawDelay.t.sol b/tests/integration/concrete/withdraw-delay/withdrawDelay.t.sol index b4d0f104..59ba6161 100644 --- a/tests/integration/concrete/withdraw-delay/withdrawDelay.t.sol +++ b/tests/integration/concrete/withdraw-delay/withdrawDelay.t.sol @@ -21,7 +21,7 @@ contract WithdrawDelay_Integration_Concrete_Test is Integration_Test { // Assert that one token has been unlocked. vm.warp(initialSnapshotTime + 87 seconds); - assertEq(flow.ongoingDebtOf(streamId), 1); + assertEq(getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)), 1); // Withdraw the token. (uint128 withdrawnAmount,) = flow.withdrawMax(streamId, users.recipient); @@ -29,7 +29,9 @@ contract WithdrawDelay_Integration_Concrete_Test is Integration_Test { // Now warp to the expected third token unlock. vm.warp(initialSnapshotTime + 260 seconds); - assertEq(withdrawnAmount + flow.ongoingDebtOf(streamId), 3); + assertEq( + withdrawnAmount + getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)), 3 + ); } function test_Withdraw_LongestDelay() external { @@ -45,7 +47,7 @@ contract WithdrawDelay_Integration_Concrete_Test is Integration_Test { // Assert that there is still only one token unlocked. vm.warp(initialSnapshotTime + 172 seconds); - assertEq(flow.ongoingDebtOf(streamId), 1); + assertEq(getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)), 1); // Withdraw the token. (uint128 withdrawnAmount,) = flow.withdrawMax(streamId, users.recipient); @@ -53,15 +55,21 @@ contract WithdrawDelay_Integration_Concrete_Test is Integration_Test { // Warp to a second before second token unlock so that we prove the delay. vm.warp(initialSnapshotTime + 258 seconds); - assertEq(withdrawnAmount + flow.ongoingDebtOf(streamId), 1); + assertEq( + withdrawnAmount + getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)), 1 + ); // Warp to the expected second token unlock. vm.warp(initialSnapshotTime + 259 seconds); - assertEq(withdrawnAmount + flow.ongoingDebtOf(streamId), 2); + assertEq( + withdrawnAmount + getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)), 2 + ); // Warp to the expected third token unlock. vm.warp(initialSnapshotTime + 345 seconds); - assertEq(withdrawnAmount + flow.ongoingDebtOf(streamId), 3); + assertEq( + withdrawnAmount + getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)), 3 + ); } /// @dev A test that demonstrates there is no delay when the rate per second is greater than the scale scaleFactor, @@ -98,9 +106,11 @@ contract WithdrawDelay_Integration_Concrete_Test is Integration_Test { // Find the time when the ongoing debt has increased by 38 uint256 diff; while (diff != 39) { - uint256 beforeWarpOd = flow.ongoingDebtOf(streamId); + uint256 beforeWarpOd = + getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)); vm.warp(getBlockTimestamp() + 1 seconds); - diff = flow.ongoingDebtOf(streamId) - beforeWarpOd; + diff = getDescaledAmount(flow.ongoingDebtScaledOf(streamId), flow.getTokenDecimals(streamId)) + - beforeWarpOd; } (uint128 withdrawnAmount,) = flow.withdrawMax(streamId, users.recipient); diff --git a/tests/integration/concrete/withdraw-max/withdrawMax.t.sol b/tests/integration/concrete/withdraw-max/withdrawMax.t.sol index ee507d63..3a4a9f01 100644 --- a/tests/integration/concrete/withdraw-max/withdrawMax.t.sol +++ b/tests/integration/concrete/withdraw-max/withdrawMax.t.sol @@ -71,7 +71,7 @@ contract WithdrawMax_Integration_Concrete_Test is Integration_Test { assertEq(vars.actualStreamBalance, vars.expectedStreamBalance, "stream balance"); // It should set the snapshot debt to zero. - vars.actualSnapshotDebt = flow.getSnapshotDebt(defaultStreamId); + vars.actualSnapshotDebt = flow.getSnapshotDebtScaled(defaultStreamId); assertEq(vars.actualSnapshotDebt, 0, "snapshot debt"); if (flow.getRatePerSecond(defaultStreamId).unwrap() > 0) { diff --git a/tests/integration/fuzz/adjustRatePerSecond.t.sol b/tests/integration/fuzz/adjustRatePerSecond.t.sol index d7d43203..f519c6bf 100644 --- a/tests/integration/fuzz/adjustRatePerSecond.t.sol +++ b/tests/integration/fuzz/adjustRatePerSecond.t.sol @@ -47,7 +47,7 @@ contract AdjustRatePerSecond_Integration_Fuzz_Test is Shared_Integration_Fuzz_Te // Adjust the rate per second. flow.adjustRatePerSecond(streamId, newRatePerSecond); - assertEq(flow.ongoingDebtOf(streamId), 0, "ongoing debt"); + assertEq(flow.ongoingDebtScaledOf(streamId), 0, "ongoing debt"); assertEq(previousTotalDebt, flow.totalDebtOf(streamId), "rate per second"); } @@ -108,7 +108,7 @@ contract AdjustRatePerSecond_Integration_Fuzz_Test is Shared_Integration_Fuzz_Te // Adjust the rate per second. flow.adjustRatePerSecond(streamId, newRatePerSecond); - assertEq(flow.ongoingDebtOf(streamId), 0, "ongoing debt"); + assertEq(flow.ongoingDebtScaledOf(streamId), 0, "ongoing debt"); assertEq(previousTotalDebt, flow.totalDebtOf(streamId), "rate per second"); } diff --git a/tests/integration/fuzz/create.t.sol b/tests/integration/fuzz/create.t.sol index e9811b59..bc9d5a3c 100644 --- a/tests/integration/fuzz/create.t.sol +++ b/tests/integration/fuzz/create.t.sol @@ -73,7 +73,7 @@ contract Create_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { assertEq(flow.getSnapshotTime(actualStreamId), getBlockTimestamp()); assertEq(flow.getRatePerSecond(actualStreamId), ratePerSecond); assertEq(flow.getRecipient(actualStreamId), recipient); - assertEq(flow.getSnapshotDebt(actualStreamId), 0); + assertEq(flow.getSnapshotDebtScaled(actualStreamId), 0); assertEq(flow.getSender(actualStreamId), sender); assertEq(flow.getToken(actualStreamId), token); assertEq(flow.getTokenDecimals(actualStreamId), decimals); diff --git a/tests/integration/fuzz/ongoingDebtOf.t.sol b/tests/integration/fuzz/ongoingDebtOf.t.sol index c264843c..3fcd5185 100644 --- a/tests/integration/fuzz/ongoingDebtOf.t.sol +++ b/tests/integration/fuzz/ongoingDebtOf.t.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.22; import { Shared_Integration_Fuzz_Test } from "./Fuzz.t.sol"; -contract OngoingDebtOf_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { +contract OngoingDebtScaledOf_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { /// @dev It should return the expected value. /// /// Given enough runs, all of the following scenarios should be fuzzed: @@ -21,14 +21,14 @@ contract OngoingDebtOf_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { // Pause the stream. flow.pause(streamId); - uint256 expectedOngoingDebt = flow.ongoingDebtOf(streamId); + uint256 expectedOngoingDebtScaled = flow.ongoingDebtScaledOf(streamId); // Simulate the passage of time after pause. vm.warp({ newTimestamp: getBlockTimestamp() + timeJump }); // Assert that the ongoing debt did not change. - uint256 actualOngoingDebt = flow.ongoingDebtOf(streamId); - assertEq(actualOngoingDebt, expectedOngoingDebt, "ongoing debt"); + uint256 actualOngoingDebtScaled = flow.ongoingDebtScaledOf(streamId); + assertEq(actualOngoingDebtScaled, expectedOngoingDebtScaled, "ongoing debt"); } /// @dev It should return 0. @@ -56,8 +56,8 @@ contract OngoingDebtOf_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { updateSnapshotTimeAndWarp(streamId); // Assert that ongoing debt is zero. - uint256 actualOngoingDebt = flow.ongoingDebtOf(streamId); - assertEq(actualOngoingDebt, 0, "ongoing debt"); + uint256 actualOngoingDebtScaled = flow.ongoingDebtScaledOf(streamId); + assertEq(actualOngoingDebtScaled, 0, "ongoing debt"); } /// @dev It should return the ongoing debt. @@ -65,7 +65,7 @@ contract OngoingDebtOf_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { /// Given enough runs, all of the following scenarios should be fuzzed: /// - Multiple non-paused streams, each with different token decimals and rps. /// - Multiple points in time after the value of snapshotTime. - function testFuzz_OngoingDebtOf( + function testFuzz_OngoingDebtScaledOf( uint256 streamId, uint40 timeJump, uint8 decimals @@ -88,8 +88,8 @@ contract OngoingDebtOf_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { uint128 ratePerSecond = flow.getRatePerSecond(streamId).unwrap(); // Assert that the ongoing debt equals the expected value. - uint256 actualOngoingDebt = flow.ongoingDebtOf(streamId); - uint256 expectedOngoingDebt = getDescaledAmount(ratePerSecond * timeJump, decimals); - assertEq(actualOngoingDebt, expectedOngoingDebt, "ongoing debt"); + uint256 actualOngoingDebtScaled = flow.ongoingDebtScaledOf(streamId); + uint256 expectedOngoingDebtScaled = ratePerSecond * timeJump; + assertEq(actualOngoingDebtScaled, expectedOngoingDebtScaled, "ongoing debt"); } } diff --git a/tests/integration/fuzz/pause.t.sol b/tests/integration/fuzz/pause.t.sol index 0a73f4d4..cd3edfcb 100644 --- a/tests/integration/fuzz/pause.t.sol +++ b/tests/integration/fuzz/pause.t.sol @@ -85,7 +85,7 @@ contract Pause_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { // Assert that the stream is paused. assertTrue(flow.isPaused(streamId), "paused"); - assertEq(flow.ongoingDebtOf(streamId), 0, "ongoing debt"); + assertEq(flow.ongoingDebtScaledOf(streamId), 0, "ongoing debt"); // Assert that the rate per second is 0. assertEq(flow.getRatePerSecond(streamId), 0, "rate per second"); diff --git a/tests/integration/fuzz/void.t.sol b/tests/integration/fuzz/void.t.sol index 2e942b19..adbf845e 100644 --- a/tests/integration/fuzz/void.t.sol +++ b/tests/integration/fuzz/void.t.sol @@ -154,7 +154,7 @@ contract Void_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { assertTrue(flow.isVoided(streamId), "voided"); assertTrue(flow.isPaused(streamId), "paused"); assertEq(flow.getRatePerSecond(streamId), 0, "rate per second"); - assertEq(flow.ongoingDebtOf(streamId), 0, "ongoing debt"); + assertEq(flow.ongoingDebtScaledOf(streamId), 0, "ongoing debt"); assertEq(flow.uncoveredDebtOf(streamId), 0, "uncovered debt"); assertEq(flow.totalDebtOf(streamId), expectedTotalDebt, "total debt"); } diff --git a/tests/integration/fuzz/withdraw.t.sol b/tests/integration/fuzz/withdraw.t.sol index f7d0c5a7..52f1f13b 100644 --- a/tests/integration/fuzz/withdraw.t.sol +++ b/tests/integration/fuzz/withdraw.t.sol @@ -147,9 +147,10 @@ contract Withdraw_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { vars.previousAggregateAmount = flow.aggregateBalance(token); vars.previousTokenBalance = token.balanceOf(address(flow)); - vars.previousOngoingDebt = flow.totalDebtOf(streamId); - vars.previousTotalDebt = getDescaledAmount(flow.getSnapshotDebt(streamId), flow.getTokenDecimals(streamId)) - + vars.previousOngoingDebt; + vars.previousOngoingDebtScaled = flow.totalDebtOf(streamId); + vars.previousTotalDebt = getDescaledAmount( + flow.getSnapshotDebtScaled(streamId), flow.getTokenDecimals(streamId) + ) + vars.previousOngoingDebtScaled; vars.previousStreamBalance = flow.getBalance(streamId); vars.expectedProtocolRevenue = flow.protocolRevenue(token); @@ -185,7 +186,7 @@ contract Withdraw_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { assertEq(vars.actualWithdrawnAmount, withdrawAmount - vars.protocolFeeAmount, "withdrawn amount"); assertEq(vars.actualProtocolFeeAmount, vars.protocolFeeAmount, "protocol fee amount"); - assertEq(flow.ongoingDebtOf(streamId), 0, "ongoing debt"); + assertEq(flow.ongoingDebtScaledOf(streamId), 0, "ongoing debt"); // Assert the protocol revenue. vars.actualProtocolRevenue = flow.protocolRevenue(token); diff --git a/tests/integration/fuzz/withdrawMax.t.sol b/tests/integration/fuzz/withdrawMax.t.sol index bd72847e..d63c9605 100644 --- a/tests/integration/fuzz/withdrawMax.t.sol +++ b/tests/integration/fuzz/withdrawMax.t.sol @@ -122,7 +122,7 @@ contract WithdrawMax_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { assertEq(vars.actualWithdrawnAmount, withdrawAmount, "withdrawn amount"); assertEq(vars.actualProtocolFeeAmount, 0, "protocol fee amount"); - assertEq(flow.ongoingDebtOf(streamId), 0, "ongoing debt"); + assertEq(flow.ongoingDebtScaledOf(streamId), 0, "ongoing debt"); // It should update snapshot time. assertEq(flow.getSnapshotTime(streamId), vars.expectedSnapshotTime, "snapshot time"); @@ -140,8 +140,10 @@ contract WithdrawMax_Integration_Fuzz_Test is Shared_Integration_Fuzz_Test { // Assert that total debt equals snapshot debt and ongoing debt assertEq( flow.totalDebtOf(streamId), - getDescaledAmount(flow.getSnapshotDebt(streamId), flow.getTokenDecimals(streamId)) - + flow.ongoingDebtOf(streamId), + getDescaledAmount( + flow.getSnapshotDebtScaled(streamId) + flow.ongoingDebtScaledOf(streamId), + flow.getTokenDecimals(streamId) + ), "snapshot debt" ); diff --git a/tests/utils/Assertions.sol b/tests/utils/Assertions.sol index e5b89ac3..00be33c8 100644 --- a/tests/utils/Assertions.sol +++ b/tests/utils/Assertions.sol @@ -28,7 +28,7 @@ abstract contract Assertions is PRBMathAssertions { assertEq(a.isStream, b.isStream, "isStream"); assertEq(a.isTransferable, b.isTransferable, "isTransferable"); assertEq(a.isVoided, b.isVoided, "isVoided"); - assertEq(a.snapshotDebt, b.snapshotDebt, "snapshotDebt"); + assertEq(a.snapshotDebtScaled, b.snapshotDebtScaled, "snapshotDebtScaled"); assertEq(a.sender, b.sender, "sender"); assertEq(a.token, b.token, "token"); assertEq(a.tokenDecimals, b.tokenDecimals, "tokenDecimals"); diff --git a/tests/utils/Utils.sol b/tests/utils/Utils.sol index 47665ab2..88ca1d7b 100644 --- a/tests/utils/Utils.sol +++ b/tests/utils/Utils.sol @@ -49,12 +49,6 @@ abstract contract Utils is CommonBase, Constants, PRBMathUtils { return uint8(_bound(uint256(x), uint256(min), uint256(max))); } - /// @dev A function that mirrors the internal logic from {SablierFlow._scaledOngoingDebt}. - function calculateScaledOngoingDebt(uint128 ratePerSecond, uint40 snapshotTime) internal view returns (uint256) { - uint256 elapsedTime = getBlockTimestamp() - snapshotTime; - return ratePerSecond * elapsedTime; - } - /// @dev Retrieves the current block timestamp as an `uint40`. function getBlockTimestamp() internal view returns (uint40) { return uint40(block.timestamp); diff --git a/tests/utils/Vars.sol b/tests/utils/Vars.sol index d612400e..0298dad1 100644 --- a/tests/utils/Vars.sol +++ b/tests/utils/Vars.sol @@ -10,7 +10,7 @@ struct Vars { IERC20 token; // previous values. uint256 previousAggregateAmount; - uint256 previousOngoingDebt; + uint256 previousOngoingDebtScaled; uint40 previousSnapshotTime; uint128 previousStreamBalance; uint256 previousTokenBalance;