diff --git a/foundry.toml b/foundry.toml index 70f9532a8..ec0e9fa5f 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,7 +10,7 @@ ] gas_limit = 9223372036854775807 optimizer = true - optimizer_runs = 1000 + optimizer_runs = 800 out = "out" script = "script" sender = "0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38" diff --git a/src/core/SablierBatchLockup.sol b/src/core/SablierBatchLockup.sol index da878fa17..7f5f45f99 100644 --- a/src/core/SablierBatchLockup.sol +++ b/src/core/SablierBatchLockup.sol @@ -51,7 +51,7 @@ contract SablierBatchLockup is ISablierBatchLockup { streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockup.createWithDurationsLD( + streamIds[i] = lockup.createWithDurationsLD{ value: 0 }( Lockup.CreateWithDurations({ sender: batch[i].sender, recipient: batch[i].recipient, @@ -106,7 +106,7 @@ contract SablierBatchLockup is ISablierBatchLockup { } // Create the stream. - streamIds[i] = lockup.createWithTimestampsLD( + streamIds[i] = lockup.createWithTimestampsLD{ value: 0 }( Lockup.CreateWithTimestamps({ sender: batch[i].sender, recipient: batch[i].recipient, @@ -159,7 +159,7 @@ contract SablierBatchLockup is ISablierBatchLockup { streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockup.createWithDurationsLL( + streamIds[i] = lockup.createWithDurationsLL{ value: 0 }( Lockup.CreateWithDurations({ sender: batch[i].sender, recipient: batch[i].recipient, @@ -208,7 +208,7 @@ contract SablierBatchLockup is ISablierBatchLockup { streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockup.createWithTimestampsLL( + streamIds[i] = lockup.createWithTimestampsLL{ value: 0 }( Lockup.CreateWithTimestamps({ sender: batch[i].sender, recipient: batch[i].recipient, @@ -262,7 +262,7 @@ contract SablierBatchLockup is ISablierBatchLockup { streamIds = new uint256[](batchSize); for (i = 0; i < batchSize; ++i) { // Create the stream. - streamIds[i] = lockup.createWithDurationsLT( + streamIds[i] = lockup.createWithDurationsLT{ value: 0 }( Lockup.CreateWithDurations({ sender: batch[i].sender, recipient: batch[i].recipient, @@ -317,7 +317,7 @@ contract SablierBatchLockup is ISablierBatchLockup { } // Create the stream. - streamIds[i] = lockup.createWithTimestampsLT( + streamIds[i] = lockup.createWithTimestampsLT{ value: 0 }( Lockup.CreateWithTimestamps({ sender: batch[i].sender, recipient: batch[i].recipient, diff --git a/src/core/SablierLockup.sol b/src/core/SablierLockup.sol index 34a5cd4fa..727d90f59 100644 --- a/src/core/SablierLockup.sol +++ b/src/core/SablierLockup.sol @@ -136,6 +136,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase { LockupDynamic.SegmentWithDuration[] calldata segmentsWithDuration ) external + payable override noDelegateCall returns (uint256 streamId) @@ -170,6 +171,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase { LockupLinear.Durations calldata durations ) external + payable override noDelegateCall returns (uint256 streamId) @@ -212,6 +214,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase { LockupTranched.TrancheWithDuration[] calldata tranchesWithDuration ) external + payable override noDelegateCall returns (uint256 streamId) @@ -245,6 +248,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase { LockupDynamic.Segment[] calldata segments ) external + payable override noDelegateCall returns (uint256 streamId) @@ -260,6 +264,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase { uint40 cliffTime ) external + payable override noDelegateCall returns (uint256 streamId) @@ -274,6 +279,7 @@ contract SablierLockup is ISablierLockup, SablierLockupBase { LockupTranched.Tranche[] calldata tranches ) external + payable override noDelegateCall returns (uint256 streamId) diff --git a/src/core/abstracts/Batch.sol b/src/core/abstracts/Batch.sol index 47a86d48a..d6d2f7e05 100644 --- a/src/core/abstracts/Batch.sol +++ b/src/core/abstracts/Batch.sol @@ -13,7 +13,7 @@ abstract contract Batch is IBatch { //////////////////////////////////////////////////////////////////////////*/ /// @inheritdoc IBatch - function batch(bytes[] calldata calls) external override { + function batch(bytes[] calldata calls) external payable override { uint256 count = calls.length; for (uint256 i = 0; i < count; ++i) { diff --git a/src/core/abstracts/SablierLockupBase.sol b/src/core/abstracts/SablierLockupBase.sol index 3112fbf43..ac8bfd0d2 100644 --- a/src/core/abstracts/SablierLockupBase.sol +++ b/src/core/abstracts/SablierLockupBase.sol @@ -18,7 +18,7 @@ import { Batch } from "./Batch.sol"; import { NoDelegateCall } from "./NoDelegateCall.sol"; /// @title SablierLockupBase -/// @notice See the documentation in {SablierLockupBase}. +/// @notice See the documentation in {ISablierLockupBase}. abstract contract SablierLockupBase is Batch, // 1 inherited components NoDelegateCall, // 0 inherited components @@ -271,7 +271,7 @@ abstract contract SablierLockupBase is } /// @inheritdoc ISablierLockupBase - function burn(uint256 streamId) external override noDelegateCall notNull(streamId) { + function burn(uint256 streamId) external payable override noDelegateCall notNull(streamId) { // Check: only depleted streams can be burned. if (!_streams[streamId].isDepleted) { revert Errors.SablierLockupBase_StreamNotDepleted(streamId); @@ -292,7 +292,7 @@ abstract contract SablierLockupBase is } /// @inheritdoc ISablierLockupBase - function cancel(uint256 streamId) public override noDelegateCall notNull(streamId) { + function cancel(uint256 streamId) public payable override noDelegateCall notNull(streamId) { // Check: the stream is neither depleted nor canceled. if (_streams[streamId].isDepleted) { revert Errors.SablierLockupBase_StreamDepleted(streamId); @@ -310,7 +310,7 @@ abstract contract SablierLockupBase is } /// @inheritdoc ISablierLockupBase - function cancelMultiple(uint256[] calldata streamIds) external override noDelegateCall { + function cancelMultiple(uint256[] calldata streamIds) external payable override noDelegateCall { // Iterate over the provided array of stream IDs and cancel each stream. uint256 count = streamIds.length; for (uint256 i = 0; i < count; ++i) { @@ -320,7 +320,7 @@ abstract contract SablierLockupBase is } /// @inheritdoc ISablierLockupBase - function renounce(uint256 streamId) public override noDelegateCall notNull(streamId) { + function renounce(uint256 streamId) public payable override noDelegateCall notNull(streamId) { // Check: the stream is not cold. Lockup.Status status = _statusOf(streamId); if (status == Lockup.Status.DEPLETED) { @@ -347,7 +347,7 @@ abstract contract SablierLockupBase is } /// @inheritdoc ISablierLockupBase - function renounceMultiple(uint256[] calldata streamIds) external override noDelegateCall { + function renounceMultiple(uint256[] calldata streamIds) external payable override noDelegateCall { // Iterate over the provided array of stream IDs and renounce each stream. uint256 count = streamIds.length; for (uint256 i = 0; i < count; ++i) { @@ -374,7 +374,17 @@ abstract contract SablierLockupBase is } /// @inheritdoc ISablierLockupBase - function withdraw(uint256 streamId, address to, uint128 amount) public override noDelegateCall notNull(streamId) { + function withdraw( + uint256 streamId, + address to, + uint128 amount + ) + public + payable + override + noDelegateCall + notNull(streamId) + { // Check: the stream is not depleted. if (_streams[streamId].isDepleted) { revert Errors.SablierLockupBase_StreamDepleted(streamId); @@ -428,7 +438,28 @@ abstract contract SablierLockupBase is } /// @inheritdoc ISablierLockupBase - function withdrawMax(uint256 streamId, address to) external override returns (uint128 withdrawnAmount) { + function withdrawFees(address payable to) external override onlyAdmin { + // Check: the withdrawal address is not zero. + if (to == address(0)) { + revert Errors.SablierLockupBase_WithdrawFeesToZero(); + } + + uint256 feeAmount = address(this).balance; + + // Effect: transfer the fees to the provided address. + (bool success,) = to.call{ value: feeAmount }(""); + + // Revert if the call failed. + if (!success) { + revert Errors.SablierLockupBase_FeeWithdrawFailed(to, feeAmount); + } + + // Log the fee withdrawal. + emit ISablierLockupBase.WithdrawSablierFees({ admin: admin, to: to, feeAmount: feeAmount }); + } + + /// @inheritdoc ISablierLockupBase + function withdrawMax(uint256 streamId, address to) external payable override returns (uint128 withdrawnAmount) { withdrawnAmount = _withdrawableAmountOf(streamId); withdraw({ streamId: streamId, to: to, amount: withdrawnAmount }); } @@ -439,6 +470,7 @@ abstract contract SablierLockupBase is address newRecipient ) external + payable override noDelegateCall notNull(streamId) @@ -468,6 +500,7 @@ abstract contract SablierLockupBase is uint128[] calldata amounts ) external + payable override noDelegateCall { diff --git a/src/core/interfaces/IBatch.sol b/src/core/interfaces/IBatch.sol index b4125adbf..862762efb 100644 --- a/src/core/interfaces/IBatch.sol +++ b/src/core/interfaces/IBatch.sol @@ -5,5 +5,5 @@ pragma solidity >=0.8.22; interface IBatch { /// @notice Allows batched call to self, `this` contract. /// @param calls An array of inputs for each call. - function batch(bytes[] calldata calls) external; + function batch(bytes[] calldata calls) external payable; } diff --git a/src/core/interfaces/ISablierLockup.sol b/src/core/interfaces/ISablierLockup.sol index ae1918327..98ca53e61 100644 --- a/src/core/interfaces/ISablierLockup.sol +++ b/src/core/interfaces/ISablierLockup.sol @@ -96,6 +96,7 @@ interface ISablierLockup is ISablierLockupBase { LockupDynamic.SegmentWithDuration[] calldata segmentsWithDuration ) external + payable returns (uint256 streamId); /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to @@ -118,6 +119,7 @@ interface ISablierLockup is ISablierLockupBase { LockupLinear.Durations calldata durations ) external + payable returns (uint256 streamId); /// @notice Creates a stream by setting the start time to `block.timestamp`, and the end time to the sum of @@ -138,6 +140,7 @@ interface ISablierLockup is ISablierLockupBase { LockupTranched.TrancheWithDuration[] calldata tranchesWithDuration ) external + payable returns (uint256 streamId); /// @notice Creates a stream with the provided segment timestamps, implying the end time from the last timestamp. @@ -170,6 +173,7 @@ interface ISablierLockup is ISablierLockupBase { LockupDynamic.Segment[] calldata segments ) external + payable returns (uint256 streamId); /// @notice Creates a stream with the provided start time and end time. The stream is funded by `msg.sender` and is @@ -206,6 +210,7 @@ interface ISablierLockup is ISablierLockupBase { uint40 cliffTime ) external + payable returns (uint256 streamId); /// @notice Creates a stream with the provided tranche timestamps, implying the end time from the last timestamp. @@ -238,5 +243,6 @@ interface ISablierLockup is ISablierLockupBase { LockupTranched.Tranche[] calldata tranches ) external + payable returns (uint256 streamId); } diff --git a/src/core/interfaces/ISablierLockupBase.sol b/src/core/interfaces/ISablierLockupBase.sol index aa9dc165b..40bc73956 100644 --- a/src/core/interfaces/ISablierLockupBase.sol +++ b/src/core/interfaces/ISablierLockupBase.sol @@ -62,8 +62,16 @@ interface ISablierLockupBase is /// @param streamId The ID of the stream. /// @param to The address that has received the withdrawn assets. /// @param asset The contract address of the ERC-20 asset that has been withdrawn. - /// @param amount The amount of assets withdrawn, denoted in units of the asset's decimals. - event WithdrawFromLockupStream(uint256 indexed streamId, address indexed to, IERC20 indexed asset, uint128 amount); + /// @param withdrawnAmount The amount of assets withdrawn, denoted in units of the asset's decimals. + event WithdrawFromLockupStream( + uint256 indexed streamId, address indexed to, IERC20 indexed asset, uint128 withdrawnAmount + ); + + /// @notice Emitted when Sablier fees are withdrawn. + /// @param admin The address of the current contract admin. + /// @param feeAmount The amount of ETH transferred to the provided address. + /// @param to The address that has received the Sablier fees. + event WithdrawSablierFees(address indexed admin, uint256 indexed feeAmount, address indexed to); /*////////////////////////////////////////////////////////////////////////// CONSTANT FUNCTIONS @@ -227,7 +235,7 @@ interface ISablierLockupBase is /// - `msg.sender` must be either the NFT owner or an approved third party. /// /// @param streamId The ID of the stream NFT to burn. - function burn(uint256 streamId) external; + function burn(uint256 streamId) external payable; /// @notice Cancels the stream and refunds any remaining assets to the sender. /// @@ -244,7 +252,7 @@ interface ISablierLockupBase is /// - `msg.sender` must be the stream's sender. /// /// @param streamId The ID of the stream to cancel. - function cancel(uint256 streamId) external; + function cancel(uint256 streamId) external payable; /// @notice Cancels multiple streams and refunds any remaining assets to the sender. /// @@ -257,7 +265,7 @@ interface ISablierLockupBase is /// - All requirements from {cancel} must be met for each stream. /// /// @param streamIds The IDs of the streams to cancel. - function cancelMultiple(uint256[] calldata streamIds) external; + function cancelMultiple(uint256[] calldata streamIds) external payable; /// @notice Removes the right of the stream's sender to cancel the stream. /// @@ -273,7 +281,7 @@ interface ISablierLockupBase is /// - The stream must be cancelable. /// /// @param streamId The ID of the stream to renounce. - function renounce(uint256 streamId) external; + function renounce(uint256 streamId) external payable; /// @notice Renounces multiple streams. /// @@ -286,7 +294,7 @@ interface ISablierLockupBase is /// - All requirements from {renounce} must be met for each stream. /// /// @param streamIds An array of stream IDs to renounce. - function renounceMultiple(uint256[] calldata streamIds) external; + function renounceMultiple(uint256[] calldata streamIds) external payable; /// @notice Sets a new NFT descriptor contract, which produces the URI describing the Sablier stream NFTs. /// @@ -314,11 +322,26 @@ interface ISablierLockupBase is /// - `to` must not be the zero address. /// - `amount` must be greater than zero and must not exceed the withdrawable amount. /// - `to` must be the recipient if `msg.sender` is not the stream's recipient or an approved third party. + /// - The `msg.value` must not be less than the Sablier fee. /// /// @param streamId The ID of the stream to withdraw from. /// @param to The address receiving the withdrawn assets. /// @param amount The amount to withdraw, denoted in units of the asset's decimals. - function withdraw(uint256 streamId, address to, uint128 amount) external; + function withdraw(uint256 streamId, address to, uint128 amount) external payable; + + /// @notice Withdraws the Sablier fees accrued to the provided address. + /// + /// @dev Emits a {WithdrawSablierFees} event. + /// + /// Notes: + /// - This function transfers ETH to the provided address. If the receiver is a contract, it must be + /// able to receive ETH. + /// + /// Requirements: + /// - `msg.sender` must be the contract admin. + /// + /// @param to The address to receive the Sablier fees. + function withdrawFees(address payable to) external; /// @notice Withdraws the maximum withdrawable amount from the stream to the provided address `to`. /// @@ -333,7 +356,7 @@ interface ISablierLockupBase is /// @param streamId The ID of the stream to withdraw from. /// @param to The address receiving the withdrawn assets. /// @return withdrawnAmount The amount withdrawn, denoted in units of the asset's decimals. - function withdrawMax(uint256 streamId, address to) external returns (uint128 withdrawnAmount); + function withdrawMax(uint256 streamId, address to) external payable returns (uint128 withdrawnAmount); /// @notice Withdraws the maximum withdrawable amount from the stream to the current recipient, and transfers the /// NFT to `newRecipient`. @@ -357,6 +380,7 @@ interface ISablierLockupBase is address newRecipient ) external + payable returns (uint128 withdrawnAmount); /// @notice Withdraws assets from streams to the recipient of each stream. @@ -374,5 +398,5 @@ interface ISablierLockupBase is /// /// @param streamIds The IDs of the streams to withdraw from. /// @param amounts The amounts to withdraw, denoted in units of the asset's decimals. - function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external; + function withdrawMultiple(uint256[] calldata streamIds, uint128[] calldata amounts) external payable; } diff --git a/src/core/libraries/Errors.sol b/src/core/libraries/Errors.sol index 19cbddc13..4c0aab476 100644 --- a/src/core/libraries/Errors.sol +++ b/src/core/libraries/Errors.sol @@ -23,20 +23,7 @@ library Errors { error DelegateCall(); /*////////////////////////////////////////////////////////////////////////// - SABLIER-BATCH-LOCKUP - //////////////////////////////////////////////////////////////////////////*/ - - error SablierBatchLockup_BatchSizeZero(); - - /*////////////////////////////////////////////////////////////////////////// - LOCKUP-NFT-DESCRIPTOR - //////////////////////////////////////////////////////////////////////////*/ - - /// @notice Thrown when trying to generate the token URI for an unknown ERC-721 NFT contract. - error LockupNFTDescriptor_UnknownNFT(IERC721Metadata nft, string symbol); - - /*////////////////////////////////////////////////////////////////////////// - HELPERS + HELPERS //////////////////////////////////////////////////////////////////////////*/ /// @notice Thrown when the broker fee exceeds the maximum allowed fee. @@ -109,6 +96,19 @@ library Errors { uint128 depositAmount, uint128 startUnlockAmount, uint128 cliffUnlockAmount ); + /*////////////////////////////////////////////////////////////////////////// + LOCKUP-NFT-DESCRIPTOR + //////////////////////////////////////////////////////////////////////////*/ + + /// @notice Thrown when trying to generate the token URI for an unknown ERC-721 NFT contract. + error LockupNFTDescriptor_UnknownNFT(IERC721Metadata nft, string symbol); + + /*////////////////////////////////////////////////////////////////////////// + SABLIER-BATCH-LOCKUP + //////////////////////////////////////////////////////////////////////////*/ + + error SablierBatchLockup_BatchSizeZero(); + /*////////////////////////////////////////////////////////////////////////// SABLIER-LOCKUP-BASE //////////////////////////////////////////////////////////////////////////*/ @@ -119,6 +119,9 @@ library Errors { /// @notice Thrown when trying to allow to hook an address with no code. error SablierLockupBase_AllowToHookZeroCodeSize(address recipient); + /// @notice Thrown if the Sablier fees withdraw failed. + error SablierLockupBase_FeeWithdrawFailed(address to, uint256 feeAmount); + /// @notice Thrown when the hook does not return the correct selector. error SablierLockupBase_InvalidHookSelector(address recipient); @@ -152,6 +155,9 @@ library Errors { /// @notice Thrown when trying to withdraw to an address other than the recipient's. error SablierLockupBase_WithdrawalAddressNotRecipient(uint256 streamId, address caller, address to); + /// @notice Thrown when trying to withdraw fees to zero address. + error SablierLockupBase_WithdrawFeesToZero(); + /// @notice Thrown when trying to withdraw zero assets from a stream. error SablierLockupBase_WithdrawAmountZero(uint256 streamId); @@ -162,6 +168,9 @@ library Errors { /// @notice Thrown when trying to withdraw to the zero address. error SablierLockupBase_WithdrawToZeroAddress(uint256 streamId); + /// @notice Thrown when trying to withdraw with an insufficient fee payment. + error SablierLockupBase_WithdrawInsufficientFeePayment(uint256 feePaid, uint256 sablierFee); + /*////////////////////////////////////////////////////////////////////////// SABLIER-LOCKUP //////////////////////////////////////////////////////////////////////////*/ diff --git a/test/Base.t.sol b/test/Base.t.sol index a9855fc04..bfc2ecb48 100644 --- a/test/Base.t.sol +++ b/test/Base.t.sol @@ -19,6 +19,7 @@ import { ERC20Mock } from "./mocks/erc20/ERC20Mock.sol"; import { RecipientGood } from "./mocks/Hooks.sol"; import { NFTDescriptorMock } from "./mocks/NFTDescriptorMock.sol"; import { Noop } from "./mocks/Noop.sol"; +import { ContractWithoutReceiveEth, ContractWithReceiveEth } from "./mocks/ReceiveEth.sol"; import { Assertions } from "./utils/Assertions.sol"; import { Calculations } from "./utils/Calculations.sol"; import { Defaults } from "./utils/Defaults.sol"; @@ -39,6 +40,8 @@ abstract contract Base_Test is Assertions, Calculations, DeployOptimized, Modifi //////////////////////////////////////////////////////////////////////////*/ ISablierBatchLockup internal batchLockup; + ContractWithoutReceiveEth internal contractWithoutReceiveEth; + ContractWithReceiveEth internal contractWithReceiveEth; ERC20Mock internal dai; Defaults internal defaults; ISablierLockup internal lockup; @@ -58,12 +61,17 @@ abstract contract Base_Test is Assertions, Calculations, DeployOptimized, Modifi function setUp() public virtual { // Deploy the base test contracts. + contractWithoutReceiveEth = new ContractWithoutReceiveEth(); + contractWithReceiveEth = new ContractWithReceiveEth(); dai = new ERC20Mock("Dai Stablecoin", "DAI"); noop = new Noop(); recipientGood = new RecipientGood(); + vm.deal({ account: address(recipientGood), newBalance: 100 ether }); usdt = new ERC20MissingReturn("Tether USD", "USDT", 6); // Label the base test contracts. + vm.label({ account: address(contractWithoutReceiveEth), newLabel: "Contract Without Receive Eth" }); + vm.label({ account: address(contractWithReceiveEth), newLabel: "Contract With Receive Eth" }); vm.label({ account: address(dai), newLabel: "DAI" }); vm.label({ account: address(recipientGood), newLabel: "Good Recipient" }); vm.label({ account: address(noop), newLabel: "Noop" }); @@ -71,6 +79,7 @@ abstract contract Base_Test is Assertions, Calculations, DeployOptimized, Modifi // Create the protocol admin. users.admin = payable(makeAddr({ name: "Admin" })); + vm.deal({ account: users.admin, newBalance: 100 ether }); vm.startPrank({ msgSender: users.admin }); // Deploy the defaults contract. @@ -84,7 +93,7 @@ abstract contract Base_Test is Assertions, Calculations, DeployOptimized, Modifi nftDescriptorMock = new NFTDescriptorMock(); // Set the Sablier fee on the Merkle factory. - merkleFactory.setDefaultSablierFee(defaults.DEFAULT_SABLIER_FEE()); + merkleFactory.setDefaultSablierFee(SABLIER_FEE); // Create users for testing. users.alice = createUser("Alice"); diff --git a/test/core/fork/LockupDynamic.t.sol b/test/core/fork/LockupDynamic.t.sol index a0d77adfd..bbea2e69e 100644 --- a/test/core/fork/LockupDynamic.t.sol +++ b/test/core/fork/LockupDynamic.t.sol @@ -72,6 +72,7 @@ abstract contract Lockup_Dynamic_Fork_Test is Fork_Test { uint256 initialBrokerBalance; uint128 totalAmount; // Withdraw vars + uint256 initialETHLockupBalance; uint128 actualWithdrawnAmount; uint128 expectedWithdrawnAmount; uint128 withdrawableAmount; @@ -259,6 +260,7 @@ abstract contract Lockup_Dynamic_Fork_Test is Fork_Test { // Load the pre-withdraw asset balances. vars.initialLockupBalance = vars.actualLockupBalance; vars.initialRecipientBalance = FORK_ASSET.balanceOf(params.recipient); + vars.initialETHLockupBalance = address(lockup).balance; // Expect the relevant events to be emitted. vm.expectEmit({ emitter: address(lockup) }); @@ -266,14 +268,19 @@ abstract contract Lockup_Dynamic_Fork_Test is Fork_Test { streamId: vars.streamId, to: params.recipient, asset: FORK_ASSET, - amount: params.withdrawAmount + withdrawnAmount: params.withdrawAmount }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: vars.streamId }); // Make the withdrawal. resetPrank({ msgSender: params.recipient }); - lockup.withdraw({ streamId: vars.streamId, to: params.recipient, amount: params.withdrawAmount }); + vm.deal({ account: params.recipient, newBalance: SABLIER_FEE }); + lockup.withdraw{ value: SABLIER_FEE }({ + streamId: vars.streamId, + to: params.recipient, + amount: params.withdrawAmount + }); // Assert that the stream's status is correct. vars.actualStatus = lockup.statusOf(vars.streamId); @@ -286,6 +293,11 @@ abstract contract Lockup_Dynamic_Fork_Test is Fork_Test { } assertEq(vars.actualStatus, vars.expectedStatus, "post-withdraw stream status"); + // Assert that the contract's balance has been updated. + assertEq( + address(lockup).balance, vars.initialETHLockupBalance + SABLIER_FEE, "post-withdraw contract balance" + ); + // Assert that the withdrawn amount has been updated. vars.actualWithdrawnAmount = lockup.getWithdrawnAmount(vars.streamId); vars.expectedWithdrawnAmount = params.withdrawAmount; diff --git a/test/core/fork/LockupLinear.t.sol b/test/core/fork/LockupLinear.t.sol index 1efccf6bd..93ec3b53d 100644 --- a/test/core/fork/LockupLinear.t.sol +++ b/test/core/fork/LockupLinear.t.sol @@ -77,6 +77,7 @@ abstract contract Lockup_Linear_Fork_Test is Fork_Test { uint256 expectedNextStreamId; uint256 initialBrokerBalance; // Withdraw vars + uint256 initialETHLockupBalance; uint128 actualWithdrawnAmount; uint128 expectedWithdrawnAmount; uint128 withdrawableAmount; @@ -300,20 +301,27 @@ abstract contract Lockup_Linear_Fork_Test is Fork_Test { vars.initialLockupBalance = vars.actualLockupBalance; vars.initialRecipientBalance = FORK_ASSET.balanceOf(params.recipient); + vars.initialETHLockupBalance = address(lockup).balance; + // Expect the relevant events to be emitted. vm.expectEmit({ emitter: address(lockup) }); emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: vars.streamId, to: params.recipient, asset: FORK_ASSET, - amount: params.withdrawAmount + withdrawnAmount: params.withdrawAmount }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: vars.streamId }); // Make the withdrawal. resetPrank({ msgSender: params.recipient }); - lockup.withdraw({ streamId: vars.streamId, to: params.recipient, amount: params.withdrawAmount }); + vm.deal({ account: params.recipient, newBalance: SABLIER_FEE }); + lockup.withdraw{ value: SABLIER_FEE }({ + streamId: vars.streamId, + to: params.recipient, + amount: params.withdrawAmount + }); // Assert that the stream's status is correct. vars.actualStatus = lockup.statusOf(vars.streamId); @@ -326,6 +334,11 @@ abstract contract Lockup_Linear_Fork_Test is Fork_Test { } assertEq(vars.actualStatus, vars.expectedStatus, "post-withdraw stream status"); + // Assert that the contract's balance has been updated. + assertEq( + address(lockup).balance, vars.initialETHLockupBalance + SABLIER_FEE, "post-withdraw contract balance" + ); + // Assert that the withdrawn amount has been updated. vars.actualWithdrawnAmount = lockup.getWithdrawnAmount(vars.streamId); vars.expectedWithdrawnAmount = params.withdrawAmount; diff --git a/test/core/fork/LockupTranched.t.sol b/test/core/fork/LockupTranched.t.sol index 6079cd653..aca321ff0 100644 --- a/test/core/fork/LockupTranched.t.sol +++ b/test/core/fork/LockupTranched.t.sol @@ -72,6 +72,7 @@ abstract contract Lockup_Tranched_Fork_Test is Fork_Test { uint256 initialBrokerBalance; uint128 totalAmount; // Withdraw vars + uint256 initialETHLockupBalance; uint128 actualWithdrawnAmount; uint128 expectedWithdrawnAmount; uint128 withdrawableAmount; @@ -259,6 +260,7 @@ abstract contract Lockup_Tranched_Fork_Test is Fork_Test { // Load the pre-withdraw asset balances. vars.initialLockupBalance = vars.actualLockupBalance; vars.initialRecipientBalance = FORK_ASSET.balanceOf(params.recipient); + vars.initialETHLockupBalance = address(lockup).balance; // Expect the relevant events to be emitted. vm.expectEmit({ emitter: address(lockup) }); @@ -266,14 +268,19 @@ abstract contract Lockup_Tranched_Fork_Test is Fork_Test { streamId: vars.streamId, to: params.recipient, asset: FORK_ASSET, - amount: params.withdrawAmount + withdrawnAmount: params.withdrawAmount }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: vars.streamId }); // Make the withdrawal. resetPrank({ msgSender: params.recipient }); - lockup.withdraw({ streamId: vars.streamId, to: params.recipient, amount: params.withdrawAmount }); + vm.deal({ account: params.recipient, newBalance: SABLIER_FEE }); + lockup.withdraw{ value: SABLIER_FEE }({ + streamId: vars.streamId, + to: params.recipient, + amount: params.withdrawAmount + }); // Assert that the stream's status is correct. vars.actualStatus = lockup.statusOf(vars.streamId); @@ -286,6 +293,11 @@ abstract contract Lockup_Tranched_Fork_Test is Fork_Test { } assertEq(vars.actualStatus, vars.expectedStatus, "post-withdraw stream status"); + // Assert that the contract's balance has been updated. + assertEq( + address(lockup).balance, vars.initialETHLockupBalance + SABLIER_FEE, "post-withdraw contract balance" + ); + // Assert that the withdrawn amount has been updated. vars.actualWithdrawnAmount = lockup.getWithdrawnAmount(vars.streamId); vars.expectedWithdrawnAmount = params.withdrawAmount; diff --git a/test/core/integration/Integration.t.sol b/test/core/integration/Integration.t.sol index db6e42f92..ba0d92a2b 100644 --- a/test/core/integration/Integration.t.sol +++ b/test/core/integration/Integration.t.sol @@ -74,6 +74,8 @@ abstract contract Integration_Test is Base_Test { recipientInterfaceIDMissing = new RecipientInterfaceIDMissing(); recipientInvalidSelector = new RecipientInvalidSelector(); recipientReentrant = new RecipientReentrant(); + // We need to fund with ETH the reentrant contract as the withdraw function is payable. + vm.deal({ account: address(recipientReentrant), newBalance: 100 ether }); recipientReverting = new RecipientReverting(); vm.label({ account: address(recipientInterfaceIDIncorrect), newLabel: "Recipient Interface ID Incorrect" }); vm.label({ account: address(recipientInterfaceIDMissing), newLabel: "Recipient Interface ID Missing" }); @@ -114,11 +116,11 @@ abstract contract Integration_Test is Base_Test { function createDefaultStream(Lockup.CreateWithTimestamps memory params) internal returns (uint256 streamId) { if (lockupModel == Lockup.Model.LOCKUP_DYNAMIC) { - streamId = lockup.createWithTimestampsLD(params, _defaultParams.segments); + streamId = createWithTimestampsLD(params, _defaultParams.segments); } else if (lockupModel == Lockup.Model.LOCKUP_LINEAR) { - streamId = lockup.createWithTimestampsLL(params, _defaultParams.unlockAmounts, _defaultParams.cliffTime); + streamId = createWithTimestampsLL(params, _defaultParams.unlockAmounts, _defaultParams.cliffTime); } else if (lockupModel == Lockup.Model.LOCKUP_TRANCHED) { - streamId = lockup.createWithTimestampsLT(params, _defaultParams.tranches); + streamId = createWithTimestampsLT(params, _defaultParams.tranches); } } @@ -140,15 +142,13 @@ abstract contract Integration_Test is Base_Test { function createDefaultStreamWithDurations() internal returns (uint256 streamId) { if (lockupModel == Lockup.Model.LOCKUP_DYNAMIC) { - streamId = - lockup.createWithDurationsLD(_defaultParams.createWithDurations, _defaultParams.segmentsWithDurations); + streamId = createWithDurationsLD(_defaultParams.createWithDurations, _defaultParams.segmentsWithDurations); } else if (lockupModel == Lockup.Model.LOCKUP_LINEAR) { - streamId = lockup.createWithDurationsLL( + streamId = createWithDurationsLL( _defaultParams.createWithDurations, _defaultParams.unlockAmounts, _defaultParams.durations ); } else if (lockupModel == Lockup.Model.LOCKUP_TRANCHED) { - streamId = - lockup.createWithDurationsLT(_defaultParams.createWithDurations, _defaultParams.tranchesWithDurations); + streamId = createWithDurationsLT(_defaultParams.createWithDurations, _defaultParams.tranchesWithDurations); } } @@ -158,13 +158,13 @@ abstract contract Integration_Test is Base_Test { if (lockupModel == Lockup.Model.LOCKUP_DYNAMIC) { LockupDynamic.Segment[] memory segments = _defaultParams.segments; segments[1].timestamp = endTime; - streamId = lockup.createWithTimestampsLD(params, segments); + streamId = createWithTimestampsLD(params, segments); } else if (lockupModel == Lockup.Model.LOCKUP_LINEAR) { - streamId = lockup.createWithTimestampsLL(params, _defaultParams.unlockAmounts, defaults.CLIFF_TIME()); + streamId = createWithTimestampsLL(params, _defaultParams.unlockAmounts, defaults.CLIFF_TIME()); } else if (lockupModel == Lockup.Model.LOCKUP_TRANCHED) { LockupTranched.Tranche[] memory tranches = _defaultParams.tranches; tranches[1].timestamp = endTime; - streamId = lockup.createWithTimestampsLT(params, tranches); + streamId = createWithTimestampsLT(params, tranches); } } @@ -204,6 +204,17 @@ abstract contract Integration_Test is Base_Test { ); } + function expectRevert_CallerNotAdmin(bytes memory callData) internal { + resetPrank({ msgSender: users.eve }); + (bool success, bytes memory returnData) = address(lockup).call(callData); + assertFalse(success, "caller not admin call success"); + assertEq( + returnData, + abi.encodeWithSelector(Errors.CallerNotAdmin.selector, users.admin, users.eve), + "caller not admin call return data" + ); + } + function expectRevert_CANCELEDStatus(bytes memory callData) internal { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); lockup.cancel(defaultStreamId); @@ -225,7 +236,7 @@ abstract contract Integration_Test is Base_Test { function expectRevert_DEPLETEDStatus(bytes memory callData) internal { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + lockup.withdrawMax{ value: SABLIER_FEE }({ streamId: defaultStreamId, to: users.recipient }); (bool success, bytes memory returnData) = address(lockup).call(callData); assertFalse(success, "depleted status call success"); @@ -257,4 +268,133 @@ abstract contract Integration_Test is Base_Test { "settled status call return data" ); } + + /*////////////////////////////////////////////////////////////////////////// + MIRROR-FUNCTIONS + //////////////////////////////////////////////////////////////////////////*/ + + function burn(uint256 streamId) internal { + lockup.burn{ value: 0 }(streamId); + } + + function cancel(uint256 streamId) internal { + lockup.cancel{ value: 0 }(streamId); + } + + function cancelMultiple(uint256[] memory streamIds) internal { + lockup.cancelMultiple{ value: 0 }(streamIds); + } + + function createWithDurationsLD( + Lockup.CreateWithDurations memory params, + LockupDynamic.SegmentWithDuration[] memory segmentsWithDuration + ) + internal + returns (uint256 streamId) + { + streamId = lockup.createWithDurationsLD{ value: 0 }(params, segmentsWithDuration); + } + + function createWithDurationsLL( + Lockup.CreateWithDurations memory params, + LockupLinear.UnlockAmounts memory unlockAmounts, + LockupLinear.Durations memory durations + ) + internal + returns (uint256 streamId) + { + streamId = lockup.createWithDurationsLL{ value: 0 }(params, unlockAmounts, durations); + } + + function createWithDurationsLT( + Lockup.CreateWithDurations memory params, + LockupTranched.TrancheWithDuration[] memory tranchesWithDuration + ) + internal + returns (uint256 streamId) + { + streamId = lockup.createWithDurationsLT{ value: 0 }(params, tranchesWithDuration); + } + + function createWithTimestampsLD( + Lockup.CreateWithTimestamps memory params, + LockupDynamic.Segment[] memory segments + ) + internal + returns (uint256 streamId) + { + streamId = lockup.createWithTimestampsLD{ value: 0 }(params, segments); + } + + function createWithTimestampsLL( + Lockup.CreateWithTimestamps memory params, + LockupLinear.UnlockAmounts memory unlockAmounts, + uint40 cliffTime + ) + internal + returns (uint256 streamId) + { + streamId = lockup.createWithTimestampsLL{ value: 0 }(params, unlockAmounts, cliffTime); + } + + function createWithTimestampsLT( + Lockup.CreateWithTimestamps memory params, + LockupTranched.Tranche[] memory tranches + ) + internal + returns (uint256 streamId) + { + streamId = lockup.createWithTimestampsLT{ value: 0 }(params, tranches); + } + + function renounce(uint256 streamId) internal { + lockup.renounce{ value: 0 }(streamId); + } + + modifier balanceTest() { + uint256 balanceBefore = address(lockup).balance; + _; + assertEq(address(lockup).balance, balanceBefore + SABLIER_FEE, "balance after function call"); + } + + function withdraw(uint256 streamId, address to, uint128 amount) internal { + lockup.withdraw{ value: SABLIER_FEE }(streamId, to, amount); + } + + function withdrawWithBalTest(uint256 streamId, address to, uint128 amount) internal balanceTest { + withdraw(streamId, to, amount); + } + + function withdrawMax(uint256 streamId, address to) internal returns (uint128) { + return lockup.withdrawMax{ value: SABLIER_FEE }(streamId, to); + } + + function withdrawMaxWithBalTest(uint256 streamId, address to) internal balanceTest returns (uint128) { + uint128 withdrawn = withdrawMax(streamId, to); + return withdrawn; + } + + function withdrawMaxAndTransfer(uint256 streamId, address newRecipient) internal returns (uint128) { + return lockup.withdrawMaxAndTransfer{ value: SABLIER_FEE }(streamId, newRecipient); + } + + function withdrawMaxAndTransferWithBalTest( + uint256 streamId, + address newRecipient + ) + internal + balanceTest + returns (uint128) + { + uint128 withdrawn = withdrawMaxAndTransfer(streamId, newRecipient); + return withdrawn; + } + + function withdrawMultiple(uint256[] memory streamIds, uint128[] memory amounts) internal { + lockup.withdrawMultiple{ value: SABLIER_FEE }(streamIds, amounts); + } + + function withdrawMultipleWithBalTest(uint256[] memory streamIds, uint128[] memory amounts) internal balanceTest { + withdrawMultiple(streamIds, amounts); + } } diff --git a/test/core/integration/concrete/lockup-base/allow-to-hook/allowToHook.t.sol b/test/core/integration/concrete/lockup-base/allow-to-hook/allowToHook.t.sol index cbafeccf1..f0353c91f 100644 --- a/test/core/integration/concrete/lockup-base/allow-to-hook/allowToHook.t.sol +++ b/test/core/integration/concrete/lockup-base/allow-to-hook/allowToHook.t.sol @@ -8,12 +8,7 @@ import { Integration_Test } from "../../../Integration.t.sol"; contract AllowToHook_Integration_Concrete_Test is Integration_Test { function test_RevertWhen_CallerNotAdmin() external { - // Make Eve the caller in this test. - resetPrank({ msgSender: users.eve }); - - // Run the test. - vm.expectRevert(abi.encodeWithSelector(Errors.CallerNotAdmin.selector, users.admin, users.eve)); - lockup.allowToHook(users.eve); + expectRevert_CallerNotAdmin({ callData: abi.encodeCall(lockup.allowToHook, users.eve) }); } function test_RevertWhen_ProvidedAddressNotContract() external whenCallerAdmin { diff --git a/test/core/integration/concrete/lockup-base/batch/batch.t.sol b/test/core/integration/concrete/lockup-base/batch/batch.t.sol index ad2303b0f..039599fe0 100644 --- a/test/core/integration/concrete/lockup-base/batch/batch.t.sol +++ b/test/core/integration/concrete/lockup-base/batch/batch.t.sol @@ -35,7 +35,7 @@ contract Batch_Integration_Concrete_Test is Integration_Test { bytes[] memory calls = new bytes[](1); calls[0] = abi.encodeCall(lockup.cancel, (defaultStreamId)); - lockup.batch(calls); + lockup.batch{ value: 0 }(calls); assertTrue(lockup.wasCanceled(defaultStreamId)); } } diff --git a/test/core/integration/concrete/lockup-base/burn/burn.t.sol b/test/core/integration/concrete/lockup-base/burn/burn.t.sol index 2dc0d4357..a71eae61a 100644 --- a/test/core/integration/concrete/lockup-base/burn/burn.t.sol +++ b/test/core/integration/concrete/lockup-base/burn/burn.t.sol @@ -19,28 +19,28 @@ contract Burn_Integration_Concrete_Test is Integration_Test { function test_RevertGiven_PENDINGStatus() external whenNoDelegateCall givenNotNull givenNotDepletedStream { vm.warp({ newTimestamp: getBlockTimestamp() - 1 seconds }); vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotDepleted.selector, defaultStreamId)); - lockup.burn(defaultStreamId); + burn(defaultStreamId); } function test_RevertGiven_STREAMINGStatus() external whenNoDelegateCall givenNotNull givenNotDepletedStream { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotDepleted.selector, defaultStreamId)); - lockup.burn(defaultStreamId); + burn(defaultStreamId); } function test_RevertGiven_SETTLEDStatus() external whenNoDelegateCall givenNotNull givenNotDepletedStream { vm.warp({ newTimestamp: defaults.END_TIME() }); vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotDepleted.selector, defaultStreamId)); - lockup.burn(defaultStreamId); + burn(defaultStreamId); } function test_RevertGiven_CANCELEDStatus() external whenNoDelegateCall givenNotNull givenNotDepletedStream { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); resetPrank({ msgSender: users.sender }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); resetPrank({ msgSender: users.recipient }); vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotDepleted.selector, defaultStreamId)); - lockup.burn(defaultStreamId); + burn(defaultStreamId); } function test_RevertWhen_CallerMaliciousThirdParty() @@ -64,7 +64,7 @@ contract Burn_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_Unauthorized.selector, defaultStreamId, users.sender) ); - lockup.burn(defaultStreamId); + burn(defaultStreamId); } function test_WhenCallerApprovedThirdParty() @@ -89,11 +89,11 @@ contract Burn_Integration_Concrete_Test is Integration_Test { whenCallerRecipient { // Burn the NFT so that it no longer exists. - lockup.burn(defaultStreamId); + burn(defaultStreamId); // Run the test. vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, defaultStreamId)); - lockup.burn(defaultStreamId); + burn(defaultStreamId); } function test_GivenNonTransferableNFT() @@ -124,7 +124,7 @@ contract Burn_Integration_Concrete_Test is Integration_Test { emit IERC4906.MetadataUpdate({ _tokenId: streamId }); // Burn the NFT. - lockup.burn(streamId); + burn(streamId); // It should burn the NFT. vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, streamId)); diff --git a/test/core/integration/concrete/lockup-base/cancel-multiple/cancelMultiple.t.sol b/test/core/integration/concrete/lockup-base/cancel-multiple/cancelMultiple.t.sol index 796709a23..fd1936a96 100644 --- a/test/core/integration/concrete/lockup-base/cancel-multiple/cancelMultiple.t.sol +++ b/test/core/integration/concrete/lockup-base/cancel-multiple/cancelMultiple.t.sol @@ -30,7 +30,7 @@ contract CancelMultiple_Integration_Concrete_Test is Integration_Test { function test_WhenZeroArrayLength() external whenNoDelegateCall { // It should do nothing. uint256[] memory nullStreamIds = new uint256[](0); - lockup.cancelMultiple(nullStreamIds); + cancelMultiple(nullStreamIds); } function test_RevertGiven_AtleastOneNullStream() external whenNoDelegateCall whenNonZeroArrayLength { @@ -49,7 +49,7 @@ contract CancelMultiple_Integration_Concrete_Test is Integration_Test { uint256 earlyEndtimeStreamId = createDefaultStreamWithEndTime(earlyEndTime); vm.warp({ newTimestamp: earlyEndTime + 1 seconds }); vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_StreamSettled.selector, earlyEndtimeStreamId)); - lockup.cancelMultiple({ streamIds: Solarray.uint256s(streamIds[0], earlyEndtimeStreamId) }); + cancelMultiple({ streamIds: Solarray.uint256s(streamIds[0], earlyEndtimeStreamId) }); } function test_RevertWhen_CallerUnauthorizedForAny() @@ -66,7 +66,7 @@ contract CancelMultiple_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_Unauthorized.selector, streamIds[0], users.recipient) ); - lockup.cancelMultiple(streamIds); + cancelMultiple(streamIds); } function test_RevertGiven_AtleastOneNonCancelableStream() @@ -80,7 +80,7 @@ contract CancelMultiple_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotCancelable.selector, notCancelableStreamId) ); - lockup.cancelMultiple({ streamIds: Solarray.uint256s(streamIds[0], notCancelableStreamId) }); + cancelMultiple({ streamIds: Solarray.uint256s(streamIds[0], notCancelableStreamId) }); } function test_GivenAllStreamsCancelable() @@ -121,7 +121,7 @@ contract CancelMultiple_Integration_Concrete_Test is Integration_Test { }); // Cancel the streams. - lockup.cancelMultiple(streamIds); + cancelMultiple(streamIds); // It should mark the streams as canceled. Lockup.Status expectedStatus = Lockup.Status.CANCELED; diff --git a/test/core/integration/concrete/lockup-base/cancel/cancel.t.sol b/test/core/integration/concrete/lockup-base/cancel/cancel.t.sol index c62440605..2d2b2f5da 100644 --- a/test/core/integration/concrete/lockup-base/cancel/cancel.t.sol +++ b/test/core/integration/concrete/lockup-base/cancel/cancel.t.sol @@ -55,7 +55,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_Unauthorized.selector, defaultStreamId, users.recipient) ); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); } function test_RevertGiven_NonCancelableStream() @@ -68,7 +68,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotCancelable.selector, notCancelableStreamId) ); - lockup.cancel(notCancelableStreamId); + cancel(notCancelableStreamId); } function test_GivenPENDINGStatus() @@ -83,7 +83,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { vm.warp({ newTimestamp: getBlockTimestamp() - 1 seconds }); // Cancel the stream. - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // It should mark the stream as depleted. Lockup.Status actualStatus = lockup.statusOf(defaultStreamId); @@ -117,7 +117,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { }); // Cancel the stream. - lockup.cancel(recipientGoodStreamId); + cancel(recipientGoodStreamId); // It should mark the stream as canceled. Lockup.Status actualStatus = lockup.statusOf(recipientGoodStreamId); @@ -144,7 +144,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { vm.expectRevert("You shall not pass"); // Cancel the stream. - lockup.cancel(recipientRevertStreamId); + cancel(recipientRevertStreamId); } function test_RevertWhen_RecipientReturnsInvalidSelector() @@ -171,7 +171,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { ); // Cancel the stream. - lockup.cancel(recipientInvalidSelectorStreamId); + cancel(recipientInvalidSelectorStreamId); } function test_WhenReentrancy() @@ -211,7 +211,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { ); // Cancel the stream. - lockup.cancel(recipientReentrantStreamId); + cancel(recipientReentrantStreamId); // It should mark the stream as depleted. The reentrant recipient withdrew all the funds. Lockup.Status actualStatus = lockup.statusOf(recipientReentrantStreamId); @@ -263,7 +263,7 @@ abstract contract Cancel_Integration_Concrete_Test is Integration_Test { emit IERC4906.MetadataUpdate({ _tokenId: recipientGoodStreamId }); // Cancel the stream. - lockup.cancel(recipientGoodStreamId); + cancel(recipientGoodStreamId); // It should mark the stream as canceled. Lockup.Status actualStatus = lockup.statusOf(recipientGoodStreamId); diff --git a/test/core/integration/concrete/lockup-base/getters/getters.t.sol b/test/core/integration/concrete/lockup-base/getters/getters.t.sol index ea1b1315c..940318a97 100644 --- a/test/core/integration/concrete/lockup-base/getters/getters.t.sol +++ b/test/core/integration/concrete/lockup-base/getters/getters.t.sol @@ -66,10 +66,10 @@ contract Getters_Integration_Concrete_Test is Integration_Test { resetPrank({ msgSender: users.recipient }); // Deplete the stream. - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // Burn the NFT. - lockup.burn(defaultStreamId); + burn(defaultStreamId); // Expect the relevant error when retrieving the recipient. vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, defaultStreamId)); @@ -93,7 +93,7 @@ contract Getters_Integration_Concrete_Test is Integration_Test { function test_GetRefundedAmountGivenCanceledStreamAndCANCELEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); // Cancel the stream. - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // It should return the correct refunded amount. uint128 actualRefundedAmount = lockup.getRefundedAmount(defaultStreamId); @@ -104,10 +104,10 @@ contract Getters_Integration_Concrete_Test is Integration_Test { function test_GetRefundedAmountGivenCanceledStreamAndDEPLETEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); // Cancel the stream. - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // Withdraw the maximum amount to deplete the stream. - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // It should return the correct refunded amount. uint128 actualRefundedAmount = lockup.getRefundedAmount(defaultStreamId); @@ -131,7 +131,7 @@ contract Getters_Integration_Concrete_Test is Integration_Test { function test_GetRefundedAmountGivenDEPLETEDStatus() external givenNotNull givenNotCanceledStream { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); uint128 actualRefundedAmount = lockup.getRefundedAmount(defaultStreamId); uint128 expectedRefundedAmount = 0; assertEq(actualRefundedAmount, expectedRefundedAmount, "refundedAmount"); @@ -198,7 +198,7 @@ contract Getters_Integration_Concrete_Test is Integration_Test { uint128 withdrawAmount = lockup.streamedAmountOf(defaultStreamId); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: withdrawAmount }); + withdraw({ streamId: defaultStreamId, to: users.recipient, amount: withdrawAmount }); // It should return the correct withdrawn amount. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(defaultStreamId); @@ -269,13 +269,13 @@ contract Getters_Integration_Concrete_Test is Integration_Test { function test_IsColdGivenCANCELEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); assertTrue(lockup.isCold(defaultStreamId), "isCold"); } function test_IsColdGivenDEPLETEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); assertTrue(lockup.isCold(defaultStreamId), "isCold"); } @@ -293,7 +293,7 @@ contract Getters_Integration_Concrete_Test is Integration_Test { function test_IsDepletedGivenDepletedStream() external givenNotNull { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); assertTrue(lockup.isDepleted(defaultStreamId), "isDepleted"); } @@ -350,13 +350,13 @@ contract Getters_Integration_Concrete_Test is Integration_Test { function test_IsWarmGivenCANCELEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); assertFalse(lockup.isWarm(defaultStreamId), "isWarm"); } function test_IsWarmGivenDEPLETEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); assertFalse(lockup.isWarm(defaultStreamId), "isWarm"); } @@ -373,7 +373,7 @@ contract Getters_Integration_Concrete_Test is Integration_Test { } function test_WasCanceledGivenNotCanceledStream() external givenNotNull { - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); assertTrue(lockup.wasCanceled(defaultStreamId), "wasCanceled"); } } diff --git a/test/core/integration/concrete/lockup-base/refundable-amount-of/refundableAmountOf.t.sol b/test/core/integration/concrete/lockup-base/refundable-amount-of/refundableAmountOf.t.sol index 2fb5f383c..27a334308 100644 --- a/test/core/integration/concrete/lockup-base/refundable-amount-of/refundableAmountOf.t.sol +++ b/test/core/integration/concrete/lockup-base/refundable-amount-of/refundableAmountOf.t.sol @@ -17,7 +17,7 @@ abstract contract RefundableAmountOf_Integration_Concrete_Test is Integration_Te function test_GivenCanceledStreamAndCANCELEDStatus() external givenNotNull givenCancelableStream { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); uint128 actualRefundableAmount = lockup.refundableAmountOf(defaultStreamId); uint128 expectedRefundableAmount = 0; assertEq(actualRefundableAmount, expectedRefundableAmount, "refundableAmount"); @@ -25,8 +25,8 @@ abstract contract RefundableAmountOf_Integration_Concrete_Test is Integration_Te function test_GivenCanceledStreamAndDEPLETEDStatus() external givenNotNull givenCancelableStream { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + cancel(defaultStreamId); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() + 10 seconds }); uint128 actualRefundableAmount = lockup.refundableAmountOf(defaultStreamId); uint128 expectedRefundableAmount = 0; @@ -56,7 +56,7 @@ abstract contract RefundableAmountOf_Integration_Concrete_Test is Integration_Te function test_GivenDEPLETEDStatus() external givenNotNull givenCancelableStream givenNotCanceledStream { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); uint128 actualRefundableAmount = lockup.refundableAmountOf(defaultStreamId); uint128 expectedReturnableAmount = 0; assertEq(actualRefundableAmount, expectedReturnableAmount, "refundableAmount"); diff --git a/test/core/integration/concrete/lockup-base/renounce-multiple/renounceMultiple.t.sol b/test/core/integration/concrete/lockup-base/renounce-multiple/renounceMultiple.t.sol index 6648b0951..7992c0994 100644 --- a/test/core/integration/concrete/lockup-base/renounce-multiple/renounceMultiple.t.sol +++ b/test/core/integration/concrete/lockup-base/renounce-multiple/renounceMultiple.t.sol @@ -22,18 +22,18 @@ contract RenounceMultiple_Integration_Concrete_Test is Integration_Test { } function test_RevertWhen_DelegateCall() external { - expectRevert_DelegateCall({ callData: abi.encodeCall(lockup.renounceMultiple, streamIds) }); + expectRevert_DelegateCall({ callData: abi.encodeCall(lockup.renounceMultiple{ value: 0 }, streamIds) }); } function test_WhenZeroArrayLength() external whenNoDelegateCall { // It should do nothing. uint256[] memory nullStreamIds = new uint256[](0); - lockup.renounceMultiple(nullStreamIds); + lockup.renounceMultiple{ value: 0 }(nullStreamIds); } function test_RevertGiven_AtleastOneNullStream() external whenNoDelegateCall whenNonZeroArrayLength { expectRevert_Null({ - callData: abi.encodeCall(lockup.renounceMultiple, Solarray.uint256s(streamIds[0], nullStreamId)) + callData: abi.encodeCall(lockup.renounceMultiple{ value: 0 }, Solarray.uint256s(streamIds[0], nullStreamId)) }); } @@ -48,7 +48,7 @@ contract RenounceMultiple_Integration_Concrete_Test is Integration_Test { vm.warp({ newTimestamp: earlyEndTime + 1 seconds }); vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_StreamSettled.selector, earlyEndtimeStreamId)); - lockup.renounceMultiple({ streamIds: Solarray.uint256s(streamIds[0], earlyEndtimeStreamId) }); + lockup.renounceMultiple{ value: 0 }({ streamIds: Solarray.uint256s(streamIds[0], earlyEndtimeStreamId) }); } function test_RevertWhen_CallerUnauthorizedForAny() @@ -65,7 +65,7 @@ contract RenounceMultiple_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_Unauthorized.selector, streamIds[0], users.recipient) ); - lockup.renounceMultiple(streamIds); + lockup.renounceMultiple{ value: 0 }(streamIds); } function test_RevertGiven_AtleastOneNonCancelableStream() @@ -79,7 +79,7 @@ contract RenounceMultiple_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotCancelable.selector, notCancelableStreamId) ); - lockup.renounceMultiple({ streamIds: Solarray.uint256s(streamIds[0], notCancelableStreamId) }); + lockup.renounceMultiple{ value: 0 }({ streamIds: Solarray.uint256s(streamIds[0], notCancelableStreamId) }); } function test_GivenAllStreamsCancelable() @@ -101,7 +101,7 @@ contract RenounceMultiple_Integration_Concrete_Test is Integration_Test { emit IERC4906.MetadataUpdate({ _tokenId: streamIds[1] }); // Renounce the streams. - lockup.renounceMultiple(streamIds); + lockup.renounceMultiple{ value: 0 }(streamIds); // It should make streams non cancelable. assertFalse(lockup.isCancelable(streamIds[0]), "isCancelable0"); diff --git a/test/core/integration/concrete/lockup-base/renounce/renounce.t.sol b/test/core/integration/concrete/lockup-base/renounce/renounce.t.sol index d94e5143b..849fa60e1 100644 --- a/test/core/integration/concrete/lockup-base/renounce/renounce.t.sol +++ b/test/core/integration/concrete/lockup-base/renounce/renounce.t.sol @@ -56,7 +56,7 @@ abstract contract Renounce_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_StreamNotCancelable.selector, notCancelableStreamId) ); - lockup.renounce(notCancelableStreamId); + renounce(notCancelableStreamId); } function test_GivenCancelableStream() @@ -73,7 +73,7 @@ abstract contract Renounce_Integration_Concrete_Test is Integration_Test { emit IERC4906.MetadataUpdate({ _tokenId: streamId }); // Renounce the stream. - lockup.renounce(streamId); + renounce(streamId); // It should make stream non cancelable. assertFalse(lockup.isCancelable(streamId), "isCancelable"); diff --git a/test/core/integration/concrete/lockup-base/set-nft-descriptor/setNFTDescriptor.t.sol b/test/core/integration/concrete/lockup-base/set-nft-descriptor/setNFTDescriptor.t.sol index d0c048827..7dc509922 100644 --- a/test/core/integration/concrete/lockup-base/set-nft-descriptor/setNFTDescriptor.t.sol +++ b/test/core/integration/concrete/lockup-base/set-nft-descriptor/setNFTDescriptor.t.sol @@ -2,20 +2,18 @@ pragma solidity >=0.8.22 <0.9.0; import { IERC4906 } from "@openzeppelin/contracts/interfaces/IERC4906.sol"; + import { ILockupNFTDescriptor } from "src/core/interfaces/ILockupNFTDescriptor.sol"; import { ISablierLockupBase } from "src/core/interfaces/ISablierLockupBase.sol"; -import { Errors } from "src/core/libraries/Errors.sol"; import { LockupNFTDescriptor } from "src/core/LockupNFTDescriptor.sol"; + import { Integration_Test } from "./../../../Integration.t.sol"; contract SetNFTDescriptor_Integration_Concrete_Test is Integration_Test { function test_RevertWhen_CallerNotAdmin() external { - // Make Eve the caller in this test. - resetPrank({ msgSender: users.eve }); - - // Run the test. - vm.expectRevert(abi.encodeWithSelector(Errors.CallerNotAdmin.selector, users.admin, users.eve)); - lockup.setNFTDescriptor(ILockupNFTDescriptor(users.eve)); + expectRevert_CallerNotAdmin({ + callData: abi.encodeCall(lockup.setNFTDescriptor, ILockupNFTDescriptor(users.eve)) + }); } function test_WhenProvidedAddressMatchesCurrentNFTDescriptor() external whenCallerAdmin { diff --git a/test/core/integration/concrete/lockup-base/status-of/statusOf.t.sol b/test/core/integration/concrete/lockup-base/status-of/statusOf.t.sol index 8196874a2..e4bac4bcb 100644 --- a/test/core/integration/concrete/lockup-base/status-of/statusOf.t.sol +++ b/test/core/integration/concrete/lockup-base/status-of/statusOf.t.sol @@ -12,7 +12,7 @@ contract StatusOf_Integration_Concrete_Test is Integration_Test { function test_GivenAssetsFullyWithdrawn() external givenNotNull { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // It should return DEPLETED. Lockup.Status actualStatus = lockup.statusOf(defaultStreamId); @@ -22,7 +22,7 @@ contract StatusOf_Integration_Concrete_Test is Integration_Test { function test_GivenCanceledStream() external givenNotNull givenAssetsNotFullyWithdrawn { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // It should return CANCELED. Lockup.Status actualStatus = lockup.statusOf(defaultStreamId); diff --git a/test/core/integration/concrete/lockup-base/streamed-amount-of/streamedAmountOf.t.sol b/test/core/integration/concrete/lockup-base/streamed-amount-of/streamedAmountOf.t.sol index 14cef8fd8..e391c1ae2 100644 --- a/test/core/integration/concrete/lockup-base/streamed-amount-of/streamedAmountOf.t.sol +++ b/test/core/integration/concrete/lockup-base/streamed-amount-of/streamedAmountOf.t.sol @@ -10,7 +10,7 @@ abstract contract StreamedAmountOf_Integration_Concrete_Test is Integration_Test function test_GivenCanceledStreamAndCANCELEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // It should return the correct streamed amount. uint128 actualStreamedAmount = lockup.streamedAmountOf(defaultStreamId); @@ -21,10 +21,10 @@ abstract contract StreamedAmountOf_Integration_Concrete_Test is Integration_Test /// @dev This test warps a second time to ensure that {streamedAmountOf} ignores the current time. function test_GivenCanceledStreamAndDEPLETEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // Withdraw max to deplete the stream. - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() + 10 seconds }); // It should return the correct streamed amount. @@ -63,7 +63,7 @@ abstract contract StreamedAmountOf_Integration_Concrete_Test is Integration_Test function test_GivenDEPLETEDStatus() external givenNotNull givenNotCanceledStream { vm.warp({ newTimestamp: defaults.END_TIME() }); // Withdraw max to deplete the stream. - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // It should return the deposited amount. uint128 actualStreamedAmount = lockup.streamedAmountOf(defaultStreamId); diff --git a/test/core/integration/concrete/lockup-base/withdraw-fees/withdrawFees.t.sol b/test/core/integration/concrete/lockup-base/withdraw-fees/withdrawFees.t.sol new file mode 100644 index 000000000..b7959412c --- /dev/null +++ b/test/core/integration/concrete/lockup-base/withdraw-fees/withdrawFees.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.22 <0.9.0; + +import { ISablierLockupBase } from "src/core/interfaces/ISablierLockupBase.sol"; +import { Errors } from "src/core/libraries/Errors.sol"; + +import { Integration_Test } from "../../../Integration.t.sol"; + +contract WithdrawFees_Integration_Concrete_Test is Integration_Test { + function test_RevertWhen_CallerNotAdmin() external { + expectRevert_CallerNotAdmin({ callData: abi.encodeCall(lockup.withdrawFees, (users.admin)) }); + } + + function test_RevertWhen_WithdrawalAddressZero() external whenCallerAdmin { + address payable toZero = payable(address(0)); + vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_WithdrawFeesToZero.selector)); + lockup.withdrawFees(toZero); + } + + function test_WhenProvidedAddressNotContract() external whenCallerAdmin whenWithdrawalAddressNotZero { + address payable to = payable(makeAddr({ name: "to" })); + _test_WithdrawFees(to); + } + + function test_RevertWhen_ProvidedAddressNotImplementReceiveEth() + external + whenCallerAdmin + whenWithdrawalAddressNotZero + whenProvidedAddressContract + { + address payable noReceiveEth = payable(address(contractWithoutReceiveEth)); + vm.expectRevert( + abi.encodeWithSelector( + Errors.SablierLockupBase_FeeWithdrawFailed.selector, noReceiveEth, address(lockup).balance + ) + ); + lockup.withdrawFees(noReceiveEth); + } + + function test_GivenNoFeesCollected() + external + whenCallerAdmin + whenWithdrawalAddressNotZero + whenProvidedAddressContract + whenProvidedAddressImplementReceiveEth + { + assertEq(address(lockup).balance, 0, "lockup eth balance"); + uint256 prevBalance = users.admin.balance; + + // It should emit {WithdrawSablierFees} event. + vm.expectEmit({ emitter: address(lockup) }); + emit ISablierLockupBase.WithdrawSablierFees({ admin: users.admin, feeAmount: 0, to: users.admin }); + + lockup.withdrawFees(users.admin); + assertEq(address(lockup).balance, 0, "lockup eth balance"); + assertEq(users.admin.balance, prevBalance, "eth balance"); + } + + function test_GivenFeesCollected() + external + whenCallerAdmin + whenWithdrawalAddressNotZero + whenProvidedAddressContract + whenProvidedAddressImplementReceiveEth + { + address payable receiveEth = payable(address(contractWithReceiveEth)); + _test_WithdrawFees(receiveEth); + } + + function _test_WithdrawFees(address payable to) private { + vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); + + // Make a withdrawal from a stream to collect some fees. + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + + // It should emit {WithdrawSablierFees} event. + vm.expectEmit({ emitter: address(lockup) }); + emit ISablierLockupBase.WithdrawSablierFees({ admin: users.admin, feeAmount: SABLIER_FEE, to: to }); + + lockup.withdrawFees(to); + + // It should set the ETH balance to 0. + assertEq(address(lockup).balance, 0, "lockup eth balance"); + + // It should transfer fee collected in ETH to the provided address. + assertEq(to.balance, SABLIER_FEE, "eth balance"); + } +} diff --git a/test/core/integration/concrete/lockup-base/withdraw-fees/withdrawFees.tree b/test/core/integration/concrete/lockup-base/withdraw-fees/withdrawFees.tree new file mode 100644 index 000000000..5c8451ba1 --- /dev/null +++ b/test/core/integration/concrete/lockup-base/withdraw-fees/withdrawFees.tree @@ -0,0 +1,19 @@ +WithdrawFees_Integration_Concrete_Test +├── when caller not admin +│ └── it should revert +└── when caller admin + ├── when withdrawal address zero + │ └── it should revert + └── when withdrawal address not zero + ├── when provided address not contract + │ ├── it should transfer fee collected in ETH to the provided address + │ └── it should set the ETH balance to 0 + └── when provided address contract + ├── when provided address not implement receive eth + │ └── it should revert + └── when provided address implement receive eth + ├── given no fees collected + │ └── it should do nothing + └── given fees collected + ├── it should transfer fee collected in ETH to the provided address + └── it should set the ETH balance to 0 \ No newline at end of file diff --git a/test/core/integration/concrete/lockup-base/withdraw-hooks/withdrawHooks.t.sol b/test/core/integration/concrete/lockup-base/withdraw-hooks/withdrawHooks.t.sol index 63ce59d37..130f54204 100644 --- a/test/core/integration/concrete/lockup-base/withdraw-hooks/withdrawHooks.t.sol +++ b/test/core/integration/concrete/lockup-base/withdraw-hooks/withdrawHooks.t.sol @@ -40,13 +40,14 @@ contract WithdrawHooks_Integration_Concrete_Test is Integration_Test { }); // Make the withdrawal. - lockup.withdraw({ streamId: identicalSenderRecipientStreamId, to: users.sender, amount: withdrawAmount }); + withdraw({ streamId: identicalSenderRecipientStreamId, to: users.sender, amount: withdrawAmount }); } function test_WhenCallerUnknown() external givenRecipientNotSameAsSender { // Make the unknown address the caller in this test. address unknownCaller = address(0xCAFE); resetPrank({ msgSender: unknownCaller }); + vm.deal({ account: unknownCaller, newBalance: 1 ether }); // Simulate the passage of time. vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); @@ -62,11 +63,7 @@ contract WithdrawHooks_Integration_Concrete_Test is Integration_Test { }); // Make the withdrawal. - lockup.withdraw({ - streamId: differentSenderRecipientStreamId, - to: address(recipientGood), - amount: withdrawAmount - }); + withdraw({ streamId: differentSenderRecipientStreamId, to: address(recipientGood), amount: withdrawAmount }); } function test_WhenCallerApprovedThirdParty() external givenRecipientNotSameAsSender { @@ -91,11 +88,7 @@ contract WithdrawHooks_Integration_Concrete_Test is Integration_Test { }); // Make the withdrawal. - lockup.withdraw({ - streamId: differentSenderRecipientStreamId, - to: address(recipientGood), - amount: withdrawAmount - }); + withdraw({ streamId: differentSenderRecipientStreamId, to: address(recipientGood), amount: withdrawAmount }); } function test_WhenCallerSender() external givenRecipientNotSameAsSender { @@ -116,11 +109,7 @@ contract WithdrawHooks_Integration_Concrete_Test is Integration_Test { }); // Make the withdrawal. - lockup.withdraw({ - streamId: differentSenderRecipientStreamId, - to: address(recipientGood), - amount: withdrawAmount - }); + withdraw({ streamId: differentSenderRecipientStreamId, to: address(recipientGood), amount: withdrawAmount }); } function test_WhenCallerRecipient() external givenRecipientNotSameAsSender { @@ -141,10 +130,6 @@ contract WithdrawHooks_Integration_Concrete_Test is Integration_Test { }); // Make the withdrawal. - lockup.withdraw({ - streamId: differentSenderRecipientStreamId, - to: address(recipientGood), - amount: withdrawAmount - }); + withdraw({ streamId: differentSenderRecipientStreamId, to: address(recipientGood), amount: withdrawAmount }); } } diff --git a/test/core/integration/concrete/lockup-base/withdraw-max-and-transfer/withdrawMaxAndTransfer.t.sol b/test/core/integration/concrete/lockup-base/withdraw-max-and-transfer/withdrawMaxAndTransfer.t.sol index fe3e1c0ac..0dddd0dc5 100644 --- a/test/core/integration/concrete/lockup-base/withdraw-max-and-transfer/withdrawMaxAndTransfer.t.sol +++ b/test/core/integration/concrete/lockup-base/withdraw-max-and-transfer/withdrawMaxAndTransfer.t.sol @@ -24,7 +24,7 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_NotTransferable.selector, notTransferableStreamId) ); - lockup.withdrawMaxAndTransfer({ streamId: notTransferableStreamId, newRecipient: users.recipient }); + withdrawMaxAndTransfer({ streamId: notTransferableStreamId, newRecipient: users.recipient }); } function test_RevertGiven_BurnedNFT() @@ -36,14 +36,14 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { { // Deplete the stream. vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // Burn the NFT. - lockup.burn({ streamId: defaultStreamId }); + burn({ streamId: defaultStreamId }); // Run the test. vm.expectRevert(abi.encodeWithSelector(IERC721Errors.ERC721NonexistentToken.selector, defaultStreamId)); - lockup.withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: users.alice }); + withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: users.alice }); } function test_GivenZeroWithdrawableAmount() @@ -55,7 +55,7 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { whenCallerRecipient { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // It should not expect a transfer call on asset. vm.expectCall({ callee: address(dai), data: abi.encodeCall(IERC20.transfer, (users.recipient, 0)), count: 0 }); @@ -64,7 +64,7 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { vm.expectEmit({ emitter: address(lockup) }); emit IERC721.Transfer({ from: users.recipient, to: users.alice, tokenId: defaultStreamId }); - lockup.withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: users.alice }); + withdrawMaxAndTransferWithBalTest({ streamId: defaultStreamId, newRecipient: users.alice }); } function test_RevertWhen_CallerNotCurrentRecipient() @@ -82,7 +82,7 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_Unauthorized.selector, defaultStreamId, users.eve) ); - lockup.withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: users.eve }); + withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: users.eve }); } function test_WhenCallerApprovedThirdParty() @@ -110,15 +110,16 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: defaultStreamId, to: users.recipient, - amount: expectedWithdrawnAmount, - asset: dai + asset: dai, + withdrawnAmount: expectedWithdrawnAmount }); + vm.expectEmit({ emitter: address(lockup) }); emit IERC721.Transfer({ from: users.recipient, to: users.alice, tokenId: defaultStreamId }); // Make the max withdrawal and transfer the NFT. uint128 actualWithdrawnAmount = - lockup.withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: users.alice }); + withdrawMaxAndTransferWithBalTest({ streamId: defaultStreamId, newRecipient: users.alice }); // Assert that the withdrawn amount has been updated. assertEq(actualWithdrawnAmount, expectedWithdrawnAmount, "withdrawnAmount"); @@ -153,9 +154,10 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: defaultStreamId, to: users.recipient, - amount: expectedWithdrawnAmount, - asset: dai + asset: dai, + withdrawnAmount: expectedWithdrawnAmount }); + vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: defaultStreamId }); vm.expectEmit({ emitter: address(lockup) }); @@ -163,7 +165,7 @@ contract WithdrawMaxAndTransfer_Integration_Concrete_Test is Integration_Test { // Make the max withdrawal and transfer the NFT. uint128 actualWithdrawnAmount = - lockup.withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: users.alice }); + withdrawMaxAndTransferWithBalTest({ streamId: defaultStreamId, newRecipient: users.alice }); // it should update the withdrawn amount.abi assertEq(actualWithdrawnAmount, expectedWithdrawnAmount, "withdrawnAmount"); diff --git a/test/core/integration/concrete/lockup-base/withdraw-max/withdrawMax.t.sol b/test/core/integration/concrete/lockup-base/withdraw-max/withdrawMax.t.sol index d34366a84..b22f0e12e 100644 --- a/test/core/integration/concrete/lockup-base/withdraw-max/withdrawMax.t.sol +++ b/test/core/integration/concrete/lockup-base/withdraw-max/withdrawMax.t.sol @@ -19,12 +19,12 @@ contract WithdrawMax_Integration_Concrete_Test is Integration_Test { emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: defaultStreamId, to: users.recipient, - amount: defaults.DEPOSIT_AMOUNT(), - asset: dai + asset: dai, + withdrawnAmount: defaults.DEPOSIT_AMOUNT() }); // Make the max withdrawal. - uint128 actualReturnedValue = lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + uint128 actualReturnedValue = withdrawMaxWithBalTest({ streamId: defaultStreamId, to: users.recipient }); // It should return the withdrawn amount. uint128 expectedReturnedValue = defaults.DEPOSIT_AMOUNT(); @@ -65,12 +65,12 @@ contract WithdrawMax_Integration_Concrete_Test is Integration_Test { emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: defaultStreamId, to: users.recipient, - amount: expectedWithdrawnAmount, - asset: dai + asset: dai, + withdrawnAmount: expectedWithdrawnAmount }); // Make the max withdrawal. - uint128 actualWithdrawnAmount = lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + uint128 actualWithdrawnAmount = withdrawMaxWithBalTest({ streamId: defaultStreamId, to: users.recipient }); // It should return the withdrawable amount. assertEq(actualWithdrawnAmount, expectedWithdrawnAmount, "withdrawnAmount"); diff --git a/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.t.sol b/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.t.sol index 8a96bfaf0..5f1956cf9 100644 --- a/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.t.sol +++ b/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.t.sol @@ -58,7 +58,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { Errors.SablierLockupBase_WithdrawArrayCountsNotEqual.selector, streamIds.length, amounts.length ) ); - lockup.withdrawMultiple(streamIds, amounts); + withdrawMultiple(streamIds, amounts); } function test_WhenZeroArrayLength() external whenNoDelegateCall whenEqualArraysLength { @@ -66,7 +66,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { uint128[] memory amounts = new uint128[](0); // It should do nothing. - lockup.withdrawMultiple(streamIds, amounts); + withdrawMultipleWithBalTest(streamIds, amounts); } function test_RevertGiven_AtleastOneNullStream() @@ -85,7 +85,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_Null.selector, nullStreamId)); // Withdraw from multiple streams. - lockup.withdrawMultiple({ streamIds: streamIds, amounts: withdrawAmounts }); + withdrawMultiple({ streamIds: streamIds, amounts: withdrawAmounts }); } function test_RevertGiven_AtleastOneDEPLETEDStream() @@ -99,7 +99,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { vm.warp({ newTimestamp: defaults.END_TIME() }); // Deplete the first test stream. - lockup.withdrawMax({ streamId: withdrawMultipleStreamIds[0], to: users.recipient }); + withdrawMax({ streamId: withdrawMultipleStreamIds[0], to: users.recipient }); // It should revert. vm.expectRevert( @@ -107,7 +107,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { ); // Withdraw from multiple streams. - lockup.withdrawMultiple({ streamIds: withdrawMultipleStreamIds, amounts: withdrawAmounts }); + withdrawMultiple({ streamIds: withdrawMultipleStreamIds, amounts: withdrawAmounts }); } function test_RevertWhen_AtleastOneZeroAmount() @@ -128,7 +128,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_WithdrawAmountZero.selector, withdrawMultipleStreamIds[1]) ); - lockup.withdrawMultiple({ streamIds: withdrawMultipleStreamIds, amounts: amounts }); + withdrawMultiple({ streamIds: withdrawMultipleStreamIds, amounts: amounts }); } function test_RevertWhen_AtleastOneAmountOverdraws() @@ -156,7 +156,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { withdrawableAmount ) ); - lockup.withdrawMultiple({ streamIds: withdrawMultipleStreamIds, amounts: amounts }); + withdrawMultiple({ streamIds: withdrawMultipleStreamIds, amounts: amounts }); } /// @dev This modifier runs the test in three different modes: @@ -191,7 +191,7 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { // Cancel the 3rd stream. resetPrank({ msgSender: users.sender }); - lockup.cancel(withdrawMultipleStreamIds[2]); + cancel(withdrawMultipleStreamIds[2]); // Run the test with the caller provided in {whenCallerAuthorizedForAllStreams}. resetPrank({ msgSender: caller }); @@ -207,25 +207,25 @@ contract WithdrawMultiple_Integration_Concrete_Test is Integration_Test { streamId: withdrawMultipleStreamIds[0], to: users.recipient, asset: dai, - amount: withdrawAmounts[0] + withdrawnAmount: withdrawAmounts[0] }); vm.expectEmit({ emitter: address(lockup) }); emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: withdrawMultipleStreamIds[1], to: users.recipient, asset: dai, - amount: withdrawAmounts[1] + withdrawnAmount: withdrawAmounts[1] }); vm.expectEmit({ emitter: address(lockup) }); emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: withdrawMultipleStreamIds[2], to: users.recipient, asset: dai, - amount: withdrawAmounts[2] + withdrawnAmount: withdrawAmounts[2] }); // Make the withdrawals. - lockup.withdrawMultiple({ streamIds: withdrawMultipleStreamIds, amounts: withdrawAmounts }); + withdrawMultipleWithBalTest({ streamIds: withdrawMultipleStreamIds, amounts: withdrawAmounts }); // It should update the statuses. assertEq(lockup.statusOf(withdrawMultipleStreamIds[0]), Lockup.Status.STREAMING, "status0"); diff --git a/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.tree b/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.tree index 4074912f9..6fc5e7e43 100644 --- a/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.tree +++ b/test/core/integration/concrete/lockup-base/withdraw-multiple/withdrawMultiple.tree @@ -6,7 +6,7 @@ WithdrawMultiple_Integration_Concrete_Test │ └── it should revert └── when equal arrays length ├── when zero array length - │ └── it should do nothing + │ └── it should charge the fee └── when non zero array length ├── given atleast one null stream │ └── it should revert diff --git a/test/core/integration/concrete/lockup-base/withdraw/withdraw.t.sol b/test/core/integration/concrete/lockup-base/withdraw/withdraw.t.sol index 36e2dae7b..fa1f9fb8b 100644 --- a/test/core/integration/concrete/lockup-base/withdraw/withdraw.t.sol +++ b/test/core/integration/concrete/lockup-base/withdraw/withdraw.t.sol @@ -36,7 +36,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { vm.expectRevert( abi.encodeWithSelector(Errors.SablierLockupBase_WithdrawToZeroAddress.selector, defaultStreamId) ); - lockup.withdraw({ streamId: defaultStreamId, to: address(0), amount: withdrawAmount }); + withdraw({ streamId: defaultStreamId, to: address(0), amount: withdrawAmount }); } function test_RevertWhen_ZeroWithdrawAmount() @@ -45,9 +45,10 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee { vm.expectRevert(abi.encodeWithSelector(Errors.SablierLockupBase_WithdrawAmountZero.selector, defaultStreamId)); - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: 0 }); + withdraw({ streamId: defaultStreamId, to: users.recipient, amount: 0 }); } function test_RevertWhen_WithdrawAmountOverdraws() @@ -56,6 +57,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount { uint128 withdrawableAmount = 0; @@ -64,7 +66,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { Errors.SablierLockupBase_Overdraw.selector, defaultStreamId, MAX_UINT128, withdrawableAmount ) ); - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: MAX_UINT128 }); + withdraw({ streamId: defaultStreamId, to: users.recipient, amount: MAX_UINT128 }); } modifier whenWithdrawalAddressNotRecipient(bool isCallerRecipient) { @@ -103,6 +105,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressNotRecipient(false) @@ -119,7 +122,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { Errors.SablierLockupBase_WithdrawalAddressNotRecipient.selector, defaultStreamId, caller, users.alice ) ); - lockup.withdraw({ streamId: defaultStreamId, to: users.alice, amount: withdrawAmount }); + withdraw({ streamId: defaultStreamId, to: users.alice, amount: withdrawAmount }); } function test_WhenCallerApprovedThirdPartyOrRecipient() @@ -128,6 +131,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressNotRecipient(true) @@ -146,13 +150,13 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { streamId: defaultStreamId, to: users.alice, asset: dai, - amount: withdrawAmount + withdrawnAmount: withdrawAmount }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: defaultStreamId }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.alice, amount: withdrawAmount }); + withdrawWithBalTest({ streamId: defaultStreamId, to: users.alice, amount: withdrawAmount }); // It should update the withdrawn amount. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(defaultStreamId); @@ -166,18 +170,22 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient { + address unknownCaller = address(0xCAFE); + // Make the unknown address the caller in this test. - resetPrank({ msgSender: address(0xCAFE) }); + resetPrank({ msgSender: unknownCaller }); + vm.deal({ account: unknownCaller, newBalance: 1 ether }); // Simulate the passage of time. vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); + withdrawWithBalTest({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); // It should update the withdrawn amount. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(defaultStreamId); @@ -191,6 +199,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -201,7 +210,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); + withdrawWithBalTest({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); // It should update the withdrawn amount. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(defaultStreamId); @@ -215,6 +224,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -224,7 +234,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { vm.warp({ newTimestamp: defaults.END_TIME() }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.DEPOSIT_AMOUNT() }); + withdrawWithBalTest({ streamId: defaultStreamId, to: users.recipient, amount: defaults.DEPOSIT_AMOUNT() }); // It should mark the stream as depleted. Lockup.Status actualStatus = lockup.statusOf(defaultStreamId); @@ -247,6 +257,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -254,7 +265,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenEndTimeInFuture { // Cancel the stream. - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // Set the withdraw amount to the withdrawable amount. uint128 withdrawAmount = lockup.withdrawableAmountOf(defaultStreamId); @@ -265,13 +276,13 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { streamId: defaultStreamId, to: users.recipient, asset: dai, - amount: withdrawAmount + withdrawnAmount: withdrawAmount }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: defaultStreamId }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: withdrawAmount }); + withdrawWithBalTest({ streamId: defaultStreamId, to: users.recipient, amount: withdrawAmount }); // It should mark the stream as depleted. Lockup.Status actualStatus = lockup.statusOf(defaultStreamId); @@ -295,6 +306,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -314,7 +326,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { }); // Make the withdrawal. - lockup.withdraw({ streamId: recipientGoodStreamId, to: address(recipientGood), amount: withdrawAmount }); + withdrawWithBalTest({ streamId: recipientGoodStreamId, to: address(recipientGood), amount: withdrawAmount }); // It should update the withdrawn amount. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(recipientGoodStreamId); @@ -328,6 +340,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -346,7 +359,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { vm.expectRevert("You shall not pass"); // Make the withdrawal. - lockup.withdraw({ streamId: recipientRevertStreamId, to: address(recipientReverting), amount: withdrawAmount }); + withdraw({ streamId: recipientRevertStreamId, to: address(recipientReverting), amount: withdrawAmount }); } function test_RevertWhen_HookReturnsInvalidSelector() @@ -355,6 +368,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -378,7 +392,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { ); // Cancel the stream. - lockup.withdraw({ + withdraw({ streamId: recipientInvalidSelectorStreamId, to: address(recipientInvalidSelector), amount: withdrawAmount @@ -391,6 +405,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -419,7 +434,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { ); // It should make multiple withdrawals. - lockup.withdraw({ streamId: recipientReentrantStreamId, to: address(recipientReentrant), amount: withdrawAmount }); + withdraw({ streamId: recipientReentrantStreamId, to: address(recipientReentrant), amount: withdrawAmount }); // Assert that the stream's status is still "STREAMING". Lockup.Status actualStatus = lockup.statusOf(recipientReentrantStreamId); @@ -438,6 +453,7 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { givenNotNull givenNotDEPLETEDStatus whenWithdrawalAddressNotZero + givenMsgValueNotLessThanSablierFee whenNonZeroWithdrawAmount whenWithdrawAmountNotOverdraw whenWithdrawalAddressRecipient @@ -474,13 +490,13 @@ abstract contract Withdraw_Integration_Concrete_Test is Integration_Test { streamId: recipientGoodStreamId, to: address(recipientGood), asset: dai, - amount: withdrawAmount + withdrawnAmount: withdrawAmount }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: recipientGoodStreamId }); // Make the withdrawal. - lockup.withdraw({ streamId: recipientGoodStreamId, to: address(recipientGood), amount: withdrawAmount }); + withdrawWithBalTest({ streamId: recipientGoodStreamId, to: address(recipientGood), amount: withdrawAmount }); // Assert that the stream's status is still "STREAMING". Lockup.Status actualStatus = lockup.statusOf(recipientGoodStreamId); diff --git a/test/core/integration/concrete/lockup-base/withdraw/withdraw.tree b/test/core/integration/concrete/lockup-base/withdraw/withdraw.tree index 48f13661e..0ae003ef3 100644 --- a/test/core/integration/concrete/lockup-base/withdraw/withdraw.tree +++ b/test/core/integration/concrete/lockup-base/withdraw/withdraw.tree @@ -62,4 +62,5 @@ Withdraw_Integration_Concrete_Test ├── it should make the withdrawal ├── it should update the withdrawn amount ├── it should make Sablier run the recipient hook + ├── it should transfer the ETH to the lockup contract └── it should emit {WithdrawFromLockupStream} and {MetadataUpdate} events diff --git a/test/core/integration/concrete/lockup-base/withdrawable-amount-of/withdrawableAmountOf.t.sol b/test/core/integration/concrete/lockup-base/withdrawable-amount-of/withdrawableAmountOf.t.sol index b9566614c..b56671ca9 100644 --- a/test/core/integration/concrete/lockup-base/withdrawable-amount-of/withdrawableAmountOf.t.sol +++ b/test/core/integration/concrete/lockup-base/withdrawable-amount-of/withdrawableAmountOf.t.sol @@ -10,7 +10,7 @@ abstract contract WithdrawableAmountOf_Integration_Concrete_Test is Integration_ function test_GivenCanceledStreamAndCANCELEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); + cancel(defaultStreamId); // It should return the correct withdrawable amount. uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(defaultStreamId); @@ -20,8 +20,8 @@ abstract contract WithdrawableAmountOf_Integration_Concrete_Test is Integration_ function test_GivenCanceledStreamAndDEPLETEDStatus() external givenNotNull { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); - lockup.cancel(defaultStreamId); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + cancel(defaultStreamId); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() + 10 seconds }); // It should return zero. @@ -50,7 +50,7 @@ abstract contract WithdrawableAmountOf_Integration_Concrete_Test is Integration_ function test_GivenDEPLETEDStatus() external givenNotNull givenNotCanceledStream { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // It should return zero. uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(defaultStreamId); diff --git a/test/core/integration/concrete/lockup-dynamic/create-with-durations-ld/createWithDurationsLD.t.sol b/test/core/integration/concrete/lockup-dynamic/create-with-durations-ld/createWithDurationsLD.t.sol index 92fb5ee28..035346e5b 100644 --- a/test/core/integration/concrete/lockup-dynamic/create-with-durations-ld/createWithDurationsLD.t.sol +++ b/test/core/integration/concrete/lockup-dynamic/create-with-durations-ld/createWithDurationsLD.t.sol @@ -20,7 +20,7 @@ contract CreateWithDurationsLD_Integration_Concrete_Test is Lockup_Dynamic_Integ internal returns (uint256 streamId) { - streamId = lockup.createWithDurationsLD(_defaultParams.createWithDurations, segmentsWithDurations); + streamId = createWithDurationsLD(_defaultParams.createWithDurations, segmentsWithDurations); } /*////////////////////////////////////////////////////////////////////////// diff --git a/test/core/integration/concrete/lockup-dynamic/create-with-timestamps-ld/createWithTimestampsLD.t.sol b/test/core/integration/concrete/lockup-dynamic/create-with-timestamps-ld/createWithTimestampsLD.t.sol index f2c12f801..3db5e5c03 100644 --- a/test/core/integration/concrete/lockup-dynamic/create-with-timestamps-ld/createWithTimestampsLD.t.sol +++ b/test/core/integration/concrete/lockup-dynamic/create-with-timestamps-ld/createWithTimestampsLD.t.sol @@ -25,7 +25,7 @@ contract CreateWithTimestampsLD_Integration_Concrete_Test is CreateWithTimestamp //////////////////////////////////////////////////////////////////////////*/ function createDefaultStreamWithSegments(LockupDynamic.Segment[] memory segments) internal returns (uint256) { - return lockup.createWithTimestampsLD(_defaultParams.createWithTimestamps, segments); + return createWithTimestampsLD(_defaultParams.createWithTimestamps, segments); } function test_RevertWhen_SegmentCountZero() diff --git a/test/core/integration/concrete/lockup-dynamic/streamed-amount-of/streamedAmountOf.t.sol b/test/core/integration/concrete/lockup-dynamic/streamed-amount-of/streamedAmountOf.t.sol index 0695eeb52..339b2ddd5 100644 --- a/test/core/integration/concrete/lockup-dynamic/streamed-amount-of/streamedAmountOf.t.sol +++ b/test/core/integration/concrete/lockup-dynamic/streamed-amount-of/streamedAmountOf.t.sol @@ -27,7 +27,7 @@ contract StreamedAmountOf_Lockup_Dynamic_Integration_Concrete_Test is }); // Create the stream. - uint256 streamId = lockup.createWithTimestampsLD(_defaultParams.createWithTimestamps, segments); + uint256 streamId = createWithTimestampsLD(_defaultParams.createWithTimestamps, segments); // It should return the correct streamed amount. uint128 actualStreamedAmount = lockup.streamedAmountOf(streamId); diff --git a/test/core/integration/concrete/lockup-dynamic/withdrawable-amount-of/withdrawableAmountOf.t.sol b/test/core/integration/concrete/lockup-dynamic/withdrawable-amount-of/withdrawableAmountOf.t.sol index 5663bc4e0..20b5694ab 100644 --- a/test/core/integration/concrete/lockup-dynamic/withdrawable-amount-of/withdrawableAmountOf.t.sol +++ b/test/core/integration/concrete/lockup-dynamic/withdrawable-amount-of/withdrawableAmountOf.t.sol @@ -36,7 +36,7 @@ contract WithdrawableAmountOf_Lockup_Dynamic_Integration_Concrete_Test is vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() + 3750 seconds }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.STREAMED_AMOUNT_26_PERCENT() }); + withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); // Run the test. uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(defaultStreamId); diff --git a/test/core/integration/concrete/lockup-linear/withdrawable-amount-of/withdrawableAmountOf.t.sol b/test/core/integration/concrete/lockup-linear/withdrawable-amount-of/withdrawableAmountOf.t.sol index 1f705540b..907f08c32 100644 --- a/test/core/integration/concrete/lockup-linear/withdrawable-amount-of/withdrawableAmountOf.t.sol +++ b/test/core/integration/concrete/lockup-linear/withdrawable-amount-of/withdrawableAmountOf.t.sol @@ -27,7 +27,7 @@ contract WithdrawableAmountOf_Lockup_Linear_Integration_Concrete_Test is } function test_GivenPreviousWithdrawal() external givenSTREAMINGStatus givenCliffTimeNotInFuture { - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.STREAMED_AMOUNT_26_PERCENT() }); + withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(defaultStreamId); uint128 expectedWithdrawableAmount = 0; diff --git a/test/core/integration/concrete/lockup-tranched/create-with-durations-lt/createWithDurationsLT.t.sol b/test/core/integration/concrete/lockup-tranched/create-with-durations-lt/createWithDurationsLT.t.sol index 319137a4a..bdc2fb043 100644 --- a/test/core/integration/concrete/lockup-tranched/create-with-durations-lt/createWithDurationsLT.t.sol +++ b/test/core/integration/concrete/lockup-tranched/create-with-durations-lt/createWithDurationsLT.t.sol @@ -23,7 +23,7 @@ contract CreateWithDurationsLT_Integration_Concrete_Test is Lockup_Tranched_Inte internal returns (uint256 streamId) { - streamId = lockup.createWithDurationsLT(_defaultParams.createWithDurations, tranchesWithDurations); + streamId = createWithDurationsLT(_defaultParams.createWithDurations, tranchesWithDurations); } /*////////////////////////////////////////////////////////////////////////// diff --git a/test/core/integration/concrete/lockup-tranched/create-with-timestamps-lt/createWithTimestampsLT.t.sol b/test/core/integration/concrete/lockup-tranched/create-with-timestamps-lt/createWithTimestampsLT.t.sol index afc3d8606..075474691 100644 --- a/test/core/integration/concrete/lockup-tranched/create-with-timestamps-lt/createWithTimestampsLT.t.sol +++ b/test/core/integration/concrete/lockup-tranched/create-with-timestamps-lt/createWithTimestampsLT.t.sol @@ -30,7 +30,7 @@ contract CreateWithTimestampsLT_Integration_Concrete_Test is CreateWithTimestamp { LockupTranched.Tranche[] memory tranches; vm.expectRevert(Errors.SablierHelpers_TrancheCountZero.selector); - lockup.createWithTimestampsLT(_defaultParams.createWithTimestamps, tranches); + createWithTimestampsLT(_defaultParams.createWithTimestamps, tranches); } function test_RevertWhen_TrancheCountExceedsMaxValue() @@ -47,7 +47,7 @@ contract CreateWithTimestampsLT_Integration_Concrete_Test is CreateWithTimestamp uint256 trancheCount = defaults.MAX_COUNT() + 1; LockupTranched.Tranche[] memory tranches = new LockupTranched.Tranche[](trancheCount); vm.expectRevert(abi.encodeWithSelector(Errors.SablierHelpers_TrancheCountTooHigh.selector, trancheCount)); - lockup.createWithTimestampsLT(_defaultParams.createWithTimestamps, tranches); + createWithTimestampsLT(_defaultParams.createWithTimestamps, tranches); } function test_RevertWhen_TrancheAmountsSumOverflows() @@ -95,7 +95,7 @@ contract CreateWithTimestampsLT_Integration_Concrete_Test is CreateWithTimestamp ); // Create the stream. - lockup.createWithTimestampsLT(_defaultParams.createWithTimestamps, tranches); + createWithTimestampsLT(_defaultParams.createWithTimestamps, tranches); } function test_RevertWhen_StartTimeEqualsFirstTimestamp() diff --git a/test/core/integration/concrete/lockup-tranched/withdrawable-amount-of/withdrawableAmountOf.t.sol b/test/core/integration/concrete/lockup-tranched/withdrawable-amount-of/withdrawableAmountOf.t.sol index 69ac01fa5..e23f94ec0 100644 --- a/test/core/integration/concrete/lockup-tranched/withdrawable-amount-of/withdrawableAmountOf.t.sol +++ b/test/core/integration/concrete/lockup-tranched/withdrawable-amount-of/withdrawableAmountOf.t.sol @@ -35,7 +35,7 @@ contract WithdrawableAmountOf_Lockup_Tranched_Integration_Concrete_Test is vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.STREAMED_AMOUNT_26_PERCENT() }); + withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); // Run the test. uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(defaultStreamId); diff --git a/test/core/integration/concrete/nft-descriptor/generateAccentColor.t.sol b/test/core/integration/concrete/nft-descriptor/generateAccentColor.t.sol index 68b24754d..74797ff04 100644 --- a/test/core/integration/concrete/nft-descriptor/generateAccentColor.t.sol +++ b/test/core/integration/concrete/nft-descriptor/generateAccentColor.t.sol @@ -8,7 +8,7 @@ contract GenerateAccentColor_Integration_Concrete_Test is Base_Test { // Passing a dummy contract instead of a real Lockup contract to make this test easy to maintain. // Note: the address of `noop` depends on the order of the state variables in {Base_Test}. string memory actualColor = nftDescriptorMock.generateAccentColor_({ sablier: address(noop), streamId: 1337 }); - string memory expectedColor = "hsl(225,28%,77%)"; + string memory expectedColor = "hsl(115,39%,48%)"; assertEq(actualColor, expectedColor, "accentColor"); } } diff --git a/test/core/integration/fuzz/lockup-base/cancel.t.sol b/test/core/integration/fuzz/lockup-base/cancel.t.sol index 3fe7626c8..3b5a0606c 100644 --- a/test/core/integration/fuzz/lockup-base/cancel.t.sol +++ b/test/core/integration/fuzz/lockup-base/cancel.t.sol @@ -72,7 +72,7 @@ abstract contract Cancel_Integration_Fuzz_Test is Integration_Test { // Make the withdrawal only if the amount is greater than zero. if (withdrawAmount > 0) { - lockup.withdraw({ streamId: recipientGoodStreamId, to: address(recipientGood), amount: withdrawAmount }); + withdraw({ streamId: recipientGoodStreamId, to: address(recipientGood), amount: withdrawAmount }); } // Expect the assets to be refunded to the Sender. diff --git a/test/core/integration/fuzz/lockup-base/withdraw.t.sol b/test/core/integration/fuzz/lockup-base/withdraw.t.sol index 930e93012..6952a4529 100644 --- a/test/core/integration/fuzz/lockup-base/withdraw.t.sol +++ b/test/core/integration/fuzz/lockup-base/withdraw.t.sol @@ -12,7 +12,7 @@ abstract contract Withdraw_Integration_Fuzz_Test is Integration_Test { /// @dev Given enough fuzz runs, all of the following scenarios will be fuzzed: /// /// - Multiple caller addresses. - function testFuzz_Withdraw_UnknownCaller(address caller) + function testFuzz_Withdraw_UnknownCaller(address payable caller) external whenNoDelegateCall givenNotNull @@ -21,6 +21,7 @@ abstract contract Withdraw_Integration_Fuzz_Test is Integration_Test { whenWithdrawAmountNotOverdraw { vm.assume(caller != users.sender && caller != users.recipient); + vm.deal({ account: caller, newBalance: 1 ether }); // Make the fuzzed address the caller in this test. resetPrank({ msgSender: caller }); @@ -29,7 +30,7 @@ abstract contract Withdraw_Integration_Fuzz_Test is Integration_Test { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: users.recipient, amount: defaults.STREAMED_AMOUNT_26_PERCENT() }); + withdrawWithBalTest({ streamId: defaultStreamId, to: users.recipient, amount: defaults.WITHDRAW_AMOUNT() }); // Assert that the stream's status is still "STREAMING". Lockup.Status actualStatus = lockup.statusOf(defaultStreamId); @@ -63,7 +64,7 @@ abstract contract Withdraw_Integration_Fuzz_Test is Integration_Test { vm.warp({ newTimestamp: defaults.WARP_26_PERCENT() }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: to, amount: defaults.STREAMED_AMOUNT_26_PERCENT() }); + withdrawWithBalTest({ streamId: defaultStreamId, to: to, amount: defaults.WITHDRAW_AMOUNT() }); // Assert that the stream's status is still "STREAMING". Lockup.Status actualStatus = lockup.statusOf(defaultStreamId); @@ -114,12 +115,17 @@ abstract contract Withdraw_Integration_Fuzz_Test is Integration_Test { // Expect the relevant events to be emitted. vm.expectEmit({ emitter: address(lockup) }); - emit ISablierLockupBase.WithdrawFromLockupStream(defaultStreamId, to, dai, withdrawAmount); + emit ISablierLockupBase.WithdrawFromLockupStream({ + streamId: defaultStreamId, + to: to, + asset: dai, + withdrawnAmount: withdrawAmount + }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: defaultStreamId }); // Make the withdrawal. - lockup.withdraw({ streamId: defaultStreamId, to: to, amount: withdrawAmount }); + withdrawWithBalTest({ streamId: defaultStreamId, to: to, amount: withdrawAmount }); // Check if the stream has been depleted. uint128 refundedAmount = lockup.getRefundedAmount(defaultStreamId); @@ -177,12 +183,17 @@ abstract contract Withdraw_Integration_Fuzz_Test is Integration_Test { // Expect the relevant events to be emitted. vm.expectEmit({ emitter: address(lockup) }); - emit ISablierLockupBase.WithdrawFromLockupStream(defaultStreamId, to, dai, withdrawAmount); + emit ISablierLockupBase.WithdrawFromLockupStream({ + streamId: defaultStreamId, + to: to, + asset: dai, + withdrawnAmount: withdrawAmount + }); vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: defaultStreamId }); // Make the withdrawal. - lockup.withdraw(defaultStreamId, to, withdrawAmount); + withdrawWithBalTest(defaultStreamId, to, withdrawAmount); // Check if the stream is depleted or settled. It is possible for the stream to be just settled // and not depleted because the withdraw amount is fuzzed. diff --git a/test/core/integration/fuzz/lockup-base/withdrawMax.t.sol b/test/core/integration/fuzz/lockup-base/withdrawMax.t.sol index ef3631ef4..7f1e3df69 100644 --- a/test/core/integration/fuzz/lockup-base/withdrawMax.t.sol +++ b/test/core/integration/fuzz/lockup-base/withdrawMax.t.sol @@ -22,11 +22,11 @@ contract WithdrawMax_Integration_Fuzz_Test is Integration_Test { streamId: defaultStreamId, to: users.recipient, asset: dai, - amount: defaults.DEPOSIT_AMOUNT() + withdrawnAmount: defaults.DEPOSIT_AMOUNT() }); // Make the max withdrawal. - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // Assert that the withdrawn amount has been updated. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(defaultStreamId); @@ -66,11 +66,11 @@ contract WithdrawMax_Integration_Fuzz_Test is Integration_Test { streamId: defaultStreamId, to: users.recipient, asset: dai, - amount: withdrawAmount + withdrawnAmount: withdrawAmount }); // Make the max withdrawal. - lockup.withdrawMax({ streamId: defaultStreamId, to: users.recipient }); + withdrawMax({ streamId: defaultStreamId, to: users.recipient }); // Assert that the withdrawn amount has been updated. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(defaultStreamId); diff --git a/test/core/integration/fuzz/lockup-base/withdrawMaxAndTransfer.t.sol b/test/core/integration/fuzz/lockup-base/withdrawMaxAndTransfer.t.sol index e15e3b921..9c7516232 100644 --- a/test/core/integration/fuzz/lockup-base/withdrawMaxAndTransfer.t.sol +++ b/test/core/integration/fuzz/lockup-base/withdrawMaxAndTransfer.t.sol @@ -42,7 +42,7 @@ contract WithdrawMaxAndTransfer_Integration_Fuzz_Test is Integration_Test { streamId: defaultStreamId, to: users.recipient, asset: dai, - amount: withdrawAmount + withdrawnAmount: withdrawAmount }); } @@ -51,7 +51,7 @@ contract WithdrawMaxAndTransfer_Integration_Fuzz_Test is Integration_Test { emit IERC721.Transfer({ from: users.recipient, to: newRecipient, tokenId: defaultStreamId }); // Make the max withdrawal and transfer the NFT. - lockup.withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: newRecipient }); + withdrawMaxAndTransfer({ streamId: defaultStreamId, newRecipient: newRecipient }); // Assert that the withdrawn amount has been updated. uint128 actualWithdrawnAmount = lockup.getWithdrawnAmount(defaultStreamId); diff --git a/test/core/integration/fuzz/lockup-dynamic/withdraw.t.sol b/test/core/integration/fuzz/lockup-dynamic/withdraw.t.sol index d17180505..5874943ea 100644 --- a/test/core/integration/fuzz/lockup-dynamic/withdraw.t.sol +++ b/test/core/integration/fuzz/lockup-dynamic/withdraw.t.sol @@ -102,9 +102,10 @@ contract Withdraw_Lockup_Dynamic_Integration_Fuzz_Test is emit ISablierLockupBase.WithdrawFromLockupStream({ streamId: vars.streamId, to: params.to, - amount: vars.withdrawAmount, - asset: dai + asset: dai, + withdrawnAmount: vars.withdrawAmount }); + vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: vars.streamId }); @@ -112,7 +113,7 @@ contract Withdraw_Lockup_Dynamic_Integration_Fuzz_Test is resetPrank({ msgSender: users.recipient }); // Make the withdrawal. - lockup.withdraw({ streamId: vars.streamId, to: params.to, amount: vars.withdrawAmount }); + withdrawWithBalTest({ streamId: vars.streamId, to: params.to, amount: vars.withdrawAmount }); // Check if the stream is depleted or settled. It is possible for the stream to be just settled // and not depleted because the withdraw amount is fuzzed. diff --git a/test/core/integration/fuzz/lockup-dynamic/withdrawableAmountOf.t.sol b/test/core/integration/fuzz/lockup-dynamic/withdrawableAmountOf.t.sol index cc423e7bb..a0e9244b3 100644 --- a/test/core/integration/fuzz/lockup-dynamic/withdrawableAmountOf.t.sol +++ b/test/core/integration/fuzz/lockup-dynamic/withdrawableAmountOf.t.sol @@ -67,7 +67,7 @@ contract WithdrawableAmountOf_Lockup_Dynamic_Integration_Fuzz_Test is Lockup_Dyn withdrawAmount = boundUint128(withdrawAmount, 1, streamedAmount); // Make the withdrawal. - lockup.withdraw({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); + withdraw({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); // Run the test. uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(streamId); diff --git a/test/core/integration/fuzz/lockup-linear/withdrawableAmountOf.t.sol b/test/core/integration/fuzz/lockup-linear/withdrawableAmountOf.t.sol index e261d9cc2..659178834 100644 --- a/test/core/integration/fuzz/lockup-linear/withdrawableAmountOf.t.sol +++ b/test/core/integration/fuzz/lockup-linear/withdrawableAmountOf.t.sol @@ -106,7 +106,7 @@ contract WithdrawableAmountOf_Lockup_Linear_Integration_Fuzz_Test is Lockup_Line withdrawAmount = boundUint128(withdrawAmount, 1, streamedAmount); // Make the withdrawal. - lockup.withdraw({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); + withdraw({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); // Run the test. uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(streamId); diff --git a/test/core/integration/fuzz/lockup-tranched/withdraw.t.sol b/test/core/integration/fuzz/lockup-tranched/withdraw.t.sol index 102ad596d..98ced61a6 100644 --- a/test/core/integration/fuzz/lockup-tranched/withdraw.t.sol +++ b/test/core/integration/fuzz/lockup-tranched/withdraw.t.sol @@ -105,13 +105,14 @@ contract Withdraw_Lockup_Tranched_Integration_Fuzz_Test is streamId: vars.streamId, to: params.to, asset: dai, - amount: vars.withdrawAmount + withdrawnAmount: vars.withdrawAmount }); + vm.expectEmit({ emitter: address(lockup) }); emit IERC4906.MetadataUpdate({ _tokenId: vars.streamId }); // Make the withdrawal. - lockup.withdraw({ streamId: vars.streamId, to: params.to, amount: vars.withdrawAmount }); + withdrawWithBalTest({ streamId: vars.streamId, to: params.to, amount: vars.withdrawAmount }); // Check if the stream is depleted or settled. It is possible for the stream to be just settled // and not depleted because the withdraw amount is fuzzed. diff --git a/test/core/integration/fuzz/lockup-tranched/withdrawableAmountOf.t.sol b/test/core/integration/fuzz/lockup-tranched/withdrawableAmountOf.t.sol index 9b0c88604..16e2b0e01 100644 --- a/test/core/integration/fuzz/lockup-tranched/withdrawableAmountOf.t.sol +++ b/test/core/integration/fuzz/lockup-tranched/withdrawableAmountOf.t.sol @@ -70,7 +70,7 @@ contract WithdrawableAmountOf_Lockup_Tranched_Integration_Fuzz_Test is Lockup_Tr withdrawAmount = boundUint128(withdrawAmount, 1, streamedAmount); // Make the withdrawal. - lockup.withdraw({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); + withdraw({ streamId: streamId, to: users.recipient, amount: withdrawAmount }); // Run the test. uint128 actualWithdrawableAmount = lockup.withdrawableAmountOf(streamId); diff --git a/test/core/invariant/handlers/LockupHandler.sol b/test/core/invariant/handlers/LockupHandler.sol index d648fb6a7..516554158 100644 --- a/test/core/invariant/handlers/LockupHandler.sol +++ b/test/core/invariant/handlers/LockupHandler.sol @@ -53,14 +53,14 @@ contract LockupHandler is BaseHandler { } modifier useFuzzedStreamRecipient() { - uint256 lastStreamId = lockupStore.lastStreamId(); currentRecipient = lockupStore.recipients(currentStreamId); resetPrank(currentRecipient); + // Fund the recipient with the SABLIER_FEE. + vm.deal({ account: currentRecipient, newBalance: SABLIER_FEE }); _; } modifier useFuzzedStreamSender() { - uint256 lastStreamId = lockupStore.lastStreamId(); currentSender = lockupStore.senders(currentStreamId); resetPrank(currentSender); _; @@ -167,7 +167,7 @@ contract LockupHandler is BaseHandler { } // Withdraw from the stream. - lockup.withdraw({ streamId: currentStreamId, to: to, amount: withdrawAmount }); + lockup.withdraw{ value: SABLIER_FEE }({ streamId: currentStreamId, to: to, amount: withdrawAmount }); } function withdrawMax( @@ -199,7 +199,7 @@ contract LockupHandler is BaseHandler { } // Make the max withdrawal. - lockup.withdrawMax({ streamId: currentStreamId, to: to }); + lockup.withdrawMax{ value: SABLIER_FEE }({ streamId: currentStreamId, to: to }); } function withdrawMaxAndTransfer( @@ -230,7 +230,7 @@ contract LockupHandler is BaseHandler { vm.assume(lockup.withdrawableAmountOf(currentStreamId) != 0); // Make the max withdrawal and transfer the NFT. - lockup.withdrawMaxAndTransfer({ streamId: currentStreamId, newRecipient: newRecipient }); + lockup.withdrawMaxAndTransfer{ value: SABLIER_FEE }({ streamId: currentStreamId, newRecipient: newRecipient }); // Update the recipient associated with this stream ID. lockupStore.updateRecipient(currentStreamId, newRecipient); diff --git a/test/mocks/Hooks.sol b/test/mocks/Hooks.sol index 203c2a91a..885383555 100644 --- a/test/mocks/Hooks.sol +++ b/test/mocks/Hooks.sol @@ -6,6 +6,8 @@ import { IERC165, ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC import { ISablierLockupBase } from "src/core/interfaces/ISablierLockupBase.sol"; import { ISablierLockupRecipient } from "src/core/interfaces/ISablierLockupRecipient.sol"; +import { Constants } from "../utils/Constants.sol"; + contract RecipientGood is ISablierLockupRecipient, ERC165 { function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { return interfaceId == type(ISablierLockupRecipient).interfaceId; @@ -118,7 +120,7 @@ contract RecipientInvalidSelector is ISablierLockupRecipient, ERC165 { } } -contract RecipientReentrant is ISablierLockupRecipient, ERC165 { +contract RecipientReentrant is ISablierLockupRecipient, ERC165, Constants { function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC165) returns (bool) { return interfaceId == type(ISablierLockupRecipient).interfaceId; } @@ -138,7 +140,7 @@ contract RecipientReentrant is ISablierLockupRecipient, ERC165 { senderAmount; recipientAmount; - ISablierLockupBase(msg.sender).withdraw(streamId, address(this), recipientAmount); + ISablierLockupBase(msg.sender).withdraw{ value: SABLIER_FEE }(streamId, address(this), recipientAmount); return ISablierLockupRecipient.onSablierLockupCancel.selector; } @@ -158,7 +160,7 @@ contract RecipientReentrant is ISablierLockupRecipient, ERC165 { to; amount; - ISablierLockupBase(msg.sender).withdraw(streamId, address(this), amount); + ISablierLockupBase(msg.sender).withdraw{ value: SABLIER_FEE }(streamId, address(this), amount); return ISablierLockupRecipient.onSablierLockupWithdraw.selector; } diff --git a/test/periphery/Periphery.t.sol b/test/periphery/Periphery.t.sol index 064e4ab72..7429102f2 100644 --- a/test/periphery/Periphery.t.sol +++ b/test/periphery/Periphery.t.sol @@ -3,34 +3,20 @@ pragma solidity >=0.8.22 <0.9.0; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { LockupLinear } from "src/core/types/DataTypes.sol"; import { ISablierMerkleBase } from "src/periphery/interfaces/ISablierMerkleBase.sol"; import { SablierMerkleInstant } from "src/periphery/SablierMerkleInstant.sol"; import { SablierMerkleLL } from "src/periphery/SablierMerkleLL.sol"; import { SablierMerkleLT } from "src/periphery/SablierMerkleLT.sol"; import { Base_Test } from "../Base.t.sol"; -import { ContractWithoutReceiveEth, ContractWithReceiveEth } from "../mocks/ReceiveEth.sol"; contract Periphery_Test is Base_Test { - /*////////////////////////////////////////////////////////////////////////// - TEST CONTRACTS - //////////////////////////////////////////////////////////////////////////*/ - - ContractWithoutReceiveEth internal contractWithoutReceiveEth; - ContractWithReceiveEth internal contractWithReceiveEth; - /*////////////////////////////////////////////////////////////////////////// SET-UP FUNCTION //////////////////////////////////////////////////////////////////////////*/ function setUp() public virtual override { Base_Test.setUp(); - - contractWithoutReceiveEth = new ContractWithoutReceiveEth(); - contractWithReceiveEth = new ContractWithReceiveEth(); - vm.label({ account: address(contractWithoutReceiveEth), newLabel: "Contract Without Receive Eth" }); - vm.label({ account: address(contractWithReceiveEth), newLabel: "Contract With Receive Eth" }); } /*////////////////////////////////////////////////////////////////////////// diff --git a/test/periphery/fork/merkle-campaign/MerkleInstant.t.sol b/test/periphery/fork/merkle-campaign/MerkleInstant.t.sol index 2663c2a74..64922e2ea 100644 --- a/test/periphery/fork/merkle-campaign/MerkleInstant.t.sol +++ b/test/periphery/fork/merkle-campaign/MerkleInstant.t.sol @@ -99,7 +99,7 @@ abstract contract MerkleInstant_Fork_Test is Fork_Test { // Make the campaign owner as the caller. resetPrank({ msgSender: params.campaignOwner }); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; vars.expectedMerkleInstant = computeMerkleInstantAddress( params.campaignOwner, params.campaignOwner, FORK_ASSET, vars.merkleRoot, params.expiration, sablierFee diff --git a/test/periphery/fork/merkle-campaign/MerkleLL.t.sol b/test/periphery/fork/merkle-campaign/MerkleLL.t.sol index 5d2c768e5..7db05b51b 100644 --- a/test/periphery/fork/merkle-campaign/MerkleLL.t.sol +++ b/test/periphery/fork/merkle-campaign/MerkleLL.t.sol @@ -103,7 +103,7 @@ abstract contract MerkleLL_Fork_Test is Fork_Test { // Make the campaign owner as the caller. resetPrank({ msgSender: params.campaignOwner }); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; vars.expectedLL = computeMerkleLLAddress( params.campaignOwner, params.campaignOwner, FORK_ASSET, vars.merkleRoot, params.expiration, sablierFee diff --git a/test/periphery/fork/merkle-campaign/MerkleLT.t.sol b/test/periphery/fork/merkle-campaign/MerkleLT.t.sol index 2dfc1ad3c..8bbf21784 100644 --- a/test/periphery/fork/merkle-campaign/MerkleLT.t.sol +++ b/test/periphery/fork/merkle-campaign/MerkleLT.t.sol @@ -101,7 +101,7 @@ abstract contract MerkleLT_Fork_Test is Fork_Test { // Make the campaign owner as the caller. resetPrank({ msgSender: params.campaignOwner }); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; vars.expectedLT = computeMerkleLTAddress( params.campaignOwner, params.campaignOwner, FORK_ASSET, vars.merkleRoot, params.expiration, sablierFee diff --git a/test/periphery/integration/merkle-campaign/MerkleCampaign.t.sol b/test/periphery/integration/merkle-campaign/MerkleCampaign.t.sol index 85dedf95f..639d42104 100644 --- a/test/periphery/integration/merkle-campaign/MerkleCampaign.t.sol +++ b/test/periphery/integration/merkle-campaign/MerkleCampaign.t.sol @@ -39,7 +39,7 @@ abstract contract MerkleCampaign_Integration_Test is Periphery_Test { //////////////////////////////////////////////////////////////////////////*/ function claim() internal { - merkleBase.claim{ value: defaults.DEFAULT_SABLIER_FEE() }({ + merkleBase.claim{ value: SABLIER_FEE }({ index: defaults.INDEX1(), recipient: users.recipient1, amount: defaults.CLAIM_AMOUNT(), @@ -52,21 +52,16 @@ abstract contract MerkleCampaign_Integration_Test is Periphery_Test { //////////////////////////////////////////////////////////////////////////*/ function computeMerkleInstantAddress() internal view returns (address) { - return computeMerkleInstantAddress( - users.campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE() - ); + return + computeMerkleInstantAddress(users.campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleInstantAddress(address campaignOwner) internal view returns (address) { - return computeMerkleInstantAddress( - campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE() - ); + return computeMerkleInstantAddress(campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleInstantAddress(address campaignOwner, uint40 expiration) internal view returns (address) { - return computeMerkleInstantAddress( - campaignOwner, defaults.MERKLE_ROOT(), expiration, defaults.DEFAULT_SABLIER_FEE() - ); + return computeMerkleInstantAddress(campaignOwner, defaults.MERKLE_ROOT(), expiration, SABLIER_FEE); } function computeMerkleInstantAddress( @@ -82,9 +77,7 @@ abstract contract MerkleCampaign_Integration_Test is Periphery_Test { } function computeMerkleInstantAddress(address campaignOwner, bytes32 merkleRoot) internal view returns (address) { - return computeMerkleInstantAddress( - campaignOwner, merkleRoot, defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE() - ); + return computeMerkleInstantAddress(campaignOwner, merkleRoot, defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleInstantAddress( @@ -132,19 +125,15 @@ abstract contract MerkleCampaign_Integration_Test is Periphery_Test { //////////////////////////////////////////////////////////////////////////*/ function computeMerkleLLAddress() internal view returns (address) { - return computeMerkleLLAddress( - users.campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE() - ); + return computeMerkleLLAddress(users.campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleLLAddress(address campaignOwner) internal view returns (address) { - return computeMerkleLLAddress( - campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE() - ); + return computeMerkleLLAddress(campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleLLAddress(address campaignOwner, uint40 expiration) internal view returns (address) { - return computeMerkleLLAddress(campaignOwner, defaults.MERKLE_ROOT(), expiration, defaults.DEFAULT_SABLIER_FEE()); + return computeMerkleLLAddress(campaignOwner, defaults.MERKLE_ROOT(), expiration, SABLIER_FEE); } function computeMerkleLLAddress( @@ -160,7 +149,7 @@ abstract contract MerkleCampaign_Integration_Test is Periphery_Test { } function computeMerkleLLAddress(address campaignOwner, bytes32 merkleRoot) internal view returns (address) { - return computeMerkleLLAddress(campaignOwner, merkleRoot, defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE()); + return computeMerkleLLAddress(campaignOwner, merkleRoot, defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleLLAddress( @@ -212,19 +201,15 @@ abstract contract MerkleCampaign_Integration_Test is Periphery_Test { //////////////////////////////////////////////////////////////////////////*/ function computeMerkleLTAddress() internal view returns (address) { - return computeMerkleLTAddress( - users.campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE() - ); + return computeMerkleLTAddress(users.campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleLTAddress(address campaignOwner) internal view returns (address) { - return computeMerkleLTAddress( - campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE() - ); + return computeMerkleLTAddress(campaignOwner, defaults.MERKLE_ROOT(), defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleLTAddress(address campaignOwner, uint40 expiration) internal view returns (address) { - return computeMerkleLTAddress(campaignOwner, defaults.MERKLE_ROOT(), expiration, defaults.DEFAULT_SABLIER_FEE()); + return computeMerkleLTAddress(campaignOwner, defaults.MERKLE_ROOT(), expiration, SABLIER_FEE); } function computeMerkleLTAddress( @@ -240,7 +225,7 @@ abstract contract MerkleCampaign_Integration_Test is Periphery_Test { } function computeMerkleLTAddress(address campaignOwner, bytes32 merkleRoot) internal view returns (address) { - return computeMerkleLTAddress(campaignOwner, merkleRoot, defaults.EXPIRATION(), defaults.DEFAULT_SABLIER_FEE()); + return computeMerkleLTAddress(campaignOwner, merkleRoot, defaults.EXPIRATION(), SABLIER_FEE); } function computeMerkleLTAddress( diff --git a/test/periphery/integration/merkle-campaign/factory/create-merkle-instant/createMerkleInstant.t.sol b/test/periphery/integration/merkle-campaign/factory/create-merkle-instant/createMerkleInstant.t.sol index 73242a459..7f22025b7 100644 --- a/test/periphery/integration/merkle-campaign/factory/create-merkle-instant/createMerkleInstant.t.sol +++ b/test/periphery/integration/merkle-campaign/factory/create-merkle-instant/createMerkleInstant.t.sol @@ -114,7 +114,7 @@ contract CreateMerkleInstant_Integration_Test is MerkleCampaign_Integration_Test baseParams: baseParams, aggregateAmount: defaults.AGGREGATE_AMOUNT(), recipientCount: defaults.RECIPIENT_COUNT(), - sablierFee: defaults.DEFAULT_SABLIER_FEE() + sablierFee: SABLIER_FEE }); ISablierMerkleInstant actualInstant = createMerkleInstant(campaignOwner, expiration); @@ -124,7 +124,7 @@ contract CreateMerkleInstant_Integration_Test is MerkleCampaign_Integration_Test ); // It should create the campaign with custom fee. - assertEq(actualInstant.SABLIER_FEE(), defaults.DEFAULT_SABLIER_FEE(), "default sablier fee"); + assertEq(actualInstant.SABLIER_FEE(), SABLIER_FEE, "default sablier fee"); // It should set the current factory address. assertEq(actualInstant.FACTORY(), address(merkleFactory), "factory"); diff --git a/test/periphery/integration/merkle-campaign/factory/create-merkle-ll/createMerkleLL.t.sol b/test/periphery/integration/merkle-campaign/factory/create-merkle-ll/createMerkleLL.t.sol index 3ec8b3710..1fb3a219a 100644 --- a/test/periphery/integration/merkle-campaign/factory/create-merkle-ll/createMerkleLL.t.sol +++ b/test/periphery/integration/merkle-campaign/factory/create-merkle-ll/createMerkleLL.t.sol @@ -134,7 +134,7 @@ contract CreateMerkleLL_Integration_Test is MerkleCampaign_Integration_Test { schedule: defaults.schedule(), aggregateAmount: defaults.AGGREGATE_AMOUNT(), recipientCount: defaults.RECIPIENT_COUNT(), - sablierFee: defaults.DEFAULT_SABLIER_FEE() + sablierFee: SABLIER_FEE }); ISablierMerkleLL actualLL = createMerkleLL(campaignOwner, expiration); @@ -142,7 +142,7 @@ contract CreateMerkleLL_Integration_Test is MerkleCampaign_Integration_Test { assertEq(address(actualLL), expectedLL, "MerkleLL contract does not match computed address"); // It should create the campaign with custom fee. - assertEq(actualLL.SABLIER_FEE(), defaults.DEFAULT_SABLIER_FEE(), "default sablier fee"); + assertEq(actualLL.SABLIER_FEE(), SABLIER_FEE, "default sablier fee"); // It should set the current factory address. assertEq(actualLL.FACTORY(), address(merkleFactory), "factory"); diff --git a/test/periphery/integration/merkle-campaign/factory/create-merkle-lt/createMerkleLT.t.sol b/test/periphery/integration/merkle-campaign/factory/create-merkle-lt/createMerkleLT.t.sol index 71a88e301..aa805a2c1 100644 --- a/test/periphery/integration/merkle-campaign/factory/create-merkle-lt/createMerkleLT.t.sol +++ b/test/periphery/integration/merkle-campaign/factory/create-merkle-lt/createMerkleLT.t.sol @@ -140,7 +140,7 @@ contract CreateMerkleLT_Integration_Test is MerkleCampaign_Integration_Test { totalDuration: defaults.TOTAL_DURATION(), aggregateAmount: defaults.AGGREGATE_AMOUNT(), recipientCount: defaults.RECIPIENT_COUNT(), - sablierFee: defaults.DEFAULT_SABLIER_FEE() + sablierFee: SABLIER_FEE }); ISablierMerkleLT actualLT = createMerkleLT(campaignOwner, expiration); @@ -148,7 +148,7 @@ contract CreateMerkleLT_Integration_Test is MerkleCampaign_Integration_Test { assertEq(address(actualLT), expectedLT, "MerkleLT contract does not match computed address"); // It should create the campaign with custom fee. - assertEq(actualLT.SABLIER_FEE(), defaults.DEFAULT_SABLIER_FEE(), "default sablier fee"); + assertEq(actualLT.SABLIER_FEE(), SABLIER_FEE, "default sablier fee"); // It should set the current factory address. assertEq(actualLT.FACTORY(), address(merkleFactory), "factory"); } diff --git a/test/periphery/integration/merkle-campaign/factory/set-default-sablier-fee/setDefaultSablierFee.t.sol b/test/periphery/integration/merkle-campaign/factory/set-default-sablier-fee/setDefaultSablierFee.t.sol index 2b8edced8..bd9e224ce 100644 --- a/test/periphery/integration/merkle-campaign/factory/set-default-sablier-fee/setDefaultSablierFee.t.sol +++ b/test/periphery/integration/merkle-campaign/factory/set-default-sablier-fee/setDefaultSablierFee.t.sol @@ -9,7 +9,7 @@ import { MerkleCampaign_Integration_Test } from "../../MerkleCampaign.t.sol"; contract SetDefaultSablierFee_Integration_Test is MerkleCampaign_Integration_Test { function test_RevertWhen_CallerNotAdmin() external { - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; resetPrank({ msgSender: users.eve }); vm.expectRevert(abi.encodeWithSelector(CoreErrors.CallerNotAdmin.selector, users.admin, users.eve)); merkleFactory.setDefaultSablierFee({ defaultFee: sablierFee }); @@ -20,14 +20,11 @@ contract SetDefaultSablierFee_Integration_Test is MerkleCampaign_Integration_Tes // It should emit a {SetDefaultSablierFee} event. vm.expectEmit({ emitter: address(merkleFactory) }); - emit ISablierMerkleFactory.SetDefaultSablierFee({ - admin: users.admin, - defaultSablierFee: defaults.DEFAULT_SABLIER_FEE() - }); + emit ISablierMerkleFactory.SetDefaultSablierFee({ admin: users.admin, defaultSablierFee: SABLIER_FEE }); - merkleFactory.setDefaultSablierFee({ defaultFee: defaults.DEFAULT_SABLIER_FEE() }); + merkleFactory.setDefaultSablierFee({ defaultFee: SABLIER_FEE }); // It should set the default Sablier fee. - assertEq(merkleFactory.defaultSablierFee(), defaults.DEFAULT_SABLIER_FEE(), "sablier fee"); + assertEq(merkleFactory.defaultSablierFee(), SABLIER_FEE, "sablier fee"); } } diff --git a/test/periphery/integration/merkle-campaign/factory/withdraw-fees/withdrawFees.t.sol b/test/periphery/integration/merkle-campaign/factory/withdraw-fees/withdrawFees.t.sol index a8d961760..8a2378310 100644 --- a/test/periphery/integration/merkle-campaign/factory/withdraw-fees/withdrawFees.t.sol +++ b/test/periphery/integration/merkle-campaign/factory/withdraw-fees/withdrawFees.t.sol @@ -47,7 +47,7 @@ contract WithdrawFees_Integration_Test is MerkleCampaign_Integration_Test { admin: users.admin, merkleBase: merkleBase, to: users.eve, - sablierFees: defaults.DEFAULT_SABLIER_FEE() + sablierFees: SABLIER_FEE }); merkleFactory.withdrawFees(users.eve, merkleBase); @@ -55,7 +55,7 @@ contract WithdrawFees_Integration_Test is MerkleCampaign_Integration_Test { // It should set the ETH balance to 0. assertEq(address(merkleBase).balance, 0, "merkle lockup eth balance"); // It should transfer fee collected in ETH to the provided address. - assertEq(users.eve.balance, previousToBalance + defaults.DEFAULT_SABLIER_FEE(), "eth balance"); + assertEq(users.eve.balance, previousToBalance + SABLIER_FEE, "eth balance"); } function test_RevertWhen_ProvidedAddressNotImplementReceiveEth() @@ -87,7 +87,7 @@ contract WithdrawFees_Integration_Test is MerkleCampaign_Integration_Test { admin: users.admin, merkleBase: merkleBase, to: receiveEth, - sablierFees: defaults.DEFAULT_SABLIER_FEE() + sablierFees: SABLIER_FEE }); merkleFactory.withdrawFees(receiveEth, merkleBase); @@ -95,6 +95,6 @@ contract WithdrawFees_Integration_Test is MerkleCampaign_Integration_Test { // It should set the ETH balance to 0. assertEq(address(merkleBase).balance, 0, "merkle lockup eth balance"); // It should transfer fee collected in ETH to the provided address. - assertEq(receiveEth.balance, defaults.DEFAULT_SABLIER_FEE(), "eth balance"); + assertEq(receiveEth.balance, SABLIER_FEE, "eth balance"); } } diff --git a/test/periphery/integration/merkle-campaign/instant/claim/claim.t.sol b/test/periphery/integration/merkle-campaign/instant/claim/claim.t.sol index 9a60c983e..b44c1bc7a 100644 --- a/test/periphery/integration/merkle-campaign/instant/claim/claim.t.sol +++ b/test/periphery/integration/merkle-campaign/instant/claim/claim.t.sol @@ -27,11 +27,11 @@ contract Claim_MerkleInstant_Integration_Test is Claim_Integration_Test, MerkleI emit ISablierMerkleInstant.Claim(defaults.INDEX1(), users.recipient1, defaults.CLAIM_AMOUNT()); expectCallToTransfer({ to: users.recipient1, value: defaults.CLAIM_AMOUNT() }); - expectCallToClaimWithMsgValue(address(merkleInstant), defaults.DEFAULT_SABLIER_FEE()); + expectCallToClaimWithMsgValue(address(merkleInstant), SABLIER_FEE); claim(); assertTrue(merkleInstant.hasClaimed(defaults.INDEX1()), "not claimed"); - assertEq(address(merkleInstant).balance, previousFeeAccrued + defaults.DEFAULT_SABLIER_FEE(), "fee collected"); + assertEq(address(merkleInstant).balance, previousFeeAccrued + SABLIER_FEE, "fee collected"); } } diff --git a/test/periphery/integration/merkle-campaign/instant/constructor.t.sol b/test/periphery/integration/merkle-campaign/instant/constructor.t.sol index c4917d854..ef2e77c6a 100644 --- a/test/periphery/integration/merkle-campaign/instant/constructor.t.sol +++ b/test/periphery/integration/merkle-campaign/instant/constructor.t.sol @@ -30,8 +30,7 @@ contract Constructor_MerkleInstant_Integration_Test is MerkleCampaign_Integratio // Make Factory the caller for the constructor test. resetPrank(address(merkleFactory)); - SablierMerkleInstant constructedInstant = - new SablierMerkleInstant(defaults.baseParams(), defaults.DEFAULT_SABLIER_FEE()); + SablierMerkleInstant constructedInstant = new SablierMerkleInstant(defaults.baseParams(), SABLIER_FEE); Vars memory vars; @@ -64,7 +63,7 @@ contract Constructor_MerkleInstant_Integration_Test is MerkleCampaign_Integratio assertEq(bytes32(abi.encodePacked(vars.actualName)), vars.expectedName, "name"); vars.actualSablierFee = constructedInstant.SABLIER_FEE(); - vars.expectedSablierFee = defaults.DEFAULT_SABLIER_FEE(); + vars.expectedSablierFee = SABLIER_FEE; assertEq(vars.actualSablierFee, vars.expectedSablierFee, "sablierFee"); } } diff --git a/test/periphery/integration/merkle-campaign/ll/claim/claim.t.sol b/test/periphery/integration/merkle-campaign/ll/claim/claim.t.sol index 62491f044..c15c2e13e 100644 --- a/test/periphery/integration/merkle-campaign/ll/claim/claim.t.sol +++ b/test/periphery/integration/merkle-campaign/ll/claim/claim.t.sol @@ -63,7 +63,6 @@ contract Claim_MerkleLL_Integration_Test is Claim_Integration_Test, MerkleLL_Int /// @dev Helper function to test claim. function _test_Claim(uint40 startTime, uint40 cliffTime) private { - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); deal({ token: address(dai), to: address(merkleLL), give: defaults.AGGREGATE_AMOUNT() }); uint256 expectedStreamId = lockup.nextStreamId(); @@ -77,10 +76,10 @@ contract Claim_MerkleLL_Integration_Test is Claim_Integration_Test, MerkleLL_Int emit ISablierMerkleLL.Claim(defaults.INDEX1(), users.recipient1, defaults.CLAIM_AMOUNT(), expectedStreamId); expectCallToTransferFrom({ from: address(merkleLL), to: address(lockup), value: defaults.CLAIM_AMOUNT() }); - expectCallToClaimWithMsgValue(address(merkleLL), sablierFee); + expectCallToClaimWithMsgValue(address(merkleLL), SABLIER_FEE); // Claim the airstream. - merkleLL.claim{ value: sablierFee }( + merkleLL.claim{ value: SABLIER_FEE }( defaults.INDEX1(), users.recipient1, defaults.CLAIM_AMOUNT(), defaults.index1Proof() ); @@ -101,6 +100,6 @@ contract Claim_MerkleLL_Integration_Test is Claim_Integration_Test, MerkleLL_Int assertEq(lockup.getUnlockAmounts(expectedStreamId).cliff, unlockAmounts.cliff, "unlock amount cliff"); assertTrue(merkleLL.hasClaimed(defaults.INDEX1()), "not claimed"); - assertEq(address(merkleLL).balance, previousFeeAccrued + defaults.DEFAULT_SABLIER_FEE(), "fee collected"); + assertEq(address(merkleLL).balance, previousFeeAccrued + SABLIER_FEE, "fee collected"); } } diff --git a/test/periphery/integration/merkle-campaign/ll/constructor.t.sol b/test/periphery/integration/merkle-campaign/ll/constructor.t.sol index bca91849d..9d93a8ac8 100644 --- a/test/periphery/integration/merkle-campaign/ll/constructor.t.sol +++ b/test/periphery/integration/merkle-campaign/ll/constructor.t.sol @@ -47,7 +47,7 @@ contract Constructor_MerkleLL_Integration_Test is MerkleCampaign_Integration_Tes defaults.CANCELABLE(), defaults.TRANSFERABLE(), defaults.schedule(), - defaults.DEFAULT_SABLIER_FEE() + SABLIER_FEE ); Vars memory vars; @@ -111,7 +111,7 @@ contract Constructor_MerkleLL_Integration_Test is MerkleCampaign_Integration_Tes assertEq(vars.actualTransferable, vars.expectedTransferable, "transferable"); vars.actualSablierFee = constructedLL.SABLIER_FEE(); - vars.expectedSablierFee = defaults.DEFAULT_SABLIER_FEE(); + vars.expectedSablierFee = SABLIER_FEE; assertEq(vars.actualSablierFee, vars.expectedSablierFee, "sablierFee"); } } diff --git a/test/periphery/integration/merkle-campaign/lt/claim/claim.t.sol b/test/periphery/integration/merkle-campaign/lt/claim/claim.t.sol index 981878010..2c6d77bf6 100644 --- a/test/periphery/integration/merkle-campaign/lt/claim/claim.t.sol +++ b/test/periphery/integration/merkle-campaign/lt/claim/claim.t.sol @@ -16,7 +16,7 @@ contract Claim_MerkleLT_Integration_Test is Claim_Integration_Test, MerkleLT_Int } function test_RevertWhen_TotalPercentageLessThan100() external whenMerkleProofValid whenTotalPercentageNot100 { - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; // Create a MerkleLT campaign with a total percentage less than 100. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = defaults.tranchesWithPercentages(); @@ -53,7 +53,7 @@ contract Claim_MerkleLT_Integration_Test is Claim_Integration_Test, MerkleLT_Int } function test_RevertWhen_TotalPercentageGreaterThan100() external whenMerkleProofValid whenTotalPercentageNot100 { - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; // Create a MerkleLT campaign with a total percentage less than 100. MerkleLT.TrancheWithPercentage[] memory tranchesWithPercentages = defaults.tranchesWithPercentages(); tranchesWithPercentages[0].unlockPercentage = ud2x18(0.75e18); @@ -114,7 +114,7 @@ contract Claim_MerkleLT_Integration_Test is Claim_Integration_Test, MerkleLT_Int /// @dev Helper function to test claim. function _test_Claim(uint40 streamStartTime, uint40 startTime) private { - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; deal({ token: address(dai), to: address(merkleLT), give: defaults.AGGREGATE_AMOUNT() }); @@ -154,6 +154,6 @@ contract Claim_MerkleLT_Integration_Test is Claim_Integration_Test, MerkleLT_Int assertTrue(merkleLT.hasClaimed(defaults.INDEX1()), "not claimed"); - assertEq(address(merkleLT).balance, previousFeeAccrued + defaults.DEFAULT_SABLIER_FEE(), "fee collected"); + assertEq(address(merkleLT).balance, previousFeeAccrued + SABLIER_FEE, "fee collected"); } } diff --git a/test/periphery/integration/merkle-campaign/lt/constructor.t.sol b/test/periphery/integration/merkle-campaign/lt/constructor.t.sol index e07e3b274..7fe47b714 100644 --- a/test/periphery/integration/merkle-campaign/lt/constructor.t.sol +++ b/test/periphery/integration/merkle-campaign/lt/constructor.t.sol @@ -52,7 +52,7 @@ contract Constructor_MerkleLT_Integration_Test is MerkleCampaign_Integration_Tes defaults.TRANSFERABLE(), defaults.STREAM_START_TIME_ZERO(), defaults.tranchesWithPercentages(), - defaults.DEFAULT_SABLIER_FEE() + SABLIER_FEE ); Vars memory vars; @@ -98,7 +98,7 @@ contract Constructor_MerkleLT_Integration_Test is MerkleCampaign_Integration_Tes assertEq(bytes32(abi.encodePacked(vars.actualName)), vars.expectedName, "name"); vars.actualSablierFee = constructedLT.SABLIER_FEE(); - vars.expectedSablierFee = defaults.DEFAULT_SABLIER_FEE(); + vars.expectedSablierFee = SABLIER_FEE; assertEq(vars.actualSablierFee, vars.expectedSablierFee, "sablierFee"); vars.actualStreamStartTime = constructedLT.STREAM_START_TIME(); diff --git a/test/periphery/integration/merkle-campaign/shared/claim/claim.t.sol b/test/periphery/integration/merkle-campaign/shared/claim/claim.t.sol index c267ee7d8..9ccbb59fd 100644 --- a/test/periphery/integration/merkle-campaign/shared/claim/claim.t.sol +++ b/test/periphery/integration/merkle-campaign/shared/claim/claim.t.sol @@ -8,7 +8,7 @@ import { MerkleCampaign_Integration_Test } from "../../MerkleCampaign.t.sol"; abstract contract Claim_Integration_Test is MerkleCampaign_Integration_Test { function test_RevertGiven_CampaignExpired() external { uint40 expiration = defaults.EXPIRATION(); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; uint256 warpTime = expiration + 1 seconds; bytes32[] memory merkleProof; vm.warp({ newTimestamp: warpTime }); @@ -25,7 +25,7 @@ abstract contract Claim_Integration_Test is MerkleCampaign_Integration_Test { uint256 index1 = defaults.INDEX1(); uint128 amount = defaults.CLAIM_AMOUNT(); bytes32[] memory merkleProof = defaults.index1Proof(); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; vm.expectRevert(abi.encodeWithSelector(Errors.SablierMerkleBase_InsufficientFeePayment.selector, 0, sablierFee)); merkleBase.claim{ value: 0 }(index1, users.recipient1, amount, merkleProof); @@ -35,7 +35,7 @@ abstract contract Claim_Integration_Test is MerkleCampaign_Integration_Test { claim(); uint256 index1 = defaults.INDEX1(); uint128 amount = defaults.CLAIM_AMOUNT(); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; bytes32[] memory merkleProof = defaults.index1Proof(); vm.expectRevert(abi.encodeWithSelector(Errors.SablierMerkleBase_StreamClaimed.selector, index1)); merkleBase.claim{ value: sablierFee }(index1, users.recipient1, amount, merkleProof); @@ -49,7 +49,7 @@ abstract contract Claim_Integration_Test is MerkleCampaign_Integration_Test { { uint256 invalidIndex = 1337; uint128 amount = defaults.CLAIM_AMOUNT(); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; bytes32[] memory merkleProof = defaults.index1Proof(); vm.expectRevert(abi.encodeWithSelector(Errors.SablierMerkleBase_InvalidProof.selector)); merkleBase.claim{ value: sablierFee }(invalidIndex, users.recipient1, amount, merkleProof); @@ -65,7 +65,7 @@ abstract contract Claim_Integration_Test is MerkleCampaign_Integration_Test { uint256 index1 = defaults.INDEX1(); address invalidRecipient = address(1337); uint128 amount = defaults.CLAIM_AMOUNT(); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; bytes32[] memory merkleProof = defaults.index1Proof(); vm.expectRevert(abi.encodeWithSelector(Errors.SablierMerkleBase_InvalidProof.selector)); merkleBase.claim{ value: sablierFee }(index1, invalidRecipient, amount, merkleProof); @@ -81,7 +81,7 @@ abstract contract Claim_Integration_Test is MerkleCampaign_Integration_Test { { uint256 index1 = defaults.INDEX1(); uint128 invalidAmount = 1337; - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; bytes32[] memory merkleProof = defaults.index1Proof(); vm.expectRevert(abi.encodeWithSelector(Errors.SablierMerkleBase_InvalidProof.selector)); merkleBase.claim{ value: sablierFee }(index1, users.recipient1, invalidAmount, merkleProof); @@ -98,7 +98,7 @@ abstract contract Claim_Integration_Test is MerkleCampaign_Integration_Test { { uint256 index1 = defaults.INDEX1(); uint128 amount = defaults.CLAIM_AMOUNT(); - uint256 sablierFee = defaults.DEFAULT_SABLIER_FEE(); + uint256 sablierFee = SABLIER_FEE; bytes32[] memory invalidMerkleProof = defaults.index2Proof(); vm.expectRevert(abi.encodeWithSelector(Errors.SablierMerkleBase_InvalidProof.selector)); merkleBase.claim{ value: sablierFee }(index1, users.recipient1, amount, invalidMerkleProof); diff --git a/test/periphery/integration/merkle-campaign/shared/withdraw-fees/withdrawFees.t.sol b/test/periphery/integration/merkle-campaign/shared/withdraw-fees/withdrawFees.t.sol index 7ebb77a6e..9aa35a4bd 100644 --- a/test/periphery/integration/merkle-campaign/shared/withdraw-fees/withdrawFees.t.sol +++ b/test/periphery/integration/merkle-campaign/shared/withdraw-fees/withdrawFees.t.sol @@ -34,7 +34,7 @@ abstract contract WithdrawFees_Integration_Test is MerkleCampaign_Integration_Te // It should set the ETH balance to 0. assertEq(address(merkleBase).balance, 0, "merkle lockup eth balance"); // It should transfer fee collected in ETH to the provided address. - assertEq(users.admin.balance, previousToBalance + defaults.DEFAULT_SABLIER_FEE(), "eth balance"); + assertEq(users.admin.balance, previousToBalance + SABLIER_FEE, "eth balance"); } function test_RevertWhen_ProvidedAddressNotImplementReceiveEth() @@ -59,6 +59,6 @@ abstract contract WithdrawFees_Integration_Test is MerkleCampaign_Integration_Te // It should set the ETH balance to 0. assertEq(address(merkleBase).balance, 0, "merkle lockup eth balance"); // It should transfer fee collected in ETH to the provided address. - assertEq(receiveEth.balance, defaults.DEFAULT_SABLIER_FEE(), "eth balance"); + assertEq(receiveEth.balance, SABLIER_FEE, "eth balance"); } } diff --git a/test/utils/Constants.sol b/test/utils/Constants.sol index 82943e274..106222b56 100644 --- a/test/utils/Constants.sol +++ b/test/utils/Constants.sol @@ -10,4 +10,5 @@ abstract contract Constants { uint256 internal constant MAX_UINT256 = type(uint256).max; uint40 internal constant MAX_UINT40 = type(uint40).max; uint40 internal constant MAX_UNIX_TIMESTAMP = 2_147_483_647; // 2^31 - 1 + uint256 internal constant SABLIER_FEE = 0.005e18; } diff --git a/test/utils/Defaults.sol b/test/utils/Defaults.sol index 40cb03bda..5e8f26813 100644 --- a/test/utils/Defaults.sol +++ b/test/utils/Defaults.sol @@ -62,7 +62,6 @@ contract Defaults is Constants, Merkle { uint256 public constant AGGREGATE_AMOUNT = CLAIM_AMOUNT * RECIPIENT_COUNT; bool public constant CANCELABLE = false; uint128 public constant CLAIM_AMOUNT = 10_000e18; - uint256 public constant DEFAULT_SABLIER_FEE = 0.005e18; uint40 public immutable EXPIRATION; uint40 public constant FIRST_CLAIM_TIME = JULY_1_2024; uint256 public constant INDEX1 = 1; diff --git a/test/utils/Modifiers.sol b/test/utils/Modifiers.sol index 6c02edd18..3f8e1e866 100644 --- a/test/utils/Modifiers.sol +++ b/test/utils/Modifiers.sol @@ -39,6 +39,10 @@ abstract contract Modifiers is Fuzzers { _; } + modifier givenMsgValueNotLessThanSablierFee() { + _; + } + modifier givenNFTExists() { _; } @@ -139,7 +143,7 @@ abstract contract Modifiers is Fuzzers { modifier givenDepletedStream(ISablierLockup lockup, uint256 streamId) { vm.warp({ newTimestamp: defaults.END_TIME() }); - lockup.withdrawMax({ streamId: streamId, to: users.recipient }); + lockup.withdrawMax{ value: SABLIER_FEE }({ streamId: streamId, to: users.recipient }); _; } @@ -155,10 +159,6 @@ abstract contract Modifiers is Fuzzers { _; } - modifier givenMsgValueNotLessThanSablierFee() { - _; - } - modifier givenRecipientNotClaimed() { _; } @@ -514,6 +514,14 @@ abstract contract Modifiers is Fuzzers { _; } + /*////////////////////////////////////////////////////////////////////////// + WITHDRAW-FEES + //////////////////////////////////////////////////////////////////////////*/ + + modifier whenProvidedAddressImplementReceiveEth() { + _; + } + /*////////////////////////////////////////////////////////////////////////// WITHDRAW-HOOKS //////////////////////////////////////////////////////////////////////////*/ diff --git a/test/utils/Precompiles.t.sol b/test/utils/Precompiles.t.sol index d0dd63a29..5567a62d7 100644 --- a/test/utils/Precompiles.t.sol +++ b/test/utils/Precompiles.t.sol @@ -7,7 +7,6 @@ import { LibString } from "solady/src/utils/LibString.sol"; import { ILockupNFTDescriptor } from "src/core/interfaces/ILockupNFTDescriptor.sol"; import { ISablierBatchLockup } from "src/core/interfaces/ISablierBatchLockup.sol"; import { ISablierLockup } from "src/core/interfaces/ISablierLockup.sol"; -import { ISablierMerkleFactory } from "src/periphery/interfaces/ISablierMerkleFactory.sol"; import { Base_Test } from "./../Base.t.sol";