From 1010e13daa3e54d8f494c2639548b9749c7ad64d Mon Sep 17 00:00:00 2001 From: chefburger Date: Mon, 20 May 2024 12:14:31 +0800 Subject: [PATCH 1/5] feat: add salt to distinguish positions for same owner in cl-pool --- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- ...oolManagerTest#addLiquidity_fromEmpty.snap | 2 +- ...ManagerTest#addLiquidity_fromNonEmpty.snap | 2 +- ...lManagerTest#addLiquidity_nativeToken.snap | 2 +- .../CLPoolManagerTest#donateBothTokens.snap | 2 +- .../CLPoolManagerTest#gasDonateOneToken.snap | 2 +- ...oolManagerTest#initializeWithoutHooks.snap | 2 +- ...anagerTest#removeLiquidity_toNonEmpty.snap | 2 +- ...PoolManagerTest#swap_againstLiquidity.snap | 2 +- ...gerTest#swap_leaveSurplusTokenInVault.snap | 2 +- ...oolManagerTest#swap_runOutOfLiquidity.snap | 2 +- .../CLPoolManagerTest#swap_simple.snap | 2 +- ...nagerTest#swap_useSurplusTokenAsInput.snap | 2 +- .../CLPoolManagerTest#swap_withHooks.snap | 2 +- .../CLPoolManagerTest#swap_withNative.snap | 2 +- .forge-snapshots/ExtsloadTest#extsload.snap | 2 +- src/pool-cl/CLPoolManager.sol | 13 +- src/pool-cl/interfaces/ICLPoolManager.sol | 9 +- src/pool-cl/libraries/CLPool.sol | 77 +++--- src/pool-cl/libraries/CLPosition.sol | 9 +- test/pool-cl/CLFees.t.sol | 8 +- test/pool-cl/CLHookReturnsDelta.t.sol | 24 +- test/pool-cl/CLHookSkipCallback.t.sol | 32 ++- test/pool-cl/CLPoolManager.t.sol | 245 ++++++++++++------ test/pool-cl/libraries/CLPoolSwapFee.t.sol | 6 +- test/pool-cl/libraries/CLPosition.t.sol | 6 +- 26 files changed, 284 insertions(+), 177 deletions(-) diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index d55d48ce..3d7fbe59 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -29761 \ No newline at end of file +32585 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index 11b9bcd7..1d0f5eca 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -362191 \ No newline at end of file +362973 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 6efe5c04..9aad6812 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -177359 \ No newline at end of file +178141 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 953906f3..a6c3f5b0 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -247478 \ No newline at end of file +248256 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 2609f6be..58be0b63 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -170385 \ No newline at end of file +170426 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 6c75e40c..ffb8bdfd 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -114308 \ No newline at end of file +114349 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 39fffcec..472f03e4 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -59812 \ No newline at end of file +59779 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index fa5a4c1e..b1f29cef 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -123719 \ No newline at end of file +124483 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 3d40e5ae..6fea92c4 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -141376 \ No newline at end of file +141394 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index ce649d58..b519c643 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -175053 \ No newline at end of file +175073 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 907cf8b9..38b25895 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -25079271 \ No newline at end of file +25093869 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 37609b23..87926cb2 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -79245 \ No newline at end of file +79260 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 4af990dd..8adb1afc 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -153591 \ No newline at end of file +153609 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index 10db087c..44b08e04 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -95978 \ No newline at end of file +95925 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index a18d8ff8..7e0f50aa 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -79248 \ No newline at end of file +79263 \ No newline at end of file diff --git a/.forge-snapshots/ExtsloadTest#extsload.snap b/.forge-snapshots/ExtsloadTest#extsload.snap index 2531cd46..19277c69 100644 --- a/.forge-snapshots/ExtsloadTest#extsload.snap +++ b/.forge-snapshots/ExtsloadTest#extsload.snap @@ -1 +1 @@ -7468 \ No newline at end of file +7446 \ No newline at end of file diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index 5f6eed63..f64bd292 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -65,23 +65,23 @@ contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { } /// @inheritdoc ICLPoolManager - function getLiquidity(PoolId id, address _owner, int24 tickLower, int24 tickUpper) + function getLiquidity(PoolId id, address _owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view override returns (uint128 liquidity) { - return pools[id].positions.get(_owner, tickLower, tickUpper).liquidity; + return pools[id].positions.get(_owner, tickLower, tickUpper, salt).liquidity; } /// @inheritdoc ICLPoolManager - function getPosition(PoolId id, address owner, int24 tickLower, int24 tickUpper) + function getPosition(PoolId id, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view override returns (CLPosition.Info memory position) { - return pools[id].positions.get(owner, tickLower, tickUpper); + return pools[id].positions.get(owner, tickLower, tickUpper, salt); } /// @inheritdoc ICLPoolManager @@ -165,12 +165,13 @@ contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { tickLower: params.tickLower, tickUpper: params.tickUpper, liquidityDelta: params.liquidityDelta.toInt128(), - tickSpacing: key.parameters.getTickSpacing() + tickSpacing: key.parameters.getTickSpacing(), + salt: params.salt }) ); /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one - emit ModifyLiquidity(id, msg.sender, params.tickLower, params.tickUpper, params.liquidityDelta); + emit ModifyLiquidity(id, msg.sender, params.tickLower, params.tickUpper, params.salt, params.liquidityDelta); BalanceDelta hookDelta; (delta, hookDelta) = CLHooks.afterModifyLiquidity(key, params, delta + feeDelta, hookData); diff --git a/src/pool-cl/interfaces/ICLPoolManager.sol b/src/pool-cl/interfaces/ICLPoolManager.sol index c1228e44..549a92d2 100644 --- a/src/pool-cl/interfaces/ICLPoolManager.sol +++ b/src/pool-cl/interfaces/ICLPoolManager.sol @@ -45,9 +45,10 @@ interface ICLPoolManager is IProtocolFees, IPoolManager, IExtsload { /// @param sender The address that modified the pool /// @param tickLower The lower tick of the position /// @param tickUpper The upper tick of the position + /// @param salt The value used to create a unique liquidity position /// @param liquidityDelta The amount of liquidity that was added or removed event ModifyLiquidity( - PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta + PoolId indexed id, address indexed sender, int24 tickLower, int24 tickUpper, bytes32 salt, int256 liquidityDelta ); /// @notice Emitted for swaps between currency0 and currency1 @@ -96,13 +97,13 @@ interface ICLPoolManager is IProtocolFees, IPoolManager, IExtsload { function getLiquidity(PoolId id) external view returns (uint128 liquidity); /// @notice Get the current value of liquidity for the specified pool and position - function getLiquidity(PoolId id, address owner, int24 tickLower, int24 tickUpper) + function getLiquidity(PoolId id, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view returns (uint128 liquidity); /// @notice Get the position struct for a specified pool and position - function getPosition(PoolId id, address owner, int24 tickLower, int24 tickUpper) + function getPosition(PoolId id, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) external view returns (CLPosition.Info memory position); @@ -118,6 +119,8 @@ interface ICLPoolManager is IProtocolFees, IPoolManager, IExtsload { int24 tickUpper; // how to modify the liquidity int256 liquidityDelta; + // a value to set if you want unique liquidity positions at the same range + bytes32 salt; } /// @notice Modify the position for the given pool diff --git a/src/pool-cl/libraries/CLPool.sol b/src/pool-cl/libraries/CLPool.sol index 9722c9ac..3e6dcd43 100644 --- a/src/pool-cl/libraries/CLPool.sol +++ b/src/pool-cl/libraries/CLPool.sol @@ -91,6 +91,8 @@ library CLPool { int128 liquidityDelta; // the spacing between ticks int24 tickSpacing; + // used to distinguish positions of the same owner, at the same tick range + bytes32 salt; } /// @dev Effect changes to the liquidity of a position in a pool @@ -376,51 +378,52 @@ library CLPool { internal returns (uint256, uint256) { - uint256 _feeGrowthGlobal0X128 = self.feeGrowthGlobal0X128; // SLOAD for gas optimization - uint256 _feeGrowthGlobal1X128 = self.feeGrowthGlobal1X128; // SLOAD for gas optimization - //@dev avoid stack too deep UpdatePositionCache memory cache; + { + uint256 _feeGrowthGlobal0X128 = self.feeGrowthGlobal0X128; // SLOAD for gas optimization + uint256 _feeGrowthGlobal1X128 = self.feeGrowthGlobal1X128; // SLOAD for gas optimization + + ///@dev update ticks if nencessary + if (params.liquidityDelta != 0) { + cache.maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(params.tickSpacing); + cache.flippedLower = self.ticks.update( + params.tickLower, + tick, + params.liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + false, + cache.maxLiquidityPerTick + ); + cache.flippedUpper = self.ticks.update( + params.tickUpper, + tick, + params.liquidityDelta, + _feeGrowthGlobal0X128, + _feeGrowthGlobal1X128, + true, + cache.maxLiquidityPerTick + ); - ///@dev update ticks if nencessary - if (params.liquidityDelta != 0) { - cache.maxLiquidityPerTick = Tick.tickSpacingToMaxLiquidityPerTick(params.tickSpacing); - cache.flippedLower = self.ticks.update( - params.tickLower, - tick, - params.liquidityDelta, - _feeGrowthGlobal0X128, - _feeGrowthGlobal1X128, - false, - cache.maxLiquidityPerTick - ); - cache.flippedUpper = self.ticks.update( - params.tickUpper, - tick, - params.liquidityDelta, - _feeGrowthGlobal0X128, - _feeGrowthGlobal1X128, - true, - cache.maxLiquidityPerTick - ); - - if (cache.flippedLower) { - self.tickBitmap.flipTick(params.tickLower, params.tickSpacing); - } - if (cache.flippedUpper) { - self.tickBitmap.flipTick(params.tickUpper, params.tickSpacing); + if (cache.flippedLower) { + self.tickBitmap.flipTick(params.tickLower, params.tickSpacing); + } + if (cache.flippedUpper) { + self.tickBitmap.flipTick(params.tickUpper, params.tickSpacing); + } } - } - (cache.feeGrowthInside0X128, cache.feeGrowthInside1X128) = self.ticks.getFeeGrowthInside( - params.tickLower, params.tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128 - ); + (cache.feeGrowthInside0X128, cache.feeGrowthInside1X128) = self.ticks.getFeeGrowthInside( + params.tickLower, params.tickUpper, tick, _feeGrowthGlobal0X128, _feeGrowthGlobal1X128 + ); + } ///@dev update user position and collect fees /// must be done after ticks are updated in case of a 0 -> 1 flip - (cache.feesOwed0, cache.feesOwed1) = self.positions.get(params.owner, params.tickLower, params.tickUpper).update( - params.liquidityDelta, cache.feeGrowthInside0X128, cache.feeGrowthInside1X128 - ); + (cache.feesOwed0, cache.feesOwed1) = self.positions.get( + params.owner, params.tickLower, params.tickUpper, params.salt + ).update(params.liquidityDelta, cache.feeGrowthInside0X128, cache.feeGrowthInside1X128); ///@dev clear any tick data that is no longer needed /// must be done after fee collection in case of a 1 -> 0 flip diff --git a/src/pool-cl/libraries/CLPosition.sol b/src/pool-cl/libraries/CLPosition.sol index b1538021..4d56e98c 100644 --- a/src/pool-cl/libraries/CLPosition.sol +++ b/src/pool-cl/libraries/CLPosition.sol @@ -27,8 +27,9 @@ library CLPosition { /// @param owner The address of the position owner /// @param tickLower The lower tick boundary of the position /// @param tickUpper The upper tick boundary of the position + /// @param salt A unique value to differentiate between multiple positions in the same range /// @return position The position info struct of the given owners' position - function get(mapping(bytes32 => Info) storage self, address owner, int24 tickLower, int24 tickUpper) + function get(mapping(bytes32 => Info) storage self, address owner, int24 tickLower, int24 tickUpper, bytes32 salt) internal view returns (Info storage position) @@ -37,10 +38,14 @@ library CLPosition { // make use of memory scratch space // ref: https://github.com/Vectorized/solady/blob/main/src/tokens/ERC20.sol#L95 assembly { + mstore(0x26, salt) mstore(0x06, tickUpper) mstore(0x03, tickLower) mstore(0x00, owner) - key := keccak256(0x0c, 0x1a) + key := keccak256(0x0c, 0x3a) + // only 0x00 ~ 0x40 is scratch space + // 0x40 ~ 0x46 should be clear to avoid polluting free pointer + mstore(0x26, 0) } position = self[key]; } diff --git a/test/pool-cl/CLFees.t.sol b/test/pool-cl/CLFees.t.sol index 5866fb2c..321b2e59 100644 --- a/test/pool-cl/CLFees.t.sol +++ b/test/pool-cl/CLFees.t.sol @@ -110,7 +110,7 @@ contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { int256 liquidityDelta = 10000; ICLPoolManager.ModifyLiquidityParams memory params = - ICLPoolManager.ModifyLiquidityParams(-60, 60, liquidityDelta); + ICLPoolManager.ModifyLiquidityParams(-60, 60, liquidityDelta, 0); router.modifyPosition(key, params, ZERO_BYTES); // Fees dont accrue for positive liquidity delta. @@ -118,11 +118,11 @@ contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { assertEq(manager.protocolFeesAccrued(currency1), 0); ICLPoolManager.ModifyLiquidityParams memory params2 = - ICLPoolManager.ModifyLiquidityParams(-60, 60, -liquidityDelta); + ICLPoolManager.ModifyLiquidityParams(-60, 60, -liquidityDelta, 0); router.modifyPosition(key, params2, ZERO_BYTES); // add larger liquidity - params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 10e18); + params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 10e18, 0); router.modifyPosition(key, params, ZERO_BYTES); MockERC20(Currency.unwrap(currency1)).approve(address(router), type(uint256).max); @@ -147,7 +147,7 @@ contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { (CLPool.Slot0 memory slot0,,,) = manager.pools(key.toId()); assertEq(slot0.protocolFee, protocolFee); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10e18); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10e18, 0); router.modifyPosition(key, params, ZERO_BYTES); // 1 for 0 swap MockERC20(Currency.unwrap(currency1)).approve(address(router), type(uint256).max); diff --git a/test/pool-cl/CLHookReturnsDelta.t.sol b/test/pool-cl/CLHookReturnsDelta.t.sol index 459a0a58..d090c1da 100644 --- a/test/pool-cl/CLHookReturnsDelta.t.sol +++ b/test/pool-cl/CLHookReturnsDelta.t.sol @@ -63,7 +63,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { function testModifyPosition_AddMore() external { (BalanceDelta delta,) = router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether, salt: 0}), abi.encode(0 ether) ); @@ -71,7 +71,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { (BalanceDelta delta2,) = router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether, salt: 0}), abi.encode(10 ether) ); uint128 liquidity2 = poolManager.getLiquidity(key.toId()); @@ -90,7 +90,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { // add some liquidity first in case the pool is empty router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether, salt: 0}), abi.encode(10 ether) ); @@ -100,7 +100,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { (BalanceDelta delta,) = router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether, salt: 0}), abi.encode(-10 ether) ); uint128 liquidityAfter = poolManager.getLiquidity(key.toId()); @@ -119,7 +119,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { // add some liquidity first in case the pool is empty (BalanceDelta delta,) = router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether, salt: 0}), abi.encode(10 ether) ); @@ -129,7 +129,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { (BalanceDelta delta2,) = router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: -5 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: -5 ether, salt: 0}), abi.encode(-5 ether) ); uint128 liquidityAfter = poolManager.getLiquidity(key.toId()); @@ -148,7 +148,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { // add some liquidity first in case the pool is empty router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10 ether, salt: 0}), abi.encode(10 ether) ); @@ -158,7 +158,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { (BalanceDelta delta,) = router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: -5 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: -5 ether, salt: 0}), abi.encode(5 ether) ); uint128 liquidityAfter = poolManager.getLiquidity(key.toId()); @@ -177,7 +177,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { // add some liquidity first in case the pool is empty router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), abi.encode(10000 ether) ); @@ -218,7 +218,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { // add some liquidity first in case the pool is empty router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), abi.encode(10000 ether) ); @@ -262,7 +262,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { // add some liquidity first in case the pool is empty router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), abi.encode(10000 ether) ); @@ -305,7 +305,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { // add some liquidity first in case the pool is empty router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), abi.encode(10000 ether) ); diff --git a/test/pool-cl/CLHookSkipCallback.t.sol b/test/pool-cl/CLHookSkipCallback.t.sol index d019e445..95cd91f1 100644 --- a/test/pool-cl/CLHookSkipCallback.t.sol +++ b/test/pool-cl/CLHookSkipCallback.t.sol @@ -74,10 +74,14 @@ contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { // Add and remove liquidity clSkipCallbackHook.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18, salt: 0}), + "" ); clSkipCallbackHook.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18, salt: 0}), + "" ); assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); } @@ -87,10 +91,14 @@ contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { // Add and remove liquidity router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18, salt: 0}), + "" ); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18, salt: 0}), + "" ); assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 4); } @@ -100,7 +108,9 @@ contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { // Pre-req add some liqudiity clSkipCallbackHook.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18, salt: 0}), + "" ); clSkipCallbackHook.swap( @@ -118,7 +128,9 @@ contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { // Pre-req add some liqudiity clSkipCallbackHook.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18, salt: 0}), + "" ); router.swap( @@ -136,7 +148,9 @@ contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { // Pre-req add some liqudiity clSkipCallbackHook.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18, salt: 0}), + "" ); clSkipCallbackHook.donate(key, 100, 200, ZERO_BYTES); @@ -149,7 +163,9 @@ contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { // Pre-req add some liqudiity clSkipCallbackHook.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18, salt: 0}), + "" ); router.donate(key, 100, 200, ZERO_BYTES); diff --git a/test/pool-cl/CLPoolManager.t.sol b/test/pool-cl/CLPoolManager.t.sol index 3e59f73f..a53784b8 100644 --- a/test/pool-cl/CLPoolManager.t.sol +++ b/test/pool-cl/CLPoolManager.t.sol @@ -54,7 +54,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLHooks hooks ); event ModifyLiquidity( - PoolId indexed poolId, address indexed sender, int24 tickLower, int24 tickUpper, int256 liquidityDelta + PoolId indexed poolId, + address indexed sender, + int24 tickLower, + int24 tickUpper, + bytes32 salt, + int256 liquidityDelta ); event Swap( PoolId indexed poolId, @@ -706,7 +711,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: 1e24 + liquidityDelta: 1e24, + salt: 0 }), "" ); @@ -723,15 +729,17 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps assertEq(1e10 ether - token1Left, 9999999999999999999945788); assertEq(poolManager.getLiquidity(key.toId()), 1e24); - assertEq(poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK), 1e24); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0), 1e24 + ); assertEq( - poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK) + poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0) .feeGrowthInside0LastX128, 0 ); assertEq( - poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK) + poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0) .feeGrowthInside1LastX128, 0 ); @@ -743,7 +751,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: 1e4 + liquidityDelta: 1e4, + salt: 0 }), "" ); @@ -761,16 +770,17 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps assertEq(poolManager.getLiquidity(key.toId()), 1e24 + 1e4); assertEq( - poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK), 1e24 + 1e4 + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0), + 1e24 + 1e4 ); assertEq( - poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK) + poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0) .feeGrowthInside0LastX128, 0 ); assertEq( - poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK) + poolManager.getPosition(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0) .feeGrowthInside1LastX128, 0 ); @@ -808,7 +818,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: 1e18 + liquidityDelta: 1e18, + salt: 0 }), "" ); @@ -820,7 +831,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: 1e18 + liquidityDelta: 1e18, + salt: 0 }), "" ); @@ -832,7 +844,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: -1e18 + liquidityDelta: -1e18, + salt: 0 }), "" ); @@ -854,7 +867,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: 1e18 + liquidityDelta: 1e18, + salt: 0 }), "" ); @@ -868,7 +882,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: 1e18 + liquidityDelta: 1e18, + salt: 0 }), "" ); @@ -890,7 +905,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: -1e18 + liquidityDelta: -1e18, + salt: 0 }), "" ); @@ -925,7 +941,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps IERC20(Currency.unwrap(currency1)).approve(address(router), 1e30 ether); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 46055, tickUpper: 46060, liquidityDelta: 1e9}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 46055, tickUpper: 46060, liquidityDelta: 1e9, salt: 0}), + "" ); uint256 token0Left = IERC20(Currency.unwrap(currency0)).balanceOf(address(this)); @@ -939,10 +957,10 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps // no active liquidity assertEq(poolManager.getLiquidity(key.toId()), 0); - assertEq(poolManager.getLiquidity(key.toId(), address(router), 46055, 46060), 1e9); + assertEq(poolManager.getLiquidity(key.toId(), address(router), 46055, 46060, 0), 1e9); - assertEq(poolManager.getPosition(key.toId(), address(this), 46055, 46060).feeGrowthInside0LastX128, 0); - assertEq(poolManager.getPosition(key.toId(), address(this), 46055, 46060).feeGrowthInside1LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(this), 46055, 46060, 0).feeGrowthInside0LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(this), 46055, 46060, 0).feeGrowthInside1LastX128, 0); } function testModifyPosition_addLiquidity_belowCurrentTick() external { @@ -971,7 +989,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps IERC20(Currency.unwrap(currency1)).approve(address(router), 1e30 ether); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: 1e9}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: 1e9, salt: 0}), + "" ); uint256 token0Left = IERC20(Currency.unwrap(currency0)).balanceOf(address(this)); @@ -985,10 +1005,10 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps // no active liquidity assertEq(poolManager.getLiquidity(key.toId()), 0); - assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050), 1e9); + assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050, 0), 1e9); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside0LastX128, 0); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside1LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside0LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside1LastX128, 0); } function testModifyPosition_removeLiquidity_fromEmpty() external { @@ -1018,7 +1038,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps vm.expectRevert(SafeCast.SafeCastOverflow.selector); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: -1}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: -1, salt: 0}), + "" ); } @@ -1049,7 +1071,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps vm.expectRevert(CLPosition.CannotUpdateEmptyPosition.selector); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: 0}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: 0, salt: 0}), + "" ); } @@ -1079,33 +1103,39 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps IERC20(Currency.unwrap(currency1)).approve(address(router), 1e30 ether); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -1, tickUpper: 1, liquidityDelta: 100 ether}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -1, tickUpper: 1, liquidityDelta: 100 ether, salt: 0}), + "" ); assertEq(poolManager.getLiquidity(key.toId()), 100 ether, "total liquidity should be 1000"); assertEq( - poolManager.getLiquidity(key.toId(), address(router), -1, 1), 100 ether, "router's liquidity should be 1000" + poolManager.getLiquidity(key.toId(), address(router), -1, 1, 0), + 100 ether, + "router's liquidity should be 1000" ); assertEq(IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)), 4999625031247266); assertEq(IERC20(Currency.unwrap(currency1)).balanceOf(address(vault)), 4999625031247266); - assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1).feeGrowthInside0LastX128, 0); - assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1).feeGrowthInside1LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1, 0).feeGrowthInside0LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1, 0).feeGrowthInside1LastX128, 0); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -1, tickUpper: 1, liquidityDelta: -100 ether}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -1, tickUpper: 1, liquidityDelta: -100 ether, salt: 0}), + "" ); assertEq(poolManager.getLiquidity(key.toId()), 0); - assertEq(poolManager.getLiquidity(key.toId(), address(router), -1, 1), 0); + assertEq(poolManager.getLiquidity(key.toId(), address(router), -1, 1, 0), 0); // expected to receive 0, but got 1 because of precision loss assertEq(IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)), 1); assertEq(IERC20(Currency.unwrap(currency1)).balanceOf(address(vault)), 1); - assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1).feeGrowthInside0LastX128, 0); - assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1).feeGrowthInside1LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1, 0).feeGrowthInside0LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), -1, 1, 0).feeGrowthInside1LastX128, 0); } function testModifyPosition_removeLiquidity_halfAndThenAll() external { @@ -1134,7 +1164,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps IERC20(Currency.unwrap(currency1)).approve(address(router), 1e30 ether); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: 1e9}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: 1e9, salt: 0}), + "" ); { @@ -1149,17 +1181,17 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps // no active liquidity assertEq(poolManager.getLiquidity(key.toId()), 0); - assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050), 1e9); + assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050, 0), 1e9); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside0LastX128, 0); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside1LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside0LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside1LastX128, 0); } // remove half snapStart("CLPoolManagerTest#removeLiquidity_toNonEmpty"); router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: -5 * 1e8}), + ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: -5 * 1e8, salt: 0}), "" ); snapEnd(); @@ -1174,15 +1206,15 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps // no active liquidity assertEq(poolManager.getLiquidity(key.toId()), 0); - assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050), 5 * 1e8); + assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050, 0), 5 * 1e8); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside0LastX128, 0); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside1LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside0LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside1LastX128, 0); } router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: -5 * 1e8}), + ICLPoolManager.ModifyLiquidityParams({tickLower: 46000, tickUpper: 46050, liquidityDelta: -5 * 1e8, salt: 0}), "" ); @@ -1198,10 +1230,10 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps // no active liquidity assertEq(poolManager.getLiquidity(key.toId()), 0); - assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050), 0); + assertEq(poolManager.getLiquidity(key.toId(), address(router), 46000, 46050, 0), 0); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside0LastX128, 0); - assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050).feeGrowthInside1LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside0LastX128, 0); + assertEq(poolManager.getPosition(key.toId(), address(router), 46000, 46050, 0).feeGrowthInside1LastX128, 0); } } @@ -1216,7 +1248,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); vm.expectRevert(); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}), ZERO_BYTES + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}), + ZERO_BYTES ); } @@ -1235,10 +1269,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, sqrtPriceX96, ZERO_BYTES); vm.expectEmit(true, true, true, true); - emit ModifyLiquidity(key.toId(), address(router), 0, 60, 100); + emit ModifyLiquidity(key.toId(), address(router), 0, 60, 0, 100); router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}), ZERO_BYTES + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}), + ZERO_BYTES ); } @@ -1256,10 +1292,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, sqrtPriceX96, ZERO_BYTES); vm.expectEmit(true, true, true, true); - emit ModifyLiquidity(key.toId(), address(router), 0, 60, 100); + emit ModifyLiquidity(key.toId(), address(router), 0, 60, 0, 100); router.modifyPosition{value: 100}( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}), ZERO_BYTES + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}), + ZERO_BYTES ); } @@ -1278,7 +1316,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); ICLPoolManager.ModifyLiquidityParams memory params = - ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}); + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}); poolManager.initialize(key, sqrtPriceX96, ZERO_BYTES); @@ -1318,7 +1356,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); ICLPoolManager.ModifyLiquidityParams memory params = - ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}); + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); @@ -1348,7 +1386,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); ICLPoolManager.ModifyLiquidityParams memory params = - ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}); + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); @@ -1356,7 +1394,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps mockHooks.setReturnValue(mockHooks.afterAddLiquidity.selector, mockHooks.afterAddLiquidity.selector); vm.expectEmit(true, true, true, true); - emit ModifyLiquidity(key.toId(), address(router), 0, 60, 100); + emit ModifyLiquidity(key.toId(), address(router), 0, 60, 0, 100); router.modifyPosition(key, params, ZERO_BYTES); } @@ -1375,7 +1413,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps snapStart("CLPoolManagerTest#addLiquidity_nativeToken"); router.modifyPosition{value: 100}( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}), ZERO_BYTES + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}), + ZERO_BYTES ); snapEnd(); } @@ -1411,7 +1451,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: 46053, tickUpper: 46055, liquidityDelta: 1000000 ether}), + ICLPoolManager.ModifyLiquidityParams({ + tickLower: 46053, + tickUpper: 46055, + liquidityDelta: 1000000 ether, + salt: 0 + }), "" ); @@ -1473,7 +1518,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); ICLPoolManager.ModifyLiquidityParams memory modifyPositionParams = - ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether}); + ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether, salt: 0}); router.modifyPosition(key, modifyPositionParams, ZERO_BYTES); @@ -1508,7 +1553,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); ICLPoolManager.ModifyLiquidityParams memory modifyPositionParams = - ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether}); + ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether, salt: 0}); router.modifyPosition(key, modifyPositionParams, ZERO_BYTES); @@ -1549,7 +1594,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ); ICLPoolManager.ModifyLiquidityParams memory modifyPositionParams = - ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether}); + ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether, salt: 0}); router.modifyPosition(key, modifyPositionParams, ZERO_BYTES); @@ -1657,7 +1702,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); ICLPoolManager.ModifyLiquidityParams memory params = - ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}); + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}); ICLPoolManager.SwapParams memory swapParams = ICLPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10, sqrtPriceLimitX96: SQRT_RATIO_1_2}); @@ -1694,7 +1739,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); ICLPoolManager.ModifyLiquidityParams memory params = - ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100}); + ICLPoolManager.ModifyLiquidityParams({tickLower: 0, tickUpper: 60, liquidityDelta: 100, salt: 0}); ICLPoolManager.SwapParams memory swapParams = ICLPoolManager.SwapParams({zeroForOne: true, amountSpecified: 10, sqrtPriceLimitX96: SQRT_RATIO_1_2}); @@ -1733,7 +1778,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1000000000000000000}), + ICLPoolManager.ModifyLiquidityParams({ + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1000000000000000000, + salt: 0 + }), ZERO_BYTES ); @@ -1764,7 +1814,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1000000000000000000}), + ICLPoolManager.ModifyLiquidityParams({ + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1000000000000000000, + salt: 0 + }), ZERO_BYTES ); vm.expectEmit(true, true, true, true); @@ -1893,7 +1948,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1000000000000000000}), + ICLPoolManager.ModifyLiquidityParams({ + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1000000000000000000, + salt: 0 + }), ZERO_BYTES ); @@ -1921,7 +1981,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1000000000000000000}), + ICLPoolManager.ModifyLiquidityParams({ + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1000000000000000000, + salt: 0 + }), ZERO_BYTES ); router.swap(key, params, testSettings, ZERO_BYTES); @@ -1962,7 +2027,12 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); router.modifyPosition( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1000000000000000000}), + ICLPoolManager.ModifyLiquidityParams({ + tickLower: -120, + tickUpper: 120, + liquidityDelta: 1000000000000000000, + salt: 0 + }), ZERO_BYTES ); @@ -1994,7 +2064,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); router.modifyPosition{value: 1 ether}( key, - ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1 ether}), + ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1 ether, salt: 0}), ZERO_BYTES ); @@ -2052,7 +2122,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition(key, params, ZERO_BYTES); snapStart("CLPoolManagerTest#donateBothTokens"); router.donate(key, 100, 200, ZERO_BYTES); @@ -2076,7 +2146,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition{value: 1}(key, params, ZERO_BYTES); router.donate{value: 100}(key, 100, 200, ZERO_BYTES); @@ -2102,7 +2172,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition(key, params, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeDonate.selector, bytes4(0xdeadbeef)); mockHooks.setReturnValue(mockHooks.afterDonate.selector, bytes4(0xdeadbeef)); @@ -2134,7 +2204,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition(key, params, ZERO_BYTES); mockHooks.setReturnValue(mockHooks.beforeDonate.selector, mockHooks.beforeDonate.selector); @@ -2154,7 +2224,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition(key, params, ZERO_BYTES); (, int24 tick,,) = poolManager.getSlot0(key.toId()); @@ -2176,7 +2246,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition(key, params, ZERO_BYTES); snapStart("CLPoolManagerTest#gasDonateOneToken"); @@ -2202,7 +2272,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps MockERC20(Currency.unwrap(currency0)).approve(address(router), type(uint256).max); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 1000); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 1000, 0); router.modifyPosition(key, params, ZERO_BYTES); (uint256 amount0, uint256 amount1) = currency0Invalid ? (1, 0) : (0, 1); @@ -2226,7 +2296,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition(key, params, ZERO_BYTES); router.take(key, 1, 1); // assertions inside router because it takes then settles } @@ -2242,7 +2312,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps }); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100); + ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 100, 0); router.modifyPosition{value: 100}(key, params, ZERO_BYTES); router.take{value: 1}(key, 1, 1); // assertions inside router because it takes then settles } @@ -2311,7 +2381,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); assertEq(slot0.protocolFee, protocolFee); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether); + ICLPoolManager.ModifyLiquidityParams memory params = + ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0); router.modifyPosition(key, params, ZERO_BYTES); router.swap( key, @@ -2349,7 +2420,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); assertEq(slot0.protocolFee, protocolFee); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether); + ICLPoolManager.ModifyLiquidityParams memory params = + ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0); router.modifyPosition(key, params, ZERO_BYTES); router.swap( key, @@ -2388,7 +2460,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); assertEq(slot0.protocolFee, protocolFee); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether); + ICLPoolManager.ModifyLiquidityParams memory params = + ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0); router.modifyPosition{value: 10 ether}(key, params, ZERO_BYTES); router.swap{value: 10000}( key, @@ -2427,7 +2500,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); assertEq(slot0.protocolFee, protocolFee); - ICLPoolManager.ModifyLiquidityParams memory params = ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether); + ICLPoolManager.ModifyLiquidityParams memory params = + ICLPoolManager.ModifyLiquidityParams(-120, 120, 10 ether, 0); router.modifyPosition{value: 10 ether}(key, params, ZERO_BYTES); router.swap{value: 10000}( key, @@ -2535,7 +2609,8 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps ICLPoolManager.ModifyLiquidityParams({ tickLower: TickMath.MIN_TICK, tickUpper: TickMath.MAX_TICK, - liquidityDelta: 1e24 + liquidityDelta: 1e24, + salt: 0 }), "" ); @@ -2563,7 +2638,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps // pre-req add liquidity router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e24}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e24, salt: 0}), + "" ); // pause @@ -2571,7 +2648,9 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps // verify no revert router.modifyPosition( - key, ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e24}), "" + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e24, salt: 0}), + "" ); } diff --git a/test/pool-cl/libraries/CLPoolSwapFee.t.sol b/test/pool-cl/libraries/CLPoolSwapFee.t.sol index c3988adb..59f04537 100644 --- a/test/pool-cl/libraries/CLPoolSwapFee.t.sol +++ b/test/pool-cl/libraries/CLPoolSwapFee.t.sol @@ -103,7 +103,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { poolManager.initialize(dynamicFeeKey, SQRT_RATIO_1_1, ZERO_BYTES); ICLPoolManager.ModifyLiquidityParams memory modifyPositionParams = - ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether}); + ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether, salt: 0}); router.modifyPosition(dynamicFeeKey, modifyPositionParams, ZERO_BYTES); vm.expectEmit(true, true, true, true); @@ -133,7 +133,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { poolManager.initialize(staticFeeKey, SQRT_RATIO_1_1, ZERO_BYTES); ICLPoolManager.ModifyLiquidityParams memory modifyPositionParams = - ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether}); + ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether, salt: 0}); router.modifyPosition(staticFeeKey, modifyPositionParams, ZERO_BYTES); vm.expectEmit(true, true, true, true); @@ -166,7 +166,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { poolManager.initialize(dynamicFeeKey, SQRT_RATIO_1_1, ZERO_BYTES); ICLPoolManager.ModifyLiquidityParams memory modifyPositionParams = - ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether}); + ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether, salt: 0}); router.modifyPosition(dynamicFeeKey, modifyPositionParams, ZERO_BYTES); vm.expectEmit(true, true, true, true); diff --git a/test/pool-cl/libraries/CLPosition.t.sol b/test/pool-cl/libraries/CLPosition.t.sol index cd97adba..a13bd814 100644 --- a/test/pool-cl/libraries/CLPosition.t.sol +++ b/test/pool-cl/libraries/CLPosition.t.sol @@ -16,7 +16,7 @@ contract CLPositionTest is Test, GasSnapshot { CLPool.State public pool; function test_get_emptyPosition() public { - CLPosition.Info memory info = pool.positions.get(address(this), 1, 2); + CLPosition.Info memory info = pool.positions.get(address(this), 1, 2, 0); assertEq(info.liquidity, 0); assertEq(info.feeGrowthInside0LastX128, 0); assertEq(info.feeGrowthInside1LastX128, 0); @@ -27,7 +27,7 @@ contract CLPositionTest is Test, GasSnapshot { uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128 ) public { - CLPosition.Info storage info = pool.positions.get(address(this), 1, 2); + CLPosition.Info storage info = pool.positions.get(address(this), 1, 2, 0); if (liquidityDelta == 0) { vm.expectRevert(CLPosition.CannotUpdateEmptyPosition.selector); @@ -44,7 +44,7 @@ contract CLPositionTest is Test, GasSnapshot { } function test_set_updateNonEmptyPosition() public { - CLPosition.Info storage info = pool.positions.get(address(this), 1, 2); + CLPosition.Info storage info = pool.positions.get(address(this), 1, 2, 0); // init { From ac091ced67d33ca54436f09533e2d29d40405dc5 Mon Sep 17 00:00:00 2001 From: chefburger Date: Mon, 20 May 2024 15:13:46 +0800 Subject: [PATCH 2/5] feat: add salt to distinguish positions for same owner in bin-pool --- .../BinHookTest#testBurnSucceedsWithHook.snap | 2 +- ...inHookTest#testDonateSucceedsWithHook.snap | 2 +- ...okTest#testInitializeSucceedsWithHook.snap | 2 +- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- ...oolManagerTest#testBurnNativeCurrency.snap | 2 +- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- ...oolManagerTest#testFuzz_SetMaxBinStep.snap | 2 +- ...BinPoolManagerTest#testGasBurnHalfBin.snap | 2 +- ...inPoolManagerTest#testGasBurnNineBins.snap | 2 +- .../BinPoolManagerTest#testGasBurnOneBin.snap | 2 +- .../BinPoolManagerTest#testGasDonate.snap | 2 +- ...nPoolManagerTest#testGasMintNneBins-1.snap | 2 +- ...nPoolManagerTest#testGasMintNneBins-2.snap | 2 +- ...inPoolManagerTest#testGasMintOneBin-1.snap | 2 +- ...inPoolManagerTest#testGasMintOneBin-2.snap | 2 +- ...olManagerTest#testGasSwapMultipleBins.snap | 2 +- ...nagerTest#testGasSwapOverBigBinIdGate.snap | 2 +- ...nPoolManagerTest#testGasSwapSingleBin.snap | 2 +- ...oolManagerTest#testMintNativeCurrency.snap | 2 +- ...BinPoolManagerTest#testSetProtocolFee.snap | 2 +- src/pool-bin/BinPoolManager.sol | 21 ++++++---- src/pool-bin/interfaces/IBinPoolManager.sol | 12 +++++- src/pool-bin/libraries/BinPool.sol | 33 ++++++++++----- src/pool-bin/libraries/BinPosition.sol | 9 +++- test/pool-bin/BinHook.t.sol | 6 +-- test/pool-bin/BinPoolManager.t.sol | 11 ++--- test/pool-bin/helpers/BinTestHelper.sol | 26 +++++++----- test/pool-bin/libraries/BinPoolDonate.t.sol | 6 +-- test/pool-bin/libraries/BinPoolFee.t.sol | 11 ++--- .../pool-bin/libraries/BinPoolLiquidity.t.sol | 42 ++++++++++++------- test/pool-bin/libraries/BinPosition.t.sol | 28 ++++++------- test/pool-cl/libraries/CLPosition.t.sol | 11 +++++ 33 files changed, 160 insertions(+), 98 deletions(-) diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index cd24de03..80dbc371 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -181453 \ No newline at end of file +182049 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap index 807bf76e..698a7020 100644 --- a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap @@ -1 +1 @@ -185322 \ No newline at end of file +185296 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index 842c0649..eb0299a4 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -137514 \ No newline at end of file +137576 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 2b38b956..60d8e25c 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -330672 \ No newline at end of file +331255 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 91e6acf1..409059bf 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -193574 \ No newline at end of file +193526 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap index 1cdd5cc7..0abd0efc 100644 --- a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap @@ -1 +1 @@ -137944 \ No newline at end of file +138533 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index 3d7fbe59..26cdfb14 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -32585 \ No newline at end of file +32551 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap index 13505f4e..960d5d6f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap @@ -1 +1 @@ -30350 \ No newline at end of file +30417 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap index 1e524234..31e735f5 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap @@ -1 +1 @@ -147904 \ No newline at end of file +148635 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap index 8c199381..049e9b27 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap @@ -1 +1 @@ -295588 \ No newline at end of file +296486 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap index 72d9d320..9d5f9687 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap @@ -1 +1 @@ -131153 \ No newline at end of file +131743 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap index 51d67cef..57ffbe7e 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap @@ -1 +1 @@ -120046 \ No newline at end of file +120042 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index 34eead96..f9084256 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -976344 \ No newline at end of file +977485 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index ebd6c650..e6212425 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -338538 \ No newline at end of file +339675 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index 40ca220f..42dd5964 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -342112 \ No newline at end of file +342872 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index 773b6729..7124d148 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -145053 \ No newline at end of file +145815 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 35346598..6ec51e4b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -180358 \ No newline at end of file +180336 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 67cabbe0..460128db 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -186343 \ No newline at end of file +186319 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index fd343de3..1378b556 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -138725 \ No newline at end of file +138699 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index f41e273f..e94b26fb 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -308628 \ No newline at end of file +309388 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap index 12ede85c..2397c98f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap @@ -1 +1 @@ -34422 \ No newline at end of file +34487 \ No newline at end of file diff --git a/src/pool-bin/BinPoolManager.sol b/src/pool-bin/BinPoolManager.sol index 34476a9a..f6e98652 100644 --- a/src/pool-bin/BinPoolManager.sol +++ b/src/pool-bin/BinPoolManager.sol @@ -69,13 +69,13 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { } /// @inheritdoc IBinPoolManager - function getPosition(PoolId id, address owner, uint24 binId) + function getPosition(PoolId id, address owner, uint24 binId, bytes32 salt) external view override returns (BinPosition.Info memory position) { - return pools[id].positions.get(owner, binId); + return pools[id].positions.get(owner, binId, salt); } /// @inheritdoc IBinPoolManager @@ -248,7 +248,8 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { to: msg.sender, liquidityConfigs: params.liquidityConfigs, amountIn: params.amountIn, - binStep: key.parameters.getBinStep() + binStep: key.parameters.getBinStep(), + salt: params.salt }) ); @@ -260,7 +261,7 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { } /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one - emit Mint(id, msg.sender, mintArray.ids, mintArray.amounts, compositionFee, feeForProtocol); + emit Mint(id, msg.sender, mintArray.ids, params.salt, mintArray.amounts, compositionFee, feeForProtocol); BalanceDelta hookDelta; (delta, hookDelta) = BinHooks.afterMint(key, params, delta, hookData); @@ -290,11 +291,17 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { uint256[] memory binIds; bytes32[] memory amountRemoved; - (delta, binIds, amountRemoved) = - pools[id].burn(BinPool.BurnParams({from: msg.sender, ids: params.ids, amountsToBurn: params.amountsToBurn})); + (delta, binIds, amountRemoved) = pools[id].burn( + BinPool.BurnParams({ + from: msg.sender, + ids: params.ids, + amountsToBurn: params.amountsToBurn, + salt: params.salt + }) + ); /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one - emit Burn(id, msg.sender, binIds, amountRemoved); + emit Burn(id, msg.sender, binIds, params.salt, amountRemoved); BalanceDelta hookDelta; (delta, hookDelta) = BinHooks.afterBurn(key, params, delta, hookData); diff --git a/src/pool-bin/interfaces/IBinPoolManager.sol b/src/pool-bin/interfaces/IBinPoolManager.sol index e0e41c12..0423c05c 100644 --- a/src/pool-bin/interfaces/IBinPoolManager.sol +++ b/src/pool-bin/interfaces/IBinPoolManager.sol @@ -73,6 +73,7 @@ interface IBinPoolManager is IProtocolFees, IPoolManager, IExtsload { /// @param id The abi encoded hash of the pool key struct for the pool that was modified /// @param sender The address that modified the pool /// @param ids List of binId with liquidity added + /// @param salt The salt to distinguish different mint from the same owner /// @param amounts List of amount added to each bin /// @param compositionFee fee occurred /// @param pFee Protocol fee from the swap: token0 and token1 amount @@ -80,6 +81,7 @@ interface IBinPoolManager is IProtocolFees, IPoolManager, IExtsload { PoolId indexed id, address indexed sender, uint256[] ids, + bytes32 salt, bytes32[] amounts, bytes32 compositionFee, bytes32 pFee @@ -89,8 +91,9 @@ interface IBinPoolManager is IProtocolFees, IPoolManager, IExtsload { /// @param id The abi encoded hash of the pool key struct for the pool that was modified /// @param sender The address that modified the pool /// @param ids List of binId with liquidity removed + /// @param salt The salt to specify the position to burn if multiple positions are available /// @param amounts List of amount removed from each bin - event Burn(PoolId indexed id, address indexed sender, uint256[] ids, bytes32[] amounts); + event Burn(PoolId indexed id, address indexed sender, uint256[] ids, bytes32 salt, bytes32[] amounts); /// @notice Emitted when donate happen /// @param id The abi encoded hash of the pool key struct for the pool that was modified @@ -107,6 +110,8 @@ interface IBinPoolManager is IProtocolFees, IPoolManager, IExtsload { bytes32[] liquidityConfigs; /// @dev amountIn intended bytes32 amountIn; + /// the salt to distinguish different mint from the same owner + bytes32 salt; } struct BurnParams { @@ -114,6 +119,8 @@ interface IBinPoolManager is IProtocolFees, IPoolManager, IExtsload { uint256[] ids; /// @notice amount of share to burn for each bin uint256[] amountsToBurn; + /// the salt to specify the position to burn if multiple positions are available + bytes32 salt; } /// @notice Get the current value in slot0 of the given pool @@ -129,7 +136,8 @@ interface IBinPoolManager is IProtocolFees, IPoolManager, IExtsload { /// @param id The id of PoolKey /// @param owner Address of the owner /// @param binId The id of the bin - function getPosition(PoolId id, address owner, uint24 binId) + /// @param salt The salt to distinguish different positions for the same owner + function getPosition(PoolId id, address owner, uint24 binId, bytes32 salt) external view returns (BinPosition.Info memory position); diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index 77642604..00a2ed10 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -264,6 +264,7 @@ library BinPool { bytes32[] liquidityConfigs; bytes32 amountIn; uint16 binStep; + bytes32 salt; } struct MintArrays { @@ -318,6 +319,7 @@ library BinPool { address from; uint256[] ids; uint256[] amountsToBurn; + bytes32 salt; } /// @notice Burn user's share and withdraw tokens form the pool. @@ -342,7 +344,7 @@ library BinPool { bytes32 binReserves = self.reserveOfBin[id]; uint256 supply = self.shareOfBin[id]; - _subShare(self, params.from, id, amountToBurn); + _subShare(self, params.from, id, params.salt, amountToBurn); bytes32 amountsOutFromBin = binReserves.getAmountOutOfBin(amountToBurn, supply); @@ -396,10 +398,21 @@ library BinPool { amountsLeft = params.amountIn; for (uint256 i; i < params.liquidityConfigs.length;) { - (bytes32 maxAmountsInToBin, uint24 id) = params.liquidityConfigs[i].getAmountsAndId(params.amountIn); - - (uint256 shares, bytes32 amountsIn, bytes32 amountsInToBin, bytes32 binFeeAmt, bytes32 binCompositionFee) = - _updateBin(self, params, id, maxAmountsInToBin); + uint24 id; + uint256 shares; + bytes32 amountsIn; + bytes32 amountsInToBin; + bytes32 binFeeAmt; + bytes32 binCompositionFee; + + // fix stack too deep + { + bytes32 maxAmountsInToBin; + (maxAmountsInToBin, id) = params.liquidityConfigs[i].getAmountsAndId(params.amountIn); + + (shares, amountsIn, amountsInToBin, binFeeAmt, binCompositionFee) = + _updateBin(self, params, id, maxAmountsInToBin); + } amountsLeft = amountsLeft.sub(amountsIn); feeForProtocol = feeForProtocol.add(binFeeAmt); @@ -408,7 +421,7 @@ library BinPool { arrays.amounts[i] = amountsInToBin; arrays.liquidityMinted[i] = shares; - _addShare(self, params.to, id, shares); + _addShare(self, params.to, id, params.salt, shares); compositionFee = compositionFee.add(binCompositionFee); @@ -475,14 +488,14 @@ library BinPool { } /// @notice Subtract share from user's position and update total share supply of bin - function _subShare(State storage self, address owner, uint24 binId, uint256 shares) internal { - self.positions.get(owner, binId).subShare(shares); + function _subShare(State storage self, address owner, uint24 binId, bytes32 salt, uint256 shares) internal { + self.positions.get(owner, binId, salt).subShare(shares); self.shareOfBin[binId] -= shares; } /// @notice Add share to user's position and update total share supply of bin - function _addShare(State storage self, address owner, uint24 binId, uint256 shares) internal { - self.positions.get(owner, binId).addShare(shares); + function _addShare(State storage self, address owner, uint24 binId, bytes32 salt, uint256 shares) internal { + self.positions.get(owner, binId, salt).addShare(shares); self.shareOfBin[binId] += shares; } diff --git a/src/pool-bin/libraries/BinPosition.sol b/src/pool-bin/libraries/BinPosition.sol index 6cf568a3..f73413a7 100644 --- a/src/pool-bin/libraries/BinPosition.sol +++ b/src/pool-bin/libraries/BinPosition.sol @@ -18,8 +18,9 @@ library BinPosition { /// @param self The mapping containing all user positions /// @param owner The address of the position owner /// @param binId The binId + /// @param salt The salt to distinguish different positions for the same owner /// @return position The position info struct of the given owners' position - function get(mapping(bytes32 => Info) storage self, address owner, uint24 binId) + function get(mapping(bytes32 => Info) storage self, address owner, uint24 binId, bytes32 salt) internal view returns (BinPosition.Info storage position) @@ -29,9 +30,13 @@ library BinPosition { // ref: https://github.com/Vectorized/solady/blob/main/src/tokens/ERC20.sol#L95 // memory will be 12 bytes of zeros, the 20 bytes of address, 3 bytes for uint24 assembly { + mstore(0x23, salt) mstore(0x03, binId) mstore(0x00, owner) - key := keccak256(0x0c, 0x17) + key := keccak256(0x0c, 0x37) + // only 0x00 ~ 0x40 is scratch space + // 0x40 ~ 0x46 should be clear to avoid polluting free pointer + mstore(0x23, 0) } position = self[key]; } diff --git a/test/pool-bin/BinHook.t.sol b/test/pool-bin/BinHook.t.sol index f434e18b..1dcecd05 100644 --- a/test/pool-bin/BinHook.t.sol +++ b/test/pool-bin/BinHook.t.sol @@ -123,7 +123,7 @@ contract BinHookTest is BinTestHelper, GasSnapshot { poolManager.initialize(key, binId, ""); addLiquidityToBin(key, poolManager, bob, binId, 1e18, 1e18, 1e18, 1e18, new bytes(123)); - uint256 bobBal = poolManager.getPosition(key.toId(), bob, binId).share; + uint256 bobBal = poolManager.getPosition(key.toId(), bob, binId, 0).share; snapStart("BinHookTest#testBurnSucceedsWithHook"); removeLiquidityFromBin(key, poolManager, bob, binId, bobBal, new bytes(456)); @@ -144,7 +144,7 @@ contract BinHookTest is BinTestHelper, GasSnapshot { poolManager.initialize(key, binId, ""); addLiquidityToBin(key, poolManager, bob, binId, 1e18, 1e18, 1e18, 1e18, ""); - uint256 bobBal = poolManager.getPosition(key.toId(), bob, binId).share; + uint256 bobBal = poolManager.getPosition(key.toId(), bob, binId, 0).share; vm.expectRevert(Hooks.InvalidHookResponse.selector); removeLiquidityFromBin(key, poolManager, bob, binId, bobBal, ""); } @@ -160,7 +160,7 @@ contract BinHookTest is BinTestHelper, GasSnapshot { poolManager.initialize(key, binId, ""); addLiquidityToBin(key, poolManager, bob, binId, 1e18, 1e18, 1e18, 1e18, ""); - uint256 bobBal = poolManager.getPosition(key.toId(), bob, binId).share; + uint256 bobBal = poolManager.getPosition(key.toId(), bob, binId, 0).share; vm.expectRevert(Hooks.InvalidHookResponse.selector); removeLiquidityFromBin(key, poolManager, bob, binId, bobBal, ""); } diff --git a/test/pool-bin/BinPoolManager.t.sol b/test/pool-bin/BinPoolManager.t.sol index 8d206ade..31309cd2 100644 --- a/test/pool-bin/BinPoolManager.t.sol +++ b/test/pool-bin/BinPoolManager.t.sol @@ -61,11 +61,12 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { PoolId indexed id, address indexed sender, uint256[] ids, + bytes32 salt, bytes32[] amounts, bytes32 compositionFee, bytes32 pFees ); - event Burn(PoolId indexed id, address indexed sender, uint256[] ids, bytes32[] amounts); + event Burn(PoolId indexed id, address indexed sender, uint256[] ids, bytes32 salt, bytes32[] amounts); event Swap( PoolId indexed id, address indexed sender, @@ -278,7 +279,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { vm.expectEmit(); bytes32 compositionFee = uint128(0).encode(uint128(0)); bytes32 pFee = uint128(0).encode(uint128(0)); - emit Mint(key.toId(), address(binLiquidityHelper), ids, amounts, compositionFee, pFee); + emit Mint(key.toId(), address(binLiquidityHelper), ids, 0, amounts, compositionFee, pFee); snapStart("BinPoolManagerTest#testGasMintOneBin-1"); binLiquidityHelper.mint(key, mintParams, ""); @@ -327,7 +328,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { bytes32 compositionFee = uint128(0).encode(uint128(0)); bytes32 pFee = uint128(0).encode(uint128(0)); vm.expectEmit(); - emit Mint(key.toId(), address(binLiquidityHelper), ids, amounts, compositionFee, pFee); + emit Mint(key.toId(), address(binLiquidityHelper), ids, 0, amounts, compositionFee, pFee); // 1 ether as add 1 ether in native currency snapStart("BinPoolManagerTest#testMintNativeCurrency"); @@ -354,7 +355,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { ids[0] = activeId; amounts[0] = uint128(1e18).encode(uint128(1e18)); vm.expectEmit(); - emit Burn(key.toId(), address(binLiquidityHelper), ids, amounts); + emit Burn(key.toId(), address(binLiquidityHelper), ids, 0, amounts); snapStart("BinPoolManagerTest#testGasBurnOneBin"); binLiquidityHelper.burn(key, burnParams, ""); @@ -423,7 +424,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { ids[0] = activeId; amounts[0] = uint128(1e18).encode(uint128(1e18)); vm.expectEmit(); - emit Burn(key.toId(), address(binLiquidityHelper), ids, amounts); + emit Burn(key.toId(), address(binLiquidityHelper), ids, 0, amounts); snapStart("BinPoolManagerTest#testBurnNativeCurrency"); binLiquidityHelper.burn(key, burnParams, ""); diff --git a/test/pool-bin/helpers/BinTestHelper.sol b/test/pool-bin/helpers/BinTestHelper.sol index 53689423..54f32c07 100644 --- a/test/pool-bin/helpers/BinTestHelper.sol +++ b/test/pool-bin/helpers/BinTestHelper.sol @@ -41,7 +41,8 @@ abstract contract BinTestHelper is Test { IBinPoolManager.MintParams memory params = IBinPoolManager.MintParams({ liquidityConfigs: liquidityConfigurations, - amountIn: PackedUint128Math.encode(amountX.safe128(), amountY.safe128()) + amountIn: PackedUint128Math.encode(amountX.safe128(), amountY.safe128()), + salt: 0 }); vm.prank(from); @@ -87,14 +88,16 @@ abstract contract BinTestHelper is Test { params = IBinPoolManager.MintParams({ liquidityConfigs: liquidityConfigurations, - amountIn: PackedUint128Math.encode(0, amount.safe128()) + amountIn: PackedUint128Math.encode(0, amount.safe128()), + salt: 0 }); } else { liquidityConfigurations[0] = LiquidityConfigurations.encodeParams(1e18, 0, binId); params = IBinPoolManager.MintParams({ liquidityConfigs: liquidityConfigurations, - amountIn: PackedUint128Math.encode(amount.safe128(), 0) + amountIn: PackedUint128Math.encode(amount.safe128(), 0), + salt: 0 }); } } @@ -127,7 +130,8 @@ abstract contract BinTestHelper is Test { params = IBinPoolManager.MintParams({ liquidityConfigs: liquidityConfigurations, - amountIn: PackedUint128Math.encode(amountX.safe128(), amountY.safe128()) + amountIn: PackedUint128Math.encode(amountX.safe128(), amountY.safe128()), + salt: 0 }); } @@ -145,7 +149,8 @@ abstract contract BinTestHelper is Test { ids[0] = binId; amtToBurn[0] = amountsToBurn; - IBinPoolManager.BurnParams memory params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: amtToBurn}); + IBinPoolManager.BurnParams memory params = + IBinPoolManager.BurnParams({ids: ids, amountsToBurn: amtToBurn, salt: 0}); vm.prank(from); delta = poolManager.burn(key, params, hookData); @@ -158,7 +163,8 @@ abstract contract BinTestHelper is Test { uint256[] memory ids, uint256[] memory amountsToBurn ) public { - IBinPoolManager.BurnParams memory params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: amountsToBurn}); + IBinPoolManager.BurnParams memory params = + IBinPoolManager.BurnParams({ids: ids, amountsToBurn: amountsToBurn, salt: 0}); vm.prank(from); poolManager.burn(key, params, "0x00"); @@ -177,9 +183,9 @@ abstract contract BinTestHelper is Test { uint256[] memory balances = new uint256[](1); ids[0] = binId; - balances[0] = (pm.getPosition(_key.toId(), from, binId).share * sharePercentage) / 100; + balances[0] = (pm.getPosition(_key.toId(), from, binId, 0).share * sharePercentage) / 100; - params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: balances}); + params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: balances, salt: 0}); } /// @dev get burn params assuming user is burning all liquidity at the binId @@ -196,10 +202,10 @@ abstract contract BinTestHelper is Test { for (uint256 i; i < binIds.length; i++) { ids[i] = binIds[i]; - balances[i] = (pm.getPosition(_key.toId(), from, binIds[i]).share * sharePercentage) / 100; + balances[i] = (pm.getPosition(_key.toId(), from, binIds[i], 0).share * sharePercentage) / 100; } - params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: balances}); + params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: balances, salt: 0}); } function getTotalBins(uint8 nbBinX, uint8 nbBinY) public pure returns (uint256) { diff --git a/test/pool-bin/libraries/BinPoolDonate.t.sol b/test/pool-bin/libraries/BinPoolDonate.t.sol index ad727245..a189716c 100644 --- a/test/pool-bin/libraries/BinPoolDonate.t.sol +++ b/test/pool-bin/libraries/BinPoolDonate.t.sol @@ -67,9 +67,9 @@ contract BinPoolDonateTest is BinTestHelper { // Initialize. Alice/Bob both add 1e18 token0, token1 to the active bin poolManager.initialize(key, activeId, new bytes(0)); addLiquidityToBin(key, poolManager, alice, activeId, 1e18, 1e18, 1e18, 1e18, ""); - uint256 aliceShare = poolManager.getPosition(poolId, alice, activeId).share; + uint256 aliceShare = poolManager.getPosition(poolId, alice, activeId, 0).share; addLiquidityToBin(key, poolManager, bob, activeId, 1e18, 1e18, 1e18, 1e18, ""); - uint256 bobShare = poolManager.getPosition(poolId, bob, activeId).share; + uint256 bobShare = poolManager.getPosition(poolId, bob, activeId, 0).share; // Verify reserve before donate uint128 reserveX; @@ -110,7 +110,7 @@ contract BinPoolDonateTest is BinTestHelper { // Initialize and add 1e18 token0, token1 to the active bin. price of bin: 2**128, 3.4e38 poolManager.initialize(key, activeId, new bytes(0)); addLiquidityToBin(key, poolManager, bob, activeId, 1e18, 1e18, 1e18, 1e18, ""); - poolManager.getPosition(poolId, bob, activeId).share; + poolManager.getPosition(poolId, bob, activeId, 0).share; poolManager.donate(key, amt0, amt1, ""); diff --git a/test/pool-bin/libraries/BinPoolFee.t.sol b/test/pool-bin/libraries/BinPoolFee.t.sol index b23e361b..15669a9c 100644 --- a/test/pool-bin/libraries/BinPoolFee.t.sol +++ b/test/pool-bin/libraries/BinPoolFee.t.sol @@ -40,11 +40,12 @@ contract BinPoolFeeTest is BinTestHelper { PoolId indexed id, address indexed sender, uint256[] ids, + bytes32 salt, bytes32[] amounts, bytes32 compositionFee, bytes32 pFee ); - event Burn(PoolId indexed id, address indexed sender, uint256[] ids, bytes32[] amounts); + event Burn(PoolId indexed id, address indexed sender, uint256[] ids, bytes32 salt, bytes32[] amounts); event Swap( PoolId indexed id, address indexed sender, @@ -121,7 +122,7 @@ contract BinPoolFeeTest is BinTestHelper { ids[0] = binId; amounts[0] = expectedAmtInBin; vm.expectEmit(); - emit Mint(key.toId(), bob, ids, amounts, expectedFee, protocolFee); + emit Mint(key.toId(), bob, ids, 0, amounts, expectedFee, protocolFee); addLiquidityToBin(key, poolManager, bob, binId, amountX, amountY, 4e17, 5e17, ""); } @@ -187,7 +188,7 @@ contract BinPoolFeeTest is BinTestHelper { ids[0] = binId; amounts[0] = expectedAmtInBin; vm.expectEmit(); - emit Mint(key.toId(), bob, ids, amounts, expectedFee, protocolFee); + emit Mint(key.toId(), bob, ids, 0, amounts, expectedFee, protocolFee); addLiquidityToBin(key, poolManager, bob, binId, amountX, amountY, 4e17, 5e17, ""); } @@ -216,7 +217,7 @@ contract BinPoolFeeTest is BinTestHelper { ids[0] = binId; amounts[0] = expectedAmtInBin; vm.expectEmit(); - emit Mint(key.toId(), bob, ids, amounts, expectedFee, protocolFee); + emit Mint(key.toId(), bob, ids, 0, amounts, expectedFee, protocolFee); addLiquidityToBin(key, poolManager, bob, binId, amountX, amountY, 4e17, 5e17, ""); @@ -240,7 +241,7 @@ contract BinPoolFeeTest is BinTestHelper { // then remove liquidity uint256[] memory balances = new uint256[](1); uint256[] memory ids = new uint256[](1); - balances[0] = poolManager.getPosition(poolId, bob, activeId).share; + balances[0] = poolManager.getPosition(poolId, bob, activeId, 0).share; ids[0] = activeId; removeLiquidity(key, poolManager, bob, ids, balances); diff --git a/test/pool-bin/libraries/BinPoolLiquidity.t.sol b/test/pool-bin/libraries/BinPoolLiquidity.t.sol index 6edf5d96..9dc378dc 100644 --- a/test/pool-bin/libraries/BinPoolLiquidity.t.sol +++ b/test/pool-bin/libraries/BinPoolLiquidity.t.sol @@ -98,7 +98,7 @@ contract BinPoolLiquidityTest is BinTestHelper { assertEq(binReserveY, 0, "test_SimpleMint::8"); } - assertGt(poolManager.getPosition(poolId, bob, id).share, 0, "test_SimpleMint::9"); + assertGt(poolManager.getPosition(poolId, bob, id, 0).share, 0, "test_SimpleMint::9"); } } { @@ -123,7 +123,7 @@ contract BinPoolLiquidityTest is BinTestHelper { } // verify liquidity minted - assertEq(poolManager.getPosition(poolId, bob, id).share, array.liquidityMinted[i]); + assertEq(poolManager.getPosition(poolId, bob, id, 0).share, array.liquidityMinted[i]); } } } @@ -143,7 +143,7 @@ contract BinPoolLiquidityTest is BinTestHelper { for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); - balances[i] = poolManager.getPosition(poolId, bob, id).share; + balances[i] = poolManager.getPosition(poolId, bob, id, 0).share; } addLiquidity(key, poolManager, bob, activeId, amountX, amountY, nbBinX, nbBinY); @@ -169,7 +169,7 @@ contract BinPoolLiquidityTest is BinTestHelper { assertEq(binReserveY, 0, "test_SimpleMint::6"); } - assertEq(poolManager.getPosition(poolId, bob, id).share, 2 * balances[i], "test_DoubleMint:7"); + assertEq(poolManager.getPosition(poolId, bob, id, 0).share, 2 * balances[i], "test_DoubleMint:7"); } } @@ -188,7 +188,7 @@ contract BinPoolLiquidityTest is BinTestHelper { for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); - balances[i] = poolManager.getPosition(poolId, bob, id).share; + balances[i] = poolManager.getPosition(poolId, bob, id, 0).share; } addLiquidity(key, poolManager, bob, activeId, amountX, amountY, nbBinX, 0); @@ -199,14 +199,14 @@ contract BinPoolLiquidityTest is BinTestHelper { if (id == activeId) { assertApproxEqRel( - poolManager.getPosition(poolId, bob, id).share, + poolManager.getPosition(poolId, bob, id, 0).share, 2 * balances[i], 1e15, "test_MintWithDifferentBins::1" ); // composition fee } else { assertEq( - poolManager.getPosition(poolId, bob, id).share, 2 * balances[i], "test_MintWithDifferentBins::2" + poolManager.getPosition(poolId, bob, id, 0).share, 2 * balances[i], "test_MintWithDifferentBins::2" ); } } @@ -215,8 +215,11 @@ contract BinPoolLiquidityTest is BinTestHelper { function test_revert_MintEmptyConfig() public { poolManager.initialize(key, activeId, new bytes(0)); - IBinPoolManager.MintParams memory params = - IBinPoolManager.MintParams({liquidityConfigs: new bytes32[](0), amountIn: PackedUint128Math.encode(0, 0)}); + IBinPoolManager.MintParams memory params = IBinPoolManager.MintParams({ + liquidityConfigs: new bytes32[](0), + amountIn: PackedUint128Math.encode(0, 0), + salt: 0 + }); vm.expectRevert(BinPool.BinPool__EmptyLiquidityConfigs.selector); poolManager.mint(key, params, "0x00"); @@ -229,7 +232,7 @@ contract BinPoolLiquidityTest is BinTestHelper { data[0] = LiquidityConfigurations.encodeParams(1e18, 1e18, activeId); IBinPoolManager.MintParams memory params = - IBinPoolManager.MintParams({liquidityConfigs: data, amountIn: PackedUint128Math.encode(0, 0)}); + IBinPoolManager.MintParams({liquidityConfigs: data, amountIn: PackedUint128Math.encode(0, 0), salt: 0}); vm.expectRevert(abi.encodeWithSelector(BinPool.BinPool__ZeroShares.selector, activeId)); poolManager.mint(key, params, "0x00"); @@ -243,14 +246,21 @@ contract BinPoolLiquidityTest is BinTestHelper { data[1] = LiquidityConfigurations.encodeParams(0, 0.5e18 + 1, activeId); vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); - IBinPoolManager.MintParams memory params = - IBinPoolManager.MintParams({liquidityConfigs: data, amountIn: PackedUint128Math.encode(1e18, 1e18)}); + IBinPoolManager.MintParams memory params = IBinPoolManager.MintParams({ + liquidityConfigs: data, + amountIn: PackedUint128Math.encode(1e18, 1e18), + salt: 0 + }); poolManager.mint(key, params, "0x00"); data[1] = LiquidityConfigurations.encodeParams(0.5e18, 0, activeId); data[0] = LiquidityConfigurations.encodeParams(0.5e18 + 1, 0, activeId + 1); vm.expectRevert(PackedUint128Math.PackedUint128Math__SubUnderflow.selector); - params = IBinPoolManager.MintParams({liquidityConfigs: data, amountIn: PackedUint128Math.encode(1e18, 1e18)}); + params = IBinPoolManager.MintParams({ + liquidityConfigs: data, + amountIn: PackedUint128Math.encode(1e18, 1e18), + salt: 0 + }); poolManager.mint(key, params, "0x00"); } @@ -271,7 +281,7 @@ contract BinPoolLiquidityTest is BinTestHelper { for (uint256 i; i < total; ++i) { uint24 id = getId(activeId, i, nbBinY); ids[i] = id; - balances[i] = poolManager.getPosition(poolId, bob, id).share; + balances[i] = poolManager.getPosition(poolId, bob, id, 0).share; } uint256 reserveX = vault.reservesOfPoolManager(key.poolManager, key.currency0); @@ -313,7 +323,7 @@ contract BinPoolLiquidityTest is BinTestHelper { uint24 id = getId(activeId, i, nbBinY); ids[i] = id; - uint256 balance = poolManager.getPosition(poolId, bob, id).share; + uint256 balance = poolManager.getPosition(poolId, bob, id, 0).share; halfbalances[i] = balance / 2; balances[i] = balance - balance / 2; @@ -378,7 +388,7 @@ contract BinPoolLiquidityTest is BinTestHelper { uint256[] memory balances = new uint256[](1); ids[0] = activeId; - balances[0] = poolManager.getPosition(poolId, bob, activeId).share + 1; + balances[0] = poolManager.getPosition(poolId, bob, activeId, 0).share + 1; vm.expectRevert(stdError.arithmeticError); removeLiquidity(key, poolManager, bob, ids, balances); diff --git a/test/pool-bin/libraries/BinPosition.t.sol b/test/pool-bin/libraries/BinPosition.t.sol index 72b995e4..fd9bd18a 100644 --- a/test/pool-bin/libraries/BinPosition.t.sol +++ b/test/pool-bin/libraries/BinPosition.t.sol @@ -16,43 +16,43 @@ contract BinPositionTest is Test { State private _self; function testFuzz_AddShare(address owner, uint24 binId, uint256 share) public { - _self.positions.get(owner, binId).addShare(share); + _self.positions.get(owner, binId, 0).addShare(share); - assertEq(_self.positions.get(owner, binId).share, share, "testFuzz_AddShare::1"); + assertEq(_self.positions.get(owner, binId, 0).share, share, "testFuzz_AddShare::1"); } function testFuzz_AddShareMultiple(address owner, uint24 binId, uint256 share1, uint256 share2) public { share1 = bound(share1, 0, type(uint128).max); share2 = bound(share2, 0, type(uint128).max); - _self.positions.get(owner, binId).addShare(share1); - _self.positions.get(owner, binId).addShare(share2); + _self.positions.get(owner, binId, 0).addShare(share1); + _self.positions.get(owner, binId, 0).addShare(share2); - assertEq(_self.positions.get(owner, binId).share, share1 + share2, "testFuzz_AddShareMultiple::1"); + assertEq(_self.positions.get(owner, binId, 0).share, share1 + share2, "testFuzz_AddShareMultiple::1"); } function testFuzz_SubShare(address owner, uint24 binId, uint256 share) public { - _self.positions.get(owner, binId).addShare(share); - _self.positions.get(owner, binId).subShare(share); + _self.positions.get(owner, binId, 0).addShare(share); + _self.positions.get(owner, binId, 0).subShare(share); - assertEq(_self.positions.get(owner, binId).share, 0, "testFuzz_SubShare::1"); + assertEq(_self.positions.get(owner, binId, 0).share, 0, "testFuzz_SubShare::1"); } function testFuzz_SubShareMultiple(address owner, uint24 binId, uint256 share1, uint256 share2) public { share1 = bound(share1, 1, type(uint128).max); share2 = bound(share2, 0, share1 - 1); - _self.positions.get(owner, binId).addShare(share1); - _self.positions.get(owner, binId).subShare(share2); + _self.positions.get(owner, binId, 0).addShare(share1); + _self.positions.get(owner, binId, 0).subShare(share2); - assertEq(_self.positions.get(owner, binId).share, share1 - share2, "testFuzz_SubShareMultiple::1"); + assertEq(_self.positions.get(owner, binId, 0).share, share1 - share2, "testFuzz_SubShareMultiple::1"); } - function testFuzz_GetPosition(address owner, uint24 binId, uint256 share) public { + function testFuzz_GetPosition(address owner, uint24 binId, bytes32 salt, uint256 share) public { // manual keccak and add share - bytes32 key = keccak256(abi.encodePacked(owner, binId)); + bytes32 key = keccak256(abi.encodePacked(owner, binId, salt)); _self.positions[key].addShare(share); // verify assembly version of keccak retrival works - assertEq(_self.positions.get(owner, binId).share, share); + assertEq(_self.positions.get(owner, binId, salt).share, share); } } diff --git a/test/pool-cl/libraries/CLPosition.t.sol b/test/pool-cl/libraries/CLPosition.t.sol index a13bd814..ab2b1581 100644 --- a/test/pool-cl/libraries/CLPosition.t.sol +++ b/test/pool-cl/libraries/CLPosition.t.sol @@ -90,4 +90,15 @@ contract CLPositionTest is Test, GasSnapshot { assertEq(info.feeGrowthInside1LastX128, 15 * FixedPoint128.Q128); } } + + function test_MixFuzz(address owner, int24 tickLower, int24 tickUpper, bytes32 salt, int128 liquidityDelta) + public + { + liquidityDelta = int128(bound(liquidityDelta, 1, type(int128).max)); + CLPosition.Info storage info = pool.positions.get(owner, tickLower, tickUpper, salt); + info.update(liquidityDelta, 0, 0); + + bytes32 key = keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt)); + assertEq(pool.positions[key].liquidity, uint128(liquidityDelta)); + } } From 3b49d98e31801189915b7e2b0cdb6756a90c9d40 Mon Sep 17 00:00:00 2001 From: chefburger Date: Wed, 22 May 2024 20:32:35 +0800 Subject: [PATCH 3/5] optimiazaiton: small tweaks per comments --- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- ...nPoolManagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- .../BinPoolManagerTest#testFuzz_SetMaxBinStep.snap | 2 +- .../BinPoolManagerTest#testGasMintNneBins-1.snap | 2 +- .../BinPoolManagerTest#testGasMintNneBins-2.snap | 2 +- .../BinPoolManagerTest#testGasMintOneBin-1.snap | 2 +- .../BinPoolManagerTest#testGasMintOneBin-2.snap | 2 +- .../BinPoolManagerTest#testMintNativeCurrency.snap | 2 +- ...LPoolManagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- src/pool-bin/libraries/BinHooks.sol | 1 + src/pool-bin/libraries/BinPool.sol | 13 ++++++------- src/pool-bin/libraries/BinPosition.sol | 2 +- src/pool-cl/libraries/CLHooks.sol | 1 + src/pool-cl/libraries/CLPosition.sol | 2 +- 14 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 60d8e25c..3be97679 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -331255 \ No newline at end of file +331250 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index 26cdfb14..1bfb0a9d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -32551 \ No newline at end of file +32563 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap index 960d5d6f..8e96cfd1 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap @@ -1 +1 @@ -30417 \ No newline at end of file +30405 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index f9084256..d38e29c8 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -977485 \ No newline at end of file +977200 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index e6212425..a2070f30 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -339675 \ No newline at end of file +339390 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index 42dd5964..6d889ce4 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -342872 \ No newline at end of file +342867 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index 7124d148..0807b6ef 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -145815 \ No newline at end of file +145810 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index e94b26fb..6ead8637 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -309388 \ No newline at end of file +309383 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index 40100785..1832b807 100644 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -32293 \ No newline at end of file +29469 \ No newline at end of file diff --git a/src/pool-bin/libraries/BinHooks.sol b/src/pool-bin/libraries/BinHooks.sol index 13e70dc7..0b9509ca 100644 --- a/src/pool-bin/libraries/BinHooks.sol +++ b/src/pool-bin/libraries/BinHooks.sol @@ -141,6 +141,7 @@ library BinHooks { revert Hooks.InvalidHookResponse(); } + // TODO: Potentially optimization: skip decoding the second return value when afterSwapReturnDelta not set if (!key.parameters.hasOffsetEnabled(HOOKS_AFTER_SWAP_RETURNS_DELTA_OFFSET)) { hookDeltaUnspecified = 0; } diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index 00a2ed10..47ad1cb0 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -397,14 +397,13 @@ library BinPool { { amountsLeft = params.amountIn; + uint24 id; + uint256 shares; + bytes32 amountsIn; + bytes32 amountsInToBin; + bytes32 binFeeAmt; + bytes32 binCompositionFee; for (uint256 i; i < params.liquidityConfigs.length;) { - uint24 id; - uint256 shares; - bytes32 amountsIn; - bytes32 amountsInToBin; - bytes32 binFeeAmt; - bytes32 binCompositionFee; - // fix stack too deep { bytes32 maxAmountsInToBin; diff --git a/src/pool-bin/libraries/BinPosition.sol b/src/pool-bin/libraries/BinPosition.sol index f73413a7..c07f279f 100644 --- a/src/pool-bin/libraries/BinPosition.sol +++ b/src/pool-bin/libraries/BinPosition.sol @@ -34,7 +34,7 @@ library BinPosition { mstore(0x03, binId) mstore(0x00, owner) key := keccak256(0x0c, 0x37) - // only 0x00 ~ 0x40 is scratch space + // 0x00 - 0x3f is scratch space // 0x40 ~ 0x46 should be clear to avoid polluting free pointer mstore(0x23, 0) } diff --git a/src/pool-cl/libraries/CLHooks.sol b/src/pool-cl/libraries/CLHooks.sol index f5845ec8..d4cd262f 100644 --- a/src/pool-cl/libraries/CLHooks.sol +++ b/src/pool-cl/libraries/CLHooks.sol @@ -94,6 +94,7 @@ library CLHooks { } bytes4 selector; + // TODO: Potentially optimization: skip decoding the second return value when afterSwapReturnDelta not set (selector, hookDeltaSpecified) = hooks.beforeSwap(msg.sender, key, params, hookData); if (selector != ICLHooks.beforeSwap.selector) { revert Hooks.InvalidHookResponse(); diff --git a/src/pool-cl/libraries/CLPosition.sol b/src/pool-cl/libraries/CLPosition.sol index 4d56e98c..b511841a 100644 --- a/src/pool-cl/libraries/CLPosition.sol +++ b/src/pool-cl/libraries/CLPosition.sol @@ -43,7 +43,7 @@ library CLPosition { mstore(0x03, tickLower) mstore(0x00, owner) key := keccak256(0x0c, 0x3a) - // only 0x00 ~ 0x40 is scratch space + // 0x00 - 0x3f is scratch space // 0x40 ~ 0x46 should be clear to avoid polluting free pointer mstore(0x26, 0) } From dc9975eb49016ac2815d467284c4a422010573f2 Mon Sep 17 00:00:00 2001 From: chefburger Date: Thu, 23 May 2024 11:53:34 +0800 Subject: [PATCH 4/5] test: added salt != 0 cases --- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- ...oolManagerTest#testFuzz_SetMaxBinStep.snap | 2 +- ...olManagerTest#testGasSwapMultipleBins.snap | 2 +- ...nagerTest#testGasSwapOverBigBinIdGate.snap | 2 +- ...nPoolManagerTest#testGasSwapSingleBin.snap | 2 +- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- test/pool-bin/BinPoolManager.t.sol | 197 +++++++++++++++++ test/pool-bin/helpers/BinTestHelper.sol | 49 +++++ test/pool-cl/CLPoolManager.t.sol | 200 ++++++++++++++++++ 9 files changed, 452 insertions(+), 6 deletions(-) diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index 1bfb0a9d..26cdfb14 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -32563 \ No newline at end of file +32551 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap index 8e96cfd1..960d5d6f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap @@ -1 +1 @@ -30405 \ No newline at end of file +30417 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 6ec51e4b..aa5280c1 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -180336 \ No newline at end of file +180348 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 460128db..9304d8fe 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -186319 \ No newline at end of file +186331 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index 1378b556..5c92114f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -138699 \ No newline at end of file +138711 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index 1832b807..40100785 100644 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -29469 \ No newline at end of file +32293 \ No newline at end of file diff --git a/test/pool-bin/BinPoolManager.t.sol b/test/pool-bin/BinPoolManager.t.sol index 31309cd2..76bee1d8 100644 --- a/test/pool-bin/BinPoolManager.t.sol +++ b/test/pool-bin/BinPoolManager.t.sol @@ -34,6 +34,7 @@ import {BinLiquidityHelper} from "./helpers/BinLiquidityHelper.sol"; import {BinDonateHelper} from "./helpers/BinDonateHelper.sol"; import {BinTestHelper} from "./helpers/BinTestHelper.sol"; import {Hooks} from "../../src/libraries/Hooks.sol"; +import {BinPosition} from "../../src/pool-bin/libraries/BinPosition.sol"; contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { using PoolIdLibrary for PoolKey; @@ -336,6 +337,202 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { snapEnd(); } + function testMintAndBurnWithSalt() public { + bytes32 salt = bytes32(uint256(0x1234)); + poolManager.initialize(key, activeId, new bytes(0)); + + token0.mint(address(this), 10 ether); + token1.mint(address(this), 10 ether); + (IBinPoolManager.MintParams memory mintParams, uint24[] memory binIds) = + _getMultipleBinMintParams(activeId, 2 ether, 2 ether, 5, 5, salt); + binLiquidityHelper.mint(key, mintParams, ""); + + // liquidity added with salt 0x1234 not salt 0 + for (uint256 i = 0; i < binIds.length; i++) { + (uint128 binReserveX, uint128 binReserveY) = poolManager.getBin(key.toId(), binIds[i]); + + // make sure the liquidity is added to the correct bin + if (binIds[i] < activeId) { + assertEq(binReserveX, 0 ether); + assertEq(binReserveY, 0.4 ether); + } else if (binIds[i] > activeId) { + assertEq(binReserveX, 0.4 ether); + assertEq(binReserveY, 0 ether); + } else { + assertEq(binReserveX, 0.4 ether); + assertEq(binReserveY, 0.4 ether); + } + + BinPosition.Info memory position = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt); + BinPosition.Info memory position0 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], 0); + assertTrue(position.share != 0); + // position with salt = 0 + assertTrue(position0.share == 0); + } + + // burn liquidity with salt 0x1234 + IBinPoolManager.BurnParams memory burnParams = + _getMultipleBinBurnLiquidityParams(key, poolManager, binIds, address(binLiquidityHelper), 100, salt); + binLiquidityHelper.burn(key, burnParams, ""); + + for (uint256 i = 0; i < binIds.length; i++) { + (uint128 binReserveX, uint128 binReserveY) = poolManager.getBin(key.toId(), binIds[i]); + + // make sure the liquidity is added to the correct bin + assertEq(binReserveX, 0 ether); + assertEq(binReserveY, 0 ether); + + BinPosition.Info memory position = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt); + BinPosition.Info memory position0 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], 0); + assertTrue(position.share == 0); + assertTrue(position0.share == 0); + } + } + + function testMintMixWithAndWithoutSalt() public { + bytes32 salt0 = bytes32(0); + bytes32 salt1 = bytes32(uint256(0x1234)); + bytes32 salt2 = bytes32(uint256(0x5678)); + poolManager.initialize(key, activeId, new bytes(0)); + + token0.mint(address(this), 30 ether); + token1.mint(address(this), 30 ether); + + (IBinPoolManager.MintParams memory mintParams, uint24[] memory binIds) = + _getMultipleBinMintParams(activeId, 2 ether, 2 ether, 5, 5, salt1); + binLiquidityHelper.mint(key, mintParams, ""); + + // liquidity added with salt 0x1234 not salt 0 + for (uint256 i = 0; i < binIds.length; i++) { + (uint128 binReserveX, uint128 binReserveY) = poolManager.getBin(key.toId(), binIds[i]); + + // make sure the liquidity is added to the correct bin + if (binIds[i] < activeId) { + assertEq(binReserveX, 0 ether); + assertEq(binReserveY, 0.4 ether); + } else if (binIds[i] > activeId) { + assertEq(binReserveX, 0.4 ether); + assertEq(binReserveY, 0 ether); + } else { + assertEq(binReserveX, 0.4 ether); + assertEq(binReserveY, 0.4 ether); + } + + BinPosition.Info memory position0 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt0); + BinPosition.Info memory position1 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt1); + BinPosition.Info memory position2 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt2); + + // only position with salt 0x1234 should have share + assertTrue(position0.share == 0); + assertTrue(position1.share != 0); + assertTrue(position2.share == 0); + } + + { + (mintParams, binIds) = _getMultipleBinMintParams(activeId, 2 ether, 2 ether, 5, 5, salt2); + binLiquidityHelper.mint(key, mintParams, ""); + + for (uint256 i = 0; i < binIds.length; i++) { + (uint128 binReserveX, uint128 binReserveY) = poolManager.getBin(key.toId(), binIds[i]); + + // make sure the liquidity is added to the correct bin + if (binIds[i] < activeId) { + assertEq(binReserveX, 0 ether); + assertEq(binReserveY, 0.4 ether * 2); + } else if (binIds[i] > activeId) { + assertEq(binReserveX, 0.4 ether * 2); + assertEq(binReserveY, 0 ether); + } else { + assertEq(binReserveX, 0.4 ether * 2); + assertEq(binReserveY, 0.4 ether * 2); + } + + BinPosition.Info memory position0 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt0); + BinPosition.Info memory position1 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt1); + BinPosition.Info memory position2 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt2); + + // only position with salt 0 should be empty + assertTrue(position0.share == 0); + assertTrue(position1.share != 0); + assertTrue(position1.share == position2.share); + } + } + + { + (mintParams, binIds) = _getMultipleBinMintParams(activeId, 2 ether, 2 ether, 5, 5, salt0); + binLiquidityHelper.mint(key, mintParams, ""); + + for (uint256 i = 0; i < binIds.length; i++) { + (uint128 binReserveX, uint128 binReserveY) = poolManager.getBin(key.toId(), binIds[i]); + + // make sure the liquidity is added to the correct bin + if (binIds[i] < activeId) { + assertEq(binReserveX, 0 ether); + assertEq(binReserveY, 0.4 ether * 3); + } else if (binIds[i] > activeId) { + assertEq(binReserveX, 0.4 ether * 3); + assertEq(binReserveY, 0 ether); + } else { + assertEq(binReserveX, 0.4 ether * 3); + assertEq(binReserveY, 0.4 ether * 3); + } + + BinPosition.Info memory position0 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt0); + BinPosition.Info memory position1 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt1); + BinPosition.Info memory position2 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt2); + + assertTrue(position0.share != 0); + assertTrue(position1.share == position0.share); + assertTrue(position1.share == position2.share); + } + } + + // burning liquidity with salt 0x1234 should not impact position0 & position2 + IBinPoolManager.BurnParams memory burnParams = + _getMultipleBinBurnLiquidityParams(key, poolManager, binIds, address(binLiquidityHelper), 100, salt1); + binLiquidityHelper.burn(key, burnParams, ""); + + for (uint256 i = 0; i < binIds.length; i++) { + (uint128 binReserveX, uint128 binReserveY) = poolManager.getBin(key.toId(), binIds[i]); + + // make sure the liquidity is added to the correct bin + if (binIds[i] < activeId) { + assertEq(binReserveX, 0 ether); + assertEq(binReserveY, 0.4 ether * 2); + } else if (binIds[i] > activeId) { + assertEq(binReserveX, 0.4 ether * 2); + assertEq(binReserveY, 0 ether); + } else { + assertEq(binReserveX, 0.4 ether * 2); + assertEq(binReserveY, 0.4 ether * 2); + } + + BinPosition.Info memory position0 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt0); + BinPosition.Info memory position1 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt1); + BinPosition.Info memory position2 = + poolManager.getPosition(key.toId(), address(binLiquidityHelper), binIds[i], salt2); + + assertTrue(position0.share != 0); + assertTrue(position1.share == 0); + assertTrue(position0.share == position2.share); + } + } + function testGasBurnOneBin() public { // initialize poolManager.initialize(key, activeId, new bytes(0)); diff --git a/test/pool-bin/helpers/BinTestHelper.sol b/test/pool-bin/helpers/BinTestHelper.sol index 54f32c07..b0e26f56 100644 --- a/test/pool-bin/helpers/BinTestHelper.sol +++ b/test/pool-bin/helpers/BinTestHelper.sol @@ -135,6 +135,36 @@ abstract contract BinTestHelper is Test { }); } + function _getMultipleBinMintParams( + uint24 binId, + uint256 amountX, + uint256 amountY, + uint8 nbBinX, + uint8 nbBinY, + bytes32 salt + ) internal pure returns (IBinPoolManager.MintParams memory params, uint24[] memory binIds) { + uint256 total = getTotalBins(nbBinX, nbBinY); // nbBinX + nbBinY - 1 + + bytes32[] memory liquidityConfigurations = new bytes32[](total); + binIds = new uint24[](total); + + for (uint256 i; i < total; ++i) { + uint24 id = getId(binId, i, nbBinY); // all the binId from left to right :: id = activeId + i - nBinY + 1 + binIds[i] = id; + + uint64 distribX = id >= binId && nbBinX > 0 ? (Constants.PRECISION / nbBinX).safe64() : 0; + uint64 distribY = id <= binId && nbBinY > 0 ? (Constants.PRECISION / nbBinY).safe64() : 0; + + liquidityConfigurations[i] = LiquidityConfigurations.encodeParams(distribX, distribY, id); + } + + params = IBinPoolManager.MintParams({ + liquidityConfigs: liquidityConfigurations, + amountIn: PackedUint128Math.encode(amountX.safe128(), amountY.safe128()), + salt: salt + }); + } + function removeLiquidityFromBin( PoolKey memory key, BinPoolManager poolManager, @@ -208,6 +238,25 @@ abstract contract BinTestHelper is Test { params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: balances, salt: 0}); } + function _getMultipleBinBurnLiquidityParams( + PoolKey memory _key, + BinPoolManager pm, + uint24[] memory binIds, + address from, + uint256 sharePercentage, + bytes32 salt + ) internal view returns (IBinPoolManager.BurnParams memory params) { + uint256[] memory ids = new uint256[](binIds.length); + uint256[] memory balances = new uint256[](binIds.length); + + for (uint256 i; i < binIds.length; i++) { + ids[i] = binIds[i]; + balances[i] = (pm.getPosition(_key.toId(), from, binIds[i], salt).share * sharePercentage) / 100; + } + + params = IBinPoolManager.BurnParams({ids: ids, amountsToBurn: balances, salt: salt}); + } + function getTotalBins(uint8 nbBinX, uint8 nbBinY) public pure returns (uint256) { return nbBinX > 0 && nbBinY > 0 ? nbBinX + nbBinY - 1 : nbBinX + nbBinY; } diff --git a/test/pool-cl/CLPoolManager.t.sol b/test/pool-cl/CLPoolManager.t.sol index a53784b8..9600de8c 100644 --- a/test/pool-cl/CLPoolManager.t.sol +++ b/test/pool-cl/CLPoolManager.t.sol @@ -1420,6 +1420,206 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps snapEnd(); } + function testModifyPosition_withSalt_addAndRemove() external { + bytes32 salt = bytes32(uint256(0x1234)); + Currency currency0 = Currency.wrap(address(new ERC20PresetFixedSupply("C0", "C0", 1e10 ether, address(this)))); + Currency currency1 = Currency.wrap(address(new ERC20PresetFixedSupply("C1", "C1", 1e10 ether, address(this)))); + + if (currency0 > currency1) { + (currency0, currency1) = (currency1, currency0); + } + + PoolKey memory key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: IHooks(address(0)), + poolManager: poolManager, + fee: uint24(3000), + // 0 ~ 15 hookRegistrationMap = nil + // 16 ~ 24 tickSpacing = 1 + parameters: bytes32(uint256(0x10000)) + }); + + // price = 100 tick roughly 46054 + poolManager.initialize(key, uint160(10 * FixedPoint96.Q96), new bytes(0)); + + IERC20(Currency.unwrap(currency0)).approve(address(router), 1e10 ether); + IERC20(Currency.unwrap(currency1)).approve(address(router), 1e10 ether); + + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({ + tickLower: TickMath.MIN_TICK, + tickUpper: TickMath.MAX_TICK, + liquidityDelta: 1e24, + salt: salt + }), + "" + ); + + { + assertEq(poolManager.getLiquidity(key.toId()), 1e24); + + // salt = 0 returns nothing + assertEq(poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0), 0); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt), 1e24 + ); + } + + // add into that position + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({ + tickLower: TickMath.MIN_TICK, + tickUpper: TickMath.MAX_TICK, + liquidityDelta: 1e4, + salt: salt + }), + "" + ); + + { + assertEq(poolManager.getLiquidity(key.toId()), 1e24 + 1e4); + assertEq(poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0), 0); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt), + 1e24 + 1e4 + ); + } + + // decrease liquidity + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({ + tickLower: TickMath.MIN_TICK, + tickUpper: TickMath.MAX_TICK, + liquidityDelta: -1e4, + salt: salt + }), + "" + ); + + { + assertEq(poolManager.getLiquidity(key.toId()), 1e24); + assertEq(poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, 0), 0); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt), 1e24 + ); + } + } + + function testModifyPosition_mixWithAndWithoutSalt() external { + bytes32 salt0 = bytes32(0); + bytes32 salt1 = bytes32(uint256(0x1234)); + bytes32 salt2 = bytes32(uint256(0x5678)); + + Currency currency0 = Currency.wrap(address(new ERC20PresetFixedSupply("C0", "C0", 1e10 ether, address(this)))); + Currency currency1 = Currency.wrap(address(new ERC20PresetFixedSupply("C1", "C1", 1e10 ether, address(this)))); + + if (currency0 > currency1) { + (currency0, currency1) = (currency1, currency0); + } + + PoolKey memory key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: IHooks(address(0)), + poolManager: poolManager, + fee: uint24(3000), + // 0 ~ 15 hookRegistrationMap = nil + // 16 ~ 24 tickSpacing = 1 + parameters: bytes32(uint256(0x10000)) + }); + + // price = 100 tick roughly 46054 + poolManager.initialize(key, uint160(10 * FixedPoint96.Q96), new bytes(0)); + + IERC20(Currency.unwrap(currency0)).approve(address(router), 1e10 ether); + IERC20(Currency.unwrap(currency1)).approve(address(router), 1e10 ether); + + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({ + tickLower: TickMath.MIN_TICK, + tickUpper: TickMath.MAX_TICK, + liquidityDelta: 1e24, + salt: salt1 + }), + "" + ); + + { + assertEq(poolManager.getLiquidity(key.toId()), 1e24); + + // both salt0 & salt2 remains 0 + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt0), 0 + ); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt1), 1e24 + ); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt2), 0 + ); + } + + // add into another salt + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({ + tickLower: TickMath.MIN_TICK, + tickUpper: TickMath.MAX_TICK, + liquidityDelta: 1e4, + salt: salt2 + }), + "" + ); + + { + assertEq(poolManager.getLiquidity(key.toId()), 1e24 + 1e4); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt0), 0 + ); + // salt1 position should be untouched + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt1), 1e24 + ); + + // salt2 position should be updated + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt2), 1e4 + ); + } + + // add into another salt + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({ + tickLower: TickMath.MIN_TICK, + tickUpper: TickMath.MAX_TICK, + liquidityDelta: 1e10, + salt: salt0 + }), + "" + ); + + { + assertEq(poolManager.getLiquidity(key.toId()), 1e24 + 1e4 + 1e10); + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt0), 1e10 + ); + // salt1 & salt2 positions should be untouched + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt1), 1e24 + ); + + assertEq( + poolManager.getLiquidity(key.toId(), address(router), TickMath.MIN_TICK, TickMath.MAX_TICK, salt2), 1e4 + ); + } + } + // ************** *************** // // ************** swap *************** // // ************** *************** // From c4221f6ee35e021b1b20f1b558eb13a3b0d1066c Mon Sep 17 00:00:00 2001 From: chef-burger <137024020+chefburger@users.noreply.github.com> Date: Mon, 27 May 2024 13:51:59 +0800 Subject: [PATCH 5/5] [PART-3] allow `beforeSwap` to return temp lpFee & delta for both sides (#47) * feat: finish updating for cl-pool * feat: finish updating for bin-pool * chore: correct comments removed unnecessary isValid function --- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- ...olManagerTest#testGasSwapMultipleBins.snap | 2 +- ...nagerTest#testGasSwapOverBigBinIdGate.snap | 2 +- ...nPoolManagerTest#testGasSwapSingleBin.snap | 2 +- ...oolManagerTest#addLiquidity_fromEmpty.snap | 2 +- ...ManagerTest#addLiquidity_fromNonEmpty.snap | 2 +- ...lManagerTest#addLiquidity_nativeToken.snap | 2 +- .../CLPoolManagerTest#donateBothTokens.snap | 2 +- .../CLPoolManagerTest#gasDonateOneToken.snap | 2 +- ...oolManagerTest#initializeWithoutHooks.snap | 2 +- ...anagerTest#removeLiquidity_toNonEmpty.snap | 2 +- ...PoolManagerTest#swap_againstLiquidity.snap | 2 +- ...gerTest#swap_leaveSurplusTokenInVault.snap | 2 +- ...oolManagerTest#swap_runOutOfLiquidity.snap | 2 +- .../CLPoolManagerTest#swap_simple.snap | 2 +- ...nagerTest#swap_useSurplusTokenAsInput.snap | 2 +- .../CLPoolManagerTest#swap_withHooks.snap | 2 +- .../CLPoolManagerTest#swap_withNative.snap | 2 +- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 2 +- src/libraries/LPFeeLibrary.sol | 28 ++- src/pool-bin/BinPoolManager.sol | 13 +- src/pool-bin/interfaces/IBinHooks.sol | 9 +- src/pool-bin/libraries/BinHooks.sol | 35 ++- src/pool-bin/libraries/BinPool.sol | 17 +- src/pool-cl/CLPoolManager.sol | 9 +- src/pool-cl/interfaces/ICLHooks.sol | 9 +- src/pool-cl/libraries/CLHooks.sol | 31 ++- src/pool-cl/libraries/CLPool.sol | 8 +- src/test/pool-bin/MockBinHooks.sol | 9 +- src/types/BeforeSwapDelta.sol | 40 +++ test/libraries/LPFeeLibrary.t.sol | 40 ++- test/pool-bin/BinHookReturnsDelta.t.sol | 66 ++++- test/pool-bin/BinHookReturnsFee.t.sol | 199 +++++++++++++++ test/pool-bin/helpers/BaseBinTestHook.sol | 3 +- .../helpers/BinDynamicReturnsFeeHook.sol | 58 +++++ test/pool-bin/helpers/BinFeeManagerHook.sol | 5 +- test/pool-bin/helpers/BinReturnsDeltaHook.sol | 55 ++++- test/pool-bin/helpers/BinSkipCallbackHook.sol | 5 +- test/pool-cl/CLHookReturnsDelta.t.sol | 96 +++++++- test/pool-cl/CLHookReturnsFee.sol | 230 ++++++++++++++++++ test/pool-cl/CLPoolManager.t.sol | 2 +- test/pool-cl/helpers/BaseCLTestHook.sol | 3 +- .../helpers/CLDynamicReturnsFeeHook.sol | 58 +++++ test/pool-cl/helpers/CLFeeManagerHook.sol | 5 +- test/pool-cl/helpers/CLReturnsDeltaHook.sol | 64 ++++- test/pool-cl/helpers/CLSkipCallbackHook.sol | 5 +- test/pool-cl/helpers/MockHooks.sol | 9 +- test/pool-cl/libraries/CLPool.t.sol | 38 ++- 49 files changed, 1083 insertions(+), 106 deletions(-) create mode 100644 src/types/BeforeSwapDelta.sol create mode 100644 test/pool-bin/BinHookReturnsFee.t.sol create mode 100644 test/pool-bin/helpers/BinDynamicReturnsFeeHook.sol create mode 100644 test/pool-cl/CLHookReturnsFee.sol create mode 100644 test/pool-cl/helpers/CLDynamicReturnsFeeHook.sol diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 409059bf..ce686ff3 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -193526 \ No newline at end of file +193836 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index 26cdfb14..1bfb0a9d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -32551 \ No newline at end of file +32563 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index aa5280c1..c80f85ba 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -180348 \ No newline at end of file +180562 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 9304d8fe..138ac9d4 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -186331 \ No newline at end of file +186545 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index 5c92114f..a6455c0d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -138711 \ No newline at end of file +138925 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index 1d0f5eca..cf54c9af 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -362973 \ No newline at end of file +362972 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 9aad6812..7fe27b8f 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -178141 \ No newline at end of file +178140 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index a6c3f5b0..bd641552 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -248256 \ No newline at end of file +248255 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 58be0b63..fc3ed6be 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -170426 \ No newline at end of file +170425 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index ffb8bdfd..8d4eef07 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -114349 \ No newline at end of file +114348 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 472f03e4..0b05c93e 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -59779 \ No newline at end of file +59778 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index b1f29cef..8342b66c 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -124483 \ No newline at end of file +124482 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 6fea92c4..df785b22 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -141394 \ No newline at end of file +141710 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index b519c643..eeb66ea7 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -175073 \ No newline at end of file +175389 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 38b25895..3b120af9 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -25093869 \ No newline at end of file +25094185 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 87926cb2..8d89a417 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -79260 \ No newline at end of file +79576 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 8adb1afc..3bb330bc 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -153609 \ No newline at end of file +153925 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index 44b08e04..2d773734 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -95925 \ No newline at end of file +96383 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index 7e0f50aa..800f4840 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -79263 \ No newline at end of file +79579 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap index 40100785..e9b48a35 100644 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -1 +1 @@ -32293 \ No newline at end of file +32291 \ No newline at end of file diff --git a/src/libraries/LPFeeLibrary.sol b/src/libraries/LPFeeLibrary.sol index 770812bd..6c3c1f8e 100644 --- a/src/libraries/LPFeeLibrary.sol +++ b/src/libraries/LPFeeLibrary.sol @@ -13,9 +13,16 @@ library LPFeeLibrary { error FeeTooLarge(); /// @dev the flag and mask - uint24 public constant STATIC_FEE_MASK = 0x0FFFFF; + uint24 public constant FEE_MASK = 0x7FFFFF; + uint24 public constant OVERRIDE_MASK = 0xBFFFFF; + + // the top bit of the fee in a PoolKey is used to signal if a Pool's LP fee is dynamic uint24 public constant DYNAMIC_FEE_FLAG = 0x800000; + // the second bit of the fee returned by beforeSwap is used to signal if the stored LP fee should be overridden in this swap + // only dynamic-fee pools can return a fee via the beforeSwap hook + uint24 public constant OVERRIDE_FEE_FLAG = 0x400000; + /// @dev the fee is represented in hundredths of a bip /// @dev the max fee for cl pool is 100% and for bin, it is 10% uint24 public constant ONE_HUNDRED_PERCENT_FEE = 1_000_000; @@ -33,6 +40,23 @@ library LPFeeLibrary { function getInitialLPFee(uint24 self) internal pure returns (uint24 lpFee) { // the initial fee for a dynamic fee pool is 0 if (self.isDynamicLPFee()) return 0; - lpFee = self & STATIC_FEE_MASK; + lpFee = self & FEE_MASK; + } + + /// @notice returns true if the fee has the override flag set (top bit of the uint24) + function isOverride(uint24 self) internal pure returns (bool) { + return self & OVERRIDE_FEE_FLAG != 0; + } + + /// @notice returns a fee with the override flag removed + function removeOverrideFlag(uint24 self) internal pure returns (uint24) { + return self & OVERRIDE_MASK; + } + + /// @notice Removes the override flag and validates the fee (reverts if the fee is too large) + function removeOverrideAndValidate(uint24 self, uint24 maxFee) internal pure returns (uint24) { + uint24 fee = self.removeOverrideFlag(); + fee.validate(maxFee); + return fee; } } diff --git a/src/pool-bin/BinPoolManager.sol b/src/pool-bin/BinPoolManager.sol index f6e98652..94b49cdb 100644 --- a/src/pool-bin/BinPoolManager.sol +++ b/src/pool-bin/BinPoolManager.sol @@ -19,6 +19,7 @@ import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; import {PackedUint128Math} from "./libraries/math/PackedUint128Math.sol"; import {Extsload} from "../Extsload.sol"; import {BinHooks} from "./libraries/BinHooks.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; import "./interfaces/IBinHooks.sol"; /// @notice Holds the state for all bin pools @@ -141,13 +142,19 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { PoolId id = key.toId(); _checkPoolInitialized(id); - (uint128 amountToSwap, int128 hookDeltaSpecified) = BinHooks.beforeSwap(key, swapForY, amountIn, hookData); + (uint128 amountToSwap, BeforeSwapDelta beforeSwapDelta, uint24 lpFeeOverride) = + BinHooks.beforeSwap(key, swapForY, amountIn, hookData); /// @dev fix stack too deep { BinPool.SwapState memory state; (delta, state) = pools[id].swap( - BinPool.SwapParams({swapForY: swapForY, binStep: key.parameters.getBinStep()}), amountToSwap + BinPool.SwapParams({ + swapForY: swapForY, + binStep: key.parameters.getBinStep(), + lpFeeOverride: lpFeeOverride + }), + amountToSwap ); unchecked { @@ -164,7 +171,7 @@ contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { } BalanceDelta hookDelta; - (delta, hookDelta) = BinHooks.afterSwap(key, swapForY, amountIn, delta, hookData, hookDeltaSpecified); + (delta, hookDelta) = BinHooks.afterSwap(key, swapForY, amountIn, delta, hookData, beforeSwapDelta); if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) { vault.accountPoolBalanceDelta(key, hookDelta, address(key.hooks)); diff --git a/src/pool-bin/interfaces/IBinHooks.sol b/src/pool-bin/interfaces/IBinHooks.sol index e9d0e078..8d6f4adc 100644 --- a/src/pool-bin/interfaces/IBinHooks.sol +++ b/src/pool-bin/interfaces/IBinHooks.sol @@ -5,6 +5,7 @@ import {PoolKey} from "../../types/PoolKey.sol"; import {BalanceDelta} from "../../types/BalanceDelta.sol"; import {IBinPoolManager} from "./IBinPoolManager.sol"; import {IHooks} from "../../interfaces/IHooks.sol"; +import {BeforeSwapDelta} from "../../types/BeforeSwapDelta.sol"; uint8 constant HOOKS_BEFORE_INITIALIZE_OFFSET = 0; uint8 constant HOOKS_AFTER_INITIALIZE_OFFSET = 1; @@ -111,10 +112,14 @@ interface IBinHooks is IHooks { /// @param amountIn Amount of tokenX or tokenY in /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook /// @return bytes4 The function selector for the hook - /// @return int128 The hook's delta in specified currency + /// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. + /// @return uint24 Optionally override the lp fee, only used if three conditions are met: + /// 1) the Pool has a dynamic fee, + /// 2) the value's override flag is set to 1 i.e. vaule & OVERRIDE_FEE_FLAG = 0x400000 != 0 + /// 3) the value is less than or equal to the maximum fee (100_000) - 10% function beforeSwap(address sender, PoolKey calldata key, bool swapForY, uint128 amountIn, bytes calldata hookData) external - returns (bytes4, int128); + returns (bytes4, BeforeSwapDelta, uint24); /// @notice The hook called after a swap /// @param sender The initial msg.sender for the swap call diff --git a/src/pool-bin/libraries/BinHooks.sol b/src/pool-bin/libraries/BinHooks.sol index 0b9509ca..30a8414f 100644 --- a/src/pool-bin/libraries/BinHooks.sol +++ b/src/pool-bin/libraries/BinHooks.sol @@ -7,9 +7,13 @@ import {PoolKey} from "../../types/PoolKey.sol"; import {IBinPoolManager} from "../interfaces/IBinPoolManager.sol"; import {Hooks} from "../../libraries/Hooks.sol"; import {BalanceDelta, BalanceDeltaLibrary, toBalanceDelta} from "../../types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../types/BeforeSwapDelta.sol"; +import {LPFeeLibrary} from "../../libraries/LPFeeLibrary.sol"; library BinHooks { using Hooks for bytes32; + using LPFeeLibrary for uint24; + using BeforeSwapDeltaLibrary for BeforeSwapDelta; function validatePermissionsConflict(PoolKey memory key) internal pure { if ( @@ -95,29 +99,38 @@ library BinHooks { function beforeSwap(PoolKey memory key, bool swapForY, uint128 amountIn, bytes calldata hookData) internal - returns (uint128 amountToSwap, int128 hookDeltaSpecified) + returns (uint128 amountToSwap, BeforeSwapDelta beforeSwapDelta, uint24 lpFeeOverride) { IBinHooks hooks = IBinHooks(address(key.hooks)); amountToSwap = amountIn; /// @notice If the hook is not registered, return the original amount to swap if (!key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET, hooks)) { - return (amountToSwap, hookDeltaSpecified); + return (amountToSwap, BeforeSwapDeltaLibrary.ZERO_DELTA, lpFeeOverride); } bytes4 selector; - (selector, hookDeltaSpecified) = hooks.beforeSwap(msg.sender, key, swapForY, amountIn, hookData); + (selector, beforeSwapDelta, lpFeeOverride) = hooks.beforeSwap(msg.sender, key, swapForY, amountIn, hookData); if (selector != IBinHooks.beforeSwap.selector) { revert Hooks.InvalidHookResponse(); } + if (!key.fee.isDynamicLPFee()) { + lpFeeOverride = 0; + } + // Update the swap amount according to the hook's return - if (key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET) && hookDeltaSpecified != 0) { - /// @dev default overflow check make sure the swap amount is always valid - if (hookDeltaSpecified > 0) { - amountToSwap += uint128(hookDeltaSpecified); - } else { - amountToSwap -= uint128(-hookDeltaSpecified); + if (key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET)) { + // any return in unspecified is passed to the afterSwap hook for handling + int128 hookDeltaSpecified = beforeSwapDelta.getSpecifiedDelta(); + + if (hookDeltaSpecified != 0) { + /// @dev default overflow check make sure the swap amount is always valid + if (hookDeltaSpecified > 0) { + amountToSwap += uint128(hookDeltaSpecified); + } else { + amountToSwap -= uint128(-hookDeltaSpecified); + } } } } @@ -128,11 +141,12 @@ library BinHooks { uint128 amountIn, BalanceDelta delta, bytes calldata hookData, - int128 hookDeltaSpecified + BeforeSwapDelta beforeSwapDelta ) internal returns (BalanceDelta swapperDelta, BalanceDelta hookDelta) { IBinHooks hooks = IBinHooks(address(key.hooks)); swapperDelta = delta; + int128 hookDeltaSpecified = beforeSwapDelta.getSpecifiedDelta(); int128 hookDeltaUnspecified; if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET, hooks)) { bytes4 selector; @@ -146,6 +160,7 @@ library BinHooks { hookDeltaUnspecified = 0; } } + hookDeltaUnspecified += beforeSwapDelta.getUnspecifiedDelta(); if (hookDeltaUnspecified != 0 || hookDeltaSpecified != 0) { hookDelta = swapForY diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index 47ad1cb0..4a1efb36 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -14,6 +14,7 @@ import {SafeCast} from "./math/SafeCast.sol"; import {Constants} from "./Constants.sol"; import {FeeHelper} from "./FeeHelper.sol"; import {ProtocolFeeLibrary} from "../../libraries/ProtocolFeeLibrary.sol"; +import {LPFeeLibrary} from "../../libraries/LPFeeLibrary.sol"; library BinPool { using BinHelper for bytes32; @@ -30,6 +31,7 @@ library BinPool { using FeeHelper for uint128; using BinPool for State; using ProtocolFeeLibrary for uint24; + using LPFeeLibrary for uint24; error PoolNotInitialized(); error PoolAlreadyInitialized(); @@ -108,7 +110,7 @@ library BinPool { uint24 protocolFee = swapForY ? slot0Cache.protocolFee.getOneForZeroFee() : slot0Cache.protocolFee.getZeroForOneFee(); - uint24 swapFee = protocolFee.calculateSwapFee(params.lpFee); + uint24 swapFee = protocolFee == 0 ? params.lpFee : protocolFee.calculateSwapFee(params.lpFee); while (true) { uint128 binReserves = self.reserveOfBin[id].decode(!swapForY); @@ -155,7 +157,7 @@ library BinPool { { uint24 protocolFee = swapForY ? slot0Cache.protocolFee.getOneForZeroFee() : slot0Cache.protocolFee.getZeroForOneFee(); - swapFee = protocolFee.calculateSwapFee(params.lpFee); + swapFee = protocolFee == 0 ? params.lpFee : protocolFee.calculateSwapFee(params.lpFee); } while (true) { @@ -188,6 +190,7 @@ library BinPool { struct SwapParams { bool swapForY; uint16 binStep; + uint24 lpFeeOverride; } struct SwapState { @@ -210,8 +213,14 @@ library BinPool { bytes32 amountsLeft = swapForY ? amountIn.encodeFirst() : amountIn.encodeSecond(); bytes32 amountsOut; - /// @dev swap fee includes protocolFee (charged first) and lpFee - swapState.swapFee = swapState.protocolFee.calculateSwapFee(slot0Cache.lpFee); + { + uint24 lpFee = params.lpFeeOverride.isOverride() + ? params.lpFeeOverride.removeOverrideAndValidate(LPFeeLibrary.TEN_PERCENT_FEE) + : slot0Cache.lpFee; + + /// @dev swap fee includes protocolFee (charged first) and lpFee + swapState.swapFee = swapState.protocolFee == 0 ? lpFee : swapState.protocolFee.calculateSwapFee(lpFee); + } /// @notice early return if hook has updated amountIn to 0 if (amountIn == 0) return (result, swapState); diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index f64bd292..a54b2217 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -21,6 +21,7 @@ import {Extsload} from "../Extsload.sol"; import {SafeCast} from "../libraries/SafeCast.sol"; import {CLPoolGetters} from "./libraries/CLPoolGetters.sol"; import {CLHooks} from "./libraries/CLHooks.sol"; +import {BeforeSwapDelta} from "../types/BeforeSwapDelta.sol"; contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { using SafeCast for int256; @@ -195,14 +196,16 @@ contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { PoolId id = key.toId(); _checkPoolInitialized(id); - (int256 amountToSwap, int128 hookDeltaSpecified) = CLHooks.beforeSwap(key, params, hookData); + (int256 amountToSwap, BeforeSwapDelta beforeSwapDelta, uint24 lpFeeOverride) = + CLHooks.beforeSwap(key, params, hookData); CLPool.SwapState memory state; (delta, state) = pools[id].swap( CLPool.SwapParams({ tickSpacing: key.parameters.getTickSpacing(), zeroForOne: params.zeroForOne, amountSpecified: amountToSwap, - sqrtPriceLimitX96: params.sqrtPriceLimitX96 + sqrtPriceLimitX96: params.sqrtPriceLimitX96, + lpFeeOverride: lpFeeOverride }) ); @@ -226,7 +229,7 @@ contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { ); BalanceDelta hookDelta; - (delta, hookDelta) = CLHooks.afterSwap(key, params, delta, hookData, hookDeltaSpecified); + (delta, hookDelta) = CLHooks.afterSwap(key, params, delta, hookData, beforeSwapDelta); if (hookDelta != BalanceDeltaLibrary.ZERO_DELTA) { vault.accountPoolBalanceDelta(key, hookDelta, address(key.hooks)); diff --git a/src/pool-cl/interfaces/ICLHooks.sol b/src/pool-cl/interfaces/ICLHooks.sol index 126ba12c..17c4345d 100644 --- a/src/pool-cl/interfaces/ICLHooks.sol +++ b/src/pool-cl/interfaces/ICLHooks.sol @@ -5,6 +5,7 @@ import {PoolKey} from "../../types/PoolKey.sol"; import {BalanceDelta} from "../../types/BalanceDelta.sol"; import {ICLPoolManager} from "./ICLPoolManager.sol"; import {IHooks} from "../../interfaces/IHooks.sol"; +import {BeforeSwapDelta} from "../../types/BeforeSwapDelta.sol"; /// @dev Update PoolManager#_validateHookNoOp if theres a new offset with no-op uint8 constant HOOKS_BEFORE_INITIALIZE_OFFSET = 0; @@ -115,13 +116,17 @@ interface ICLHooks is IHooks { /// @param params The parameters for the swap /// @param hookData Arbitrary data handed into the PoolManager by the swapper to be be passed on to the hook /// @return bytes4 The function selector for the hook - /// @return int128 The hook's delta in specified currency + /// @return BeforeSwapDelta The hook's delta in specified and unspecified currencies. + /// @return uint24 Optionally override the lp fee, only used if three conditions are met: + /// 1) the Pool has a dynamic fee, + /// 2) the value's override flag is set to 1 i.e. vaule & OVERRIDE_FEE_FLAG = 0x400000 != 0 + /// 3) the value is less than or equal to the maximum fee (1 million) function beforeSwap( address sender, PoolKey calldata key, ICLPoolManager.SwapParams calldata params, bytes calldata hookData - ) external returns (bytes4, int128); + ) external returns (bytes4, BeforeSwapDelta, uint24); /// @notice The hook called after a swap /// @param sender The initial msg.sender for the swap call diff --git a/src/pool-cl/libraries/CLHooks.sol b/src/pool-cl/libraries/CLHooks.sol index d4cd262f..618213ca 100644 --- a/src/pool-cl/libraries/CLHooks.sol +++ b/src/pool-cl/libraries/CLHooks.sol @@ -7,9 +7,13 @@ import {PoolKey} from "../../types/PoolKey.sol"; import {ICLPoolManager} from "../interfaces/ICLPoolManager.sol"; import {Hooks} from "../../libraries/Hooks.sol"; import {BalanceDelta, BalanceDeltaLibrary, toBalanceDelta} from "../../types/BalanceDelta.sol"; +import {LPFeeLibrary} from "../../libraries/LPFeeLibrary.sol"; +import {BeforeSwapDeltaLibrary, BeforeSwapDelta} from "../../types/BeforeSwapDelta.sol"; library CLHooks { using Hooks for bytes32; + using LPFeeLibrary for uint24; + using BeforeSwapDeltaLibrary for BeforeSwapDelta; function validatePermissionsConflict(PoolKey memory key) internal pure { if ( @@ -83,28 +87,37 @@ library CLHooks { function beforeSwap(PoolKey memory key, ICLPoolManager.SwapParams memory params, bytes calldata hookData) internal - returns (int256 amountToSwap, int128 hookDeltaSpecified) + returns (int256 amountToSwap, BeforeSwapDelta beforeSwapDelta, uint24 lpFeeOverride) { ICLHooks hooks = ICLHooks(address(key.hooks)); amountToSwap = params.amountSpecified; /// @notice If the hook is not registered, return the original amount to swap if (!key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET, hooks)) { - return (amountToSwap, hookDeltaSpecified); + return (amountToSwap, beforeSwapDelta, lpFeeOverride); } bytes4 selector; // TODO: Potentially optimization: skip decoding the second return value when afterSwapReturnDelta not set - (selector, hookDeltaSpecified) = hooks.beforeSwap(msg.sender, key, params, hookData); + (selector, beforeSwapDelta, lpFeeOverride) = hooks.beforeSwap(msg.sender, key, params, hookData); if (selector != ICLHooks.beforeSwap.selector) { revert Hooks.InvalidHookResponse(); } + if (!key.fee.isDynamicLPFee()) { + lpFeeOverride = 0; + } + // Update the swap amount according to the hook's return, and check that the swap type doesnt change (exact input/output) - if (key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET) && hookDeltaSpecified != 0) { - bool exactInput = amountToSwap > 0; - amountToSwap += hookDeltaSpecified; - if (exactInput ? amountToSwap < 0 : amountToSwap > 0) revert Hooks.HookDeltaExceedsSwapAmount(); + if (key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET)) { + // any return in unspecified is passed to the afterSwap hook for handling + int128 hookDeltaSpecified = beforeSwapDelta.getSpecifiedDelta(); + + if (hookDeltaSpecified != 0) { + bool exactInput = amountToSwap > 0; + amountToSwap += hookDeltaSpecified; + if (exactInput ? amountToSwap < 0 : amountToSwap > 0) revert Hooks.HookDeltaExceedsSwapAmount(); + } } } @@ -113,11 +126,12 @@ library CLHooks { ICLPoolManager.SwapParams memory params, BalanceDelta delta, bytes calldata hookData, - int128 hookDeltaSpecified + BeforeSwapDelta beforeSwapDelta ) internal returns (BalanceDelta swapperDelta, BalanceDelta hookDelta) { ICLHooks hooks = ICLHooks(address(key.hooks)); swapperDelta = delta; + int128 hookDeltaSpecified = beforeSwapDelta.getSpecifiedDelta(); int128 hookDeltaUnspecified; if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET, hooks)) { bytes4 selector; @@ -130,6 +144,7 @@ library CLHooks { hookDeltaUnspecified = 0; } } + hookDeltaUnspecified += beforeSwapDelta.getUnspecifiedDelta(); if (hookDeltaUnspecified != 0 || hookDeltaSpecified != 0) { hookDelta = (params.amountSpecified > 0 == params.zeroForOne) diff --git a/src/pool-cl/libraries/CLPool.sol b/src/pool-cl/libraries/CLPool.sol index 3e6dcd43..e6c4cfe5 100644 --- a/src/pool-cl/libraries/CLPool.sol +++ b/src/pool-cl/libraries/CLPool.sol @@ -26,6 +26,7 @@ library CLPool { using LiquidityMath for uint128; using CLPool for State; using ProtocolFeeLibrary for uint24; + using LPFeeLibrary for uint24; /// @notice Thrown when trying to initalize an already initialized pool error PoolAlreadyInitialized(); @@ -191,6 +192,7 @@ library CLPool { bool zeroForOne; int256 amountSpecified; uint160 sqrtPriceLimitX96; + uint24 lpFeeOverride; } function swap(State storage self, SwapParams memory params) @@ -221,12 +223,16 @@ library CLPool { uint16 protocolFee = zeroForOne ? slot0Start.protocolFee.getZeroForOneFee() : slot0Start.protocolFee.getOneForZeroFee(); + uint24 lpFee = params.lpFeeOverride.isOverride() + ? params.lpFeeOverride.removeOverrideAndValidate(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) + : slot0Start.lpFee; + state = SwapState({ amountSpecifiedRemaining: params.amountSpecified, amountCalculated: 0, sqrtPriceX96: slot0Start.sqrtPriceX96, tick: slot0Start.tick, - swapFee: protocolFee == 0 ? slot0Start.lpFee : uint24(protocolFee).calculateSwapFee(slot0Start.lpFee), + swapFee: protocolFee == 0 ? lpFee : uint24(protocolFee).calculateSwapFee(lpFee), protocolFee: protocolFee, feeGrowthGlobalX128: zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128, feeForProtocol: 0, diff --git a/src/test/pool-bin/MockBinHooks.sol b/src/test/pool-bin/MockBinHooks.sol index dca034d9..25d9ad8b 100644 --- a/src/test/pool-bin/MockBinHooks.sol +++ b/src/test/pool-bin/MockBinHooks.sol @@ -6,6 +6,7 @@ import {IBinHooks} from "../../pool-bin/interfaces/IBinHooks.sol"; import {IBinPoolManager} from "../../pool-bin/interfaces/IBinPoolManager.sol"; import {PoolKey} from "../../types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../../types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../types/BeforeSwapDelta.sol"; import {PoolId, PoolIdLibrary} from "../../types/PoolId.sol"; contract MockBinHooks is IBinHooks { @@ -98,11 +99,15 @@ contract MockBinHooks is IBinHooks { function beforeSwap(address, PoolKey calldata, bool, uint128, bytes calldata hookData) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { beforeSwapData = hookData; bytes4 selector = MockBinHooks.beforeSwap.selector; - return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0); + return ( + returnValues[selector] == bytes4(0) ? selector : returnValues[selector], + BeforeSwapDeltaLibrary.ZERO_DELTA, + 0 + ); } function afterSwap(address, PoolKey calldata, bool, uint128, BalanceDelta, bytes calldata hookData) diff --git a/src/types/BeforeSwapDelta.sol b/src/types/BeforeSwapDelta.sol new file mode 100644 index 00000000..d167b5ae --- /dev/null +++ b/src/types/BeforeSwapDelta.sol @@ -0,0 +1,40 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {BalanceDelta} from "./BalanceDelta.sol"; + +// Return type of the beforeSwap hook. +// Upper 128 bits is the delta in specified tokens. Lower 128 bits is delta in unspecified tokens (to match the afterSwap hook) +type BeforeSwapDelta is int256; + +// Creates a BeforeSwapDelta from specified and unspecified +function toBeforeSwapDelta(int128 deltaSpecified, int128 deltaUnspecified) + pure + returns (BeforeSwapDelta beforeSwapDelta) +{ + /// @solidity memory-safe-assembly + assembly { + beforeSwapDelta := or(shl(128, deltaSpecified), and(sub(shl(128, 1), 1), deltaUnspecified)) + } +} + +library BeforeSwapDeltaLibrary { + BeforeSwapDelta public constant ZERO_DELTA = BeforeSwapDelta.wrap(0); + + /// extracts int128 from the upper 128 bits of the BeforeSwapDelta + /// returned by beforeSwap + function getSpecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaSpecified) { + assembly { + deltaSpecified := sar(128, delta) + } + } + + /// extracts int128 from the lower 128 bits of the BeforeSwapDelta + /// returned by beforeSwap and afterSwap + function getUnspecifiedDelta(BeforeSwapDelta delta) internal pure returns (int128 deltaUnspecified) { + /// @solidity memory-safe-assembly + assembly { + deltaUnspecified := signextend(15, delta) + } + } +} diff --git a/test/libraries/LPFeeLibrary.t.sol b/test/libraries/LPFeeLibrary.t.sol index dd1acff1..04ec1f01 100644 --- a/test/libraries/LPFeeLibrary.t.sol +++ b/test/libraries/LPFeeLibrary.t.sol @@ -25,7 +25,7 @@ contract LPFeeLibraryTest is Test { assertEq(LPFeeLibrary.isDynamicLPFee(0xFFFFFF), true); // 0111 1111 1111 1111 1111 1111 - assertEq(LPFeeLibrary.isDynamicLPFee(0x7FFFF), false); + assertEq(LPFeeLibrary.isDynamicLPFee(0x7FFFFF), false); } function testGetInitialLPFee() public { @@ -34,12 +34,14 @@ contract LPFeeLibraryTest is Test { assertEq(LPFeeLibrary.getInitialLPFee(0x000002), 0x000002); assertEq(LPFeeLibrary.getInitialLPFee(0x0F0003), 0x0F0003); assertEq(LPFeeLibrary.getInitialLPFee(0x001004), 0x001004); - assertEq(LPFeeLibrary.getInitialLPFee(0x111020), 0x011020); - assertEq(LPFeeLibrary.getInitialLPFee(0x101020), 0x001020); + assertEq(LPFeeLibrary.getInitialLPFee(0x111020), 0x111020); + assertEq(LPFeeLibrary.getInitialLPFee(0x511020), 0x511020); // dynamic assertEq(LPFeeLibrary.getInitialLPFee(0xF00F05), 0); assertEq(LPFeeLibrary.getInitialLPFee(0x800310), 0); + assertEq(LPFeeLibrary.getInitialLPFee(0x800000), 0); + assertEq(LPFeeLibrary.getInitialLPFee(0x901020), 0); } function testFuzzValidate(uint24 self, uint24 maxFee) public { @@ -48,4 +50,36 @@ contract LPFeeLibraryTest is Test { } LPFeeLibrary.validate(self, maxFee); } + + function testIsOverride() public { + // 1000 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isOverride(0x800000), false); + + // 0100 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isOverride(0x400000), true); + + // 0010 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isOverride(0x200000), false); + + // 0001 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isOverride(0x100000), false); + + // 1111 1111 1111 1111 1111 1111 + assertEq(LPFeeLibrary.isOverride(0xFFFFFF), true); + + // 0111 1111 1111 1111 1111 1111 + assertEq(LPFeeLibrary.isOverride(0x7FFFFF), true); + + // 1011 1111 1111 1111 1111 1111 + assertEq(LPFeeLibrary.isOverride(0xBFFFFF), false); + } + + function testFuzzRemoveOverrideAndValidate(uint24 self, uint24 maxFee) public { + if ((self & 0xBFFFFF) > maxFee) { + vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); + } + + uint24 fee = self.removeOverrideAndValidate(maxFee); + assertEq(fee, self & 0xBFFFFF); + } } diff --git a/test/pool-bin/BinHookReturnsDelta.t.sol b/test/pool-bin/BinHookReturnsDelta.t.sol index ed31ccfd..063bc1f1 100644 --- a/test/pool-bin/BinHookReturnsDelta.t.sol +++ b/test/pool-bin/BinHookReturnsDelta.t.sol @@ -144,7 +144,7 @@ contract BinHookReturnsDelta is Test, GasSnapshot, BinTestHelper { uint256 amt1Before = token1.balanceOf(address(vault)); BalanceDelta delta = - binSwapHelper.swap(key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(-1 ether)); + binSwapHelper.swap(key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(-1 ether, 0, 0)); uint256 amt0After = token0.balanceOf(address(vault)); uint256 amt1After = token1.balanceOf(address(vault)); @@ -162,6 +162,66 @@ contract BinHookReturnsDelta is Test, GasSnapshot, BinTestHelper { assertEq(token0.balanceOf(address(binReturnsDeltaHook)), 1 ether); } + function testSwap_noSwap_returnUnspecifiedInBeforeSwap() external { + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 10 ether, 10 ether); + binLiquidityHelper.mint(key, mintParams, abi.encode(0)); + + token1.transfer(address(binReturnsDeltaHook), 1 ether); + + uint256 amt0Before = token0.balanceOf(address(vault)); + uint256 amt1Before = token1.balanceOf(address(vault)); + + BalanceDelta delta = binSwapHelper.swap( + key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(-1 ether, 1 ether, 0) + ); + + uint256 amt0After = token0.balanceOf(address(vault)); + uint256 amt1After = token1.balanceOf(address(vault)); + + assertEq(amt0After - amt0Before, 0); + assertEq(amt1After - amt1Before, 0); + + // user pays 1 ether of currency0 to hook and no swap happens + + // trader's payment & return + assertEq(delta.amount0(), 1 ether); + assertEq(delta.amount1(), -1 ether); + + // hook's payment & return + assertEq(token0.balanceOf(address(binReturnsDeltaHook)), 1 ether); + assertEq(token1.balanceOf(address(binReturnsDeltaHook)), 0 ether); + } + + function testSwap_noSwap_returnUnspecifiedInBeforeSwapAndAfterSwap() external { + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 10 ether, 10 ether); + binLiquidityHelper.mint(key, mintParams, abi.encode(0)); + + token1.transfer(address(binReturnsDeltaHook), 1 ether); + + uint256 amt0Before = token0.balanceOf(address(vault)); + uint256 amt1Before = token1.balanceOf(address(vault)); + + BalanceDelta delta = binSwapHelper.swap( + key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(-1 ether, 0.5 ether, 0.5 ether) + ); + + uint256 amt0After = token0.balanceOf(address(vault)); + uint256 amt1After = token1.balanceOf(address(vault)); + + assertEq(amt0After - amt0Before, 0); + assertEq(amt1After - amt1Before, 0); + + // user pays 1 ether of currency0 to hook and no swap happens + + // trader's payment & return + assertEq(delta.amount0(), 1 ether); + assertEq(delta.amount1(), -1 ether); + + // hook's payment & return + assertEq(token0.balanceOf(address(binReturnsDeltaHook)), 1 ether); + assertEq(token1.balanceOf(address(binReturnsDeltaHook)), 0 ether); + } + function testSwap_swapMore() external { IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 10 ether, 10 ether); binLiquidityHelper.mint(key, mintParams, abi.encode(0)); @@ -172,7 +232,7 @@ contract BinHookReturnsDelta is Test, GasSnapshot, BinTestHelper { token0.transfer(address(binReturnsDeltaHook), 1 ether); BalanceDelta delta = - binSwapHelper.swap(key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(1 ether)); + binSwapHelper.swap(key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(1 ether, 0, 0)); uint256 amt0After = token0.balanceOf(address(vault)); uint256 amt1After = token1.balanceOf(address(vault)); @@ -198,7 +258,7 @@ contract BinHookReturnsDelta is Test, GasSnapshot, BinTestHelper { uint256 amt1Before = token1.balanceOf(address(vault)); BalanceDelta delta = - binSwapHelper.swap(key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(-0.5 ether)); + binSwapHelper.swap(key, true, 1 ether, BinSwapHelper.TestSettings(true, true), abi.encode(-0.5 ether, 0, 0)); uint256 amt0After = token0.balanceOf(address(vault)); uint256 amt1After = token1.balanceOf(address(vault)); diff --git a/test/pool-bin/BinHookReturnsFee.t.sol b/test/pool-bin/BinHookReturnsFee.t.sol new file mode 100644 index 00000000..510a6a56 --- /dev/null +++ b/test/pool-bin/BinHookReturnsFee.t.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {Vault} from "../../src/Vault.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; +import {Hooks} from "../../src/libraries/Hooks.sol"; +import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; +import {IProtocolFees} from "../../src/interfaces/IProtocolFees.sol"; +import {IBinHooks} from "../../src/pool-bin/interfaces/IBinHooks.sol"; +import {PoolKey} from "../../src/types/PoolKey.sol"; +import {BinPoolManager} from "../../src/pool-bin/BinPoolManager.sol"; +import {IBinPoolManager} from "../../src/pool-bin/interfaces/IBinPoolManager.sol"; +import {BinDynamicReturnsFeeHook} from "./helpers/BinDynamicReturnsFeeHook.sol"; +import {Currency, CurrencyLibrary} from "../../src/types/Currency.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {FullMath} from "../../src/pool-cl/libraries/FullMath.sol"; +import {BalanceDelta} from "../../src/types/BalanceDelta.sol"; +import {BinTestHelper} from "./helpers/BinTestHelper.sol"; +import {BinLiquidityHelper} from "./helpers/BinLiquidityHelper.sol"; +import {BinSwapHelper} from "./helpers/BinSwapHelper.sol"; +import {BinPoolParametersHelper} from "../../src/pool-bin/libraries/BinPoolParametersHelper.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract BinHookReturnsFeeTest is Test, BinTestHelper { + using PoolIdLibrary for PoolKey; + using LPFeeLibrary for uint24; + using BinPoolParametersHelper for bytes32; + + Vault public vault; + BinPoolManager public poolManager; + BinDynamicReturnsFeeHook dynamicReturnsFeesHook; + + BinSwapHelper public binSwapHelper; + BinLiquidityHelper public binLiquidityHelper; + + uint24 activeId = 2 ** 23; // where token0 and token1 price is the same + + PoolKey key; + bytes32 poolParam; + MockERC20 token0; + MockERC20 token1; + Currency currency0; + Currency currency1; + + event Swap( + PoolId indexed poolId, + address sender, + int128 amount0, + int128 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick, + uint24 fee + ); + + function setUp() public { + vault = new Vault(); + poolManager = new BinPoolManager(IVault(address(vault)), 500000); + vault.registerPoolManager(address(poolManager)); + + // initializeTokens + token0 = new MockERC20("TestA", "A", 18); + token1 = new MockERC20("TestB", "B", 18); + (token0, token1) = token0 < token1 ? (token0, token1) : (token1, token0); + currency0 = Currency.wrap(address(token0)); + currency1 = Currency.wrap(address(token1)); + + token0.mint(address(this), 1000 ether); + token1.mint(address(this), 1000 ether); + + dynamicReturnsFeesHook = new BinDynamicReturnsFeeHook(); + dynamicReturnsFeesHook.setManager(poolManager); + + binSwapHelper = new BinSwapHelper(poolManager, vault); + binLiquidityHelper = new BinLiquidityHelper(poolManager, vault); + token0.approve(address(binSwapHelper), 1000 ether); + token1.approve(address(binSwapHelper), 1000 ether); + token0.approve(address(binLiquidityHelper), 1000 ether); + token1.approve(address(binLiquidityHelper), 1000 ether); + + token0.approve(address(dynamicReturnsFeesHook), 1000 ether); + token1.approve(address(dynamicReturnsFeesHook), 1000 ether); + + key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: dynamicReturnsFeesHook, + poolManager: poolManager, + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG, // 3000 = 0.3% + parameters: bytes32(uint256(dynamicReturnsFeesHook.getHooksRegistrationBitmap())).setBinStep(10) + }); + + poolManager.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binLiquidityHelper.mint(key, mintParams, abi.encode(0)); + } + + function test_fuzz_dynamicReturnSwapFee(uint24 fee) public { + // hook will handle adding the override flag + dynamicReturnsFeesHook.setFee(fee); + + uint24 actualFee = fee.removeOverrideFlag(); + + uint128 amountSpecified = 10000; + BalanceDelta result; + if (actualFee > LPFeeLibrary.TEN_PERCENT_FEE) { + vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); + result = + binSwapHelper.swap(key, true, amountSpecified, BinSwapHelper.TestSettings(true, true), new bytes(0)); + return; + } else { + result = + binSwapHelper.swap(key, true, amountSpecified, BinSwapHelper.TestSettings(true, true), new bytes(0)); + } + assertEq(result.amount0(), int128(amountSpecified)); + + assertApproxEqAbs( + uint256(-int256(result.amount1())), FullMath.mulDiv(uint256(amountSpecified), (1e6 - actualFee), 1e6), 1 wei + ); + } + + function test_dynamicReturnSwapFee_initializeZeroSwapFee() public { + key.parameters = + BinPoolParametersHelper.setBinStep(bytes32(uint256(dynamicReturnsFeesHook.getHooksRegistrationBitmap())), 1); + poolManager.initialize(key, activeId, new bytes(0)); + assertEq(_fetchPoolSwapFee(key), 0); + } + + function test_dynamicReturnSwapFee_notUsedIfPoolIsStaticFee() public { + key.fee = 3000; // static fee + dynamicReturnsFeesHook.setFee(1000); // 0.10% fee is NOT used because the pool has a static fee + + poolManager.initialize(key, activeId, new bytes(0)); + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binLiquidityHelper.mint(key, mintParams, abi.encode(0)); + assertEq(_fetchPoolSwapFee(key), 3000); + + // despite returning a valid swap fee (1000), the static fee is used + uint128 amountSpecified = 10000; + BalanceDelta result; + result = binSwapHelper.swap(key, true, amountSpecified, BinSwapHelper.TestSettings(true, true), new bytes(0)); + + // after swapping ~1:1, the amount out (amount1) should be approximately 0.30% less than the amount specified + assertEq(result.amount0(), int128(amountSpecified)); + assertApproxEqAbs( + uint256(-int256(result.amount1())), FullMath.mulDiv(uint256(amountSpecified), (1e6 - 3000), 1e6), 1 wei + ); + } + + function test_dynamicReturnSwapFee_notStored() public { + // fees returned by beforeSwap are not written to storage + + // create a new pool with an initial fee of 123 + key.parameters = + BinPoolParametersHelper.setBinStep(bytes32(uint256(dynamicReturnsFeesHook.getHooksRegistrationBitmap())), 1); + poolManager.initialize(key, activeId, new bytes(0)); + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binLiquidityHelper.mint(key, mintParams, abi.encode(0)); + + uint24 initialFee = 123; + dynamicReturnsFeesHook.forcePoolFeeUpdate(key, initialFee); + assertEq(_fetchPoolSwapFee(key), initialFee); + + // swap with a different fee + uint24 newFee = 3000; + dynamicReturnsFeesHook.setFee(newFee); + + uint128 amountSpecified = 10000; + BalanceDelta result = + binSwapHelper.swap(key, true, amountSpecified, BinSwapHelper.TestSettings(true, true), new bytes(0)); + + assertApproxEqAbs( + uint256(-int256(result.amount1())), FullMath.mulDiv(uint256(amountSpecified), (1e6 - newFee), 1e6), 1 wei + ); + + // the fee from beforeSwap is not stored + assertEq(_fetchPoolSwapFee(key), initialFee); + } + + function test_dynamicReturnSwapFee_revertIfFeeTooLarge() public { + assertEq(_fetchPoolSwapFee(key), 0); + + // hook adds the override flag + dynamicReturnsFeesHook.setFee(1000001); + + // a large fee is not used + uint128 amountSpecified = 10000; + vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); + binSwapHelper.swap(key, true, amountSpecified, BinSwapHelper.TestSettings(true, true), new bytes(0)); + } + + function _fetchPoolSwapFee(PoolKey memory _key) internal view returns (uint256 swapFee) { + PoolId id = _key.toId(); + (,, swapFee) = poolManager.getSlot0(id); + } +} diff --git a/test/pool-bin/helpers/BaseBinTestHook.sol b/test/pool-bin/helpers/BaseBinTestHook.sol index ffb8476d..02597b1d 100644 --- a/test/pool-bin/helpers/BaseBinTestHook.sol +++ b/test/pool-bin/helpers/BaseBinTestHook.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import "../../../src/pool-bin/interfaces/IBinHooks.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; +import {BeforeSwapDelta} from "../../../src/types/BeforeSwapDelta.sol"; import {IBinHooks} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager.sol"; @@ -74,7 +75,7 @@ contract BaseBinTestHook is IBinHooks { function beforeSwap(address, PoolKey calldata, bool, uint128, bytes calldata) external virtual - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { revert HookNotImplemented(); } diff --git a/test/pool-bin/helpers/BinDynamicReturnsFeeHook.sol b/test/pool-bin/helpers/BinDynamicReturnsFeeHook.sol new file mode 100644 index 00000000..ddb56389 --- /dev/null +++ b/test/pool-bin/helpers/BinDynamicReturnsFeeHook.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {BaseBinTestHook} from "./BaseBinTestHook.sol"; +import {PoolKey} from "../../../src/types/PoolKey.sol"; +import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager.sol"; +import {IBinHooks} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; + +contract BinDynamicReturnsFeeHook is BaseBinTestHook { + using LPFeeLibrary for uint24; + + uint24 internal fee; + IBinPoolManager manager; + + function getHooksRegistrationBitmap() external pure override returns (uint16) { + return _hooksRegistrationBitmapFrom( + Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeMint: false, + afterMint: false, + beforeBurn: false, + afterBurn: false, + beforeSwap: true, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnsDelta: false, + afterSwapReturnsDelta: false, + afterMintReturnsDelta: false, + afterBurnReturnsDelta: false + }) + ); + } + + function setManager(IBinPoolManager _manager) external { + manager = _manager; + } + + function setFee(uint24 _fee) external { + fee = _fee; + } + + function beforeSwap(address, PoolKey calldata, bool, uint128, bytes calldata) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + // attach the fee flag to `fee` to enable overriding the pool's stored fee + return (IBinHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); + } + + function forcePoolFeeUpdate(PoolKey calldata _key, uint24 _fee) external { + manager.updateDynamicLPFee(_key, _fee); + } +} diff --git a/test/pool-bin/helpers/BinFeeManagerHook.sol b/test/pool-bin/helpers/BinFeeManagerHook.sol index c91ed947..338b1656 100644 --- a/test/pool-bin/helpers/BinFeeManagerHook.sol +++ b/test/pool-bin/helpers/BinFeeManagerHook.sol @@ -9,6 +9,7 @@ import {IBinDynamicFeeManager} from "../../../src/pool-bin/interfaces/IBinDynami import {PoolId, PoolIdLibrary} from "../../../src/types/PoolId.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BaseBinTestHook} from "./BaseBinTestHook.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; contract BinFeeManagerHook is BaseBinTestHook, IBinDynamicFeeManager { using PoolIdLibrary for PoolKey; @@ -61,7 +62,7 @@ contract BinFeeManagerHook is BaseBinTestHook, IBinDynamicFeeManager { function beforeSwap(address, PoolKey calldata key, bool, uint128, bytes calldata hookData) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { if (hookData.length > 0) { (bool _update, uint24 _fee) = abi.decode(hookData, (bool, uint24)); @@ -71,6 +72,6 @@ contract BinFeeManagerHook is BaseBinTestHook, IBinDynamicFeeManager { } } - return (IBinHooks.beforeSwap.selector, 0); + return (IBinHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } } diff --git a/test/pool-bin/helpers/BinReturnsDeltaHook.sol b/test/pool-bin/helpers/BinReturnsDeltaHook.sol index 5f363d94..776fcc60 100644 --- a/test/pool-bin/helpers/BinReturnsDeltaHook.sol +++ b/test/pool-bin/helpers/BinReturnsDeltaHook.sol @@ -8,6 +8,7 @@ import {PoolKey} from "../../../src/types/PoolKey.sol"; import {Currency, CurrencyLibrary} from "../../../src/types/Currency.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {toBalanceDelta, BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, toBeforeSwapDelta} from "../../../src/types/BeforeSwapDelta.sol"; import {BaseBinTestHook} from "./BaseBinTestHook.sol"; contract BinReturnsDeltaHook is BaseBinTestHook { @@ -87,9 +88,9 @@ contract BinReturnsDeltaHook is BaseBinTestHook { function beforeSwap(address, PoolKey calldata key, bool swapForY, uint128, bytes calldata data) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { - (int128 hookDeltaSpecified) = abi.decode(data, (int128)); + (int128 hookDeltaSpecified, int128 hookDeltaUnspecified,) = abi.decode(data, (int128, int128, int128)); if (swapForY) { // the specified token is token0 @@ -100,6 +101,14 @@ contract BinReturnsDeltaHook is BaseBinTestHook { } else { vault.take(key.currency0, address(this), uint128(-hookDeltaSpecified)); } + + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency1); + key.currency1.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency1); + } else { + vault.take(key.currency1, address(this), uint128(-hookDeltaUnspecified)); + } } else { // the specified token is token1 if (hookDeltaSpecified > 0) { @@ -109,16 +118,50 @@ contract BinReturnsDeltaHook is BaseBinTestHook { } else { vault.take(key.currency1, address(this), uint128(-hookDeltaSpecified)); } + + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency0); + key.currency0.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency0); + } else { + vault.take(key.currency0, address(this), uint128(-hookDeltaUnspecified)); + } } - return (this.beforeSwap.selector, hookDeltaSpecified); + + return (this.beforeSwap.selector, toBeforeSwapDelta(hookDeltaSpecified, hookDeltaUnspecified), 0); } - function afterSwap(address, PoolKey calldata, bool, uint128, BalanceDelta, bytes calldata) + function afterSwap(address, PoolKey calldata key, bool swapForY, uint128, BalanceDelta, bytes calldata data) external - pure override returns (bytes4, int128) { - return (this.afterSwap.selector, 0); + (,, int128 hookDeltaUnspecified) = abi.decode(data, (int128, int128, int128)); + + if (hookDeltaUnspecified == 0) { + return (this.afterSwap.selector, 0); + } + + if (swapForY) { + // the unspecified token is token1 + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency1); + key.currency1.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency1); + } else { + vault.take(key.currency1, address(this), uint128(-hookDeltaUnspecified)); + } + } else { + // the unspecified token is token0 + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency0); + key.currency0.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency0); + } else { + vault.take(key.currency0, address(this), uint128(-hookDeltaUnspecified)); + } + } + + return (this.afterSwap.selector, hookDeltaUnspecified); } } diff --git a/test/pool-bin/helpers/BinSkipCallbackHook.sol b/test/pool-bin/helpers/BinSkipCallbackHook.sol index 19ad9a2f..0aa6c71e 100644 --- a/test/pool-bin/helpers/BinSkipCallbackHook.sol +++ b/test/pool-bin/helpers/BinSkipCallbackHook.sol @@ -7,6 +7,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; import {IBinHooks} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager.sol"; import {Hooks} from "../../../src/libraries/Hooks.sol"; @@ -271,10 +272,10 @@ contract BinSkipCallbackHook is BaseBinTestHook { function beforeSwap(address, PoolKey calldata, bool, uint128, bytes calldata) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { hookCounterCallbackCount++; - return (BinSkipCallbackHook.beforeSwap.selector, 0); + return (BinSkipCallbackHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function afterSwap(address, PoolKey calldata, bool, uint128, BalanceDelta, bytes calldata) diff --git a/test/pool-cl/CLHookReturnsDelta.t.sol b/test/pool-cl/CLHookReturnsDelta.t.sol index d090c1da..f657071f 100644 --- a/test/pool-cl/CLHookReturnsDelta.t.sol +++ b/test/pool-cl/CLHookReturnsDelta.t.sol @@ -193,7 +193,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1 }), CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), - abi.encode(-1 ether) + abi.encode(-1 ether, 0, 0) ); uint256 amt0After = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); @@ -237,7 +237,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1 }), CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), - abi.encode(1 ether) + abi.encode(1 ether, 0, 0) ); uint256 amt0After = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); @@ -258,6 +258,94 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { assertEq(liquidityBefore, liquidityAfter); } + function testSwap_noSwap_returnUnspecifiedInBeforeSwap() external { + // add some liquidity first in case the pool is empty + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), + abi.encode(10000 ether) + ); + + currency1.transfer(address(clReturnsDeltaHook), 1 ether); + + uint256 amt0Before = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); + uint256 amt1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(vault)); + uint128 liquidityBefore = poolManager.getLiquidity(key.toId()); + + (BalanceDelta delta) = router.swap( + key, + ICLPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: 1 ether, + sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1 + }), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + abi.encode(-1 ether, 1 ether, 0) + ); + + uint256 amt0After = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); + uint256 amt1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(vault)); + uint128 liquidityAfter = poolManager.getLiquidity(key.toId()); + + // user pays 1 ether of currency0 to hook and no swap happens + + // trader's payment & return + assertEq(delta.amount0(), 1 ether); + assertEq(delta.amount1(), -1 ether); + + // hook's payment & return + assertEq(IERC20(Currency.unwrap(currency0)).balanceOf(address(clReturnsDeltaHook)), 1 ether); + assertEq(IERC20(Currency.unwrap(currency1)).balanceOf(address(clReturnsDeltaHook)), 0 ether); + + assertEq(amt0Before, amt0After); + assertEq(amt1Before, amt1After); + assertEq(liquidityBefore, liquidityAfter); + } + + function testSwap_noSwap_returnUnspecifiedInBeforeSwapAndAfterSwap() external { + // add some liquidity first in case the pool is empty + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), + abi.encode(10000 ether) + ); + + currency1.transfer(address(clReturnsDeltaHook), 1 ether); + + uint256 amt0Before = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); + uint256 amt1Before = IERC20(Currency.unwrap(currency1)).balanceOf(address(vault)); + uint128 liquidityBefore = poolManager.getLiquidity(key.toId()); + + (BalanceDelta delta) = router.swap( + key, + ICLPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: 1 ether, + sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1 + }), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + abi.encode(-1 ether, 0.5 ether, 0.5 ether) + ); + + uint256 amt0After = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); + uint256 amt1After = IERC20(Currency.unwrap(currency1)).balanceOf(address(vault)); + uint128 liquidityAfter = poolManager.getLiquidity(key.toId()); + + // user pays 1 ether of currency0 to hook and no swap happens + + // trader's payment & return + assertEq(delta.amount0(), 1 ether); + assertEq(delta.amount1(), -1 ether); + + // hook's payment & return + assertEq(IERC20(Currency.unwrap(currency0)).balanceOf(address(clReturnsDeltaHook)), 1 ether); + assertEq(IERC20(Currency.unwrap(currency1)).balanceOf(address(clReturnsDeltaHook)), 0 ether); + + assertEq(amt0Before, amt0After); + assertEq(amt1Before, amt1After); + assertEq(liquidityBefore, liquidityAfter); + } + function testSwap_SwapMore() external { // add some liquidity first in case the pool is empty router.modifyPosition( @@ -282,7 +370,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { }), CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), // double the swap amt - abi.encode(1 ether) + abi.encode(1 ether, 0, 0) ); uint256 amt0After = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); @@ -321,7 +409,7 @@ contract CLHookReturnsDeltaTest is Test, Deployers, TokenFixture, GasSnapshot { sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1 }), CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), - abi.encode(-0.5 ether) + abi.encode(-0.5 ether, 0, 0) ); uint256 amt0After = IERC20(Currency.unwrap(currency0)).balanceOf(address(vault)); diff --git a/test/pool-cl/CLHookReturnsFee.sol b/test/pool-cl/CLHookReturnsFee.sol new file mode 100644 index 00000000..e6a9a30f --- /dev/null +++ b/test/pool-cl/CLHookReturnsFee.sol @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import {Test} from "forge-std/Test.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; +import {Hooks} from "../../src/libraries/Hooks.sol"; +import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; +import {IProtocolFees} from "../../src/interfaces/IProtocolFees.sol"; +import {ICLHooks} from "../../src/pool-cl/interfaces/ICLHooks.sol"; +import {PoolKey} from "../../src/types/PoolKey.sol"; +import {CLPoolManager} from "../../src/pool-cl/CLPoolManager.sol"; +import {ICLPoolManager} from "../../src/pool-cl/interfaces/ICLPoolManager.sol"; +import {Deployers} from "./helpers/Deployers.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {CLDynamicReturnsFeeHook} from "./helpers/CLDynamicReturnsFeeHook.sol"; +import {Currency, CurrencyLibrary} from "../../src/types/Currency.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {FullMath} from "../../src/pool-cl/libraries/FullMath.sol"; +import {BalanceDelta} from "../../src/types/BalanceDelta.sol"; +import {TokenFixture} from "../helpers/TokenFixture.sol"; +import {CLPoolManagerRouter} from "./helpers/CLPoolManagerRouter.sol"; +import {CLPoolParametersHelper} from "../../src/pool-cl/libraries/CLPoolParametersHelper.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +contract CLHookReturnsFeeTest is Test, Deployers, TokenFixture, GasSnapshot { + using PoolIdLibrary for PoolKey; + using LPFeeLibrary for uint24; + + IVault vault; + ICLPoolManager poolManager; + CLDynamicReturnsFeeHook dynamicReturnsFeesHook; + CLPoolManagerRouter router; + + PoolKey key; + + event Swap( + PoolId indexed poolId, + address sender, + int128 amount0, + int128 amount1, + uint160 sqrtPriceX96, + uint128 liquidity, + int24 tick, + uint24 fee + ); + + function setUp() public { + dynamicReturnsFeesHook = new CLDynamicReturnsFeeHook(); + + (vault, poolManager) = createFreshManager(); + dynamicReturnsFeesHook.setManager(poolManager); + router = new CLPoolManagerRouter(vault, poolManager); + + initializeTokens(); + key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: dynamicReturnsFeesHook, + poolManager: poolManager, + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG, + parameters: CLPoolParametersHelper.setTickSpacing( + bytes32(uint256(dynamicReturnsFeesHook.getHooksRegistrationBitmap())), 1 + ) + }); + + poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + + IERC20(Currency.unwrap(currency0)).approve(address(router), type(uint256).max); + IERC20(Currency.unwrap(currency1)).approve(address(router), type(uint256).max); + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), + ZERO_BYTES + ); + } + + function test_fuzz_dynamicReturnSwapFee(uint24 fee) public { + // hook will handle adding the override flag + dynamicReturnsFeesHook.setFee(fee); + + uint24 actualFee = fee.removeOverrideFlag(); + + int256 amountSpecified = 10000; + BalanceDelta result; + if (actualFee > LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) { + vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); + result = router.swap( + key, + ICLPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + ZERO_BYTES + ); + return; + } else { + result = router.swap( + key, + ICLPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + ZERO_BYTES + ); + } + assertEq(result.amount0(), amountSpecified); + + assertApproxEqAbs( + uint256(-int256(result.amount1())), FullMath.mulDiv(uint256(amountSpecified), (1e6 - actualFee), 1e6), 1 wei + ); + } + + function test_dynamicReturnSwapFee_initializeZeroSwapFee() public { + key.parameters = CLPoolParametersHelper.setTickSpacing( + bytes32(uint256(dynamicReturnsFeesHook.getHooksRegistrationBitmap())), 10 + ); + poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + assertEq(_fetchPoolSwapFee(key), 0); + } + + function test_dynamicReturnSwapFee_notUsedIfPoolIsStaticFee() public { + key.fee = 3000; // static fee + dynamicReturnsFeesHook.setFee(1000); // 0.10% fee is NOT used because the pool has a static fee + + poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + + IERC20(Currency.unwrap(currency0)).approve(address(router), type(uint256).max); + IERC20(Currency.unwrap(currency1)).approve(address(router), type(uint256).max); + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), + ZERO_BYTES + ); + + assertEq(_fetchPoolSwapFee(key), 3000); + + // despite returning a valid swap fee (1000), the static fee is used + int256 amountSpecified = 10000; + BalanceDelta result = router.swap( + key, + ICLPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + ZERO_BYTES + ); + + // after swapping ~1:1, the amount out (amount1) should be approximately 0.30% less than the amount specified + assertEq(result.amount0(), amountSpecified); + assertApproxEqAbs( + uint256(-int256(result.amount1())), FullMath.mulDiv(uint256(amountSpecified), (1e6 - 3000), 1e6), 1 wei + ); + } + + function test_dynamicReturnSwapFee_notStored() public { + // fees returned by beforeSwap are not written to storage + + // create a new pool with an initial fee of 123 + key.parameters = CLPoolParametersHelper.setTickSpacing( + bytes32(uint256(dynamicReturnsFeesHook.getHooksRegistrationBitmap())), 10 + ); + poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + + IERC20(Currency.unwrap(currency0)).approve(address(router), type(uint256).max); + IERC20(Currency.unwrap(currency1)).approve(address(router), type(uint256).max); + router.modifyPosition( + key, + ICLPoolManager.ModifyLiquidityParams({tickLower: -10, tickUpper: 10, liquidityDelta: 10000 ether, salt: 0}), + ZERO_BYTES + ); + uint24 initialFee = 123; + dynamicReturnsFeesHook.forcePoolFeeUpdate(key, initialFee); + assertEq(_fetchPoolSwapFee(key), initialFee); + + // swap with a different fee + uint24 newFee = 3000; + dynamicReturnsFeesHook.setFee(newFee); + + int256 amountSpecified = 10000; + BalanceDelta result = router.swap( + key, + ICLPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + ZERO_BYTES + ); + assertApproxEqAbs( + uint256(-int256(result.amount1())), FullMath.mulDiv(uint256(amountSpecified), (1e6 - newFee), 1e6), 1 wei + ); + + // the fee from beforeSwap is not stored + assertEq(_fetchPoolSwapFee(key), initialFee); + } + + function test_dynamicReturnSwapFee_revertIfFeeTooLarge() public { + assertEq(_fetchPoolSwapFee(key), 0); + + // hook adds the override flag + dynamicReturnsFeesHook.setFee(1000001); + + // a large fee is not used + int256 amountSpecified = 10000; + vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); + router.swap( + key, + ICLPoolManager.SwapParams({ + zeroForOne: true, + amountSpecified: amountSpecified, + sqrtPriceLimitX96: SQRT_RATIO_1_2 + }), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + ZERO_BYTES + ); + } + + function _fetchPoolSwapFee(PoolKey memory _key) internal view returns (uint256 swapFee) { + PoolId id = _key.toId(); + (,,, swapFee) = poolManager.getSlot0(id); + } +} diff --git a/test/pool-cl/CLPoolManager.t.sol b/test/pool-cl/CLPoolManager.t.sol index 9600de8c..344537ab 100644 --- a/test/pool-cl/CLPoolManager.t.sol +++ b/test/pool-cl/CLPoolManager.t.sol @@ -359,7 +359,7 @@ contract CLPoolManagerTest is Test, NoIsolate, Deployers, TokenFixture, GasSnaps } else if (!_validateHookPermissionsConflict(key)) { vm.expectRevert(abi.encodeWithSelector(Hooks.HookPermissionsValidationError.selector)); poolManager.initialize(key, sqrtPriceX96, ZERO_BYTES); - } else if (key.fee & LPFeeLibrary.STATIC_FEE_MASK > LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) { + } else if (key.fee & LPFeeLibrary.FEE_MASK > LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) { vm.expectRevert(abi.encodeWithSelector(IProtocolFees.FeeTooLarge.selector)); poolManager.initialize(key, sqrtPriceX96, ZERO_BYTES); } else { diff --git a/test/pool-cl/helpers/BaseCLTestHook.sol b/test/pool-cl/helpers/BaseCLTestHook.sol index 268b3306..bdecb524 100644 --- a/test/pool-cl/helpers/BaseCLTestHook.sol +++ b/test/pool-cl/helpers/BaseCLTestHook.sol @@ -6,6 +6,7 @@ import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; import {ICLHooks} from "../../../src/pool-cl/interfaces/ICLHooks.sol"; import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; contract BaseCLTestHook is ICLHooks { error HookNotImplemented(); @@ -84,7 +85,7 @@ contract BaseCLTestHook is ICLHooks { function beforeSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, bytes calldata) external virtual - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { revert HookNotImplemented(); } diff --git a/test/pool-cl/helpers/CLDynamicReturnsFeeHook.sol b/test/pool-cl/helpers/CLDynamicReturnsFeeHook.sol new file mode 100644 index 00000000..84e22609 --- /dev/null +++ b/test/pool-cl/helpers/CLDynamicReturnsFeeHook.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {BaseCLTestHook} from "./BaseCLTestHook.sol"; +import {PoolKey} from "../../../src/types/PoolKey.sol"; +import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; +import {ICLHooks} from "../../../src/pool-cl/interfaces/ICLHooks.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; + +contract CLDynamicReturnsFeeHook is BaseCLTestHook { + using LPFeeLibrary for uint24; + + uint24 internal fee; + ICLPoolManager manager; + + function getHooksRegistrationBitmap() external pure override returns (uint16) { + return _hooksRegistrationBitmapFrom( + Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + afterAddLiquidity: false, + beforeRemoveLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + befreSwapReturnsDelta: false, + afterSwapReturnsDelta: false, + afterAddLiquidityReturnsDelta: false, + afterRemoveLiquidityReturnsDelta: false + }) + ); + } + + function setManager(ICLPoolManager _manager) external { + manager = _manager; + } + + function setFee(uint24 _fee) external { + fee = _fee; + } + + function beforeSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, bytes calldata) + external + override + returns (bytes4, BeforeSwapDelta, uint24) + { + // attach the fee flag to `fee` to enable overriding the pool's stored fee + return (ICLHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, fee | LPFeeLibrary.OVERRIDE_FEE_FLAG); + } + + function forcePoolFeeUpdate(PoolKey calldata _key, uint24 _fee) external { + manager.updateDynamicLPFee(_key, _fee); + } +} diff --git a/test/pool-cl/helpers/CLFeeManagerHook.sol b/test/pool-cl/helpers/CLFeeManagerHook.sol index edc13a4f..d06aeb87 100644 --- a/test/pool-cl/helpers/CLFeeManagerHook.sol +++ b/test/pool-cl/helpers/CLFeeManagerHook.sol @@ -8,6 +8,7 @@ import {IHooks} from "../../../src/interfaces/IHooks.sol"; import {PoolId, PoolIdLibrary} from "../../../src/types/PoolId.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BaseCLTestHook} from "./BaseCLTestHook.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; contract CLFeeManagerHook is BaseCLTestHook { using PoolIdLibrary for PoolKey; @@ -49,7 +50,7 @@ contract CLFeeManagerHook is BaseCLTestHook { function beforeSwap(address, PoolKey calldata key, ICLPoolManager.SwapParams calldata, bytes calldata hookData) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { if (hookData.length > 0) { (bool _update, uint24 _fee) = abi.decode(hookData, (bool, uint24)); @@ -59,6 +60,6 @@ contract CLFeeManagerHook is BaseCLTestHook { } } - return (ICLHooks.beforeSwap.selector, 0); + return (ICLHooks.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } } diff --git a/test/pool-cl/helpers/CLReturnsDeltaHook.sol b/test/pool-cl/helpers/CLReturnsDeltaHook.sol index 7f9b9314..2c7a8985 100644 --- a/test/pool-cl/helpers/CLReturnsDeltaHook.sol +++ b/test/pool-cl/helpers/CLReturnsDeltaHook.sol @@ -9,6 +9,7 @@ import {Currency, CurrencyLibrary} from "../../../src/types/Currency.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; import {BaseCLTestHook} from "./BaseCLTestHook.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary, toBeforeSwapDelta} from "../../../src/types/BeforeSwapDelta.sol"; contract CLReturnsDeltaHook is BaseCLTestHook { error InvalidAction(); @@ -80,9 +81,9 @@ contract CLReturnsDeltaHook is BaseCLTestHook { function beforeSwap(address, PoolKey calldata key, ICLPoolManager.SwapParams calldata params, bytes calldata data) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { - (int128 hookDeltaSpecified) = abi.decode(data, (int128)); + (int128 hookDeltaSpecified, int128 hookDeltaUnspecified,) = abi.decode(data, (int128, int128, int128)); if (params.zeroForOne == params.amountSpecified > 0) { // the specified token is token0 @@ -93,6 +94,14 @@ contract CLReturnsDeltaHook is BaseCLTestHook { } else { vault.take(key.currency0, address(this), uint128(-hookDeltaSpecified)); } + + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency1); + key.currency1.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency1); + } else { + vault.take(key.currency1, address(this), uint128(-hookDeltaUnspecified)); + } } else { // the specified token is token1 if (hookDeltaSpecified > 0) { @@ -102,16 +111,51 @@ contract CLReturnsDeltaHook is BaseCLTestHook { } else { vault.take(key.currency1, address(this), uint128(-hookDeltaSpecified)); } + + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency0); + key.currency0.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency0); + } else { + vault.take(key.currency0, address(this), uint128(-hookDeltaUnspecified)); + } } - return (this.beforeSwap.selector, hookDeltaSpecified); + return (this.beforeSwap.selector, toBeforeSwapDelta(hookDeltaSpecified, hookDeltaUnspecified), 0); } - function afterSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) - external - pure - override - returns (bytes4, int128) - { - return (this.afterSwap.selector, 0); + function afterSwap( + address, + PoolKey calldata key, + ICLPoolManager.SwapParams calldata params, + BalanceDelta, + bytes calldata data + ) external override returns (bytes4, int128) { + (,, int128 hookDeltaUnspecified) = abi.decode(data, (int128, int128, int128)); + + if (hookDeltaUnspecified == 0) { + return (this.afterSwap.selector, 0); + } + + if (params.zeroForOne == params.amountSpecified > 0) { + // the unspecified token is token1 + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency1); + key.currency1.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency1); + } else { + vault.take(key.currency1, address(this), uint128(-hookDeltaUnspecified)); + } + } else { + // the unspecified token is token0 + if (hookDeltaUnspecified > 0) { + vault.sync(key.currency0); + key.currency0.transfer(address(vault), uint128(hookDeltaUnspecified)); + vault.settle(key.currency0); + } else { + vault.take(key.currency0, address(this), uint128(-hookDeltaUnspecified)); + } + } + + return (this.afterSwap.selector, hookDeltaUnspecified); } } diff --git a/test/pool-cl/helpers/CLSkipCallbackHook.sol b/test/pool-cl/helpers/CLSkipCallbackHook.sol index 7149c6b7..318f5e7f 100644 --- a/test/pool-cl/helpers/CLSkipCallbackHook.sol +++ b/test/pool-cl/helpers/CLSkipCallbackHook.sol @@ -9,6 +9,7 @@ import {Currency, CurrencyLibrary} from "../../../src/types/Currency.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; import {BaseCLTestHook} from "./BaseCLTestHook.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; /// @notice CL hook which does a callback contract CLSkipCallbackHook is BaseCLTestHook { @@ -340,10 +341,10 @@ contract CLSkipCallbackHook is BaseCLTestHook { function beforeSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, bytes calldata) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { hookCounterCallbackCount++; - return (CLSkipCallbackHook.beforeSwap.selector, 0); + return (CLSkipCallbackHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); } function afterSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) diff --git a/test/pool-cl/helpers/MockHooks.sol b/test/pool-cl/helpers/MockHooks.sol index 7fe2b713..6516d08e 100644 --- a/test/pool-cl/helpers/MockHooks.sol +++ b/test/pool-cl/helpers/MockHooks.sol @@ -6,6 +6,7 @@ import {ICLHooks} from "../../../src/pool-cl/interfaces/ICLHooks.sol"; import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; +import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "../../../src/types/BeforeSwapDelta.sol"; import {PoolId, PoolIdLibrary} from "../../../src/types/PoolId.sol"; contract MockHooks is ICLHooks { @@ -106,11 +107,15 @@ contract MockHooks is ICLHooks { function beforeSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, bytes calldata hookData) external override - returns (bytes4, int128) + returns (bytes4, BeforeSwapDelta, uint24) { beforeSwapData = hookData; bytes4 selector = MockHooks.beforeSwap.selector; - return (returnValues[selector] == bytes4(0) ? selector : returnValues[selector], 0); + return ( + returnValues[selector] == bytes4(0) ? selector : returnValues[selector], + BeforeSwapDeltaLibrary.ZERO_DELTA, + 0 + ); } function afterSwap( diff --git a/test/pool-cl/libraries/CLPool.t.sol b/test/pool-cl/libraries/CLPool.t.sol index 5a66a669..3bff44ed 100644 --- a/test/pool-cl/libraries/CLPool.t.sol +++ b/test/pool-cl/libraries/CLPool.t.sol @@ -16,38 +16,42 @@ import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; import {FullMath} from "../../../src/pool-cl/libraries/FullMath.sol"; import {FixedPoint128} from "../../../src/pool-cl/libraries/FixedPoint128.sol"; import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; +import {ProtocolFeeLibrary} from "../../../src/libraries/ProtocolFeeLibrary.sol"; contract PoolTest is Test { using CLPool for CLPool.State; + using LPFeeLibrary for uint24; + using ProtocolFeeLibrary for uint24; CLPool.State state; - function testPoolInitialize(uint160 sqrtPriceX96, uint16 protocolFee, uint24 swapFee) public { + function testPoolInitialize(uint160 sqrtPriceX96, uint16 protocolFee, uint24 lpFee) public { protocolFee = uint16(bound(protocolFee, 0, 2 ** 16 - 1)); - swapFee = uint24(bound(swapFee, 0, 999999)); + lpFee = uint24(bound(lpFee, 0, 999999)); if (sqrtPriceX96 < TickMath.MIN_SQRT_RATIO || sqrtPriceX96 >= TickMath.MAX_SQRT_RATIO) { vm.expectRevert(TickMath.InvalidSqrtRatio.selector); - state.initialize(sqrtPriceX96, protocolFee, swapFee); + state.initialize(sqrtPriceX96, protocolFee, lpFee); } else { - state.initialize(sqrtPriceX96, protocolFee, swapFee); + state.initialize(sqrtPriceX96, protocolFee, lpFee); assertEq(state.slot0.sqrtPriceX96, sqrtPriceX96); assertEq(state.slot0.protocolFee, protocolFee); assertEq(state.slot0.tick, TickMath.getTickAtSqrtRatio(sqrtPriceX96)); assertLt(state.slot0.tick, TickMath.MAX_TICK); assertGt(state.slot0.tick, TickMath.MIN_TICK - 1); - assertEq(state.slot0.lpFee, swapFee); + assertEq(state.slot0.lpFee, lpFee); } } - function testModifyPosition(uint160 sqrtPriceX96, CLPool.ModifyLiquidityParams memory params, uint24 swapFee) + function testModifyPosition(uint160 sqrtPriceX96, CLPool.ModifyLiquidityParams memory params, uint24 lpFee) public { // Assumptions tested in PoolManager.t.sol params.tickSpacing = int24(bound(params.tickSpacing, 1, 32767)); - swapFee = uint24(bound(swapFee, 0, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE - 1)); + lpFee = uint24(bound(lpFee, 0, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE - 1)); - testPoolInitialize(sqrtPriceX96, 0, swapFee); + testPoolInitialize(sqrtPriceX96, 0, lpFee); if (params.tickLower >= params.tickUpper) { vm.expectRevert(abi.encodeWithSelector(Tick.TicksMisordered.selector, params.tickLower, params.tickUpper)); @@ -92,15 +96,27 @@ contract PoolTest is Test { uint160 sqrtPriceX96, CLPool.ModifyLiquidityParams memory modifyLiquidityParams, CLPool.SwapParams memory swapParams, - uint24 swapFee + uint24 lpFee ) public { swapParams.amountSpecified = int256(bound(swapParams.amountSpecified, 0, type(int128).max)); - testModifyPosition(sqrtPriceX96, modifyLiquidityParams, swapFee); + testModifyPosition(sqrtPriceX96, modifyLiquidityParams, lpFee); swapParams.tickSpacing = modifyLiquidityParams.tickSpacing; CLPool.Slot0 memory slot0 = state.slot0; + // avoid lpFee override valid + if ( + swapParams.lpFeeOverride.isOverride() + && swapParams.lpFeeOverride.removeOverrideFlag() > LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + ) { + return; + } + + uint24 swapFee = swapParams.lpFeeOverride.isOverride() + ? swapParams.lpFeeOverride.removeOverrideAndValidate(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) + : lpFee; + if (swapParams.zeroForOne) { if (swapParams.sqrtPriceLimitX96 >= slot0.sqrtPriceX96) { vm.expectRevert( @@ -129,6 +145,8 @@ contract PoolTest is Test { ) ); } + } else if (swapParams.amountSpecified <= 0 && swapFee == LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) { + vm.expectRevert(CLPool.InvalidFeeForExactOut.selector); } state.swap(swapParams);