Skip to content

Commit

Permalink
Merge branch 'main' into feat/hook-no-callback
Browse files Browse the repository at this point in the history
  • Loading branch information
ChefMist authored May 7, 2024
2 parents 0ba3f32 + ca20e08 commit 59caab5
Show file tree
Hide file tree
Showing 29 changed files with 388 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -1 +1 @@
349002
349002
Original file line number Diff line number Diff line change
@@ -1 +1 @@
59550
59550
Original file line number Diff line number Diff line change
@@ -1 +1 @@
242595
242595
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerTest#donateBothTokens.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
82660
82660
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
52622
52622
Original file line number Diff line number Diff line change
@@ -1 +1 @@
36693
36693
Original file line number Diff line number Diff line change
@@ -1 +1 @@
42520
42520
Original file line number Diff line number Diff line change
@@ -1 +1 @@
54818
54818
Original file line number Diff line number Diff line change
@@ -1 +1 @@
100894
100894
Original file line number Diff line number Diff line change
@@ -1 +1 @@
25040604
25040604
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerTest#swap_simple.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
36018
36018
Original file line number Diff line number Diff line change
@@ -1 +1 @@
101656
101656
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerTest#swap_withHooks.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
41733
41733
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerTest#swap_withNative.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
36021
36021
Original file line number Diff line number Diff line change
@@ -1 +1 @@
4835
4857
Original file line number Diff line number Diff line change
@@ -1 +1 @@
19431
19431
Original file line number Diff line number Diff line change
@@ -1 +1 @@
37643
37643
Original file line number Diff line number Diff line change
@@ -1 +1 @@
29618
29618
2 changes: 1 addition & 1 deletion .forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
21883
21883
2 changes: 1 addition & 1 deletion .forge-snapshots/ExtsloadTest#extsloadInBatch.snap
Original file line number Diff line number Diff line change
@@ -1 +1 @@
11352
11374
29 changes: 22 additions & 7 deletions src/pool-cl/CLPoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {PoolId, PoolIdLibrary} from "../types/PoolId.sol";
import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol";
import {Extsload} from "../Extsload.sol";
import {SafeCast} from "../libraries/SafeCast.sol";
import {CLPoolGetters} from "./libraries/CLPoolGetters.sol";

