From 10c67a33712e247b463b384eb441ab4a67427aa7 Mon Sep 17 00:00:00 2001 From: dianakocsis Date: Fri, 2 Aug 2024 12:39:39 -0400 Subject: [PATCH] clean --- ...er_increaseLiquidity_erc20_withClose.snap} | 0 ...ncreaseLiquidity_erc20_withSettlePair.snap | 1 + .../PositionManager_mint_nativeWithSweep.snap | 1 - ...anager_mint_nativeWithSweep_withClose.snap | 1 + ...r_mint_nativeWithSweep_withSettlePair.snap | 1 + ...nManager_mint_settleWithBalance_sweep.snap | 2 +- ...ap => PositionManager_mint_withClose.snap} | 0 .../PositionManager_mint_withSettlePair.snap | 1 + src/PositionManager.sol | 10 ++ src/libraries/Actions.sol | 10 +- src/libraries/CalldataDecoder.sol | 8 ++ test/libraries/CalldataDecoder.t.sol | 8 ++ test/mocks/MockCalldataDecoder.sol | 4 + test/position-managers/Execute.t.sol | 40 ++++++- .../position-managers/IncreaseLiquidity.t.sol | 2 +- test/position-managers/NativeToken.t.sol | 101 +++++++++++++++++- .../PositionManager.gas.t.sol | 93 ++++++++++++---- .../PositionManager.multicall.t.sol | 2 +- test/shared/LiquidityOperations.sol | 10 +- test/shared/Planner.sol | 7 +- test/shared/fuzz/LiquidityFuzzers.sol | 2 +- 21 files changed, 260 insertions(+), 44 deletions(-) rename .forge-snapshots/{PositionManager_increaseLiquidity_erc20.snap => PositionManager_increaseLiquidity_erc20_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap delete mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep.snap create mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap create mode 100644 .forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap rename .forge-snapshots/{PositionManager_mint.snap => PositionManager_mint_withClose.snap} (100%) create mode 100644 .forge-snapshots/PositionManager_mint_withSettlePair.snap diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_increaseLiquidity_erc20.snap rename to .forge-snapshots/PositionManager_increaseLiquidity_erc20_withClose.snap diff --git a/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap new file mode 100644 index 000000000..6ce3534b4 --- /dev/null +++ b/.forge-snapshots/PositionManager_increaseLiquidity_erc20_withSettlePair.snap @@ -0,0 +1 @@ +151341 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap deleted file mode 100644 index 15689f38b..000000000 --- a/.forge-snapshots/PositionManager_mint_nativeWithSweep.snap +++ /dev/null @@ -1 +0,0 @@ -345244 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap new file mode 100644 index 000000000..378d9bfb1 --- /dev/null +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withClose.snap @@ -0,0 +1 @@ +345267 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap new file mode 100644 index 000000000..9f308f43c --- /dev/null +++ b/.forge-snapshots/PositionManager_mint_nativeWithSweep_withSettlePair.snap @@ -0,0 +1 @@ +344532 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap index a6459af08..817d77abd 100644 --- a/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap +++ b/.forge-snapshots/PositionManager_mint_settleWithBalance_sweep.snap @@ -1 +1 @@ -370018 \ No newline at end of file +370064 \ No newline at end of file diff --git a/.forge-snapshots/PositionManager_mint.snap b/.forge-snapshots/PositionManager_mint_withClose.snap similarity index 100% rename from .forge-snapshots/PositionManager_mint.snap rename to .forge-snapshots/PositionManager_mint_withClose.snap diff --git a/.forge-snapshots/PositionManager_mint_withSettlePair.snap b/.forge-snapshots/PositionManager_mint_withSettlePair.snap new file mode 100644 index 000000000..99ea27ab7 --- /dev/null +++ b/.forge-snapshots/PositionManager_mint_withSettlePair.snap @@ -0,0 +1 @@ +371253 \ No newline at end of file diff --git a/src/PositionManager.sol b/src/PositionManager.sol index 43cafe544..285493f5f 100644 --- a/src/PositionManager.sol +++ b/src/PositionManager.sol @@ -127,6 +127,9 @@ contract PositionManager is } else if (action == Actions.SETTLE_WITH_BALANCE) { Currency currency = params.decodeCurrency(); _settleWithBalance(currency); + } else if (action == Actions.SETTLE_PAIR) { + (Currency currency0, Currency currency1) = params.decodeCurrencyPair(); + _settlePair(currency0, currency1); } else if (action == Actions.SWEEP) { (Currency currency, address to) = params.decodeCurrencyAndAddress(); _sweep(currency, to); @@ -221,6 +224,13 @@ contract PositionManager is _settle(currency, address(this), _getFullSettleAmount(currency)); } + function _settlePair(Currency currency0, Currency currency1) internal { + // the locker is the payer when settling + address caller = _msgSender(); + _settle(currency0, caller, _getFullSettleAmount(currency0)); + _settle(currency1, caller, _getFullSettleAmount(currency1)); + } + /// @dev this is overloaded with ERC721Permit._burn function _burn( uint256 tokenId, diff --git a/src/libraries/Actions.sol b/src/libraries/Actions.sol index 132fde68a..b60f3095e 100644 --- a/src/libraries/Actions.sol +++ b/src/libraries/Actions.sol @@ -23,17 +23,17 @@ library Actions { uint256 constant SETTLE = 0x10; uint256 constant SETTLE_ALL = 0x11; uint256 constant SETTLE_WITH_BALANCE = 0x12; + uint256 constant SETTLE_PAIR = 0x13; // taking uint256 constant TAKE = 0x13; uint256 constant TAKE_ALL = 0x14; uint256 constant TAKE_PORTION = 0x15; uint256 constant CLOSE_CURRENCY = 0x16; - uint256 constant CLOSE_PAIR = 0x17; - uint256 constant CLEAR = 0x18; - uint256 constant SWEEP = 0x19; + uint256 constant CLEAR = 0x17; + uint256 constant SWEEP = 0x18; // minting/burning 6909s to close deltas - uint256 constant MINT_6909 = 0x20; - uint256 constant BURN_6909 = 0x21; + uint256 constant MINT_6909 = 0x19; + uint256 constant BURN_6909 = 0x20; } diff --git a/src/libraries/CalldataDecoder.sol b/src/libraries/CalldataDecoder.sol index 11ba49d4c..6c48685d2 100644 --- a/src/libraries/CalldataDecoder.sol +++ b/src/libraries/CalldataDecoder.sol @@ -117,6 +117,14 @@ library CalldataDecoder { } } + /// @dev equivalent to: abi.decode(params, (Currency, Currency)) in calldata + function decodeCurrencyPair(bytes calldata params) internal pure returns (Currency currency0, Currency currency1) { + assembly ("memory-safe") { + currency0 := calldataload(params.offset) + currency1 := calldataload(add(params.offset, 0x20)) + } + } + /// @dev equivalent to: abi.decode(params, (Currency, address)) in calldata function decodeCurrencyAndAddress(bytes calldata params) internal diff --git a/test/libraries/CalldataDecoder.t.sol b/test/libraries/CalldataDecoder.t.sol index bfc9c08e3..04bb82ff0 100644 --- a/test/libraries/CalldataDecoder.t.sol +++ b/test/libraries/CalldataDecoder.t.sol @@ -98,6 +98,14 @@ contract CalldataDecoderTest is Test { assertEq(Currency.unwrap(currency), Currency.unwrap(_currency)); } + function test_fuzz_decodeCurrencyPair(Currency _currency0, Currency _currency1) public view { + bytes memory params = abi.encode(_currency0, _currency1); + (Currency currency0, Currency currency1) = decoder.decodeCurrencyPair(params); + + assertEq(Currency.unwrap(currency0), Currency.unwrap(_currency0)); + assertEq(Currency.unwrap(currency1), Currency.unwrap(_currency1)); + } + function test_fuzz_decodeCurrencyAndUint256(Currency _currency, uint256 _amount) public view { bytes memory params = abi.encode(_currency, _amount); (Currency currency, uint256 amount) = decoder.decodeCurrencyAndUint256(params); diff --git a/test/mocks/MockCalldataDecoder.sol b/test/mocks/MockCalldataDecoder.sol index b60bd7077..b57a0aa58 100644 --- a/test/mocks/MockCalldataDecoder.sol +++ b/test/mocks/MockCalldataDecoder.sol @@ -65,6 +65,10 @@ contract MockCalldataDecoder { return params.decodeCurrency(); } + function decodeCurrencyPair(bytes calldata params) external pure returns (Currency currency0, Currency currency1) { + return params.decodeCurrencyPair(); + } + function decodeCurrencyAndUint256(bytes calldata params) external pure returns (Currency currency, uint256 _uint) { return params.decodeCurrencyAndUint256(); } diff --git a/test/position-managers/Execute.t.sol b/test/position-managers/Execute.t.sol index d75f3ac75..792359273 100644 --- a/test/position-managers/Execute.t.sol +++ b/test/position-managers/Execute.t.sol @@ -78,7 +78,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(liquidity, initialLiquidity + liquidityToAdd); } - function test_fuzz_execute_increaseLiquidity_twice( + function test_fuzz_execute_increaseLiquidity_twice_withClose( uint256 initialLiquidity, uint256 liquidityToAdd, uint256 liquidityToAdd2 @@ -100,7 +100,39 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { abi.encode(tokenId, config, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); + lpm.modifyLiquidities(calls, _deadline); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + + assertEq(liquidity, initialLiquidity + liquidityToAdd + liquidityToAdd2); + } + + function test_fuzz_execute_increaseLiquidity_twice_withSettlePair( + uint256 initialLiquidity, + uint256 liquidityToAdd, + uint256 liquidityToAdd2 + ) public { + initialLiquidity = bound(initialLiquidity, 1e18, 1000e18); + liquidityToAdd = bound(liquidityToAdd, 1e18, 1000e18); + liquidityToAdd2 = bound(liquidityToAdd2, 1e18, 1000e18); + uint256 tokenId = lpm.nextTokenId(); + mint(config, initialLiquidity, address(this), ZERO_BYTES); + + Plan memory planner = Planner.init(); + + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, liquidityToAdd2, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + + bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); lpm.modifyLiquidities(calls, _deadline); bytes32 positionId = @@ -130,7 +162,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); bytes32 positionId = @@ -175,7 +207,7 @@ contract ExecuteTest is Test, PosmTestSetup, LiquidityFuzzers { Actions.MINT_POSITION, abi.encode(newConfig, newLiquidity, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); { diff --git a/test/position-managers/IncreaseLiquidity.t.sol b/test/position-managers/IncreaseLiquidity.t.sol index 49e057c6e..8807d69bb 100644 --- a/test/position-managers/IncreaseLiquidity.t.sol +++ b/test/position-managers/IncreaseLiquidity.t.sol @@ -119,7 +119,7 @@ contract IncreaseLiquidityTest is Test, PosmTestSetup, Fuzzers { planner.add( Actions.INCREASE_LIQUIDITY, abi.encode(tokenIdAlice, config, liquidityDelta, 0 wei, 0 wei, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.startPrank(alice); lpm.modifyLiquidities(calls, _deadline); vm.stopPrank(); diff --git a/test/position-managers/NativeToken.t.sol b/test/position-managers/NativeToken.t.sol index b7a8e901c..8cebd15de 100644 --- a/test/position-managers/NativeToken.t.sol +++ b/test/position-managers/NativeToken.t.sol @@ -96,7 +96,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { } // minting with excess native tokens are returned to caller - function test_fuzz_mint_native_excess(IPoolManager.ModifyLiquidityParams memory params) public { + function test_fuzz_mint_native_excess_withClose(IPoolManager.ModifyLiquidityParams memory params) public { params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity @@ -144,6 +144,53 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); } + function test_fuzz_mint_native_excess_withSettlePair(IPoolManager.ModifyLiquidityParams memory params) public { + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + uint256 liquidityToAdd = + params.liquidityDelta < 0 ? uint256(-params.liquidityDelta) : uint256(params.liquidityDelta); + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + uint256 balance0Before = currency0.balanceOfSelf(); + uint256 balance1Before = currency1.balanceOfSelf(); + + uint256 tokenId = lpm.nextTokenId(); + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode(config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + ); + planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); + // sweep the excess eth + planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); + + bytes memory calls = planner.encode(); + + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(params.tickLower), + TickMath.getSqrtPriceAtTick(params.tickUpper), + liquidityToAdd.toUint128() + ); + + // Mint with excess native tokens + lpm.modifyLiquidities{value: amount0 * 2 + 1}(calls, _deadline); + BalanceDelta delta = getLastDelta(); + + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, uint256(params.liquidityDelta)); + + // only paid the delta amount, with excess tokens returned to caller + assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0()))); + assertEq(balance0Before - currency0.balanceOfSelf(), amount0 + 1); // TODO: off by one?? + assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); + } + function test_fuzz_burn_native_emptyPosition(IPoolManager.ModifyLiquidityParams memory params) public { uint256 balance0Start = address(this).balance; uint256 balance1Start = currency1.balanceOfSelf(); @@ -287,7 +334,7 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { } // overpaying native tokens on increase liquidity is returned to caller - function test_fuzz_increaseLiquidity_native_excess(IPoolManager.ModifyLiquidityParams memory params) public { + function test_fuzz_increaseLiquidity_native_excess_withClose(IPoolManager.ModifyLiquidityParams memory params) public { // fuzz for the range params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity @@ -338,6 +385,56 @@ contract PositionManagerTest is Test, PosmTestSetup, LiquidityFuzzers { assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); } + function test_fuzz_increaseLiquidity_native_excess_withSettlePair(IPoolManager.ModifyLiquidityParams memory params) public { + // fuzz for the range + params = createFuzzyLiquidityParams(nativeKey, params, SQRT_PRICE_1_1); + vm.assume(params.tickLower < 0 && 0 < params.tickUpper); // two-sided liquidity + + // TODO: figure out if we can fuzz the increase liquidity delta. we're annoyingly getting TickLiquidityOverflow + uint256 liquidityToAdd = 1e18; + PositionConfig memory config = + PositionConfig({poolKey: nativeKey, tickLower: params.tickLower, tickUpper: params.tickUpper}); + + // mint the position with native token liquidity + uint256 tokenId = lpm.nextTokenId(); + mintWithNative(SQRT_PRICE_1_1, config, liquidityToAdd, address(this), ZERO_BYTES); + + uint256 balance0Before = address(this).balance; + uint256 balance1Before = currency1.balanceOfSelf(); + + // calculate how much native token is required for the liquidity increase (doubling the liquidity) + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(params.tickLower), + TickMath.getSqrtPriceAtTick(params.tickUpper), + uint128(liquidityToAdd) + ); + + Plan memory planner = Planner.init(); + planner.add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); + // sweep the excess eth + planner.add(Actions.SWEEP, abi.encode(currency0, address(this))); + bytes memory calls = planner.encode(); + + lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); // overpay on increase liquidity + BalanceDelta delta = getLastDelta(); + + // verify position liquidity increased + bytes32 positionId = + Position.calculatePositionKey(address(lpm), config.tickLower, config.tickUpper, bytes32(tokenId)); + (uint256 liquidity,,) = manager.getPositionInfo(config.poolKey.toId(), positionId); + assertEq(liquidity, liquidityToAdd + liquidityToAdd); // liquidity was doubled + + // verify native token balances changed as expected, with overpaid tokens returned + assertEq(balance0Before - currency0.balanceOfSelf(), amount0 + 1 wei); + assertEq(balance0Before - currency0.balanceOfSelf(), uint256(int256(-delta.amount0()))); + assertEq(balance1Before - currency1.balanceOfSelf(), uint256(int256(-delta.amount1()))); + } + function test_fuzz_decreaseLiquidity_native( IPoolManager.ModifyLiquidityParams memory params, uint256 decreaseLiquidityDelta diff --git a/test/position-managers/PositionManager.gas.t.sol b/test/position-managers/PositionManager.gas.t.sol index 8f9aade0c..23ac63d6f 100644 --- a/test/position-managers/PositionManager.gas.t.sol +++ b/test/position-managers/PositionManager.gas.t.sol @@ -69,14 +69,24 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { configNative = PositionConfig({poolKey: nativeKey, tickLower: -300, tickUpper: 300}); } - function test_gas_mint() public { + function test_gas_mint_withClose() public { Plan memory planner = Planner.init().add( Actions.MINT_POSITION, abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_mint"); + snapLastCall("PositionManager_mint_withClose"); + } + + function test_gas_mint_withSettlePair() public { + Plan memory planner = Planner.init().add( + Actions.MINT_POSITION, + abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) + ); + bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_mint_withSettlePair"); } function test_gas_mint_differentRanges() public { @@ -90,7 +100,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_warmedPool_differentRange"); @@ -107,7 +117,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_onSameTickLower"); @@ -124,13 +134,27 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_onSameTickUpper"); } - function test_gas_increaseLiquidity_erc20() public { + function test_gas_increaseLiquidity_erc20_withClose() public { + uint256 tokenId = lpm.nextTokenId(); + mint(config, 10_000 ether, address(this), ZERO_BYTES); + + Plan memory planner = Planner.init().add( + Actions.INCREASE_LIQUIDITY, + abi.encode(tokenId, config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) + ); + + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); + lpm.modifyLiquidities(calls, _deadline); + snapLastCall("PositionManager_increaseLiquidity_erc20_withClose"); + } + + function test_gas_increaseLiquidity_erc20_withSettlePair() public { uint256 tokenId = lpm.nextTokenId(); mint(config, 10_000 ether, address(this), ZERO_BYTES); @@ -139,9 +163,9 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 10_000 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithSettlePair(config.poolKey); lpm.modifyLiquidities(calls, _deadline); - snapLastCall("PositionManager_increaseLiquidity_erc20"); + snapLastCall("PositionManager_increaseLiquidity_erc20_withSettlePair"); } function test_gas_autocompound_exactUnclaimedFees() public { @@ -278,7 +302,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenIdAlice, config, liquidityDelta, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); @@ -294,7 +318,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decreaseLiquidity"); } @@ -317,7 +341,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) ); - bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); @@ -338,7 +362,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_collect"); } @@ -351,7 +375,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.MINT_POSITION, abi.encode(config, 10_001 ether, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(alice), ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); vm.prank(alice); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_mint_sameRange"); @@ -371,7 +395,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 10_000 ether, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_sameRange_allLiquidity"); } @@ -393,7 +417,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { abi.encode(tokenId, config, 0, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_collect_sameRange"); } @@ -405,7 +429,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Plan memory planner = Planner.init().add( Actions.BURN_POSITION, abi.encode(tokenId, config, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_burn_nonEmpty"); @@ -441,7 +465,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { ); // We must include CLOSE commands. - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_burnEmpty"); } @@ -465,7 +489,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { snapLastCall("PositionManager_mint_native"); } - function test_gas_mint_native_excess() public { + function test_gas_mint_native_excess_withClose() public { uint256 liquidityToAdd = 10_000 ether; Plan memory planner = Planner.init(); @@ -488,7 +512,32 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { ); // overpay on the native token lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); - snapLastCall("PositionManager_mint_nativeWithSweep"); + snapLastCall("PositionManager_mint_nativeWithSweep_withClose"); + } + + function test_gas_mint_native_excess_withSettlePair() public { + uint256 liquidityToAdd = 10_000 ether; + + Plan memory planner = Planner.init(); + planner.add( + Actions.MINT_POSITION, + abi.encode( + configNative, liquidityToAdd, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES + ) + ); + planner.add(Actions.SETTLE_PAIR, abi.encode(nativeKey.currency0, nativeKey.currency1)); + planner.add(Actions.SWEEP, abi.encode(CurrencyLibrary.NATIVE, address(this))); + bytes memory calls = planner.encode(); + + (uint256 amount0,) = LiquidityAmounts.getAmountsForLiquidity( + SQRT_PRICE_1_1, + TickMath.getSqrtPriceAtTick(configNative.tickLower), + TickMath.getSqrtPriceAtTick(configNative.tickUpper), + uint128(liquidityToAdd) + ); + // overpay on the native token + lpm.modifyLiquidities{value: amount0 * 2}(calls, _deadline); + snapLastCall("PositionManager_mint_nativeWithSweep_withSettlePair"); } function test_gas_increase_native() public { @@ -537,7 +586,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { Actions.BURN_POSITION, abi.encode(tokenId, configNative, MIN_SLIPPAGE_DECREASE, MIN_SLIPPAGE_DECREASE, ZERO_BYTES) ); - bytes memory calls = planner.finalizeModifyLiquidity(configNative.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_burn_nonEmpty_native"); @@ -572,7 +621,7 @@ contract PosMGasTest is Test, PosmTestSetup, GasSnapshot { planner.add(Actions.BURN_POSITION, abi.encode(tokenId, configNative, 0 wei, 0 wei, ZERO_BYTES)); // We must include CLOSE commands. - bytes memory calls = planner.finalizeModifyLiquidity(configNative.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(configNative.poolKey); lpm.modifyLiquidities(calls, _deadline); snapLastCall("PositionManager_decrease_burnEmpty_native"); } diff --git a/test/position-managers/PositionManager.multicall.t.sol b/test/position-managers/PositionManager.multicall.t.sol index da8774fdb..c47a98521 100644 --- a/test/position-managers/PositionManager.multicall.t.sol +++ b/test/position-managers/PositionManager.multicall.t.sol @@ -59,7 +59,7 @@ contract PositionManagerMulticallTest is Test, PosmTestSetup, LiquidityFuzzers { Actions.MINT_POSITION, abi.encode(config, 100e18, MAX_SLIPPAGE_INCREASE, MAX_SLIPPAGE_INCREASE, address(this), ZERO_BYTES) ); - bytes memory actions = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory actions = planner.finalizeModifyLiquidityWithClose(config.poolKey); calls[1] = abi.encodeWithSelector(IPositionManager.modifyLiquidities.selector, actions, _deadline); diff --git a/test/shared/LiquidityOperations.sol b/test/shared/LiquidityOperations.sol index a46230920..026abe8c2 100644 --- a/test/shared/LiquidityOperations.sol +++ b/test/shared/LiquidityOperations.sol @@ -101,7 +101,7 @@ abstract contract LiquidityOperations is CommonBase { Plan memory planner = Planner.init(); planner.add(Actions.MINT_POSITION, abi.encode(config, liquidity, amount0Max, amount1Max, recipient, hookData)); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getIncreaseEncoded( @@ -127,7 +127,7 @@ abstract contract LiquidityOperations is CommonBase { planner.add( Actions.INCREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityToAdd, amount0Max, amount1Max, hookData) ); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getDecreaseEncoded( @@ -153,7 +153,7 @@ abstract contract LiquidityOperations is CommonBase { planner.add( Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, liquidityToRemove, amount0Min, amount1Min, hookData) ); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getCollectEncoded(uint256 tokenId, PositionConfig memory config, bytes memory hookData) @@ -173,7 +173,7 @@ abstract contract LiquidityOperations is CommonBase { ) internal pure returns (bytes memory) { Plan memory planner = Planner.init(); planner.add(Actions.DECREASE_LIQUIDITY, abi.encode(tokenId, config, 0, amount0Min, amount1Min, hookData)); - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } function getBurnEncoded(uint256 tokenId, PositionConfig memory config, bytes memory hookData) @@ -194,6 +194,6 @@ abstract contract LiquidityOperations is CommonBase { Plan memory planner = Planner.init(); planner.add(Actions.BURN_POSITION, abi.encode(tokenId, config, amount0Min, amount1Min, hookData)); // Close needed on burn in case there is liquidity left in the position. - return planner.finalizeModifyLiquidity(config.poolKey); + return planner.finalizeModifyLiquidityWithClose(config.poolKey); } } diff --git a/test/shared/Planner.sol b/test/shared/Planner.sol index 71020abed..8c8a2f860 100644 --- a/test/shared/Planner.sol +++ b/test/shared/Planner.sol @@ -37,12 +37,17 @@ library Planner { return plan; } - function finalizeModifyLiquidity(Plan memory plan, PoolKey memory poolKey) internal pure returns (bytes memory) { + function finalizeModifyLiquidityWithClose(Plan memory plan, PoolKey memory poolKey) internal pure returns (bytes memory) { plan.add(Actions.CLOSE_CURRENCY, abi.encode(poolKey.currency0)); plan.add(Actions.CLOSE_CURRENCY, abi.encode(poolKey.currency1)); return plan.encode(); } + function finalizeModifyLiquidityWithSettlePair(Plan memory plan, PoolKey memory poolKey) internal pure returns (bytes memory) { + plan.add(Actions.SETTLE_PAIR, abi.encode(poolKey.currency0, poolKey.currency1)); + return plan.encode(); + } + function encode(Plan memory plan) internal pure returns (bytes memory) { return abi.encode(plan.actions, plan.params); } diff --git a/test/shared/fuzz/LiquidityFuzzers.sol b/test/shared/fuzz/LiquidityFuzzers.sol index 5bbfbc73e..02d6583f4 100644 --- a/test/shared/fuzz/LiquidityFuzzers.sol +++ b/test/shared/fuzz/LiquidityFuzzers.sol @@ -41,7 +41,7 @@ contract LiquidityFuzzers is Fuzzers { ); uint256 tokenId = lpm.nextTokenId(); - bytes memory calls = planner.finalizeModifyLiquidity(config.poolKey); + bytes memory calls = planner.finalizeModifyLiquidityWithClose(config.poolKey); lpm.modifyLiquidities(calls, block.timestamp + 1); return (tokenId, params);