From 179c7b9c395093f5d5825077f3a6652e69d9e443 Mon Sep 17 00:00:00 2001 From: Justin Pulley Date: Wed, 23 Aug 2023 15:53:28 -0500 Subject: [PATCH 1/2] feat: cancel streams & track stream type of linear or dyn --- src/JBSips.sol | 23 +++++++++++++++++++---- src/abstract/JBSablier.sol | 6 ++++++ test/Sips_Integration.t.sol | 30 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 4 deletions(-) diff --git a/src/JBSips.sol b/src/JBSips.sol index 609a750..a0ab2c5 100644 --- a/src/JBSips.sol +++ b/src/JBSips.sol @@ -24,6 +24,8 @@ import {IERC20} from 'lib/v2-periphery/lib/v2-core/src/types/Tokens.sol'; import {IPRBProxy} from '@sablier/v2-periphery/src/types/Proxy.sol'; +import {Batch} from '@sablier/v2-periphery/src/types/DataTypes.sol'; + /** * @custom:benediction DEVS BENEDICAT ET PROTEGAT CONTRACTVS MEAM * @@ -45,10 +47,6 @@ contract JBSips is JBSablier, JBOperatable, IJBSplitAllocator { error JuiceSips_Unauthorized(); error JuiceSips_MaximumSlippage(); - //*********************************************************************// - // ----------------------------- events ----------------------------- // - //*********************************************************************// - //*********************************************************************// // --------------------- public stored properties -------------------- // //*********************************************************************// @@ -163,6 +161,23 @@ contract JBSips is JBSablier, JBOperatable, IJBSplitAllocator { super._deployStreams(_streams, _cycle.number); } + function batchCancelStreams( + Batch.CancelMultiple[] calldata _batch, + IERC20[] calldata _assets + ) + external + requirePermission(controller.projects().ownerOf(projectId), projectId, JBOperations.SET_SPLITS) + { + bytes memory data = abi.encodeCall( + proxyTarget.batchCancelMultiple, + (_batch, _assets) + ); + + // Create a batch of Lockup Linear streams via the proxy and Sablier's proxy target + bytes memory response = proxy.execute(address(proxyTarget), data); + /* uint256[] memory streamIds = abi.decode(response, (uint256[])); */ + } + /// @notice Withdraws ETH.. function withdrawETH() external diff --git a/src/abstract/JBSablier.sol b/src/abstract/JBSablier.sol index 2c03b45..6340ea6 100644 --- a/src/abstract/JBSablier.sol +++ b/src/abstract/JBSablier.sol @@ -51,6 +51,8 @@ abstract contract JBSablier is ERC165, ERC1271, IUniswapV3SwapCallback { mapping(uint256 cycleNumber => mapping(address user => uint256[] streamIds)) public streamsByCycleAndAddress; + mapping(uint256 streamId => bool isLinear) public isStreamLinear; + uint256 public immutable projectId; IJBDirectory public directory; IJBController3_1 public controller; @@ -233,6 +235,7 @@ abstract contract JBSablier is ERC165, ERC1271, IUniswapV3SwapCallback { address user = _data.linWithDur[i].recipient; uint256 id = streamIds[i]; streamsByCycleAndAddress[_cycleNumber][user].push(id); + isStreamLinear[id] = true; emit StreamForUser(user, id); } } @@ -250,6 +253,7 @@ abstract contract JBSablier is ERC165, ERC1271, IUniswapV3SwapCallback { address user = _data.linWithRange[i].recipient; uint256 id = streamIds[i]; streamsByCycleAndAddress[_cycleNumber][user].push(id); + isStreamLinear[id] = true; emit StreamForUser(user, id); } } @@ -267,6 +271,7 @@ abstract contract JBSablier is ERC165, ERC1271, IUniswapV3SwapCallback { address user = _data.dynWithDelta[i].recipient; uint256 id = streamIds[i]; streamsByCycleAndAddress[_cycleNumber][user].push(id); + isStreamLinear[id] = false; emit StreamForUser(user, id); } } @@ -284,6 +289,7 @@ abstract contract JBSablier is ERC165, ERC1271, IUniswapV3SwapCallback { address user = _data.dynWithMiles[i].recipient; uint256 id = streamIds[i]; streamsByCycleAndAddress[_cycleNumber][user].push(id); + isStreamLinear[id] = false; emit StreamForUser(user, id); } } diff --git a/test/Sips_Integration.t.sol b/test/Sips_Integration.t.sol index 0ea52a2..dfe2d0c 100644 --- a/test/Sips_Integration.t.sol +++ b/test/Sips_Integration.t.sol @@ -15,6 +15,7 @@ import {ISablierV2ProxyPlugin} from '@sablier/v2-periphery/src/interfaces/ISabli import {ISablierV2ProxyTarget} from '@sablier/v2-periphery/src/interfaces/ISablierV2ProxyTarget.sol'; import {LockupLinear, LockupDynamic} from '@sablier/v2-periphery/src/types/DataTypes.sol'; import {Batch, Broker} from '@sablier/v2-periphery/src/types/DataTypes.sol'; +import { ISablierV2Lockup } from "@sablier/v2-core/src/interfaces/ISablierV2Lockup.sol"; import {ud2x18, ud60x18} from '@sablier/v2-core/src/types/Math.sol'; import {IJBDelegatesRegistry} from '@jbx-protocol/juice-delegates-registry/src/interfaces/IJBDelegatesRegistry.sol'; @@ -318,6 +319,35 @@ contract SipsTest is TestBaseWorkflowV3 { _sips.withdrawAllTokenDust(USDC); } + function testBatchCancelStreams() public { + // Arrange our data for proxy call of batchCancelMultiple + uint256[] memory _ids = new uint256[](1); + uint256[] memory ids = _sips.getStreamsByCycleAndAddress( + 1, + 0x000000000000000000000000000000000000cafE + ); + _ids[0] = ids[0]; + + Batch.CancelMultiple memory stream1; + Batch.CancelMultiple[] memory batch = new Batch.CancelMultiple[](1); + + IERC20[] memory tokens = new IERC20[](1); + + tokens[0] = USDC; + + stream1.streamIds = _ids; + _sips.isStreamLinear(ids[0]) ? stream1.lockup = lockupLinear : stream1.lockup = lockupDynamic; + + batch[0] = stream1; + + // Cancel the stream + vm.startPrank(address(123)); + _sips.batchCancelStreams(batch, tokens); + + // Ensure cancelled stream tokens were refunded to sips contract + _sips.withdrawAllTokenDust(USDC); + } + function testStreamWithdraw() public { // Since we've forked mainnet, we can't really expect specific values, but there should be 2 stream ids uint256[] memory ids = _sips.getStreamsByCycleAndAddress( From 1fd4c26b3da692e3161bbb27cec87a7516936d5b Mon Sep 17 00:00:00 2001 From: Justin Pulley Date: Wed, 23 Aug 2023 16:46:39 -0500 Subject: [PATCH 2/2] fix: correctly assert if stream is cancelled --- src/JBSips.sol | 12 ++++++++---- test/Sips_Integration.t.sol | 21 +++++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/src/JBSips.sol b/src/JBSips.sol index a0ab2c5..f25fae6 100644 --- a/src/JBSips.sol +++ b/src/JBSips.sol @@ -126,7 +126,7 @@ contract JBSips is JBSablier, JBOperatable, IJBSplitAllocator { } //*********************************************************************// - // ----------------------- admin functions --------------------------- // + // ---------------------- stream management -------------------------- // //*********************************************************************// /// @notice Deploys PRBProxy and plugin via JBSablier @@ -173,11 +173,15 @@ contract JBSips is JBSablier, JBOperatable, IJBSplitAllocator { (_batch, _assets) ); - // Create a batch of Lockup Linear streams via the proxy and Sablier's proxy target - bytes memory response = proxy.execute(address(proxyTarget), data); - /* uint256[] memory streamIds = abi.decode(response, (uint256[])); */ + proxy.execute(address(proxyTarget), data); } + //*********************************************************************// + // ----------------------- admin functions --------------------------- // + //*********************************************************************// + + + /// @notice Withdraws ETH.. function withdrawETH() external diff --git a/test/Sips_Integration.t.sol b/test/Sips_Integration.t.sol index dfe2d0c..b79d83e 100644 --- a/test/Sips_Integration.t.sol +++ b/test/Sips_Integration.t.sol @@ -33,12 +33,12 @@ import {TickMath} from '@uniswap/v3-core/contracts/libraries/TickMath.sol'; import {AddStreamsData} from '../src/structs/Streams.sol'; import {IPRBProxy, IPRBProxyRegistry} from '@sablier/v2-periphery/src/types/Proxy.sol'; +import { Lockup } from 'lib/v2-periphery/lib/v2-core/src/types/DataTypes.sol'; import {Test, console2} from 'forge-std/Test.sol'; -contract SipsTest is TestBaseWorkflowV3 { +contract SipsTest_Int is TestBaseWorkflowV3 { using JBFundingCycleMetadataResolver for JBFundingCycle; - using stdStorage for StdStorage; // Assigned when project is launched uint256 _projectId; @@ -314,12 +314,12 @@ contract SipsTest is TestBaseWorkflowV3 { _sips.swapAndDeployStreams(3 ether, _sData); } - function testWithdrawAllDust() public { + function test_WithdrawAllDust() public { vm.prank(address(123)); _sips.withdrawAllTokenDust(USDC); } - function testBatchCancelStreams() public { + function test_BatchCancelStreams() public { // Arrange our data for proxy call of batchCancelMultiple uint256[] memory _ids = new uint256[](1); uint256[] memory ids = _sips.getStreamsByCycleAndAddress( @@ -340,15 +340,20 @@ contract SipsTest is TestBaseWorkflowV3 { batch[0] = stream1; + vm.warp(block.timestamp + 1 weeks); + // Cancel the stream vm.startPrank(address(123)); _sips.batchCancelStreams(batch, tokens); - // Ensure cancelled stream tokens were refunded to sips contract - _sips.withdrawAllTokenDust(USDC); + Lockup.Status expectedStatus = Lockup.Status.CANCELED; + Lockup.Status actualLinearStatus = lockupLinear.statusOf(stream1.streamIds[0]); + if (expectedStatus != actualLinearStatus){ + revert(); + } } - function testStreamWithdraw() public { + function test_StreamWithdraw() public { // Since we've forked mainnet, we can't really expect specific values, but there should be 2 stream ids uint256[] memory ids = _sips.getStreamsByCycleAndAddress( 1, @@ -394,7 +399,7 @@ contract SipsTest is TestBaseWorkflowV3 { _sips.deployProxy(); } - function testPoolValidity() public { + function test_PoolValidity() public { emit log_address(address(_sips.POOL())); } }