contract CLPoolManager is ICLPoolManager, Fees, Extsload {
using SafeCast for int256;
Expand All @@ -28,6 +29,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload {
using CLPoolParametersHelper for bytes32;
using CLPool for *;
using CLPosition for mapping(bytes32 => CLPosition.Info);
using CLPoolGetters for CLPool.State;

/// @inheritdoc ICLPoolManager
int24 public constant override MAX_TICK_SPACING = type(int16).max;
Expand Down Expand Up @@ -129,7 +131,12 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload {
PoolKey memory key,
ICLPoolManager.ModifyLiquidityParams memory params,
bytes calldata hookData
) external override poolManagerMatch(address(key.poolManager)) returns (BalanceDelta delta) {
)
external
override
poolManagerMatch(address(key.poolManager))
returns (BalanceDelta delta, BalanceDelta feeDelta)
{
// Do not allow add liquidity when paused()
if (paused() && params.liquidityDelta > 0) revert PoolPaused();

Expand All @@ -142,7 +149,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload {
bytes4 selector = hooks.beforeAddLiquidity(msg.sender, key, params, hookData);
if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) {
// Sentinel return value used to signify that a NoOp occurred.
return BalanceDeltaLibrary.MAXIMUM_DELTA;
return (BalanceDeltaLibrary.MAXIMUM_DELTA, BalanceDeltaLibrary.ZERO_DELTA);
} else if (selector != ICLHooks.beforeAddLiquidity.selector) {
revert Hooks.InvalidHookResponse();
}
Expand All @@ -151,13 +158,13 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload {
bytes4 selector = hooks.beforeRemoveLiquidity(msg.sender, key, params, hookData);
if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) {
// Sentinel return value used to signify that a NoOp occurred.
return BalanceDeltaLibrary.MAXIMUM_DELTA;
return (BalanceDeltaLibrary.MAXIMUM_DELTA, BalanceDeltaLibrary.ZERO_DELTA);
} else if (selector != ICLHooks.beforeRemoveLiquidity.selector) {
revert Hooks.InvalidHookResponse();
}
}

delta = pools[id].modifyLiquidity(
(delta, feeDelta) = pools[id].modifyLiquidity(
CLPool.ModifyLiquidityParams({
owner: msg.sender,
tickLower: params.tickLower,
Expand All @@ -167,7 +174,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload {
})
);

vault.accountPoolBalanceDelta(key, delta, msg.sender);
vault.accountPoolBalanceDelta(key, delta + feeDelta, msg.sender);

/// @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);
Expand Down Expand Up @@ -289,11 +296,19 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload {
}

function getPoolTickInfo(PoolId id, int24 tick) external view returns (Tick.Info memory) {
return pools[id].ticks[tick];
return pools[id].getPoolTickInfo(tick);
}

function getPoolBitmapInfo(PoolId id, int16 word) external view returns (uint256 tickBitmap) {
return pools[id].tickBitmap[word];
return pools[id].getPoolBitmapInfo(word);
}

function getFeeGrowthGlobals(PoolId id)
external
view
returns (uint256 feeGrowthGlobal0x128, uint256 feeGrowthGlobal1x128)
{
return pools[id].getFeeGrowthGlobals();
}

/// @inheritdoc IPoolManager
Expand Down
4 changes: 3 additions & 1 deletion src/pool-cl/interfaces/ICLPoolManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,11 @@ interface ICLPoolManager is IFees, IPoolManager, IExtsload {
}

/// @notice Modify the position for the given pool
/// @return delta The balance delta of the liquidity change
/// @return feeDelta The balance delta of the fees generated in the liquidity range
function modifyLiquidity(PoolKey memory key, ModifyLiquidityParams memory params, bytes calldata hookData)
external
returns (BalanceDelta);
returns (BalanceDelta delta, BalanceDelta feeDelta);

struct SwapParams {
bool zeroForOne;
Expand Down
7 changes: 4 additions & 3 deletions src/pool-cl/libraries/CLPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,11 @@ library CLPool {

/// @dev Effect changes to the liquidity of a position in a pool
/// @param params the position details and the change to the position's liquidity to effect
/// @return delta the deltas of the token balances of the pool
/// @return delta the deltas from liquidity changes
/// @return feeDelta the delta of the fees generated in the liquidity range
function modifyLiquidity(State storage self, ModifyLiquidityParams memory params)
internal
returns (BalanceDelta delta)
returns (BalanceDelta delta, BalanceDelta feeDelta)
{
Slot0 memory _slot0 = self.slot0; // SLOAD for gas optimization

Expand Down Expand Up @@ -144,7 +145,7 @@ library CLPool {
}

// Fees earned from LPing are removed from the pool balance.
delta = delta - toBalanceDelta(feesOwed0.toInt128(), feesOwed1.toInt128());
feeDelta = toBalanceDelta(feesOwed0.toInt128(), feesOwed1.toInt128());
}

struct SwapCache {
Expand Down
24 changes: 24 additions & 0 deletions src/pool-cl/libraries/CLPoolGetters.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2024 PancakeSwap
pragma solidity ^0.8.24;

import {CLPool} from "./CLPool.sol";
import {Tick} from "./Tick.sol";

library CLPoolGetters {
function getPoolTickInfo(CLPool.State storage pool, int24 tick) internal view returns (Tick.Info memory) {
return pool.ticks[tick];
}

function getPoolBitmapInfo(CLPool.State storage pool, int16 word) internal view returns (uint256 tickBitmap) {
return pool.tickBitmap[word];
}

function getFeeGrowthGlobals(CLPool.State storage pool)
internal
view
returns (uint256 feeGrowthGlobal0x128, uint256 feeGrowthGlobal1x128)
{
return (pool.feeGrowthGlobal0X128, pool.feeGrowthGlobal1X128);
}
}
3 changes: 3 additions & 0 deletions src/types/BalanceDelta.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ library BalanceDeltaLibrary {
// Sentinel return value used to signify that a NoOp occurred.
BalanceDelta public constant MAXIMUM_DELTA = BalanceDelta.wrap(int256(type(uint256).max));

// Sentinel return value used for feeDelta to signify that a NoOp occurred.
BalanceDelta public constant ZERO_DELTA = BalanceDelta.wrap(0);

function amount0(BalanceDelta balanceDelta) internal pure returns (int128 _amount0) {
/// @solidity memory-safe-assembly
assembly {
Expand Down
122 changes: 121 additions & 1 deletion test/pool-cl/CLPoolManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,124 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot {
}
}

function testModifyPosition_feeDelta() external {
Currency currency0 = Currency.wrap(address(new ERC20PresetFixedSupply("C0", "C0", 1e30 ether, address(this))));
Currency currency1 = Currency.wrap(address(new ERC20PresetFixedSupply("C1", "C1", 1e30 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))
});

poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES);

IERC20(Currency.unwrap(currency0)).approve(address(router), 1e30 ether);
IERC20(Currency.unwrap(currency1)).approve(address(router), 1e30 ether);

BalanceDelta feeDelta;
// Step 1: Add liquidity to new pool, verify feeDelta = 0
(, feeDelta) = router.modifyPosition(
key,
ICLPoolManager.ModifyLiquidityParams({
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
liquidityDelta: 1e18
}),
""
);
assertTrue(feeDelta == BalanceDeltaLibrary.ZERO_DELTA);

// Step 2: Add liquidity again to pool, verify feeDelta = 0
(, feeDelta) = router.modifyPosition(
key,
ICLPoolManager.ModifyLiquidityParams({
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
liquidityDelta: 1e18
}),
""
);
assertTrue(feeDelta == BalanceDeltaLibrary.ZERO_DELTA);

// step 3: Remove liquidity from pool, verify feeDelta = 0
(, feeDelta) = router.modifyPosition(
key,
ICLPoolManager.ModifyLiquidityParams({
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
liquidityDelta: -1e18
}),
""
);
assertTrue(feeDelta == BalanceDeltaLibrary.ZERO_DELTA);

// step 4: Perform a swap then add liquidity, verify feeDelta != 0
router.swap(
key,
ICLPoolManager.SwapParams({
zeroForOne: true,
amountSpecified: 0.1 ether,
sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1
}),
CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}),
""
);
(, feeDelta) = router.modifyPosition(
key,
ICLPoolManager.ModifyLiquidityParams({
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
liquidityDelta: 1e18
}),
""
);
assertApproxEqRel(uint256(int256(feeDelta.amount0())), 0.003 * 0.1 ether, 1e16); // around 0.3% fee

// step 5: Add liquidity, verify feeDelta == 0
(, feeDelta) = router.modifyPosition(
key,
ICLPoolManager.ModifyLiquidityParams({
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
liquidityDelta: 1e18
}),
""
);
assertTrue(feeDelta == BalanceDeltaLibrary.ZERO_DELTA);

// step 6: Perform a swap then remove liquidity, verify feeDelta != 0
router.swap(
key,
ICLPoolManager.SwapParams({
zeroForOne: true,
amountSpecified: 0.1 ether,
sqrtPriceLimitX96: TickMath.MIN_SQRT_RATIO + 1
}),
CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}),
""
);
(, feeDelta) = router.modifyPosition(
key,
ICLPoolManager.ModifyLiquidityParams({
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
liquidityDelta: -1e18
}),
""
);
assertApproxEqRel(uint256(int256(feeDelta.amount0())), 0.003 * 0.1 ether, 1e16); // around 0.3% fee
}

function testModifyPosition_Liquidity_aboveCurrentTick() external {
Currency currency0 = Currency.wrap(address(new ERC20PresetFixedSupply("C0", "C0", 1e30 ether, address(this))));
Currency currency1 = Currency.wrap(address(new ERC20PresetFixedSupply("C1", "C1", 1e30 ether, address(this))));
Expand Down Expand Up @@ -2320,13 +2438,15 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot {
snapEnd();

BalanceDelta delta;
BalanceDelta feeDelta;

// Action 1: modify
ICLPoolManager.ModifyLiquidityParams memory params;
snapStart("CLPoolManagerTest#testNoOp_gas_ModifyPosition");
delta = router.modifyPosition(key, params, ZERO_BYTES);
(delta, feeDelta) = router.modifyPosition(key, params, ZERO_BYTES);
snapEnd();
assertTrue(delta == BalanceDeltaLibrary.MAXIMUM_DELTA);
assertTrue(feeDelta == BalanceDeltaLibrary.ZERO_DELTA);

// Action 2: swap
snapStart("CLPoolManagerTest#testNoOp_gas_Swap");
Expand Down
Loading

0 comments on commit 59caab5

Please sign in to comment.