From c8a2f3e7711de194064a5b535c08fb206e1a0629 Mon Sep 17 00:00:00 2001 From: chef-burger <137024020+chefburger@users.noreply.github.com> Date: Tue, 7 May 2024 13:31:06 +0800 Subject: [PATCH 1/5] feat: support getters for clpool to access pool state (#26) --- ...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 +- ...agerTest#testFuzzUpdateDynamicSwapFee.snap | 2 +- ...CLPoolManagerTest#testNoOp_gas_Donate.snap | 2 +- ...olManagerTest#testNoOp_gas_Initialize.snap | 2 +- ...nagerTest#testNoOp_gas_ModifyPosition.snap | 2 +- .../CLPoolManagerTest#testNoOp_gas_Swap.snap | 2 +- .../ExtsloadTest#extsloadInBatch.snap | 2 +- src/pool-cl/CLPoolManager.sol | 14 +- src/pool-cl/libraries/CLPoolGetters.sol | 24 +++ test/pool-cl/libraries/CLPoolGetters.t.sol | 158 ++++++++++++++++++ 23 files changed, 214 insertions(+), 22 deletions(-) create mode 100644 src/pool-cl/libraries/CLPoolGetters.sol create mode 100644 test/pool-cl/libraries/CLPoolGetters.t.sol diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index ed36e4f9..7f00140b 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -348848 \ No newline at end of file +348884 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 234f701a..0e7e22d9 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -59396 \ No newline at end of file +59432 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 2c5a90c6..1eca4578 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -242441 \ No newline at end of file +242477 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 79604bfa..2bb73b48 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -82514 \ No newline at end of file +82478 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 499386f7..87a1592c 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -52476 \ No newline at end of file +52440 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 8812e0dc..87813a8b 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -36547 \ No newline at end of file +36559 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index 1e077618..b644f6ff 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -42374 \ No newline at end of file +42386 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 8470dee1..f4a9218c 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -54672 \ No newline at end of file +54614 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index a15d1b9b..cfceb785 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -100748 \ No newline at end of file +100690 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 4aee4462..14edd531 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -25040458 \ No newline at end of file +25040400 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 8490e1e1..f5858bb9 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -35872 \ No newline at end of file +35814 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 017f6b15..1a0eac1b 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -101510 \ No newline at end of file +101452 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index 8fbed506..ab8d8afa 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -41522 \ No newline at end of file +41473 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index b8acbea3..900959ea 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -35875 \ No newline at end of file +35817 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap index 4153d68f..c888a1c0 100644 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap @@ -1 +1 @@ -4835 \ No newline at end of file +4857 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap index cfb243eb..617e26db 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap @@ -1 +1 @@ -19218 \ No newline at end of file +19187 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap index 91c6b09a..3333d53a 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap @@ -1 +1 @@ -37497 \ No newline at end of file +37517 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap index 692872a2..ee2b956c 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap @@ -1 +1 @@ -29405 \ No newline at end of file +29418 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap index c6b439c0..fe1f4523 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap @@ -1 +1 @@ -21667 \ No newline at end of file +21614 \ No newline at end of file diff --git a/.forge-snapshots/ExtsloadTest#extsloadInBatch.snap b/.forge-snapshots/ExtsloadTest#extsloadInBatch.snap index 6c07f02b..8f979894 100644 --- a/.forge-snapshots/ExtsloadTest#extsloadInBatch.snap +++ b/.forge-snapshots/ExtsloadTest#extsloadInBatch.snap @@ -1 +1 @@ -11352 \ No newline at end of file +11374 \ No newline at end of file diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index 18e26f3d..695785fe 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -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; @@ -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; @@ -287,11 +289,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 diff --git a/src/pool-cl/libraries/CLPoolGetters.sol b/src/pool-cl/libraries/CLPoolGetters.sol new file mode 100644 index 00000000..c3e102f2 --- /dev/null +++ b/src/pool-cl/libraries/CLPoolGetters.sol @@ -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); + } +} diff --git a/test/pool-cl/libraries/CLPoolGetters.t.sol b/test/pool-cl/libraries/CLPoolGetters.t.sol new file mode 100644 index 00000000..b7bccfc8 --- /dev/null +++ b/test/pool-cl/libraries/CLPoolGetters.t.sol @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {Test} from "forge-std/Test.sol"; +import {CLPoolGetters} from "../../../src/pool-cl/libraries/CLPoolGetters.sol"; +import {CLPool} from "../../../src/pool-cl/libraries/CLPool.sol"; +import {Tick} from "../../../src/pool-cl/libraries/Tick.sol"; + +contract CLPoolGettersTest is Test, GasSnapshot { + CLPool.State pool; + + using CLPoolGetters for CLPool.State; + + function testGetPoolTickInfo() public { + // info stored for each initialized individual tick + // struct Info { + // uint128 liquidityGross; + // int128 liquidityNet; + // uint256 feeGrowthOutside0X128; + // uint256 feeGrowthOutside1X128; + // } + + int24 tick = 5; + int24 randomTick = 15; + + { + Tick.Info memory info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + + pool.ticks[tick] = Tick.Info(100, 200, 300, 400); + info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 100); + assertEq(info.liquidityNet, 200); + assertEq(info.feeGrowthOutside0X128, 300); + assertEq(info.feeGrowthOutside1X128, 400); + + // access random tick + info = pool.getPoolTickInfo(randomTick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + + // tick clear + delete pool.ticks[tick]; + info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + } + + tick = -5; + randomTick = -15; + { + Tick.Info memory info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + + pool.ticks[tick] = Tick.Info(100, 200, 300, 400); + info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 100); + assertEq(info.liquidityNet, 200); + assertEq(info.feeGrowthOutside0X128, 300); + assertEq(info.feeGrowthOutside1X128, 400); + + // access random tick + info = pool.getPoolTickInfo(randomTick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + + // tick clear + delete pool.ticks[tick]; + info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + } + + tick = 0; + randomTick = type(int24).max; + { + Tick.Info memory info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + + pool.ticks[tick] = Tick.Info(100, 200, 300, 400); + info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 100); + assertEq(info.liquidityNet, 200); + assertEq(info.feeGrowthOutside0X128, 300); + assertEq(info.feeGrowthOutside1X128, 400); + + // access random tick + info = pool.getPoolTickInfo(randomTick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + + // tick clear + delete pool.ticks[tick]; + info = pool.getPoolTickInfo(tick); + assertEq(info.liquidityGross, 0); + assertEq(info.liquidityNet, 0); + assertEq(info.feeGrowthOutside0X128, 0); + assertEq(info.feeGrowthOutside1X128, 0); + } + } + + function testGetPoolBitmapInfo() public { + uint256 bitmap = pool.getPoolBitmapInfo(10); + assertEq(bitmap, 0); + + pool.tickBitmap[10] = 100; + bitmap = pool.getPoolBitmapInfo(10); + assertEq(bitmap, 100); + + // access random word + bitmap = pool.getPoolBitmapInfo(100); + assertEq(bitmap, 0); + + // set it back + pool.tickBitmap[10] = 0; + pool.getPoolBitmapInfo(10); + assertEq(bitmap, 0); + } + + function testGetFeeGrowthGlobals() public { + (uint256 feeGrowthGlobal0X128, uint256 feeGrowthGlobal1X128) = pool.getFeeGrowthGlobals(); + assertEq(feeGrowthGlobal0X128, 0); + assertEq(feeGrowthGlobal1X128, 0); + + pool.feeGrowthGlobal0X128 = 100; + pool.feeGrowthGlobal1X128 = 200; + (feeGrowthGlobal0X128, feeGrowthGlobal1X128) = pool.getFeeGrowthGlobals(); + assertEq(feeGrowthGlobal0X128, 100); + assertEq(feeGrowthGlobal1X128, 200); + + // set it back + pool.feeGrowthGlobal0X128 = 0; + pool.feeGrowthGlobal1X128 = 0; + (feeGrowthGlobal0X128, feeGrowthGlobal1X128) = pool.getFeeGrowthGlobals(); + assertEq(feeGrowthGlobal0X128, 0); + assertEq(feeGrowthGlobal1X128, 0); + } +} From ca20e08a44f0af64c8e018b40a9df6c470644696 Mon Sep 17 00:00:00 2001 From: ChefMist <133624774+ChefMist@users.noreply.github.com> Date: Tue, 7 May 2024 13:48:06 +0800 Subject: [PATCH 2/5] feat: return fee delta (#32) * feat: return fee delta * doc: update CLPool to include feeDelta doc * test: updated gas --- ...oolManagerTest#addLiquidity_fromEmpty.snap | 2 +- ...ManagerTest#addLiquidity_fromNonEmpty.snap | 2 +- ...lManagerTest#addLiquidity_nativeToken.snap | 2 +- .../CLPoolManagerTest#donateBothTokens.snap | 2 +- .../CLPoolManagerTest#gasDonateOneToken.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 +- .../CLPoolManagerTest#swap_withHooks.snap | 2 +- .../CLPoolManagerTest#swap_withNative.snap | 2 +- ...CLPoolManagerTest#testNoOp_gas_Donate.snap | 2 +- ...nagerTest#testNoOp_gas_ModifyPosition.snap | 2 +- .../CLPoolManagerTest#testNoOp_gas_Swap.snap | 2 +- src/pool-cl/CLPoolManager.sol | 15 ++- src/pool-cl/interfaces/ICLPoolManager.sol | 4 +- src/pool-cl/libraries/CLPool.sol | 7 +- src/types/BalanceDelta.sol | 3 + test/pool-cl/CLPoolManager.t.sol | 122 +++++++++++++++++- test/pool-cl/helpers/CLPoolManagerRouter.sol | 36 +++--- .../helpers/PoolModifyPositionTest.sol | 25 ++-- 22 files changed, 189 insertions(+), 53 deletions(-) diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index 7f00140b..662576dd 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -348884 \ No newline at end of file +349756 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 0e7e22d9..5303fc08 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -59432 \ No newline at end of file +60304 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 1eca4578..2c307e55 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -242477 \ No newline at end of file +243346 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 2bb73b48..25c0d962 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -82478 \ No newline at end of file +82481 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 87a1592c..983a98ad 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -52440 \ No newline at end of file +52443 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index b644f6ff..499ddfbe 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -42386 \ No newline at end of file +43238 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index f4a9218c..8c808890 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -54614 \ No newline at end of file +54615 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index cfceb785..3eb01f78 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -100690 \ No newline at end of file +100694 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 14edd531..269682f5 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -25040400 \ No newline at end of file +25040401 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index f5858bb9..613adf9a 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -35814 \ No newline at end of file +35815 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index ab8d8afa..7cfc7b68 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -41473 \ No newline at end of file +41474 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index 900959ea..59b09f1e 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -35817 \ No newline at end of file +35818 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap index 617e26db..87656c3f 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap @@ -1 +1 @@ -19187 \ No newline at end of file +19186 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap index ee2b956c..816372ab 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap @@ -1 +1 @@ -29418 \ No newline at end of file +29901 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap index fe1f4523..12b5d2ae 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap @@ -1 +1 @@ -21614 \ No newline at end of file +21613 \ No newline at end of file diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index 695785fe..ff7cb317 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -131,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(); @@ -144,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(); } @@ -152,13 +157,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, @@ -168,7 +173,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); diff --git a/src/pool-cl/interfaces/ICLPoolManager.sol b/src/pool-cl/interfaces/ICLPoolManager.sol index 604324db..2ab1dc1a 100644 --- a/src/pool-cl/interfaces/ICLPoolManager.sol +++ b/src/pool-cl/interfaces/ICLPoolManager.sol @@ -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; diff --git a/src/pool-cl/libraries/CLPool.sol b/src/pool-cl/libraries/CLPool.sol index 281b3cef..e0c7602b 100644 --- a/src/pool-cl/libraries/CLPool.sol +++ b/src/pool-cl/libraries/CLPool.sol @@ -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 @@ -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 { diff --git a/src/types/BalanceDelta.sol b/src/types/BalanceDelta.sol index 7872c730..32c5918c 100644 --- a/src/types/BalanceDelta.sol +++ b/src/types/BalanceDelta.sol @@ -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 { diff --git a/test/pool-cl/CLPoolManager.t.sol b/test/pool-cl/CLPoolManager.t.sol index 58f83040..1fb0ac54 100644 --- a/test/pool-cl/CLPoolManager.t.sol +++ b/test/pool-cl/CLPoolManager.t.sol @@ -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)))); @@ -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"); diff --git a/test/pool-cl/helpers/CLPoolManagerRouter.sol b/test/pool-cl/helpers/CLPoolManagerRouter.sol index 08e423dc..359aa441 100644 --- a/test/pool-cl/helpers/CLPoolManagerRouter.sol +++ b/test/pool-cl/helpers/CLPoolManagerRouter.sol @@ -43,12 +43,12 @@ contract CLPoolManagerRouter { PoolKey memory key, ICLPoolManager.ModifyLiquidityParams memory params, bytes memory hookData - ) external payable returns (BalanceDelta delta) { - delta = abi.decode( + ) external payable returns (BalanceDelta delta, BalanceDelta feeDelta) { + (delta, feeDelta) = abi.decode( vault.lock( abi.encode("modifyPosition", abi.encode(ModifyPositionCallbackData(msg.sender, key, params, hookData))) ), - (BalanceDelta) + (BalanceDelta, BalanceDelta) ); // if any ethers left @@ -61,45 +61,47 @@ contract CLPoolManagerRouter { function modifyPositionCallback(bytes memory rawData) private returns (bytes memory) { ModifyPositionCallbackData memory data = abi.decode(rawData, (ModifyPositionCallbackData)); - BalanceDelta delta = poolManager.modifyLiquidity(data.key, data.params, data.hookData); - + (BalanceDelta delta, BalanceDelta feeDelta) = poolManager.modifyLiquidity(data.key, data.params, data.hookData); if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { revert HookMissingNoOpPermission(); } - return abi.encode(delta); + return abi.encode(delta, feeDelta); } - if (delta.amount0() > 0) { + // For now assume to always settle feeDelta in the same way as delta + BalanceDelta totalDelta = delta + feeDelta; + + if (totalDelta.amount0() > 0) { if (data.key.currency0.isNative()) { - vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + vault.settle{value: uint128(totalDelta.amount0())}(data.key.currency0); } else { IERC20(Currency.unwrap(data.key.currency0)).transferFrom( - data.sender, address(vault), uint128(delta.amount0()) + data.sender, address(vault), uint128(totalDelta.amount0()) ); vault.settle(data.key.currency0); } } - if (delta.amount1() > 0) { + if (totalDelta.amount1() > 0) { if (data.key.currency1.isNative()) { - vault.settle{value: uint128(delta.amount1())}(data.key.currency1); + vault.settle{value: uint128(totalDelta.amount1())}(data.key.currency1); } else { IERC20(Currency.unwrap(data.key.currency1)).transferFrom( - data.sender, address(vault), uint128(delta.amount1()) + data.sender, address(vault), uint128(totalDelta.amount1()) ); vault.settle(data.key.currency1); } } - if (delta.amount0() < 0) { - vault.take(data.key.currency0, data.sender, uint128(-delta.amount0())); + if (totalDelta.amount0() < 0) { + vault.take(data.key.currency0, data.sender, uint128(-totalDelta.amount0())); } - if (delta.amount1() < 0) { - vault.take(data.key.currency1, data.sender, uint128(-delta.amount1())); + if (totalDelta.amount1() < 0) { + vault.take(data.key.currency1, data.sender, uint128(-totalDelta.amount1())); } - return abi.encode(delta); + return abi.encode(delta, feeDelta); } struct SwapTestSettings { diff --git a/test/pool-cl/helpers/PoolModifyPositionTest.sol b/test/pool-cl/helpers/PoolModifyPositionTest.sol index bc942f54..77cfb5e1 100644 --- a/test/pool-cl/helpers/PoolModifyPositionTest.sol +++ b/test/pool-cl/helpers/PoolModifyPositionTest.sol @@ -47,36 +47,39 @@ contract PoolModifyPositionTest is ILockCallback { CallbackData memory data = abi.decode(rawData, (CallbackData)); - BalanceDelta delta = manager.modifyLiquidity(data.key, data.params, data.hookData); + (BalanceDelta delta, BalanceDelta feeDelta) = manager.modifyLiquidity(data.key, data.params, data.hookData); - if (delta.amount0() > 0) { + // For now assume to always settle feeDelta in the same way as delta + BalanceDelta totalDelta = delta + feeDelta; + + if (totalDelta.amount0() > 0) { if (data.key.currency0.isNative()) { - vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + vault.settle{value: uint128(totalDelta.amount0())}(data.key.currency0); } else { IERC20(Currency.unwrap(data.key.currency0)).transferFrom( - data.sender, address(vault), uint128(delta.amount0()) + data.sender, address(vault), uint128(totalDelta.amount0()) ); vault.settle(data.key.currency0); } } - if (delta.amount1() > 0) { + if (totalDelta.amount1() > 0) { if (data.key.currency1.isNative()) { vault.settle{value: uint128(delta.amount1())}(data.key.currency1); } else { IERC20(Currency.unwrap(data.key.currency1)).transferFrom( - data.sender, address(vault), uint128(delta.amount1()) + data.sender, address(vault), uint128(totalDelta.amount1()) ); vault.settle(data.key.currency1); } } - if (delta.amount0() < 0) { - vault.take(data.key.currency0, data.sender, uint128(-delta.amount0())); + if (totalDelta.amount0() < 0) { + vault.take(data.key.currency0, data.sender, uint128(-totalDelta.amount0())); } - if (delta.amount1() < 0) { - vault.take(data.key.currency1, data.sender, uint128(-delta.amount1())); + if (totalDelta.amount1() < 0) { + vault.take(data.key.currency1, data.sender, uint128(-totalDelta.amount1())); } - return abi.encode(delta); + return abi.encode(totalDelta); } } From 2c24eb341ee26735d4e3dbc0ac9473ee2b0c41fa Mon Sep 17 00:00:00 2001 From: ChefMist <133624774+ChefMist@users.noreply.github.com> Date: Tue, 7 May 2024 14:00:09 +0800 Subject: [PATCH 3/5] feat: do not callback hook if caller is hook (#29) * feat: do not callback hook if caller is hook * chore: gas tweak * test: update test after merge --- .../BinHookTest#testBurnSucceedsWithHook.snap | 2 +- ...inHookTest#testDonateSucceedsWithHook.snap | 2 +- ...okTest#testInitializeSucceedsWithHook.snap | 2 +- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- ...oolManagerTest#testBurnNativeCurrency.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#testNoOpGas_Burn.snap | 2 +- ...BinPoolManagerTest#testNoOpGas_Donate.snap | 2 +- ...oolManagerTest#testNoOpGas_Initialize.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Mint.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Swap.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 +- ...CLPoolManagerTest#testNoOp_gas_Donate.snap | 2 +- ...olManagerTest#testNoOp_gas_Initialize.snap | 2 +- ...nagerTest#testNoOp_gas_ModifyPosition.snap | 2 +- .../CLPoolManagerTest#testNoOp_gas_Swap.snap | 2 +- src/libraries/Hooks.sol | 12 +- src/pool-bin/BinPoolManager.sol | 30 +- src/pool-cl/CLPoolManager.sol | 32 +- test/libraries/Hooks/Hooks.t.sol | 34 +- test/pool-bin/BinHookSkipCallback.t.sol | 175 +++++++++ test/pool-bin/helpers/BaseBinTestHook.sol | 43 ++ test/pool-bin/helpers/BinLiquidityHelper.sol | 2 +- test/pool-bin/helpers/BinSkipCallbackHook.sol | 297 ++++++++++++++ test/pool-bin/helpers/BinSwapHelper.sol | 2 +- test/pool-cl/CLHookSkipCallback.t.sol | 159 ++++++++ test/pool-cl/helpers/BaseCLTestHook.sol | 43 ++ test/pool-cl/helpers/CLPoolManagerRouter.sol | 6 +- test/pool-cl/helpers/CLSkipCallbackHook.sol | 369 ++++++++++++++++++ 54 files changed, 1191 insertions(+), 95 deletions(-) create mode 100644 test/pool-bin/BinHookSkipCallback.t.sol create mode 100644 test/pool-bin/helpers/BinSkipCallbackHook.sol create mode 100644 test/pool-cl/CLHookSkipCallback.t.sol create mode 100644 test/pool-cl/helpers/CLSkipCallbackHook.sol diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index e39570f1..cfddf70e 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -143699 \ No newline at end of file +143903 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap index f1685993..db45cf73 100644 --- a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap @@ -1 +1 @@ -135043 \ No newline at end of file +135247 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index feed235a..82b81a8c 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -108914 \ No newline at end of file +109118 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 11adaddb..086607f7 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -301091 \ No newline at end of file +301295 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index b3a4b1df..59e2fe2a 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -139136 \ No newline at end of file +139320 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap index f57e6954..0e901dcc 100644 --- a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap @@ -1 +1 @@ -90479 \ No newline at end of file +90617 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap index 0d077daa..77509a32 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap @@ -1 +1 @@ -65672 \ No newline at end of file +65810 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap index 636780b6..c9417e09 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap @@ -1 +1 @@ -149227 \ No newline at end of file +149365 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap index 5d28fd52..715b65be 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap @@ -1 +1 @@ -66851 \ No newline at end of file +66989 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap index b6666ed0..05f5091e 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap @@ -1 +1 @@ -52444 \ No newline at end of file +52582 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index b87a958f..aa25e617 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -968444 \ No newline at end of file +968582 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index 6d2afda6..f22c5e5a 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -119958 \ No newline at end of file +120096 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index 634f8883..a27a0639 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -337634 \ No newline at end of file +337772 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index b635cba9..6c3b1dd4 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -54695 \ No newline at end of file +54833 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 1d4e2bf7..c12c16ee 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -89312 \ No newline at end of file +89370 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 2035602e..96d5c05d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -91297 \ No newline at end of file +91355 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index bd838801..c724ad85 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -70741 \ No newline at end of file +70859 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index ff78ebe9..345aa66a 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -319341 \ No newline at end of file +319479 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap index 768aed05..3293c880 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap @@ -1 +1 @@ -41503 \ No newline at end of file +41726 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap index ca967971..2dfafdff 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap @@ -1 +1 @@ -19392 \ No newline at end of file +19494 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap index a238b0f8..07d18bee 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap @@ -1 +1 @@ -37117 \ No newline at end of file +37255 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap index e12ba3b7..a7ed01ba 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap @@ -1 +1 @@ -39136 \ No newline at end of file +39359 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap index e75d7399..cbc2e8f4 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap @@ -1 +1 @@ -22507 \ No newline at end of file +22739 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index 662576dd..f6f105a0 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -349756 \ No newline at end of file +349902 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 5303fc08..80610c10 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -60304 \ No newline at end of file +60450 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 2c307e55..723e01c8 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -243346 \ No newline at end of file +243492 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 25c0d962..d9dd718c 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -82481 \ No newline at end of file +82627 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 983a98ad..8d6144ca 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -52443 \ No newline at end of file +52589 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 87813a8b..864c111e 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -36559 \ No newline at end of file +36705 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index 499ddfbe..d49ddc20 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -43238 \ No newline at end of file +43384 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 8c808890..eb50e3ca 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -54615 \ No newline at end of file +54761 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index 3eb01f78..fa438b60 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -100694 \ No newline at end of file +100840 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 269682f5..bcd80721 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -25040401 \ No newline at end of file +25040547 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 613adf9a..f72983be 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -35815 \ No newline at end of file +35961 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 1a0eac1b..454154ca 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -101452 \ No newline at end of file +101598 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index 7cfc7b68..964bd6c9 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -41474 \ No newline at end of file +41680 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index 59b09f1e..1f313c58 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -35818 \ No newline at end of file +35964 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap index 87656c3f..ddc2964e 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap @@ -1 +1 @@ -19186 \ No newline at end of file +19394 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap index 3333d53a..663d520f 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap @@ -1 +1 @@ -37517 \ No newline at end of file +37663 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap index 816372ab..dc8a3822 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap @@ -1 +1 @@ -29901 \ No newline at end of file +30109 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap index 12b5d2ae..b2fcd4d8 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap @@ -1 +1 @@ -21613 \ No newline at end of file +21824 \ No newline at end of file diff --git a/src/libraries/Hooks.sol b/src/libraries/Hooks.sol index 892ebdcf..f1e9be5b 100644 --- a/src/libraries/Hooks.sol +++ b/src/libraries/Hooks.sol @@ -45,14 +45,22 @@ library Hooks { } } - function shouldCall(bytes32 parameters, uint8 offset) internal pure returns (bool) { + /// @return true if parameter has offset enabled + function hasOffsetEnabled(bytes32 parameters, uint8 offset) internal pure returns (bool) { return parameters.decodeBool(offset); } + /// @notice checks if hook should be called -- based on 2 factors: + /// 1. whether pool.parameters has the callback offset registered + /// 2. whether msg.sender is the hook itself + function shouldCall(bytes32 parameters, uint8 offset, IHooks hook) internal view returns (bool) { + return hasOffsetEnabled(parameters, offset) && address(hook) != msg.sender; + } + /// @dev Verify hook return value matches no-op when these 2 conditions are met /// 1) Hook have permission for no-op /// 2) Return value is no-op selector function isValidNoOpCall(bytes32 parameters, uint8 noOpOffset, bytes4 selector) internal pure returns (bool) { - return shouldCall(parameters, noOpOffset) && selector == NO_OP_SELECTOR; + return hasOffsetEnabled(parameters, noOpOffset) && selector == NO_OP_SELECTOR; } } diff --git a/src/pool-bin/BinPoolManager.sol b/src/pool-bin/BinPoolManager.sol index 64c725d0..4f673523 100644 --- a/src/pool-bin/BinPoolManager.sol +++ b/src/pool-bin/BinPoolManager.sol @@ -106,7 +106,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { uint24 swapFee = key.fee.getInitialSwapFee(); if (swapFee.isSwapFeeTooLarge(SwapFeeLibrary.TEN_PERCENT_FEE)) revert FeeTooLarge(); - if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET, hooks)) { if (hooks.beforeInitialize(msg.sender, key, activeId, hookData) != IBinHooks.beforeInitialize.selector) { revert Hooks.InvalidHookResponse(); } @@ -120,7 +120,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Initialize(id, key.currency0, key.currency1, key.fee, binStep, hooks); - if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET, hooks)) { if (hooks.afterInitialize(msg.sender, key, activeId, hookData) != IBinHooks.afterInitialize.selector) { revert Hooks.InvalidHookResponse(); } @@ -139,7 +139,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET, hooks)) { bytes4 selector = hooks.beforeSwap(msg.sender, key, swapForY, amountIn, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -170,7 +170,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { emit Swap(id, msg.sender, delta.amount0(), delta.amount1(), activeId, swapFee, feeForProtocol); } - if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET, hooks)) { if (hooks.afterSwap(msg.sender, key, swapForY, amountIn, delta, hookData) != IBinHooks.afterSwap.selector) { revert Hooks.InvalidHookResponse(); } @@ -241,7 +241,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_MINT_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_MINT_OFFSET, hooks)) { bytes4 selector = hooks.beforeMint(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -274,7 +274,7 @@ contract BinPoolManager is IBinPoolManager, Fees, 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); - if (key.parameters.shouldCall(HOOKS_AFTER_MINT_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_MINT_OFFSET, hooks)) { if (hooks.afterMint(msg.sender, key, params, delta, hookData) != IBinHooks.afterMint.selector) { revert Hooks.InvalidHookResponse(); } @@ -292,7 +292,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_BURN_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_BURN_OFFSET, hooks)) { bytes4 selector = hooks.beforeBurn(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -312,7 +312,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @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); - if (key.parameters.shouldCall(HOOKS_AFTER_BURN_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_BURN_OFFSET, hooks)) { if (hooks.afterBurn(msg.sender, key, params, delta, hookData) != IBinHooks.afterBurn.selector) { revert Hooks.InvalidHookResponse(); } @@ -330,7 +330,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET, hooks)) { bytes4 selector = hooks.beforeDonate(msg.sender, key, amount0, amount1, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -347,7 +347,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Donate(id, msg.sender, delta.amount0(), delta.amount1(), binId); - if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET, hooks)) { if (hooks.afterDonate(msg.sender, key, amount0, amount1, hookData) != IBinHooks.afterDonate.selector) { revert Hooks.InvalidHookResponse(); } @@ -386,12 +386,12 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { function _validateHookNoOp(PoolKey memory key) internal pure { // if no-op is active for hook, there must be a before* hook active too - if (key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (key.parameters.hasOffsetEnabled(HOOKS_NO_OP_OFFSET)) { if ( - !key.parameters.shouldCall(HOOKS_BEFORE_MINT_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_BURN_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET) + !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_MINT_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_BURN_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_DONATE_OFFSET) ) { revert Hooks.NoOpHookMissingBeforeCall(); } diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index ff7cb317..5eb7cf36 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -103,7 +103,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { uint24 swapFee = key.fee.getInitialSwapFee(); if (swapFee.isSwapFeeTooLarge(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE)) revert FeeTooLarge(); - if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET, hooks)) { if (hooks.beforeInitialize(msg.sender, key, sqrtPriceX96, hookData) != ICLHooks.beforeInitialize.selector) { revert Hooks.InvalidHookResponse(); } @@ -116,7 +116,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Initialize(id, key.currency0, key.currency1, key.fee, tickSpacing, hooks); - if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET, hooks)) { if ( hooks.afterInitialize(msg.sender, key, sqrtPriceX96, tick, hookData) != ICLHooks.afterInitialize.selector @@ -145,7 +145,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { ICLHooks hooks = ICLHooks(address(key.hooks)); - if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET)) { + if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET, hooks)) { 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. @@ -153,7 +153,8 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { } else if (selector != ICLHooks.beforeAddLiquidity.selector) { revert Hooks.InvalidHookResponse(); } - } else if (params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET)) { + } else if (params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET, hooks)) + { 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. @@ -178,13 +179,14 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { /// @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); - if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_AFTER_ADD_LIQUIDITY_OFFSET)) { + if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_AFTER_ADD_LIQUIDITY_OFFSET, hooks)) { if ( hooks.afterAddLiquidity(msg.sender, key, params, delta, hookData) != ICLHooks.afterAddLiquidity.selector ) { revert Hooks.InvalidHookResponse(); } - } else if (params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET)) { + } else if (params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET, hooks)) + { if ( hooks.afterRemoveLiquidity(msg.sender, key, params, delta, hookData) != ICLHooks.afterRemoveLiquidity.selector @@ -207,7 +209,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { ICLHooks hooks = ICLHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET, hooks)) { bytes4 selector = hooks.beforeSwap(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -250,7 +252,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { state.protocolFee ); - if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET, hooks)) { if (hooks.afterSwap(msg.sender, key, params, delta, hookData) != ICLHooks.afterSwap.selector) { revert Hooks.InvalidHookResponse(); } @@ -269,7 +271,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { _checkPoolInitialized(id); ICLHooks hooks = ICLHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET, hooks)) { bytes4 selector = hooks.beforeDonate(msg.sender, key, amount0, amount1, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -286,7 +288,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Donate(id, msg.sender, amount0, amount1, tick); - if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET, hooks)) { if (hooks.afterDonate(msg.sender, key, amount0, amount1, hookData) != ICLHooks.afterDonate.selector) { revert Hooks.InvalidHookResponse(); } @@ -334,12 +336,12 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { function _validateHookNoOp(PoolKey memory key) internal pure { // if no-op is active for hook, there must be a before* hook active too - if (key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (key.parameters.hasOffsetEnabled(HOOKS_NO_OP_OFFSET)) { if ( - !key.parameters.shouldCall(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET) + !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_DONATE_OFFSET) ) { revert Hooks.NoOpHookMissingBeforeCall(); } diff --git a/test/libraries/Hooks/Hooks.t.sol b/test/libraries/Hooks/Hooks.t.sol index e14a860d..7fb583a2 100644 --- a/test/libraries/Hooks/Hooks.t.sol +++ b/test/libraries/Hooks/Hooks.t.sol @@ -71,24 +71,24 @@ contract HooksTest is Test { this.toCallAsCalldata(poolKey); } - function testShouldCall() public { + function testhasOffsetEnabled() public { // 0b1010101010101010 - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 0), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 1), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 2), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 3), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 4), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 5), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 6), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 7), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 8), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 9), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 10), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 11), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 12), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 13), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 14), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 15), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 0), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 1), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 2), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 3), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 4), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 5), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 6), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 7), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 8), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 9), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 10), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 11), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 12), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 13), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 14), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 15), true); } function testIsValidNoOpCall(bytes32 parameters, uint8 noOpOffset, bytes4 selector) public { diff --git a/test/pool-bin/BinHookSkipCallback.t.sol b/test/pool-bin/BinHookSkipCallback.t.sol new file mode 100644 index 00000000..b66d7dff --- /dev/null +++ b/test/pool-bin/BinHookSkipCallback.t.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; +import {IBinPoolManager} from "../../src/pool-bin/interfaces/IBinPoolManager.sol"; +import {Vault} from "../../src/Vault.sol"; +import {Currency} from "../../src/types/Currency.sol"; +import {PoolKey} from "../../src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../../src/types/BalanceDelta.sol"; +import {BinPoolManager} from "../../src/pool-bin/BinPoolManager.sol"; +import {BinPool} from "../../src/pool-bin/libraries/BinPool.sol"; +import {PackedUint128Math} from "../../src/pool-bin/libraries/math/PackedUint128Math.sol"; +import {SafeCast} from "../../src/pool-bin/libraries/math/SafeCast.sol"; +import {BinPoolParametersHelper} from "../../src/pool-bin/libraries/BinPoolParametersHelper.sol"; +import {Constants} from "../../src/pool-bin/libraries/Constants.sol"; +import {IBinHooks} from "../../src/pool-bin/interfaces/IBinHooks.sol"; +import {BinFeeManagerHook} from "./helpers/BinFeeManagerHook.sol"; +import {IHooks} from "../../src/interfaces/IHooks.sol"; +import {IBinHooks} from "../../src/pool-bin/interfaces/IBinHooks.sol"; +import {BinSwapHelper} from "./helpers/BinSwapHelper.sol"; +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 {BinSkipCallbackHook} from "./helpers/BinSkipCallbackHook.sol"; + +contract BinHookSkipCallbackTest is Test, GasSnapshot, BinTestHelper { + using PoolIdLibrary for PoolKey; + using SafeCast for uint256; + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + using BinPoolParametersHelper for bytes32; + + Vault public vault; + BinPoolManager public poolManager; + BinSkipCallbackHook public binSkipCallbackHook; + + BinSwapHelper public binSwapHelper; + BinLiquidityHelper public binLiquidityHelper; + BinDonateHelper public binDonateHelper; + + uint24 activeId = 2 ** 23; // where token0 and token1 price is the same + + PoolKey key; + bytes32 poolParam; + MockERC20 token0; + MockERC20 token1; + Currency currency0; + Currency currency1; + + function setUp() public { + vault = new Vault(); + poolManager = new BinPoolManager(IVault(address(vault)), 500000); + + vault.registerPoolManager(address(poolManager)); + + 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); + + IBinPoolManager iBinPoolManager = IBinPoolManager(address(poolManager)); + IVault iVault = IVault(address(vault)); + + binSwapHelper = new BinSwapHelper(iBinPoolManager, iVault); + binLiquidityHelper = new BinLiquidityHelper(iBinPoolManager, iVault); + binDonateHelper = new BinDonateHelper(iBinPoolManager, iVault); + 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(binDonateHelper), 1000 ether); + token1.approve(address(binDonateHelper), 1000 ether); + + binSkipCallbackHook = new BinSkipCallbackHook(iVault, iBinPoolManager); + token0.approve(address(binSkipCallbackHook), 1000 ether); + token1.approve(address(binSkipCallbackHook), 1000 ether); + + key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: binSkipCallbackHook, + poolManager: IPoolManager(address(poolManager)), + fee: uint24(3000), // 3000 = 0.3% + parameters: bytes32(uint256(binSkipCallbackHook.getHooksRegistrationBitmap())).setBinStep(10) + }); + } + + function testInitialize_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testInitialize_NotfromHook() external { + poolManager.initialize(key, activeId, new bytes(0)); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testMint_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binLiquidityHelper.mint(key, mintParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testMint_NotFromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testBurn_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + IBinPoolManager.BurnParams memory burnParams = + _getSingleBinBurnLiquidityParams(key, poolManager, activeId, address(binSkipCallbackHook), 100); + + binSkipCallbackHook.burn(key, burnParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testBurn_NotFromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binLiquidityHelper.mint(key, mintParams, ""); + + IBinPoolManager.BurnParams memory burnParams = + _getSingleBinBurnLiquidityParams(key, poolManager, activeId, address(binLiquidityHelper), 100); + + binLiquidityHelper.burn(key, burnParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 4); + } + + function testDonate_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + binSkipCallbackHook.donate(key, 10 ether, 10 ether, ""); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testDonate_NotFromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + binDonateHelper.donate(key, 10 ether, 10 ether, ""); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + receive() external payable {} +} diff --git a/test/pool-bin/helpers/BaseBinTestHook.sol b/test/pool-bin/helpers/BaseBinTestHook.sol index 58d30334..3b1085c9 100644 --- a/test/pool-bin/helpers/BaseBinTestHook.sol +++ b/test/pool-bin/helpers/BaseBinTestHook.sol @@ -1,6 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; +import { + HOOKS_BEFORE_INITIALIZE_OFFSET, + HOOKS_AFTER_INITIALIZE_OFFSET, + HOOKS_BEFORE_MINT_OFFSET, + HOOKS_AFTER_MINT_OFFSET, + HOOKS_BEFORE_BURN_OFFSET, + HOOKS_AFTER_BURN_OFFSET, + HOOKS_BEFORE_SWAP_OFFSET, + HOOKS_AFTER_SWAP_OFFSET, + HOOKS_BEFORE_DONATE_OFFSET, + HOOKS_AFTER_DONATE_OFFSET, + HOOKS_NO_OP_OFFSET +} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; import {IBinHooks} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; @@ -9,6 +22,20 @@ import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager. contract BaseBinTestHook is IBinHooks { error HookNotImplemented(); + struct Permissions { + bool beforeInitialize; + bool afterInitialize; + bool beforeMint; + bool afterMint; + bool beforeBurn; + bool afterBurn; + bool beforeSwap; + bool afterSwap; + bool beforeDonate; + bool afterDonate; + bool noOp; + } + function getHooksRegistrationBitmap() external view virtual returns (uint16) { revert HookNotImplemented(); } @@ -80,4 +107,20 @@ contract BaseBinTestHook is IBinHooks { { revert HookNotImplemented(); } + + function _hooksRegistrationBitmapFrom(Permissions memory permissions) internal pure returns (uint16) { + return uint16( + (permissions.beforeInitialize ? 1 << HOOKS_BEFORE_INITIALIZE_OFFSET : 0) + | (permissions.afterInitialize ? 1 << HOOKS_AFTER_INITIALIZE_OFFSET : 0) + | (permissions.beforeMint ? 1 << HOOKS_BEFORE_MINT_OFFSET : 0) + | (permissions.afterMint ? 1 << HOOKS_AFTER_MINT_OFFSET : 0) + | (permissions.beforeBurn ? 1 << HOOKS_BEFORE_BURN_OFFSET : 0) + | (permissions.afterBurn ? 1 << HOOKS_AFTER_BURN_OFFSET : 0) + | (permissions.beforeSwap ? 1 << HOOKS_BEFORE_SWAP_OFFSET : 0) + | (permissions.afterSwap ? 1 << HOOKS_AFTER_SWAP_OFFSET : 0) + | (permissions.beforeDonate ? 1 << HOOKS_BEFORE_DONATE_OFFSET : 0) + | (permissions.afterDonate ? 1 << HOOKS_AFTER_DONATE_OFFSET : 0) + | (permissions.noOp ? 1 << HOOKS_NO_OP_OFFSET : 0) + ); + } } diff --git a/test/pool-bin/helpers/BinLiquidityHelper.sol b/test/pool-bin/helpers/BinLiquidityHelper.sol index 17e203fd..b1c86634 100644 --- a/test/pool-bin/helpers/BinLiquidityHelper.sol +++ b/test/pool-bin/helpers/BinLiquidityHelper.sol @@ -88,7 +88,7 @@ contract BinLiquidityHelper { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); diff --git a/test/pool-bin/helpers/BinSkipCallbackHook.sol b/test/pool-bin/helpers/BinSkipCallbackHook.sol new file mode 100644 index 00000000..25557968 --- /dev/null +++ b/test/pool-bin/helpers/BinSkipCallbackHook.sol @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IVault} from "../../../src/interfaces/IVault.sol"; +import {Currency, CurrencyLibrary} from "../../../src/types/Currency.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {PoolKey} from "../../../src/types/PoolKey.sol"; +import {BalanceDelta} from "../../../src/types/BalanceDelta.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"; +import {BaseBinTestHook} from "./BaseBinTestHook.sol"; + +/// @notice CL hook which does a callback +contract BinSkipCallbackHook is BaseBinTestHook { + error InvalidAction(); + + using CurrencyLibrary for Currency; + using Hooks for bytes32; + + IBinPoolManager public immutable poolManager; + IVault public immutable vault; + ActionType public actionType; + + enum ActionType { + Mint, + Burn, + Swap, + Donate + } + + uint16 bitmap; + uint256 public hookCounterCallbackCount; + + constructor(IVault _vault, IBinPoolManager _poolManager) { + vault = _vault; + poolManager = _poolManager; + } + + function getHooksRegistrationBitmap() external pure override returns (uint16) { + return _hooksRegistrationBitmapFrom( + Permissions({ + beforeInitialize: true, + afterInitialize: true, + beforeMint: true, + afterMint: true, + beforeBurn: true, + afterBurn: true, + beforeSwap: true, + afterSwap: true, + beforeDonate: true, + afterDonate: true, + noOp: false + }) + ); + } + + struct CallbackData { + bytes action; + bytes rawCallbackData; + } + + struct BurnCallbackData { + address sender; + PoolKey key; + IBinPoolManager.BurnParams params; + bytes hookData; + } + + function burn(PoolKey memory key, IBinPoolManager.BurnParams memory params, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + BurnCallbackData memory data = BurnCallbackData(msg.sender, key, params, hookData); + actionType = ActionType.Burn; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + } + + struct MintCallbackData { + address sender; + PoolKey key; + IBinPoolManager.MintParams params; + bytes hookData; + } + + function mint(PoolKey memory key, IBinPoolManager.MintParams memory params, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + MintCallbackData memory data = MintCallbackData(msg.sender, key, params, hookData); + actionType = ActionType.Mint; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + } + + struct SwapCallbackData { + address sender; + TestSettings testSettings; + PoolKey key; + bool swapForY; + uint128 amountIn; + bytes hookData; + } + + struct TestSettings { + bool withdrawTokens; + bool settleUsingTransfer; + } + + function swap( + PoolKey memory key, + bool swapForY, + uint128 amountIn, + TestSettings memory testSettings, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + SwapCallbackData memory data = SwapCallbackData(msg.sender, testSettings, key, swapForY, amountIn, hookData); + actionType = ActionType.Swap; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + struct DonateCallbackData { + address sender; + PoolKey key; + uint128 amount0; + uint128 amount1; + bytes hookData; + } + + function donate(PoolKey memory key, uint128 amount0, uint128 amount1, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + DonateCallbackData memory data = DonateCallbackData(msg.sender, key, amount0, amount1, hookData); + actionType = ActionType.Donate; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + function lockAcquired(bytes calldata callbackData) external returns (bytes memory) { + require(msg.sender == address(vault)); + BalanceDelta delta; + PoolKey memory key; + address sender; + + if (actionType == ActionType.Burn) { + BurnCallbackData memory data = abi.decode(callbackData, (BurnCallbackData)); + + key = data.key; + sender = data.sender; + delta = poolManager.burn(data.key, data.params, data.hookData); + } else if (actionType == ActionType.Mint) { + MintCallbackData memory data = abi.decode(callbackData, (MintCallbackData)); + + key = data.key; + sender = data.sender; + (delta,) = poolManager.mint(data.key, data.params, data.hookData); + } else if (actionType == ActionType.Swap) { + SwapCallbackData memory data = abi.decode(callbackData, (SwapCallbackData)); + + key = data.key; + sender = data.sender; + delta = poolManager.swap(data.key, data.swapForY, data.amountIn, data.hookData); + } else if (actionType == ActionType.Donate) { + DonateCallbackData memory data = abi.decode(callbackData, (DonateCallbackData)); + + key = data.key; + sender = data.sender; + (delta,) = poolManager.donate(data.key, data.amount0, data.amount1, data.hookData); + } + + if (delta.amount0() > 0) { + if (key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(key.currency0); + } else { + IERC20(Currency.unwrap(key.currency0)).transferFrom(sender, address(vault), uint128(delta.amount0())); + vault.settle(key.currency0); + } + } + + if (delta.amount1() > 0) { + if (key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(key.currency1); + } else { + IERC20(Currency.unwrap(key.currency1)).transferFrom(sender, address(vault), uint128(delta.amount1())); + vault.settle(key.currency1); + } + } + + if (delta.amount0() < 0) { + vault.take(key.currency0, sender, uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + vault.take(key.currency1, sender, uint128(-delta.amount1())); + } + + return abi.encode(delta); + } + + function initialize(PoolKey memory key, uint24 activeId, bytes memory hookData) external { + poolManager.initialize(key, activeId, hookData); + } + + function beforeInitialize(address, PoolKey calldata, uint24, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeInitialize.selector; + } + + function afterInitialize(address, PoolKey calldata, uint24, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterInitialize.selector; + } + + function beforeMint(address, PoolKey calldata, IBinPoolManager.MintParams calldata, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeMint.selector; + } + + function afterMint(address, PoolKey calldata, IBinPoolManager.MintParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterMint.selector; + } + + function beforeBurn(address, PoolKey calldata, IBinPoolManager.BurnParams calldata, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeBurn.selector; + } + + function afterBurn(address, PoolKey calldata, IBinPoolManager.BurnParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterBurn.selector; + } + + function beforeSwap(address, PoolKey calldata, bool, uint128, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeSwap.selector; + } + + function afterSwap(address, PoolKey calldata, bool, uint128, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterSwap.selector; + } + + function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeDonate.selector; + } + + function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterDonate.selector; + } +} diff --git a/test/pool-bin/helpers/BinSwapHelper.sol b/test/pool-bin/helpers/BinSwapHelper.sol index fc75b53b..9a764e1e 100644 --- a/test/pool-bin/helpers/BinSwapHelper.sol +++ b/test/pool-bin/helpers/BinSwapHelper.sol @@ -63,7 +63,7 @@ contract BinSwapHelper { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); diff --git a/test/pool-cl/CLHookSkipCallback.t.sol b/test/pool-cl/CLHookSkipCallback.t.sol new file mode 100644 index 00000000..f3bbadfa --- /dev/null +++ b/test/pool-cl/CLHookSkipCallback.t.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {Vault} from "../../src/Vault.sol"; +import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; +import {ICLPoolManager} from "../../src/pool-cl/interfaces/ICLPoolManager.sol"; +import {CLPoolManager} from "../../src/pool-cl/CLPoolManager.sol"; +import {CLPool} from "../../src/pool-cl/libraries/CLPool.sol"; +import {Currency, CurrencyLibrary} from "../../src/types/Currency.sol"; +import {PoolKey} from "../../src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; +import {IHooks} from "../../src/interfaces/IHooks.sol"; +import {Hooks} from "../../src/libraries/Hooks.sol"; +import {CLPoolManagerRouter} from "./helpers/CLPoolManagerRouter.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Deployers} from "./helpers/Deployers.sol"; +import {TokenFixture} from "../helpers/TokenFixture.sol"; +import {SwapFeeLibrary} from "../../src/libraries/SwapFeeLibrary.sol"; +import {CLPoolParametersHelper} from "../../src/pool-cl/libraries/CLPoolParametersHelper.sol"; +import {ParametersHelper} from "../../src/libraries/math/ParametersHelper.sol"; +import {CLSkipCallbackHook} from "./helpers/CLSkipCallbackHook.sol"; + +contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + using CLPoolParametersHelper for bytes32; + using ParametersHelper for bytes32; + using SwapFeeLibrary for uint24; + + PoolKey key; + IVault public vault; + CLPoolManager public poolManager; + CLPoolManagerRouter public router; + // hook with all callback registered + CLSkipCallbackHook public clSkipCallbackHook; + + function setUp() public { + initializeTokens(); + (vault, poolManager) = createFreshManager(); + + router = new CLPoolManagerRouter(vault, poolManager); + clSkipCallbackHook = new CLSkipCallbackHook(vault, poolManager); + + IERC20(Currency.unwrap(currency0)).approve(address(router), 1000 ether); + IERC20(Currency.unwrap(currency1)).approve(address(router), 1000 ether); + IERC20(Currency.unwrap(currency0)).approve(address(clSkipCallbackHook), 1000 ether); + IERC20(Currency.unwrap(currency1)).approve(address(clSkipCallbackHook), 1000 ether); + + key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: clSkipCallbackHook, + poolManager: poolManager, + fee: uint24(3000), + parameters: bytes32(uint256(clSkipCallbackHook.getHooksRegistrationBitmap())).setTickSpacing(10) + }); + } + + function testInitialize_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testInitialize_NotFromHook() external { + poolManager.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testModifyPosition_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Add and remove liquidity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18}), "" + ); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testModifyPosition_NotFromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Add and remove liquidity + router.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + router.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18}), "" + ); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 4); + } + + function testSwap_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + clSkipCallbackHook.swap( + key, + ICLPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000, sqrtPriceLimitX96: SQRT_RATIO_1_2}), + CLSkipCallbackHook.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + "" + ); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testSwap_NotFromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + router.swap( + key, + ICLPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000, sqrtPriceLimitX96: SQRT_RATIO_1_2}), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + "" + ); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testDonate_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + clSkipCallbackHook.donate(key, 100, 200, ZERO_BYTES); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testDonate_NotFromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + router.donate(key, 100, 200, ZERO_BYTES); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 2); + } +} diff --git a/test/pool-cl/helpers/BaseCLTestHook.sol b/test/pool-cl/helpers/BaseCLTestHook.sol index 84917e84..ebc52da4 100644 --- a/test/pool-cl/helpers/BaseCLTestHook.sol +++ b/test/pool-cl/helpers/BaseCLTestHook.sol @@ -1,6 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; +import { + HOOKS_BEFORE_INITIALIZE_OFFSET, + HOOKS_AFTER_INITIALIZE_OFFSET, + HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET, + HOOKS_AFTER_ADD_LIQUIDITY_OFFSET, + HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET, + HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET, + HOOKS_BEFORE_SWAP_OFFSET, + HOOKS_AFTER_SWAP_OFFSET, + HOOKS_BEFORE_DONATE_OFFSET, + HOOKS_AFTER_DONATE_OFFSET, + HOOKS_NO_OP_OFFSET +} from "../../../src/pool-cl/interfaces/ICLHooks.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; import {ICLHooks} from "../../../src/pool-cl/interfaces/ICLHooks.sol"; @@ -9,6 +22,20 @@ import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol contract BaseCLTestHook is ICLHooks { error HookNotImplemented(); + struct Permissions { + bool beforeInitialize; + bool afterInitialize; + bool beforeAddLiquidity; + bool afterAddLiquidity; + bool beforeRemoveLiquidity; + bool afterRemoveLiquidity; + bool beforeSwap; + bool afterSwap; + bool beforeDonate; + bool afterDonate; + bool noOp; + } + function getHooksRegistrationBitmap() external view virtual returns (uint16) { revert HookNotImplemented(); } @@ -94,4 +121,20 @@ contract BaseCLTestHook is ICLHooks { { revert HookNotImplemented(); } + + function _hooksRegistrationBitmapFrom(Permissions memory permissions) internal pure returns (uint16) { + return uint16( + (permissions.beforeInitialize ? 1 << HOOKS_BEFORE_INITIALIZE_OFFSET : 0) + | (permissions.afterInitialize ? 1 << HOOKS_AFTER_INITIALIZE_OFFSET : 0) + | (permissions.beforeAddLiquidity ? 1 << HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET : 0) + | (permissions.afterAddLiquidity ? 1 << HOOKS_AFTER_ADD_LIQUIDITY_OFFSET : 0) + | (permissions.beforeRemoveLiquidity ? 1 << HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET : 0) + | (permissions.afterRemoveLiquidity ? 1 << HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET : 0) + | (permissions.beforeSwap ? 1 << HOOKS_BEFORE_SWAP_OFFSET : 0) + | (permissions.afterSwap ? 1 << HOOKS_AFTER_SWAP_OFFSET : 0) + | (permissions.beforeDonate ? 1 << HOOKS_BEFORE_DONATE_OFFSET : 0) + | (permissions.afterDonate ? 1 << HOOKS_AFTER_DONATE_OFFSET : 0) + | (permissions.noOp ? 1 << HOOKS_NO_OP_OFFSET : 0) + ); + } } diff --git a/test/pool-cl/helpers/CLPoolManagerRouter.sol b/test/pool-cl/helpers/CLPoolManagerRouter.sol index 359aa441..5820f15e 100644 --- a/test/pool-cl/helpers/CLPoolManagerRouter.sol +++ b/test/pool-cl/helpers/CLPoolManagerRouter.sol @@ -64,7 +64,7 @@ contract CLPoolManagerRouter { (BalanceDelta delta, BalanceDelta feeDelta) = poolManager.modifyLiquidity(data.key, data.params, data.hookData); if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta, feeDelta); @@ -141,7 +141,7 @@ contract CLPoolManagerRouter { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); @@ -233,7 +233,7 @@ contract CLPoolManagerRouter { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); diff --git a/test/pool-cl/helpers/CLSkipCallbackHook.sol b/test/pool-cl/helpers/CLSkipCallbackHook.sol new file mode 100644 index 00000000..e137cdfa --- /dev/null +++ b/test/pool-cl/helpers/CLSkipCallbackHook.sol @@ -0,0 +1,369 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IVault} from "../../../src/interfaces/IVault.sol"; +import {Hooks} from "../../../src/libraries/Hooks.sol"; +import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; +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 {BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; +import {BaseCLTestHook} from "./BaseCLTestHook.sol"; + +/// @notice CL hook which does a callback +contract CLSkipCallbackHook is BaseCLTestHook { + error InvalidAction(); + + using CurrencyLibrary for Currency; + using Hooks for bytes32; + + IVault public immutable vault; + ICLPoolManager public immutable poolManager; + + uint16 bitmap; + uint256 public hookCounterCallbackCount; + + constructor(IVault _vault, ICLPoolManager _poolManager) { + vault = _vault; + poolManager = _poolManager; + } + + function getHooksRegistrationBitmap() external pure override returns (uint16) { + return _hooksRegistrationBitmapFrom( + Permissions({ + beforeInitialize: true, + afterInitialize: true, + beforeAddLiquidity: true, + afterAddLiquidity: true, + beforeRemoveLiquidity: true, + afterRemoveLiquidity: true, + beforeSwap: true, + afterSwap: true, + beforeDonate: true, + afterDonate: true, + noOp: false + }) + ); + } + + struct CallbackData { + bytes action; + bytes rawCallbackData; + } + + function initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes memory hookData) external { + poolManager.initialize(key, sqrtPriceX96, hookData); + } + + struct ModifyPositionCallbackData { + address sender; + PoolKey key; + ICLPoolManager.ModifyLiquidityParams params; + bytes hookData; + } + + function modifyPosition( + PoolKey memory key, + ICLPoolManager.ModifyLiquidityParams memory params, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + delta = abi.decode( + vault.lock( + abi.encode("modifyPosition", abi.encode(ModifyPositionCallbackData(msg.sender, key, params, hookData))) + ), + (BalanceDelta) + ); + + // if any ethers left + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + function modifyPositionCallback(bytes memory rawData) private returns (bytes memory) { + ModifyPositionCallbackData memory data = abi.decode(rawData, (ModifyPositionCallbackData)); + + (BalanceDelta delta, BalanceDelta feeDelta) = poolManager.modifyLiquidity(data.key, data.params, data.hookData); + + // For now assume to always settle feeDelta in the same way as delta + BalanceDelta totalDelta = delta + feeDelta; + + if (delta.amount0() > 0) { + if (data.key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + } else { + IERC20(Currency.unwrap(data.key.currency0)).transferFrom( + data.sender, address(vault), uint128(delta.amount0()) + ); + vault.settle(data.key.currency0); + } + } + if (delta.amount1() > 0) { + if (data.key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(data.key.currency1); + } else { + IERC20(Currency.unwrap(data.key.currency1)).transferFrom( + data.sender, address(vault), uint128(delta.amount1()) + ); + vault.settle(data.key.currency1); + } + } + + if (delta.amount0() < 0) { + vault.take(data.key.currency0, data.sender, uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + vault.take(data.key.currency1, data.sender, uint128(-delta.amount1())); + } + + return abi.encode(delta); + } + + struct SwapTestSettings { + bool withdrawTokens; + bool settleUsingTransfer; + } + + struct SwapCallbackData { + address sender; + SwapTestSettings testSettings; + PoolKey key; + ICLPoolManager.SwapParams params; + bytes hookData; + } + + function swap( + PoolKey memory key, + ICLPoolManager.SwapParams memory params, + SwapTestSettings memory testSettings, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + delta = abi.decode( + vault.lock( + abi.encode("swap", abi.encode(SwapCallbackData(msg.sender, testSettings, key, params, hookData))) + ), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + + function swapCallback(bytes memory rawData) private returns (bytes memory) { + SwapCallbackData memory data = abi.decode(rawData, (SwapCallbackData)); + + BalanceDelta delta = poolManager.swap(data.key, data.params, data.hookData); + if (data.params.zeroForOne) { + if (delta.amount0() > 0) { + if (data.testSettings.settleUsingTransfer) { + if (data.key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + } else { + IERC20(Currency.unwrap(data.key.currency0)).transferFrom( + data.sender, address(vault), uint128(delta.amount0()) + ); + vault.settle(data.key.currency0); + } + } else { + // the received hook on this transfer will burn the tokens + vault.transferFrom(data.sender, address(this), data.key.currency0, uint128(delta.amount0())); + vault.burn(address(this), data.key.currency0, uint128(delta.amount0())); + } + } + if (delta.amount1() < 0) { + if (data.testSettings.withdrawTokens) { + vault.take(data.key.currency1, data.sender, uint128(-delta.amount1())); + } else { + vault.mint(data.sender, data.key.currency1, uint128(-delta.amount1())); + } + } + } else { + if (delta.amount1() > 0) { + if (data.testSettings.settleUsingTransfer) { + if (data.key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(data.key.currency1); + } else { + IERC20(Currency.unwrap(data.key.currency1)).transferFrom( + data.sender, address(vault), uint128(delta.amount1()) + ); + vault.settle(data.key.currency1); + } + } else { + // the received hook on this transfer will burn the tokens + vault.transferFrom(data.sender, address(this), data.key.currency1, uint128(delta.amount1())); + vault.burn(address(this), data.key.currency1, uint128(delta.amount1())); + } + } + if (delta.amount0() < 0) { + if (data.testSettings.withdrawTokens) { + vault.take(data.key.currency0, data.sender, uint128(-delta.amount0())); + } else { + vault.mint(data.sender, data.key.currency0, uint128(-delta.amount0())); + } + } + } + + return abi.encode(delta); + } + + struct DonateCallbackData { + address sender; + PoolKey key; + uint256 amount0; + uint256 amount1; + bytes hookData; + } + + function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + delta = abi.decode( + vault.lock( + abi.encode("donate", abi.encode(DonateCallbackData(msg.sender, key, amount0, amount1, hookData))) + ), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + function donateCallback(bytes memory rawData) private returns (bytes memory) { + DonateCallbackData memory data = abi.decode(rawData, (DonateCallbackData)); + + BalanceDelta delta = poolManager.donate(data.key, data.amount0, data.amount1, data.hookData); + if (delta.amount0() > 0) { + if (data.key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + } else { + IERC20(Currency.unwrap(data.key.currency0)).transferFrom( + data.sender, address(vault), uint128(delta.amount0()) + ); + vault.settle(data.key.currency0); + } + } + if (delta.amount1() > 0) { + if (data.key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(data.key.currency1); + } else { + IERC20(Currency.unwrap(data.key.currency1)).transferFrom( + data.sender, address(vault), uint128(delta.amount1()) + ); + vault.settle(data.key.currency1); + } + } + + return abi.encode(delta); + } + + function lockAcquired(bytes calldata data) external returns (bytes memory) { + (bytes memory action, bytes memory rawCallbackData) = abi.decode(data, (bytes, bytes)); + + if (keccak256(action) == keccak256("modifyPosition")) { + return modifyPositionCallback(rawCallbackData); + } else if (keccak256(action) == keccak256("swap")) { + return swapCallback(rawCallbackData); + } else if (keccak256(action) == keccak256("donate")) { + return donateCallback(rawCallbackData); + } else { + revert InvalidAction(); + } + } + + function beforeInitialize(address, PoolKey calldata, uint160, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeInitialize.selector; + } + + function afterInitialize(address, PoolKey calldata, uint160, int24, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterInitialize.selector; + } + + function beforeAddLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeAddLiquidity.selector; + } + + function afterAddLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeRemoveLiquidity.selector; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterRemoveLiquidity.selector; + } + + function beforeSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeSwap.selector; + } + + function afterSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterSwap.selector; + } + + function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeDonate.selector; + } + + function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterDonate.selector; + } +} From 134ca24bf918b531d2c430534e447af939aa512d Mon Sep 17 00:00:00 2001 From: ChefMist <133624774+ChefMist@users.noreply.github.com> Date: Tue, 7 May 2024 15:00:09 +0800 Subject: [PATCH 4/5] feat: gas optimization (#28) * chore: handle handling of eth in reserveOfVault * chore: minor gas tweak * test: minor refactoring * feat: gas optimization in modify liquidity * feat: cache zeroForOne in pool.swap * feat: cache params.sqrtPriceLimitX96 in pool.swap * feat: cache self.liquidity in CLPosition.sol * feat: cache params.swapForY in BinPool * feat: cache param.liquidityDelta in CLPool.sol * feat: cache tickLower and tickUpper in CLPool.sol * feat: cache swapForY in BinPool.sol --- .../BinHookTest#testBurnSucceedsWithHook.snap | 2 +- ...inHookTest#testDonateSucceedsWithHook.snap | 2 +- ...okTest#testInitializeSucceedsWithHook.snap | 2 +- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- ...oolManagerTest#testBurnNativeCurrency.snap | 2 +- ...olManagerTest#testExtLoadPoolActiveId.snap | 2 +- ...agerTest#testFuzzUpdateDynamicSwapFee.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#testNoOpGas_Burn.snap | 2 +- ...BinPoolManagerTest#testNoOpGas_Donate.snap | 2 +- ...oolManagerTest#testNoOpGas_Initialize.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Mint.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Swap.snap | 2 +- ...BinPoolManagerTest#testSetProtocolFee.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 +- ...agerTest#testFuzzUpdateDynamicSwapFee.snap | 2 +- ...CLPoolManagerTest#testNoOp_gas_Donate.snap | 2 +- ...olManagerTest#testNoOp_gas_Initialize.snap | 2 +- ...nagerTest#testNoOp_gas_ModifyPosition.snap | 2 +- .../CLPoolManagerTest#testNoOp_gas_Swap.snap | 2 +- .../CLPositionTest#Position_update_add.snap | 2 +- ...CLPositionTest#Position_update_remove.snap | 2 +- .forge-snapshots/VaultTest#Vault.snap | 2 +- .forge-snapshots/VaultTest#collectFee.snap | 2 +- ...VaultTest#lockSettledWhenAddLiquidity.snap | 2 +- .../VaultTest#lockSettledWhenFlashloan.snap | 2 +- ...VaultTest#lockSettledWhenMultiHopSwap.snap | 2 +- .../VaultTest#lockSettledWhenSwap.snap | 2 +- .forge-snapshots/VaultTest#testLock_NoOp.snap | 2 +- ...Test#testSettleAndMintRefund_WithMint.snap | 2 +- ...t#testSettleAndMintRefund_WithoutMint.snap | 2 +- src/Vault.sol | 30 +++++--- src/interfaces/IVault.sol | 3 + src/pool-bin/libraries/BinPool.sol | 37 ++++----- src/pool-cl/libraries/CLPool.sol | 76 +++++++++---------- src/pool-cl/libraries/CLPosition.sol | 18 ++--- src/types/PoolId.sol | 7 +- test/libraries/PoolIdLibrary.t.sol | 30 ++++++++ test/vault/FakePoolManagerRouter.sol | 17 +++++ test/vault/Vault.t.sol | 58 +++++++++++--- 65 files changed, 242 insertions(+), 146 deletions(-) create mode 100644 test/libraries/PoolIdLibrary.t.sol diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index cfddf70e..33dc6386 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -143903 \ No newline at end of file +143154 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap index db45cf73..ff4ea856 100644 --- a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap @@ -1 +1 @@ -135247 \ No newline at end of file +134501 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index 82b81a8c..d4c40291 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -109118 \ No newline at end of file +108737 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 086607f7..0dec00a9 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -301295 \ No newline at end of file +300549 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 59e2fe2a..80d7303c 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -139320 \ No newline at end of file +138567 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap index 0e901dcc..3b765b08 100644 --- a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap @@ -1 +1 @@ -90617 \ No newline at end of file +89917 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap b/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap index b054fa03..49b43c45 100644 --- a/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap +++ b/.forge-snapshots/BinPoolManagerTest#testExtLoadPoolActiveId.snap @@ -1 +1 @@ -2242 \ No newline at end of file +1866 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap index 1e818734..8eb54da3 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap @@ -1 +1 @@ -5062 \ No newline at end of file +4679 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap index 77509a32..c6ffd4c5 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap @@ -1 +1 @@ -65810 \ No newline at end of file +65499 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap index c9417e09..36cf538a 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap @@ -1 +1 @@ -149365 \ No newline at end of file +149045 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap index 715b65be..ef487ea2 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap @@ -1 +1 @@ -66989 \ No newline at end of file +66678 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap index 05f5091e..ee82ec75 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap @@ -1 +1 @@ -52582 \ No newline at end of file +52325 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index aa25e617..78e8e2dd 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -968582 \ No newline at end of file +968335 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index f22c5e5a..1158be7b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -120096 \ No newline at end of file +119849 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index a27a0639..318d166f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -337772 \ No newline at end of file +337527 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index 6c3b1dd4..c7581d3d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -54833 \ No newline at end of file +54588 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index c12c16ee..45154b6c 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -89370 \ No newline at end of file +89025 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 96d5c05d..d41f4000 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -91355 \ No newline at end of file +91010 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index c724ad85..9a9d837c 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -70859 \ No newline at end of file +70574 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index 345aa66a..67c86f8b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -319479 \ No newline at end of file +296760 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap index 3293c880..5f52d88e 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap @@ -1 +1 @@ -41726 \ No newline at end of file +41345 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap index 2dfafdff..cd943f51 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap @@ -1 +1 @@ -19494 \ No newline at end of file +19114 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap index 07d18bee..a71eeb21 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap @@ -1 +1 @@ -37255 \ No newline at end of file +36874 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap index a7ed01ba..6e77b98a 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap @@ -1 +1 @@ -39359 \ No newline at end of file +38978 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap index cbc2e8f4..f3179101 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap @@ -1 +1 @@ -22739 \ No newline at end of file +22359 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap index f40efcf6..56ecd7e5 100644 --- a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap @@ -1 +1 @@ -8148 \ No newline at end of file +7461 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index f6f105a0..60cdd631 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -349902 \ No newline at end of file +349410 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 80610c10..c043af4e 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -60450 \ No newline at end of file +59958 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 723e01c8..350247e1 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -243492 \ No newline at end of file +220457 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index d9dd718c..83655bbf 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -82627 \ No newline at end of file +82382 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 8d6144ca..f45ee22b 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -52589 \ No newline at end of file +52276 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 864c111e..192055ae 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -36705 \ No newline at end of file +36324 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index d49ddc20..4587a3cc 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -43384 \ No newline at end of file +42829 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index eb50e3ca..48e56260 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -54761 \ No newline at end of file +54399 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index fa438b60..c3cc24d9 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -100840 \ No newline at end of file +100389 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index bcd80721..2972925d 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -25040547 \ No newline at end of file +24843277 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index f72983be..53085439 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -35961 \ No newline at end of file +35478 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 454154ca..e6c0765f 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -101598 \ No newline at end of file +101168 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index 964bd6c9..d94e1895 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -41680 \ No newline at end of file +41197 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index 1f313c58..a34182c5 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -35964 \ No newline at end of file +35481 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap index c888a1c0..d5df029e 100644 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap @@ -1 +1 @@ -4857 \ No newline at end of file +4453 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap index ddc2964e..34c7063c 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap @@ -1 +1 @@ -19394 \ No newline at end of file +19014 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap index 663d520f..65dc7231 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap @@ -1 +1 @@ -37663 \ No newline at end of file +37282 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap index dc8a3822..40c53563 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap @@ -1 +1 @@ -30109 \ No newline at end of file +29728 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap index b2fcd4d8..a64034e9 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap @@ -1 +1 @@ -21824 \ No newline at end of file +21443 \ No newline at end of file diff --git a/.forge-snapshots/CLPositionTest#Position_update_add.snap b/.forge-snapshots/CLPositionTest#Position_update_add.snap index a4156ede..40efa8ca 100644 --- a/.forge-snapshots/CLPositionTest#Position_update_add.snap +++ b/.forge-snapshots/CLPositionTest#Position_update_add.snap @@ -1 +1 @@ -1545 \ No newline at end of file +1427 \ No newline at end of file diff --git a/.forge-snapshots/CLPositionTest#Position_update_remove.snap b/.forge-snapshots/CLPositionTest#Position_update_remove.snap index bc7f6228..da9ac390 100644 --- a/.forge-snapshots/CLPositionTest#Position_update_remove.snap +++ b/.forge-snapshots/CLPositionTest#Position_update_remove.snap @@ -1 +1 @@ -2014 \ No newline at end of file +1899 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#Vault.snap b/.forge-snapshots/VaultTest#Vault.snap index a75c38b3..b70512a0 100644 --- a/.forge-snapshots/VaultTest#Vault.snap +++ b/.forge-snapshots/VaultTest#Vault.snap @@ -1 +1 @@ -7314 \ No newline at end of file +7437 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#collectFee.snap b/.forge-snapshots/VaultTest#collectFee.snap index df5ec7a9..2a59609b 100644 --- a/.forge-snapshots/VaultTest#collectFee.snap +++ b/.forge-snapshots/VaultTest#collectFee.snap @@ -1 +1 @@ -25576 \ No newline at end of file +25611 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap index 830ad9f8..e7aaee6e 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap @@ -1 +1 @@ -120730 \ No newline at end of file +120866 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap b/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap index 02c149c9..3dd0ec6a 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap @@ -1 +1 @@ -156938 \ No newline at end of file +157144 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap index d816d6d9..b4eb840c 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap @@ -1 +1 @@ -45186 \ No newline at end of file +45289 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap index 0a374883..2fdc2d44 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap @@ -1 +1 @@ -45185 \ No newline at end of file +45288 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#testLock_NoOp.snap b/.forge-snapshots/VaultTest#testLock_NoOp.snap index 23cdb97c..06d678ad 100644 --- a/.forge-snapshots/VaultTest#testLock_NoOp.snap +++ b/.forge-snapshots/VaultTest#testLock_NoOp.snap @@ -1 +1 @@ -11327 \ No newline at end of file +11517 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap index 7a0dcd43..51e6da8a 100644 --- a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap +++ b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap @@ -1 +1 @@ -71681 \ No newline at end of file +71811 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap index fc54fab9..c2d8ee13 100644 --- a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap +++ b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap @@ -1 +1 @@ -33288 \ No newline at end of file +33418 \ No newline at end of file diff --git a/src/Vault.sol b/src/Vault.sol index 749104db..527951c2 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -46,9 +46,6 @@ contract Vault is IVault, VaultToken, Ownable { _; } - /// @notice receive native tokens for native pools - receive() external payable {} - /// @inheritdoc IVault function registerPoolManager(address poolManager) external override onlyOwner { isPoolManagerRegistered[poolManager] = true; @@ -107,7 +104,7 @@ contract Vault is IVault, VaultToken, Ownable { /// @inheritdoc IVault function take(Currency currency, address to, uint256 amount) external override isLocked { SettlementGuard.accountDelta(msg.sender, currency, amount.toInt128()); - reservesOfVault[currency] -= amount; + if (!currency.isNative()) reservesOfVault[currency] -= amount; currency.transfer(to, amount); } @@ -119,9 +116,15 @@ contract Vault is IVault, VaultToken, Ownable { /// @inheritdoc IVault function settle(Currency currency) external payable override isLocked returns (uint256 paid) { - uint256 reservesBefore = reservesOfVault[currency]; - reservesOfVault[currency] = currency.balanceOfSelf(); - paid = reservesOfVault[currency] - reservesBefore; + if (!currency.isNative()) { + if (msg.value > 0) revert SettleNonNativeCurrencyWithValue(); + uint256 reservesBefore = reservesOfVault[currency]; + reservesOfVault[currency] = currency.balanceOfSelf(); + paid = reservesOfVault[currency] - reservesBefore; + } else { + paid = msg.value; + } + // subtraction must be safe SettlementGuard.accountDelta(msg.sender, currency, -(paid.toInt128())); } @@ -133,9 +136,14 @@ contract Vault is IVault, VaultToken, Ownable { isLocked returns (uint256 paid, uint256 refund) { - uint256 reservesBefore = reservesOfVault[currency]; - reservesOfVault[currency] = currency.balanceOfSelf(); - paid = reservesOfVault[currency] - reservesBefore; + if (!currency.isNative()) { + if (msg.value > 0) revert SettleNonNativeCurrencyWithValue(); + uint256 reservesBefore = reservesOfVault[currency]; + reservesOfVault[currency] = currency.balanceOfSelf(); + paid = reservesOfVault[currency] - reservesBefore; + } else { + paid = msg.value; + } int256 currentDelta = SettlementGuard.getCurrencyDelta(msg.sender, currency); if (currentDelta >= 0) { @@ -169,7 +177,7 @@ contract Vault is IVault, VaultToken, Ownable { /// @inheritdoc IVault function collectFee(Currency currency, uint256 amount, address recipient) external { reservesOfPoolManager[IPoolManager(msg.sender)][currency] -= amount; - reservesOfVault[currency] -= amount; + if (!currency.isNative()) reservesOfVault[currency] -= amount; currency.transfer(recipient, amount); } diff --git a/src/interfaces/IVault.sol b/src/interfaces/IVault.sol index b9ac6194..811f6b20 100644 --- a/src/interfaces/IVault.sol +++ b/src/interfaces/IVault.sol @@ -24,6 +24,9 @@ interface IVault is IVaultToken { /// @param locker The address of the current locker error LockerAlreadySet(address locker); + /// @notice Thrown when passing in msg.value for non-native currency + error SettleNonNativeCurrencyWithValue(); + /// @notice Thrown when there is no locker error NoLocker(); diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index 9133994d..9e4c9618 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -108,18 +108,19 @@ library BinPool { view returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee) { + bool swapForY = params.swapForY; uint24 id = self.slot0.activeId; amountOutLeft = amountOut; while (true) { - uint128 binReserves = self.reserveOfBin[id].decode(!params.swapForY); + uint128 binReserves = self.reserveOfBin[id].decode(!swapForY); if (binReserves > 0) { uint256 price = id.getPriceFromId(params.binStep); uint128 amountOutOfBin = binReserves > amountOutLeft ? amountOutLeft : binReserves; uint128 amountInWithoutFee = uint128( - params.swapForY + swapForY ? uint256(amountOutOfBin).shiftDivRoundUp(Constants.SCALE_OFFSET, price) : uint256(amountOutOfBin).mulShiftRoundUp(price, Constants.SCALE_OFFSET) ); @@ -135,7 +136,7 @@ library BinPool { if (amountOutLeft == 0) { break; } else { - uint24 nextId = getNextNonEmptyBin(self, params.swapForY, id); + uint24 nextId = getNextNonEmptyBin(self, swapForY, id); if (nextId == 0 || nextId == type(uint24).max) break; id = nextId; } @@ -147,34 +148,35 @@ library BinPool { view returns (uint128 amountInLeft, uint128 amountOut, uint128 fee) { + bool swapForY = params.swapForY; uint24 id = self.slot0.activeId; - bytes32 amountsInLeft = amountIn.encode(params.swapForY); + bytes32 amountsInLeft = amountIn.encode(swapForY); while (true) { bytes32 binReserves = self.reserveOfBin[id]; - if (!binReserves.isEmpty(!params.swapForY)) { + if (!binReserves.isEmpty(!swapForY)) { (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) = - binReserves.getAmounts(params.fee, params.binStep, params.swapForY, id, amountsInLeft); + binReserves.getAmounts(params.fee, params.binStep, swapForY, id, amountsInLeft); if (amountsInWithFees > 0) { amountsInLeft = amountsInLeft.sub(amountsInWithFees); - amountOut += amountsOutOfBin.decode(!params.swapForY); + amountOut += amountsOutOfBin.decode(!swapForY); - fee += totalFees.decode(params.swapForY); + fee += totalFees.decode(swapForY); } } if (amountsInLeft == 0) { break; } else { - uint24 nextId = getNextNonEmptyBin(self, params.swapForY, id); + uint24 nextId = getNextNonEmptyBin(self, swapForY, id); if (nextId == 0 || nextId == type(uint24).max) break; id = nextId; } } - amountInLeft = amountsInLeft.decode(params.swapForY); + amountInLeft = amountsInLeft.decode(swapForY); } struct SwapParams { @@ -186,19 +188,20 @@ library BinPool { internal returns (BalanceDelta result, bytes32 feeForProtocol, uint24 activeId, uint24 swapFee) { - activeId = self.slot0.activeId; - if (amountIn == 0) revert BinPool__InsufficientAmountIn(); - bytes32 amountsLeft = params.swapForY ? amountIn.encodeFirst() : amountIn.encodeSecond(); + activeId = self.slot0.activeId; + bool swapForY = params.swapForY; + + bytes32 amountsLeft = swapForY ? amountIn.encodeFirst() : amountIn.encodeSecond(); bytes32 amountsOut; swapFee = self.slot0.swapFee; while (true) { bytes32 binReserves = self.reserveOfBin[activeId]; - if (!binReserves.isEmpty(!params.swapForY)) { + if (!binReserves.isEmpty(!swapForY)) { (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) = - binReserves.getAmounts(swapFee, params.binStep, params.swapForY, activeId, amountsLeft); + binReserves.getAmounts(swapFee, params.binStep, swapForY, activeId, amountsLeft); if (amountsInWithFees > 0) { amountsLeft = amountsLeft.sub(amountsInWithFees); @@ -217,7 +220,7 @@ library BinPool { if (amountsLeft == 0) { break; } else { - uint24 nextId = getNextNonEmptyBin(self, params.swapForY, activeId); + uint24 nextId = getNextNonEmptyBin(self, swapForY, activeId); if (nextId == 0 || nextId == type(uint24).max) revert BinPool__OutOfLiquidity(); activeId = nextId; } @@ -227,7 +230,7 @@ library BinPool { self.slot0.activeId = activeId; - if (params.swapForY) { + if (swapForY) { uint128 consumed = amountIn - amountsLeft.decodeX(); result = toBalanceDelta(consumed.safeInt128(), -(amountsOut.decodeY().safeInt128())); } else { diff --git a/src/pool-cl/libraries/CLPool.sol b/src/pool-cl/libraries/CLPool.sol index e0c7602b..bfa0ac49 100644 --- a/src/pool-cl/libraries/CLPool.sol +++ b/src/pool-cl/libraries/CLPool.sol @@ -103,40 +103,39 @@ library CLPool { internal returns (BalanceDelta delta, BalanceDelta feeDelta) { - Slot0 memory _slot0 = self.slot0; // SLOAD for gas optimization + int24 tickLower = params.tickLower; + int24 tickUpper = params.tickUpper; + Tick.checkTicks(tickLower, tickUpper); - Tick.checkTicks(params.tickLower, params.tickUpper); - - (uint256 feesOwed0, uint256 feesOwed1) = _updatePosition(self, params, _slot0.tick); + int24 tick = self.slot0.tick; + (uint256 feesOwed0, uint256 feesOwed1) = _updatePosition(self, params, tick); ///@dev calculate the tokens delta needed - if (params.liquidityDelta != 0) { + int128 liquidityDelta = params.liquidityDelta; + if (liquidityDelta != 0) { + uint160 sqrtPriceX96 = self.slot0.sqrtPriceX96; int128 amount0; int128 amount1; - if (_slot0.tick < params.tickLower) { + if (tick < tickLower) { // current tick is below the passed range; liquidity can only become in range by crossing from left to // right, when we'll need _more_ currency0 (it's becoming more valuable) so user must provide it amount0 = SqrtPriceMath.getAmount0Delta( - TickMath.getSqrtRatioAtTick(params.tickLower), - TickMath.getSqrtRatioAtTick(params.tickUpper), - params.liquidityDelta + TickMath.getSqrtRatioAtTick(tickLower), TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta ).toInt128(); - } else if (_slot0.tick < params.tickUpper) { + } else if (tick < tickUpper) { amount0 = SqrtPriceMath.getAmount0Delta( - _slot0.sqrtPriceX96, TickMath.getSqrtRatioAtTick(params.tickUpper), params.liquidityDelta + sqrtPriceX96, TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta ).toInt128(); amount1 = SqrtPriceMath.getAmount1Delta( - TickMath.getSqrtRatioAtTick(params.tickLower), _slot0.sqrtPriceX96, params.liquidityDelta + TickMath.getSqrtRatioAtTick(tickLower), sqrtPriceX96, liquidityDelta ).toInt128(); - self.liquidity = LiquidityMath.addDelta(self.liquidity, params.liquidityDelta); + self.liquidity = LiquidityMath.addDelta(self.liquidity, liquidityDelta); } else { // current tick is above the passed range; liquidity can only become in range by crossing from right to // left, when we'll need _more_ currency1 (it's becoming more valuable) so user must provide it amount1 = SqrtPriceMath.getAmount1Delta( - TickMath.getSqrtRatioAtTick(params.tickLower), - TickMath.getSqrtRatioAtTick(params.tickUpper), - params.liquidityDelta + TickMath.getSqrtRatioAtTick(tickLower), TickMath.getSqrtRatioAtTick(tickUpper), liquidityDelta ).toInt128(); } @@ -206,24 +205,21 @@ library CLPool { if (params.amountSpecified == 0) revert SwapAmountCannotBeZero(); Slot0 memory slot0Start = self.slot0; + // Declare zeroForOne and sqrtPriceLimitX96 upfront for gas optmization + bool zeroForOne = params.zeroForOne; + uint160 sqrtPriceLimitX96 = params.sqrtPriceLimitX96; if ( - params.zeroForOne - ? ( - params.sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96 - || params.sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO - ) - : ( - params.sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96 - || params.sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO - ) + zeroForOne + ? (sqrtPriceLimitX96 >= slot0Start.sqrtPriceX96 || sqrtPriceLimitX96 <= TickMath.MIN_SQRT_RATIO) + : (sqrtPriceLimitX96 <= slot0Start.sqrtPriceX96 || sqrtPriceLimitX96 >= TickMath.MAX_SQRT_RATIO) ) { - revert InvalidSqrtPriceLimit(slot0Start.sqrtPriceX96, params.sqrtPriceLimitX96); + revert InvalidSqrtPriceLimit(slot0Start.sqrtPriceX96, sqrtPriceLimitX96); } SwapCache memory cache = SwapCache({ liquidityStart: self.liquidity, /// @dev 8 bits for protocol swap fee instead of 4 bits in v3 - protocolFee: params.zeroForOne ? uint8(slot0Start.protocolFee % 256) : uint8(slot0Start.protocolFee >> 8) + protocolFee: zeroForOne ? uint8(slot0Start.protocolFee % 256) : uint8(slot0Start.protocolFee >> 8) }); bool exactInput = params.amountSpecified > 0; @@ -234,18 +230,18 @@ library CLPool { sqrtPriceX96: slot0Start.sqrtPriceX96, tick: slot0Start.tick, swapFee: slot0Start.swapFee, - feeGrowthGlobalX128: params.zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128, + feeGrowthGlobalX128: zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128, protocolFee: 0, liquidity: cache.liquidityStart }); StepComputations memory step; // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit - while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != params.sqrtPriceLimitX96) { + while (state.amountSpecifiedRemaining != 0 && state.sqrtPriceX96 != sqrtPriceLimitX96) { step.sqrtPriceStartX96 = state.sqrtPriceX96; (step.tickNext, step.initialized) = - self.tickBitmap.nextInitializedTickWithinOneWord(state.tick, params.tickSpacing, params.zeroForOne); + self.tickBitmap.nextInitializedTickWithinOneWord(state.tick, params.tickSpacing, zeroForOne); // ensure that we do not overshoot the min/max tick, as the tick bitmap is not aware of these bounds if (step.tickNext < TickMath.MIN_TICK) { @@ -260,11 +256,9 @@ library CLPool { // compute values to swap to the target tick, price limit, or point where input/output amount is exhausted (state.sqrtPriceX96, step.amountIn, step.amountOut, step.feeAmount) = SwapMath.computeSwapStep( state.sqrtPriceX96, - ( - params.zeroForOne - ? step.sqrtPriceNextX96 < params.sqrtPriceLimitX96 - : step.sqrtPriceNextX96 > params.sqrtPriceLimitX96 - ) ? params.sqrtPriceLimitX96 : step.sqrtPriceNextX96, + (zeroForOne ? step.sqrtPriceNextX96 < sqrtPriceLimitX96 : step.sqrtPriceNextX96 > sqrtPriceLimitX96) + ? sqrtPriceLimitX96 + : step.sqrtPriceNextX96, state.liquidity, state.amountSpecifiedRemaining, state.swapFee @@ -307,20 +301,20 @@ library CLPool { if (step.initialized) { int128 liquidityNet = self.ticks.cross( step.tickNext, - (params.zeroForOne ? state.feeGrowthGlobalX128 : self.feeGrowthGlobal0X128), - (params.zeroForOne ? self.feeGrowthGlobal1X128 : state.feeGrowthGlobalX128) + (zeroForOne ? state.feeGrowthGlobalX128 : self.feeGrowthGlobal0X128), + (zeroForOne ? self.feeGrowthGlobal1X128 : state.feeGrowthGlobalX128) ); // if we're moving leftward, we interpret liquidityNet as the opposite sign // safe because liquidityNet cannot be type(int128).min unchecked { - if (params.zeroForOne) liquidityNet = -liquidityNet; + if (zeroForOne) liquidityNet = -liquidityNet; } state.liquidity = state.liquidity.addDelta(liquidityNet); } unchecked { - state.tick = params.zeroForOne ? step.tickNext - 1 : step.tickNext; + state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext; } } else if (state.sqrtPriceX96 != step.sqrtPriceStartX96) { // recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved @@ -340,14 +334,14 @@ library CLPool { if (cache.liquidityStart != state.liquidity) self.liquidity = state.liquidity; // update fee growth global - if (params.zeroForOne) { + if (zeroForOne) { self.feeGrowthGlobal0X128 = state.feeGrowthGlobalX128; } else { self.feeGrowthGlobal1X128 = state.feeGrowthGlobalX128; } unchecked { - (int128 amount0, int128 amount1) = params.zeroForOne == exactInput + (int128 amount0, int128 amount1) = zeroForOne == exactInput ? ((params.amountSpecified - state.amountSpecifiedRemaining).toInt128(), state.amountCalculated.toInt128()) : ( (state.amountCalculated.toInt128()), diff --git a/src/pool-cl/libraries/CLPosition.sol b/src/pool-cl/libraries/CLPosition.sol index ed9577e5..b1538021 100644 --- a/src/pool-cl/libraries/CLPosition.sol +++ b/src/pool-cl/libraries/CLPosition.sol @@ -58,24 +58,22 @@ library CLPosition { uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128 ) internal returns (uint256 feesOwed0, uint256 feesOwed1) { - Info memory _self = self; + uint128 liquidity = self.liquidity; uint128 liquidityNext; if (liquidityDelta == 0) { - if (_self.liquidity == 0) revert CannotUpdateEmptyPosition(); // disallow pokes for 0 liquidity positions - liquidityNext = _self.liquidity; + if (liquidity == 0) revert CannotUpdateEmptyPosition(); // disallow pokes for 0 liquidity positions + liquidityNext = liquidity; } else { - liquidityNext = LiquidityMath.addDelta(_self.liquidity, liquidityDelta); + liquidityNext = LiquidityMath.addDelta(liquidity, liquidityDelta); } ///@dev Tho overflow is expected, it's technically possible users can lose their rewards if it hits type(uint128).max unchecked { - feesOwed0 = FullMath.mulDiv( - feeGrowthInside0X128 - _self.feeGrowthInside0LastX128, _self.liquidity, FixedPoint128.Q128 - ); - feesOwed1 = FullMath.mulDiv( - feeGrowthInside1X128 - _self.feeGrowthInside1LastX128, _self.liquidity, FixedPoint128.Q128 - ); + feesOwed0 = + FullMath.mulDiv(feeGrowthInside0X128 - self.feeGrowthInside0LastX128, liquidity, FixedPoint128.Q128); + feesOwed1 = + FullMath.mulDiv(feeGrowthInside1X128 - self.feeGrowthInside1LastX128, liquidity, FixedPoint128.Q128); } // update the position diff --git a/src/types/PoolId.sol b/src/types/PoolId.sol index 10fcaf72..0ad863f0 100644 --- a/src/types/PoolId.sol +++ b/src/types/PoolId.sol @@ -7,7 +7,10 @@ type PoolId is bytes32; /// @notice Library for computing the ID of a pool library PoolIdLibrary { - function toId(PoolKey memory poolKey) internal pure returns (PoolId) { - return PoolId.wrap(keccak256(abi.encode(poolKey))); + function toId(PoolKey memory poolKey) internal pure returns (PoolId poolId) { + // @solidity memory-safe-assembly + assembly { + poolId := keccak256(poolKey, mul(32, 6)) + } } } diff --git a/test/libraries/PoolIdLibrary.t.sol b/test/libraries/PoolIdLibrary.t.sol new file mode 100644 index 00000000..a7fd0612 --- /dev/null +++ b/test/libraries/PoolIdLibrary.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import {Currency} from "../../src/types/Currency.sol"; +import {PoolKey} from "../../src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; +import {IHooks} from "../../src/interfaces/IHooks.sol"; +import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; + +contract PoolIdLibraryTest is Test { + using PoolIdLibrary for PoolKey; + + function test_toId() public { + PoolKey memory key = PoolKey({ + currency0: Currency.wrap(makeAddr("currency0")), + currency1: Currency.wrap(makeAddr("currency1")), + hooks: IHooks(makeAddr("hook")), + poolManager: IPoolManager(makeAddr("pm")), + fee: 100, + parameters: hex"1022" + }); + + bytes32 id = PoolId.unwrap(key.toId()); + bytes32 abiEncodedId = keccak256(abi.encode(key)); + + assertEq(id, abiEncodedId); + } +} diff --git a/test/vault/FakePoolManagerRouter.sol b/test/vault/FakePoolManagerRouter.sol index 36d3fbda..c2fdabc6 100644 --- a/test/vault/FakePoolManagerRouter.sol +++ b/test/vault/FakePoolManagerRouter.sol @@ -148,6 +148,23 @@ contract FakePoolManagerRouter { vault.burn(address(0x01), poolKey.currency0, amt); vault.take(poolKey.currency0, address(this), amt); + } else if (data[0] == 0x21) { + // currency0 is native and currency1 is erc20 + poolManager.mockAccounting(poolKey, 10 ether, 10 ether); + vault.settle{value: 10 ether}(poolKey.currency0); + vault.settle(poolKey.currency1); + } else if (data[0] == 0x22) { + // currency0 is native and currency1 is erc20 + vault.take(poolKey.currency0, address(this), 20 ether); + vault.take(poolKey.currency1, address(this), 20 ether); + + // ... flashloan logic + + // only for erc20 as native will call settle with value + poolKey.currency1.transfer(address(vault), 20 ether); + + vault.settle{value: 20 ether}(poolKey.currency0); + vault.settle(poolKey.currency1); } return ""; diff --git a/test/vault/Vault.t.sol b/test/vault/Vault.t.sol index dd49bc57..f103899a 100644 --- a/test/vault/Vault.t.sol +++ b/test/vault/Vault.t.sol @@ -205,6 +205,21 @@ contract VaultTest is Test, GasSnapshot { assertEq(vault.balanceOf(alice, currency0), 0 ether); } + function testSettleAndMintRefund_SettleNonNativeCurrencyWithValue() public { + address alice = makeAddr("alice"); + + assertEq(vault.balanceOf(alice, currency0), 0 ether); + + // settleAndRefund works even if there's no excess currency + vm.prank(address(fakePoolManagerRouter)); + snapStart("VaultTest#testSettleAndMintRefund_WithoutMint"); + vault.lock(abi.encodePacked(hex"18", alice)); + snapEnd(); + + // verify no extra token minted + assertEq(vault.balanceOf(alice, currency0), 0 ether); + } + function testSettleAndMintRefund_NegativeBalanceDelta() public { // pre-req: ensure vault has some value in reserveOfVault[] before currency0.transfer(address(vault), 10 ether); @@ -612,18 +627,43 @@ contract VaultTest is Test, GasSnapshot { // transfer in & settle { - CurrencyLibrary.NATIVE.transfer(address(vault), 10 ether); + // ETH to router as router call .settle{value} + CurrencyLibrary.NATIVE.transfer(address(router), 10 ether); currency1.transfer(address(vault), 10 ether); vm.prank(address(router)); - vault.lock(hex"02"); + vault.lock(hex"21"); assertEq(CurrencyLibrary.NATIVE.balanceOf(address(vault)), 10 ether); - assertEq(vault.reservesOfVault(CurrencyLibrary.NATIVE), 10 ether); assertEq(vault.reservesOfPoolManager(fakePoolManager, CurrencyLibrary.NATIVE), 10 ether); } } + function testVault_ethSupport_SettleNonNativeCurrencyWithValue() public { + FakePoolManagerRouter router = new FakePoolManagerRouter( + vault, + PoolKey({ + currency0: currency1, + currency1: CurrencyLibrary.NATIVE, + hooks: IHooks(address(0)), + poolManager: fakePoolManager, + fee: 0, + parameters: 0x00 + }) + ); + + // transfer in & settle + { + // ETH to router as router call .settle{value} + currency0.transfer(address(vault), 10 ether); + CurrencyLibrary.NATIVE.transfer(address(router), 10 ether); + + vm.expectRevert(IVault.SettleNonNativeCurrencyWithValue.selector); + vm.prank(address(router)); + vault.lock(hex"21"); // 0x02 assume both token are ERC20, so it call settle for ETH without value + } + } + function testVault_ethSupport_settleAndTake() public { FakePoolManagerRouter router = new FakePoolManagerRouter( vault, @@ -663,21 +703,21 @@ contract VaultTest is Test, GasSnapshot { }) ); - // make sure vault has enough tokens - CurrencyLibrary.NATIVE.transfer(address(vault), 10 ether); + // make sure vault has enough tokens and ETH to router as router call .settle{value} + CurrencyLibrary.NATIVE.transfer(address(router), 10 ether); currency1.transfer(address(vault), 10 ether); vm.prank(address(router)); - vault.lock(hex"02"); + vault.lock(hex"21"); - CurrencyLibrary.NATIVE.transfer(address(vault), 10 ether); + CurrencyLibrary.NATIVE.transfer(address(router), 10 ether); currency1.transfer(address(vault), 10 ether); vm.prank(address(router)); - vault.lock(hex"02"); + vault.lock(hex"21"); // take and settle { vm.prank(address(router)); - vault.lock(hex"05"); + vault.lock(hex"22"); } } } From ee06ebc9176956e477a497896feaafc970659797 Mon Sep 17 00:00:00 2001 From: ChefMist <133624774+ChefMist@users.noreply.github.com> Date: Tue, 7 May 2024 15:41:12 +0800 Subject: [PATCH 5/5] test: use --isolate for test (#35) * feat: use --isolate for test * doc: update readme with forge test --isolate * test: updated forge test gas --- .../BinHookTest#testBurnSucceedsWithHook.snap | 2 +- ...inHookTest#testDonateSucceedsWithHook.snap | 2 +- ...okTest#testInitializeSucceedsWithHook.snap | 2 +- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- ...oolManagerTest#testBurnNativeCurrency.snap | 2 +- ...agerTest#testFuzzUpdateDynamicSwapFee.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#testNoOpGas_Burn.snap | 2 +- ...BinPoolManagerTest#testNoOpGas_Donate.snap | 2 +- ...oolManagerTest#testNoOpGas_Initialize.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Mint.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Swap.snap | 2 +- ...BinPoolManagerTest#testSetProtocolFee.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 +- ...agerTest#testFuzzUpdateDynamicSwapFee.snap | 2 +- ...CLPoolManagerTest#testNoOp_gas_Donate.snap | 2 +- ...olManagerTest#testNoOp_gas_Initialize.snap | 2 +- ...nagerTest#testNoOp_gas_ModifyPosition.snap | 2 +- .../CLPoolManagerTest#testNoOp_gas_Swap.snap | 2 +- .forge-snapshots/VaultTest#collectFee.snap | 2 +- ...VaultTest#lockSettledWhenAddLiquidity.snap | 2 +- .../VaultTest#lockSettledWhenFlashloan.snap | 2 +- ...VaultTest#lockSettledWhenMultiHopSwap.snap | 2 +- .../VaultTest#lockSettledWhenSwap.snap | 2 +- .../VaultTest#registerPoolManager.snap | 2 +- .forge-snapshots/VaultTest#testLock_NoOp.snap | 2 +- ...Test#testSettleAndMintRefund_WithMint.snap | 2 +- ...t#testSettleAndMintRefund_WithoutMint.snap | 2 +- .github/workflows/test.yml | 2 +- README.md | 4 +- package.json | 6 +-- test/Isolate.t.sol | 40 +++++++++++++++++++ 58 files changed, 101 insertions(+), 59 deletions(-) create mode 100644 test/Isolate.t.sol diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index 33dc6386..307d5ed7 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -143154 \ No newline at end of file +181146 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap index ff4ea856..f9e66567 100644 --- a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap @@ -1 +1 @@ -134501 \ No newline at end of file +185453 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index d4c40291..9cc8a7b6 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -108737 \ No newline at end of file +136997 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 0dec00a9..0c0e956b 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -300549 \ No newline at end of file +329657 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 80d7303c..12dca088 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -138567 \ No newline at end of file +192259 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap index 3b765b08..211bbe6d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap @@ -1 +1 @@ -89917 \ No newline at end of file +142035 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap index 8eb54da3..c577b7f3 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap @@ -1 +1 @@ -4679 \ No newline at end of file +32483 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap index 3e7e9aaa..b23c0f1f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap @@ -1 +1 @@ -9179 \ No newline at end of file +30395 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap index c6ffd4c5..7c39107a 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap @@ -1 +1 @@ -65499 \ No newline at end of file +158207 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap index 36cf538a..7d6a6244 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap @@ -1 +1 @@ -149045 \ No newline at end of file +303831 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap index ef487ea2..a962c50f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap @@ -1 +1 @@ -66678 \ No newline at end of file +139396 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap index ee82ec75..11a442d2 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap @@ -1 +1 @@ -52325 \ No newline at end of file +125193 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index 78e8e2dd..4c797212 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -968335 \ No newline at end of file +1013119 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index 1158be7b..21a445e2 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -119849 \ No newline at end of file +341133 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index 318d166f..0e0be6c5 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -337527 \ No newline at end of file +380471 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index c7581d3d..f25c5394 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -54588 \ No newline at end of file +149232 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 45154b6c..1722be47 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -89025 \ No newline at end of file +187313 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index d41f4000..6fb6cf04 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -91010 \ No newline at end of file +193298 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index 9a9d837c..9ab54b3a 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -70574 \ No newline at end of file +145662 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index 67c86f8b..30eaf7e3 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -296760 \ No newline at end of file +327364 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap index 5f52d88e..ab7d7ce2 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap @@ -1 +1 @@ -41345 \ No newline at end of file +76641 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap index cd943f51..03115631 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap @@ -1 +1 @@ -19114 \ No newline at end of file +54134 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap index a71eeb21..bc077469 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap @@ -1 +1 @@ -36874 \ No newline at end of file +64622 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap index 6e77b98a..e65039fd 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap @@ -1 +1 @@ -38978 \ No newline at end of file +69634 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap index f3179101..acd8adaa 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap @@ -1 +1 @@ -22359 \ No newline at end of file +57563 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap index 56ecd7e5..9e13c6e2 100644 --- a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap @@ -1 +1 @@ -7461 \ No newline at end of file +41349 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index 60cdd631..0797313d 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -349410 \ No newline at end of file +401866 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index c043af4e..2e48818b 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -59958 \ No newline at end of file +182854 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 350247e1..4016b72f 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -220457 \ No newline at end of file +247993 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 83655bbf..34900b6a 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -82382 \ No newline at end of file +175518 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index f45ee22b..222fd3b5 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -52276 \ No newline at end of file +116900 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 192055ae..dcded754 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -36324 \ No newline at end of file +59356 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index 4587a3cc..63e482c1 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -42829 \ No newline at end of file +129601 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 48e56260..c06a528a 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -54399 \ No newline at end of file +148767 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index c3cc24d9..5668dc15 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -100389 \ No newline at end of file +177177 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 2972925d..3a7673b9 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -24843277 \ No newline at end of file +24940941 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 53085439..60f4a534 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -35478 \ No newline at end of file +78822 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index e6c0765f..3e858947 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -101168 \ No newline at end of file +158284 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index d94e1895..c43f046a 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -41197 \ No newline at end of file +95329 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index a34182c5..0e675ddc 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -35481 \ No newline at end of file +78825 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap index d5df029e..6d0e4885 100644 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap @@ -1 +1 @@ -4453 \ No newline at end of file +32257 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap index 34c7063c..7378cd40 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap @@ -1 +1 @@ -19014 \ No newline at end of file +53926 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap index 65dc7231..4b3b26e6 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap @@ -1 +1 @@ -37282 \ No newline at end of file +65078 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap index 40c53563..26e3655a 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap @@ -1 +1 @@ -29728 \ No newline at end of file +60244 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap index a64034e9..f2a6d359 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap @@ -1 +1 @@ -21443 \ No newline at end of file +56919 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#collectFee.snap b/.forge-snapshots/VaultTest#collectFee.snap index 2a59609b..206f0c3a 100644 --- a/.forge-snapshots/VaultTest#collectFee.snap +++ b/.forge-snapshots/VaultTest#collectFee.snap @@ -1 +1 @@ -25611 \ No newline at end of file +53360 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap index e7aaee6e..ab88a8ce 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap @@ -1 +1 @@ -120866 \ No newline at end of file +159350 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap b/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap index 3dd0ec6a..4fdddea1 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenFlashloan.snap @@ -1 +1 @@ -157144 \ No newline at end of file +103575 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap index b4eb840c..1f7bb15b 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap @@ -1 +1 @@ -45289 \ No newline at end of file +118273 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap index 2fdc2d44..f2e8eba3 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap @@ -1 +1 @@ -45288 \ No newline at end of file +118272 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#registerPoolManager.snap b/.forge-snapshots/VaultTest#registerPoolManager.snap index f8a88baa..d3457db4 100644 --- a/.forge-snapshots/VaultTest#registerPoolManager.snap +++ b/.forge-snapshots/VaultTest#registerPoolManager.snap @@ -1 +1 @@ -24484 \ No newline at end of file +47916 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#testLock_NoOp.snap b/.forge-snapshots/VaultTest#testLock_NoOp.snap index 06d678ad..924fa5c1 100644 --- a/.forge-snapshots/VaultTest#testLock_NoOp.snap +++ b/.forge-snapshots/VaultTest#testLock_NoOp.snap @@ -1 +1 @@ -11517 \ No newline at end of file +32989 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap index 51e6da8a..b18d08ba 100644 --- a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap +++ b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithMint.snap @@ -1 +1 @@ -71811 \ No newline at end of file +100035 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap index c2d8ee13..372de2e1 100644 --- a/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap +++ b/.forge-snapshots/VaultTest#testSettleAndMintRefund_WithoutMint.snap @@ -1 +1 @@ -33418 \ No newline at end of file +55142 \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 72b10081..5df94cb9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,6 @@ jobs: version: nightly - name: Run tests - run: forge test -vvv + run: forge test --isolate -vvv env: FOUNDRY_PROFILE: ${{ github.ref_name == 'main' && 'ci_main' || 'ci' }} diff --git a/README.md b/README.md index a38937e0..1c5c62ef 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ ## Running test 1. Install dependencies with `forge install` and `yarn` -2. Run test with `forge test` +2. Run test with `forge test --isolate` + +See https://github.com/pancakeswap/pancake-v4-core/pull/35 on why `--isolate` flag is used. ## Update dependencies diff --git a/package.json b/package.json index 581237fd..f38777bf 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,9 @@ }, "scripts": { "compile": "forge compile", - "test": "forge test", - "dev": "forge test -vvv -w", - "snapshot": "rm -fr .forge-snapshots && forge test", + "test": "forge test --isolate", + "dev": "forge test --isolate -vvv -w", + "snapshot": "rm -fr .forge-snapshots && forge test --isolate", "prettier": "forge fmt src/ && forge fmt test/", "prettier-check": "forge fmt --check", "prepare": "husky install" diff --git a/test/Isolate.t.sol b/test/Isolate.t.sol new file mode 100644 index 00000000..48d50e27 --- /dev/null +++ b/test/Isolate.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; + +/// @dev A test contract to ensure developers are using `--isolate` flag when running forge test +contract IsolateTest is Test { + StorageLib storageLib; + + function setUp() public { + storageLib = new StorageLib(); + } + + function testIsolateTest() public { + // tstore key: 1 with value :2 + storageLib.tstore(1, 2); + + // toload key: 1 + uint256 val = storageLib.tload(1); + + // If the test is run with `--isolate` flag, the value should be 0 + // as --isolate run each top level call as seperate transaction, so tload will return 0 + assertEq(val, 0, "did you forget to use --isolate flag for 'forge test'?"); + } +} + +contract StorageLib { + function tstore(uint256 key, uint256 val) public { + assembly { + tstore(key, val) + } + } + + function tload(uint256 key) public view returns (uint256 val) { + assembly { + val := tload(key) + } + return val; + } +}