Skip to content

Commit

Permalink
test(refactor): use bulloak for concrete tests (#153)
Browse files Browse the repository at this point in the history
* test(refactor): use bulloak for concrete tests

test(bulloak): deposit
test(refactor): use 'delegate call' instead of 'deletage called'
test(modifiers): rename to whenDepositAmountIsNotZero

test(bulloak): adjustRatePerSecond
test: use "when no delegate call", whenNoDelegateCall
test: rename whenCallerIsNotSender to whenCallerNotSender

test(bulloak): pause
test: use getBlockTimestamp()

test(bulloak): recent-amount-of

test(bulloak): refund

test(bulloak): refundableAmountOf

test(bulloak): restart

test(bulloak): restartAndDeposit

test(bulloak): streamDebtOf

test(bulloak): transferFrom

test(bulloak): withdrawableAmountOf
test(refactor): rename createDefaultStreamWithAsset to createStreamWithAsset
test(refactor): use "when" for last time update in branches

test(bulloak): withdrawMax

test(bulloak): create and withdrawAt

test(refactor): remove redundant modifiers

* fix: merge conflicts

* test(refactor): move Helper libraries to Integration

* test(fix): test_WhenAmountOwedExceedsBalance in WithdrawAt_Integration_Concrete_Test

* refactor: rename custom error

test: move functions in Utils
test: move function in Integration_Test
test: remove "Is" from tree branches
test: "It should revert" comments

* test: fix comments

* test: fix bulloak warnings

---------

Co-authored-by: andreivladbrg <andreivladbrg@gmail.com>
  • Loading branch information
smol-ninja and andreivladbrg authored Jun 6, 2024
1 parent 56ae6ab commit 239d8c6
Show file tree
Hide file tree
Showing 52 changed files with 914 additions and 1,103 deletions.
4 changes: 2 additions & 2 deletions src/SablierFlow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -531,9 +531,9 @@ contract SablierFlow is

/// @dev See the documentation for the user-facing functions that call this internal function.
function _deposit(uint256 streamId, uint128 transferAmount) internal {
// Check: the deposit amount is not zero.
// Check: the transfer amount is not zero.
if (transferAmount == 0) {
revert Errors.SablierFlow_DepositAmountZero();
revert Errors.SablierFlow_TransferAmountZero();
}

// Retrieve the ERC-20 asset from storage.
Expand Down
4 changes: 2 additions & 2 deletions src/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ library Errors {
/// @notice Thrown when trying to create a stream with a broker recipient address as zero.
error SablierFlow_BrokerAddressZero();

/// @notice Thrown when trying to create a stream with a zero deposit amount.
error SablierFlow_DepositAmountZero();
/// @notice Thrown when trying to create a stream with a zero transfer amount.
error SablierFlow_TransferAmountZero();

/// @notice Thrown when trying to create a stream with an asset with no decimals.
error SablierFlow_InvalidAssetDecimals(address asset);
Expand Down
20 changes: 18 additions & 2 deletions test/integration/Integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ abstract contract Integration_Test is Base_Test {
}

function createDefaultStream() internal returns (uint256) {
return createDefaultStreamWithAsset(dai);
return createStreamWithAsset(dai);
}

function createDefaultStreamWithAsset(IERC20 asset_) internal returns (uint256) {
function createStreamWithAsset(IERC20 asset_) internal returns (uint256) {
return flow.create({
sender: users.sender,
recipient: users.recipient,
Expand All @@ -48,6 +48,22 @@ abstract contract Integration_Test is Base_Test {
flow.deposit(defaultStreamId, TRANSFER_AMOUNT);
}

function depositToStreamId(uint256 streamId, uint128 amount) internal {
flow.deposit(streamId, amount);
}

/// @dev Update the `lastTimeUpdate` of a stream to the current block timestamp.
function updateLastTimeToBlockTimestamp(uint256 streamId) internal {
resetPrank(users.sender);
uint128 ratePerSecond = flow.getRatePerSecond(streamId);

// Updates the last time update via `adjustRatePerSecond`.
flow.adjustRatePerSecond(streamId, 1);

// Restores the rate per second.
flow.adjustRatePerSecond(streamId, ratePerSecond);
}

/*//////////////////////////////////////////////////////////////////////////
COMMON-REVERT-TESTS
//////////////////////////////////////////////////////////////////////////*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,117 +6,74 @@ import { Errors } from "src/libraries/Errors.sol";
import { Integration_Test } from "../../Integration.t.sol";

contract AdjustRatePerSecond_Integration_Concrete_Test is Integration_Test {
function setUp() public override {
Integration_Test.setUp();
}

function test_RevertWhen_DelegateCall() external {
bytes memory callData = abi.encodeCall(flow.adjustRatePerSecond, (defaultStreamId, RATE_PER_SECOND));
expectRevert_DelegateCall(callData);
}

function test_RevertGiven_Null() external whenNotDelegateCalled {
function test_RevertGiven_Null() external whenNoDelegateCall {
bytes memory callData = abi.encodeCall(flow.adjustRatePerSecond, (nullStreamId, RATE_PER_SECOND));
expectRevert_Null(callData);
}

function test_RevertGiven_Paused() external whenNotDelegateCalled givenNotNull {
function test_RevertGiven_Paused() external whenNoDelegateCall givenNotNull {
bytes memory callData = abi.encodeCall(flow.adjustRatePerSecond, (defaultStreamId, RATE_PER_SECOND));
expectRevert_Paused(callData);
}

function test_RevertWhen_CallerRecipient()
external
whenNotDelegateCalled
whenNoDelegateCall
givenNotNull
givenNotPaused
whenCallerIsNotSender
whenCallerNotSender
{
bytes memory callData = abi.encodeCall(flow.adjustRatePerSecond, (defaultStreamId, RATE_PER_SECOND));
expectRevert_CallerRecipient(callData);
}

function test_RevertWhen_CallerMaliciousThirdParty()
external
whenNotDelegateCalled
whenNoDelegateCall
givenNotNull
givenNotPaused
whenCallerIsNotSender
whenCallerNotSender
{
bytes memory callData = abi.encodeCall(flow.adjustRatePerSecond, (defaultStreamId, RATE_PER_SECOND));
expectRevert_CallerMaliciousThirdParty(callData);
}

function test_RevertWhen_RatePerSecondZero()
function test_RevertWhen_NewRatePerSecondZero()
external
whenNotDelegateCalled
whenNoDelegateCall
givenNotNull
givenNotPaused
whenCallerIsSender
whenCallerSender
{
vm.expectRevert(Errors.SablierFlow_RatePerSecondZero.selector);
flow.adjustRatePerSecond({ streamId: defaultStreamId, newRatePerSecond: 0 });
}

function test_RevertWhen_RatePerSecondNotDifferent()
function test_RevertWhen_NewRatePerSecondEqualsCurrentRatePerSecond()
external
whenNotDelegateCalled
whenNoDelegateCall
givenNotNull
givenNotPaused
whenCallerIsSender
whenRatePerSecondIsNotZero
whenCallerSender
whenNewRatePerSecondNotZero
{
vm.expectRevert(abi.encodeWithSelector(Errors.SablierFlow_RatePerSecondNotDifferent.selector, RATE_PER_SECOND));
flow.adjustRatePerSecond({ streamId: defaultStreamId, newRatePerSecond: RATE_PER_SECOND });
}

function test_AdjustRatePerSecond_WithdrawableAmountZero()
function test_WhenNewRatePerSecondNotEqualsCurrentRatePerSecond()
external
whenNotDelegateCalled
whenNoDelegateCall
givenNotNull
givenNotPaused
whenCallerIsSender
whenRatePerSecondIsNotZero
whenRatePerSecondNotDifferent
whenCallerSender
whenNewRatePerSecondNotZero
{
vm.warp({ newTimestamp: WARP_ONE_MONTH });

uint40 actualLastTimeUpdate = flow.getLastTimeUpdate(defaultStreamId);
uint40 expectedLastTimeUpdate = uint40(block.timestamp - ONE_MONTH);
assertEq(actualLastTimeUpdate, expectedLastTimeUpdate, "last time updated");

uint128 newRatePerSecond = RATE_PER_SECOND / 2;

vm.expectEmit({ emitter: address(flow) });
emit AdjustFlowStream({
streamId: defaultStreamId,
oldRatePerSecond: RATE_PER_SECOND,
newRatePerSecond: newRatePerSecond,
amountOwed: ONE_MONTH_STREAMED_AMOUNT
});

vm.expectEmit({ emitter: address(flow) });
emit MetadataUpdate({ _tokenId: defaultStreamId });

uint128 actualRemainingAmount = flow.getRemainingAmount(defaultStreamId);
assertEq(actualRemainingAmount, 0, "remaining amount");

flow.adjustRatePerSecond({ streamId: defaultStreamId, newRatePerSecond: newRatePerSecond });

actualRemainingAmount = flow.getRemainingAmount(defaultStreamId);
uint128 expectedRemainingAmount = ONE_MONTH_STREAMED_AMOUNT;
assertEq(actualRemainingAmount, expectedRemainingAmount, "remaining amount");

uint128 actualRatePerSecond = flow.getRatePerSecond(defaultStreamId);
uint128 expectedRatePerSecond = newRatePerSecond;
assertEq(actualRatePerSecond, expectedRatePerSecond, "rate per second");

actualLastTimeUpdate = flow.getLastTimeUpdate(defaultStreamId);
expectedLastTimeUpdate = uint40(block.timestamp);
assertEq(actualLastTimeUpdate, expectedLastTimeUpdate, "last time updated");
}

function test_AdjustRatePerSecond() external whenNotDelegateCalled givenNotNull givenNotPaused whenCallerIsSender {
flow.deposit(defaultStreamId, TRANSFER_AMOUNT);
vm.warp({ newTimestamp: WARP_ONE_MONTH });

Expand All @@ -125,7 +82,7 @@ contract AdjustRatePerSecond_Integration_Concrete_Test is Integration_Test {
assertEq(actualRatePerSecond, expectedRatePerSecond, "rate per second");

uint40 actualLastTimeUpdate = flow.getLastTimeUpdate(defaultStreamId);
uint40 expectedLastTimeUpdate = uint40(block.timestamp - ONE_MONTH);
uint40 expectedLastTimeUpdate = getBlockTimestamp() - ONE_MONTH;
assertEq(actualLastTimeUpdate, expectedLastTimeUpdate, "last time updated");

uint128 actualRemainingAmount = flow.getRemainingAmount(defaultStreamId);
Expand All @@ -134,6 +91,7 @@ contract AdjustRatePerSecond_Integration_Concrete_Test is Integration_Test {

uint128 newRatePerSecond = RATE_PER_SECOND / 2;

// It should emit 1 {AdjustFlowStream}, 1 {MetadataUpdate} events.
vm.expectEmit({ emitter: address(flow) });
emit AdjustFlowStream({
streamId: defaultStreamId,
Expand All @@ -147,16 +105,19 @@ contract AdjustRatePerSecond_Integration_Concrete_Test is Integration_Test {

flow.adjustRatePerSecond({ streamId: defaultStreamId, newRatePerSecond: newRatePerSecond });

// It should update remaining amount.
actualRemainingAmount = flow.getRemainingAmount(defaultStreamId);
expectedRemainingAmount = ONE_MONTH_STREAMED_AMOUNT;
assertEq(actualRemainingAmount, expectedRemainingAmount, "remaining amount");

// It should set the new rate per second
actualRatePerSecond = flow.getRatePerSecond(defaultStreamId);
expectedRatePerSecond = newRatePerSecond;
assertEq(actualRatePerSecond, expectedRatePerSecond, "rate per second");

// It should update lastTimeUpdate
actualLastTimeUpdate = flow.getLastTimeUpdate(defaultStreamId);
expectedLastTimeUpdate = uint40(block.timestamp);
expectedLastTimeUpdate = getBlockTimestamp();
assertEq(actualLastTimeUpdate, expectedLastTimeUpdate, "last time updated");
}
}
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
adjustRatePerSecond.t.sol
β”œβ”€β”€ when delegate called
AdjustRatePerSecond_Integration_Concrete_Test
β”œβ”€β”€ when delegate call
β”‚ └── it should revert
└── when not delegate called
β”œβ”€β”€ given the id references a null stream
└── when no delegate call
β”œβ”€β”€ given null
β”‚ └── it should revert
└── given the id does not reference a null stream
β”œβ”€β”€ given the id references a paused stream
└── given not null
β”œβ”€β”€ given paused
β”‚ └── it should revert
└── given the id does not reference a paused stream
β”œβ”€β”€ when the caller is not the sender
β”‚ β”œβ”€β”€ when the caller is the recipient
└── given not paused
β”œβ”€β”€ when caller not sender
β”‚ β”œβ”€β”€ when caller recipient
β”‚ β”‚ └── it should revert
β”‚ └── when the caller is a malicious third party
β”‚ └── when caller malicious third party
β”‚ └── it should revert
└── when the caller is the sender
β”œβ”€β”€ when the provided rate per second is zero
└── when caller sender
β”œβ”€β”€ when new rate per second zero
β”‚ └── it should revert
└── when the provided rate per second is not zero
β”œβ”€β”€ when the provided rate per second is equal to the actual rate per second
└── when new rate per second not zero
β”œβ”€β”€ when new rate per second equals current rate per second
β”‚ └── it should revert
└── when the provided rate per second is not equal to the actual rate per second
β”œβ”€β”€ given the withdrawable amount is zero
β”‚ β”œβ”€β”€ it should update the stream remaining amount
β”‚ β”œβ”€β”€ it should update the rate per second
β”‚ β”œβ”€β”€ it should update the stream time
β”‚ β”œβ”€β”€ it should emit a {AdjustFlowStream} event
β”‚ └── it should emit a {MetadataUpdate} event
└── given the withdrawable amount is not zero
β”œβ”€β”€ it should update the stream remaining amount
β”œβ”€β”€ it should update the rate per second
β”œβ”€β”€ it should update the stream time
β”œβ”€β”€ it should emit a {AdjustFlowStream} event
└── it should emit a {MetadataUpdate} event
└── when new rate per second not equals current rate per second
β”œβ”€β”€ it should update remaining amount
β”œβ”€β”€ it should update lastTimeUpdate
β”œβ”€β”€ it should set the new rate per second
└── it should emit 1 {AdjustFlowStream}, 1 {MetadataUpdate} events
3 changes: 2 additions & 1 deletion test/integration/concrete/amount-owed-of/amountOwedOf.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ contract AmountOwedOf_Integration_Concrete_Test is Integration_Test {
function setUp() public virtual override {
Integration_Test.setUp();

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

Expand Down Expand Up @@ -37,7 +38,7 @@ contract AmountOwedOf_Integration_Concrete_Test is Integration_Test {
assertEq(amountOwed, remainingAmount, "amount owed");
}

function test_WhenCurrentTimeIsGreaterThanLastTimeUpdate() external view givenNotNull givenNotPaused {
function test_WhenCurrentTimeGreaterThanLastTimeUpdate() external view givenNotNull givenNotPaused {
// Fetch updated remaining amount
uint128 remainingAmount = flow.getRemainingAmount(defaultStreamId);
uint128 recentAmount = flow.recentAmountOf(defaultStreamId);
Expand Down
2 changes: 1 addition & 1 deletion test/integration/concrete/amount-owed-of/amountOwedOf.tree
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ AmountOwedOf_Integration_Concrete_Test
└── given not paused
β”œβ”€β”€ when current time equals last time update
β”‚ └── it should return remaining amount
└── when current time is greater than last time update
└── when current time greater than last time update
└── it should return the sum of remaining amount and recent amount.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract CreateAndDepositViaBroker_Integration_Concrete_Test is Integration_Test
expectRevert_DelegateCall(callData);
}

function test_WhenNotDelegateCalled() external {
function test_WhenNoDelegateCall() external {
uint256 expectedStreamId = flow.nextStreamId();

// It should emit events: 1 {MetadataUpdate}, 1 {CreateFlowStream}, 2 {Transfer}, 1
Expand All @@ -39,7 +39,7 @@ contract CreateAndDepositViaBroker_Integration_Concrete_Test is Integration_Test
recipient: users.recipient,
ratePerSecond: RATE_PER_SECOND,
asset: dai,
lastTimeUpdate: uint40(block.timestamp)
lastTimeUpdate: getBlockTimestamp()
});

vm.expectEmit({ emitter: address(dai) });
Expand Down Expand Up @@ -77,7 +77,7 @@ contract CreateAndDepositViaBroker_Integration_Concrete_Test is Integration_Test
asset: dai,
assetDecimals: 18,
balance: DEPOSIT_AMOUNT,
lastTimeUpdate: uint40(block.timestamp),
lastTimeUpdate: getBlockTimestamp(),
isPaused: false,
isStream: true,
isTransferable: IS_TRANFERABLE,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CreateAndDepositViaBroker_Integration_Concrete_Test
β”œβ”€β”€ when delegate call
β”‚ └── it should revert
└── when not delegate call
└── when no delegate call
β”œβ”€β”€ it should create the stream
β”œβ”€β”€ it should bump the next stream id
β”œβ”€β”€ it should mint the NFT
Expand Down
Loading

0 comments on commit 239d8c6

Please sign in to comment.