From 29822f3a4d894dc6910ad50cc805d7a6a7c1a4f3 Mon Sep 17 00:00:00 2001 From: chef-burger <137024020+chefburger@users.noreply.github.com> Date: Mon, 13 May 2024 12:26:06 +0800 Subject: [PATCH] feat: refactor the fee mechanism for both pools (#36) * feat: refactor the fee mechanism for both pools * fix: make it compiles * fix: updating related test cases * feat: more accuarate calculation for protocolFee * optimization: remove unnecessary struct in clpool#swap * optimization: comments and small adjustment per review * optimization: cache memory variable in clpool#swap * chore: add gas snapshots back --- .../BinHookTest#testBurnSucceedsWithHook.snap | 2 +- ...okTest#testInitializeSucceedsWithHook.snap | 2 +- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- ...oolManagerTest#testBurnNativeCurrency.snap | 2 +- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 1 + ...agerTest#testFuzzUpdateDynamicSwapFee.snap | 1 - ...oolManagerTest#testFuzz_SetMaxBinStep.snap | 2 +- ...BinPoolManagerTest#testGasBurnHalfBin.snap | 2 +- ...inPoolManagerTest#testGasBurnNineBins.snap | 2 +- .../BinPoolManagerTest#testGasBurnOneBin.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 +- ...oolManagerTest#testNoOpGas_Initialize.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 +- ...anagerTest#testFuzzUpdateDynamicLPFee.snap | 1 + ...agerTest#testFuzzUpdateDynamicSwapFee.snap | 1 - ...CLPoolManagerTest#testNoOp_gas_Donate.snap | 2 +- ...olManagerTest#testNoOp_gas_Initialize.snap | 2 +- ...nagerTest#testNoOp_gas_ModifyPosition.snap | 2 +- ...pMathTest#SwapOneForZeroExactInCapped.snap | 2 +- ...MathTest#SwapOneForZeroExactOutCapped.snap | 2 +- ...pMathTest#SwapZeroForOneExactInCapped.snap | 2 +- ...MathTest#SwapZeroForOneExactOutCapped.snap | 2 +- ...VaultTest#lockSettledWhenAddLiquidity.snap | 2 +- ...VaultTest#lockSettledWhenMultiHopSwap.snap | 2 +- .../VaultTest#lockSettledWhenSwap.snap | 2 +- src/{Fees.sol => ProtocolFees.sol} | 46 ++--- src/interfaces/IPoolManager.sol | 30 ++- src/interfaces/IProtocolFeeController.sol | 2 +- .../{IFees.sol => IProtocolFees.sol} | 22 ++- src/libraries/Hooks.sol | 6 +- src/libraries/LPFeeLibrary.sol | 38 ++++ src/libraries/ProtocolFeeLibrary.sol | 43 +++++ src/libraries/SwapFeeLibrary.sol | 39 ---- .../math}/UnsafeMath.sol | 0 src/pool-bin/BinPoolManager.sol | 88 ++++----- src/pool-bin/interfaces/IBinPoolManager.sol | 14 +- src/pool-bin/libraries/BinHelper.sol | 33 +++- src/pool-bin/libraries/BinPool.sol | 108 ++++++----- .../libraries/math/PackedUint128Math.sol | 26 ++- src/pool-cl/CLPoolManager.sol | 49 +++-- src/pool-cl/interfaces/ICLPoolManager.sol | 14 +- src/pool-cl/libraries/CLPool.sol | 104 ++++++----- src/pool-cl/libraries/SqrtPriceMath.sol | 2 +- src/pool-cl/libraries/SwapMath.sol | 2 +- src/test/MockFeePoolManager.sol | 29 +-- src/test/fee/MockFeeManagerHook.sol | 2 +- src/test/fee/MockProtocolFeeController.sol | 22 +-- src/types/PoolKey.sol | 2 +- test/{Fees.t.sol => ProtocolFees.t.sol} | 96 ++++++---- test/libraries/FeeLibrary.sol | 48 ----- test/libraries/Hooks/Hooks.t.sol | 4 +- test/libraries/LPFeeLibrary.t.sol | 51 +++++ .../math}/UnsafeMath.t.sol | 2 +- test/pool-bin/BinPoolManager.t.sol | 88 +++++---- test/pool-bin/helpers/BinFeeManagerHook.sol | 4 +- test/pool-bin/libraries/BinHelper.t.sol | 10 +- test/pool-bin/libraries/BinPoolFee.t.sol | 46 ++--- test/pool-bin/libraries/BinPoolSwap.t.sol | 28 +-- test/pool-bin/libraries/FeeHelper.t.sol | 8 +- .../libraries/math/PackedUint128Math.t.sol | 45 +++-- test/pool-cl/CLFees.t.sol | 72 +++---- test/pool-cl/CLHookSkipCallback.t.sol | 4 +- test/pool-cl/CLPoolManager.t.sol | 175 ++++++++++++------ test/pool-cl/helpers/CLFeeManagerHook.sol | 4 +- test/pool-cl/helpers/Deployers.sol | 6 +- .../helpers/ProtocolFeeControllerTest.sol | 6 +- test/pool-cl/libraries/CLPool.t.sol | 6 +- test/pool-cl/libraries/CLPoolSwapFee.t.sol | 30 +-- test/vault/FakePoolManager.sol | 4 +- 92 files changed, 855 insertions(+), 695 deletions(-) create mode 100644 .forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap delete mode 100644 .forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap create mode 100644 .forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap delete mode 100644 .forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap rename src/{Fees.sol => ProtocolFees.sol} (71%) rename src/interfaces/{IFees.sol => IProtocolFees.sol} (60%) create mode 100644 src/libraries/LPFeeLibrary.sol create mode 100644 src/libraries/ProtocolFeeLibrary.sol delete mode 100644 src/libraries/SwapFeeLibrary.sol rename src/{pool-cl/libraries => libraries/math}/UnsafeMath.sol (100%) rename test/{Fees.t.sol => ProtocolFees.t.sol} (75%) delete mode 100644 test/libraries/FeeLibrary.sol create mode 100644 test/libraries/LPFeeLibrary.t.sol rename test/{pool-cl/libraries => libraries/math}/UnsafeMath.t.sol (85%) diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index 307d5ed7..5ef512ea 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -181146 \ No newline at end of file +181124 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index 9cc8a7b6..bfcb7cad 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -136997 \ No newline at end of file +137037 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 0c0e956b..22fed2fe 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -329657 \ No newline at end of file +329719 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 12dca088..fa139b82 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -192259 \ No newline at end of file +192675 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap index 211bbe6d..24bc5c20 100644 --- a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap @@ -1 +1 @@ -142035 \ No newline at end of file +142017 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap new file mode 100644 index 00000000..3d7fbe59 --- /dev/null +++ b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -0,0 +1 @@ +32585 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap b/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap deleted file mode 100644 index c577b7f3..00000000 --- a/.forge-snapshots/BinPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap +++ /dev/null @@ -1 +0,0 @@ -32483 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap index b23c0f1f..13505f4e 100644 --- a/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap +++ b/.forge-snapshots/BinPoolManagerTest#testFuzz_SetMaxBinStep.snap @@ -1 +1 @@ -30395 \ No newline at end of file +30350 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap index 7c39107a..3a5c6070 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap @@ -1 +1 @@ -158207 \ No newline at end of file +158185 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap index 7d6a6244..767b7892 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap @@ -1 +1 @@ -303831 \ No newline at end of file +303813 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap index a962c50f..df816042 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap @@ -1 +1 @@ -139396 \ No newline at end of file +139378 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index 4c797212..1030999b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -1013119 \ No newline at end of file +1014201 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index 21a445e2..13aed349 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -341133 \ No newline at end of file +342215 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index 0e0be6c5..5cfb45a7 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -380471 \ No newline at end of file +380533 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index f25c5394..c53d4dde 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -149232 \ No newline at end of file +149294 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 1722be47..9901ae99 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -187313 \ No newline at end of file +187651 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 6fb6cf04..8248c717 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -193298 \ No newline at end of file +193636 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index 9ab54b3a..45aff27a 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -145662 \ No newline at end of file +146078 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index 30eaf7e3..1bd2a1ee 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -327364 \ No newline at end of file +327426 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap index ab7d7ce2..0e7b8511 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap @@ -1 +1 @@ -76641 \ No newline at end of file +76619 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap index bc077469..83c8f740 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap @@ -1 +1 @@ -64622 \ No newline at end of file +64662 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap index 9e13c6e2..12ede85c 100644 --- a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap @@ -1 +1 @@ -41349 \ No newline at end of file +34422 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index 5441f030..6f174376 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -401422 \ No newline at end of file +401400 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 87bb66dc..8500a53f 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -182410 \ No newline at end of file +182388 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 7ada3d1d..e2ea1fdb 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -247549 \ No newline at end of file +247527 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 34900b6a..8d19448a 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -175518 \ No newline at end of file +175562 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 222fd3b5..4a0b0e1b 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -116900 \ No newline at end of file +116944 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index dcded754..a047ff13 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -59356 \ No newline at end of file +59393 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index 887d205b..231b27c4 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -128995 \ No newline at end of file +128973 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index c06a528a..9a2af0bc 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -148767 \ No newline at end of file +148772 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index 5668dc15..9b016f69 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -177177 \ No newline at end of file +177222 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 134ba728..43f2e4ad 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -24940739 \ No newline at end of file +25086624 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 60f4a534..061f91d4 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -78822 \ No newline at end of file +78867 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 3e858947..f71d0575 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -158284 \ No newline at end of file +158439 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index c43f046a..a19d1d77 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -95329 \ No newline at end of file +95374 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index 0e675ddc..bff11caf 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -78825 \ No newline at end of file +78870 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap new file mode 100644 index 00000000..40100785 --- /dev/null +++ b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicLPFee.snap @@ -0,0 +1 @@ +32293 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap b/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap deleted file mode 100644 index 6d0e4885..00000000 --- a/.forge-snapshots/CLPoolManagerTest#testFuzzUpdateDynamicSwapFee.snap +++ /dev/null @@ -1 +0,0 @@ -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 7378cd40..890d0b3f 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap @@ -1 +1 @@ -53926 \ No newline at end of file +53970 \ 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 4b3b26e6..21de32ba 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap @@ -1 +1 @@ -65078 \ No newline at end of file +65115 \ 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 26e3655a..62af65f1 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap @@ -1 +1 @@ -60244 \ No newline at end of file +60222 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap index 2336e7f0..9eca385b 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactInCapped.snap @@ -1 +1 @@ -2197 \ No newline at end of file +2237 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap index 7411188d..d8f80992 100644 --- a/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapOneForZeroExactOutCapped.snap @@ -1 +1 @@ -1954 \ No newline at end of file +1994 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap index f808a8d2..ee99c5c7 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactInCapped.snap @@ -1 +1 @@ -2187 \ No newline at end of file +2227 \ No newline at end of file diff --git a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap index a2d01105..a43f9e1b 100644 --- a/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap +++ b/.forge-snapshots/SwapMathTest#SwapZeroForOneExactOutCapped.snap @@ -1 +1 @@ -1944 \ No newline at end of file +1984 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap index ab88a8ce..af78ee04 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenAddLiquidity.snap @@ -1 +1 @@ -159350 \ No newline at end of file +159328 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap index 1f7bb15b..584d5812 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenMultiHopSwap.snap @@ -1 +1 @@ -118273 \ No newline at end of file +118251 \ No newline at end of file diff --git a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap index f2e8eba3..edf15509 100644 --- a/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap +++ b/.forge-snapshots/VaultTest#lockSettledWhenSwap.snap @@ -1 +1 @@ -118272 \ No newline at end of file +118250 \ No newline at end of file diff --git a/src/Fees.sol b/src/ProtocolFees.sol similarity index 71% rename from src/Fees.sol rename to src/ProtocolFees.sol index 095dafba..b5e0acf0 100644 --- a/src/Fees.sol +++ b/src/ProtocolFees.sol @@ -5,12 +5,15 @@ pragma solidity ^0.8.24; import {PausableRole} from "./PausableRole.sol"; import {Currency} from "./types/Currency.sol"; import {IProtocolFeeController} from "./interfaces/IProtocolFeeController.sol"; -import {IFees} from "./interfaces/IFees.sol"; +import {IProtocolFees} from "./interfaces/IProtocolFees.sol"; +import {ProtocolFeeLibrary} from "./libraries/ProtocolFeeLibrary.sol"; import {PoolKey} from "./types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "./types/PoolId.sol"; import {IVault} from "./interfaces/IVault.sol"; -abstract contract Fees is IFees, PausableRole { - uint8 public constant MIN_PROTOCOL_FEE_DENOMINATOR = 4; +abstract contract ProtocolFees is IProtocolFees, PausableRole { + using PoolIdLibrary for PoolKey; + using ProtocolFeeLibrary for uint24; mapping(Currency currency => uint256) public protocolFeesAccrued; @@ -24,11 +27,22 @@ abstract contract Fees is IFees, PausableRole { controllerGasLimit = _controllerGasLimit; } + function _setProtocolFee(PoolId id, uint24 newProtocolFee) internal virtual; + + /// @inheritdoc IProtocolFees + function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external virtual { + if (msg.sender != address(protocolFeeController)) revert InvalidCaller(); + if (!newProtocolFee.validate()) revert FeeTooLarge(); + PoolId id = key.toId(); + _setProtocolFee(id, newProtocolFee); + emit ProtocolFeeUpdated(id, newProtocolFee); + } + /// @notice Fetch the protocol fee for a given pool, returning false if the call fails or the returned fee is invalid. /// @dev to prevent an invalid protocol fee controller from blocking pools from being initialized /// the success of this function is NOT checked on initialize and if the call fails, the protocol fee is set to 0. /// @dev the success of this function must be checked when called in setProtocolFee - function _fetchProtocolFee(PoolKey memory key) internal returns (bool success, uint16 protocolFee) { + function _fetchProtocolFee(PoolKey memory key) internal returns (bool success, uint24 protocolFee) { if (address(protocolFeeController) != address(0)) { // note that EIP-150 mandates that calls requesting more than 63/64ths of remaining gas // will be allotted no more than this amount, so controllerGasLimit must be set with this @@ -45,26 +59,12 @@ abstract contract Fees is IFees, PausableRole { assembly { returnData := mload(add(_data, 0x20)) } - // Ensure return data does not overflow a uint16 and that the underlying fees are within bounds. - (success, protocolFee) = returnData == uint16(returnData) && _isFeeWithinBounds(uint16(returnData)) - ? (true, uint16(returnData)) - : (false, 0); - } - } - /// @dev the lower 8 bits for fee0 and upper 8 bits for fee1 - function _isFeeWithinBounds(uint16 fee) internal pure returns (bool) { - if (fee != 0) { - uint16 fee0 = fee % 256; - uint16 fee1 = fee >> 8; - // The fee is specified as a denominator so it cannot be LESS than the MIN_PROTOCOL_FEE_DENOMINATOR (unless it is 0). - if ( - (fee0 != 0 && fee0 < MIN_PROTOCOL_FEE_DENOMINATOR) || (fee1 != 0 && fee1 < MIN_PROTOCOL_FEE_DENOMINATOR) - ) { - return false; - } + // Ensure return data does not overflow a uint24 and that the underlying fees are within bounds. + (success, protocolFee) = (returnData == uint24(returnData)) && uint24(returnData).validate() + ? (true, uint24(returnData)) + : (false, 0); } - return true; } function setProtocolFeeController(IProtocolFeeController controller) external onlyOwner { @@ -77,7 +77,7 @@ abstract contract Fees is IFees, PausableRole { override returns (uint256 amountCollected) { - if (msg.sender != owner() && msg.sender != address(protocolFeeController)) revert InvalidProtocolFeeCollector(); + if (msg.sender != owner() && msg.sender != address(protocolFeeController)) revert InvalidCaller(); amountCollected = (amount == 0) ? protocolFeesAccrued[currency] : amount; protocolFeesAccrued[currency] -= amountCollected; diff --git a/src/interfaces/IPoolManager.sol b/src/interfaces/IPoolManager.sol index b01546e5..e88489e5 100644 --- a/src/interfaces/IPoolManager.sol +++ b/src/interfaces/IPoolManager.sol @@ -11,26 +11,18 @@ interface IPoolManager { /// @notice PoolKey must have currencies where address(currency0) < address(currency1) error CurrenciesInitializedOutOfOrder(); - /// @notice Thrown when a call to updateDynamicSwapFee is made by an address that is not the hook, - /// or on a pool that does not have a dynamic swap fee. - error UnauthorizedDynamicSwapFeeUpdate(); + /// @notice Thrown when a call to updateDynamicLPFee is made by an address that is not the hook, + /// or on a pool is not a dynamic fee pool. + error UnauthorizedDynamicLPFeeUpdate(); - /// @notice Emitted when protocol fee is updated - /// @dev The event is emitted even if the updated protocolFee is the same as previous protocolFee - event ProtocolFeeUpdated(PoolId indexed id, uint16 protocolFee); + /// @notice Emitted when lp fee is updated + /// @dev The event is emitted even if the updated fee value is the same as previous one + event DynamicLPFeeUpdated(PoolId indexed id, uint24 dynamicLPFee); - /// @notice Emitted when swap fee is updated - /// @dev The event is emitted even if the updated swap fee is the same as previous swap fee - event DynamicSwapFeeUpdated(PoolId indexed id, uint24 dynamicSwapFee); - - /// @notice Sets the protocol's swap fee for the given pool - /// Protocol fee is always a portion of swap fee that is owed. If that underlying fee is 0, no protocol fee will accrue even if it is set to > 0. - function setProtocolFee(PoolKey memory key) external; - - /// @notice Updates the pools swap fee for the a pool that has enabled dynamic swap fee. + /// @notice Updates lp fee for a dyanmic fee pool /// @dev Some of the use case could be: - /// 1) when hook#beforeSwap() is called and hook call this function to update the swap fee - /// 2) For BinPool only, when hook#beforeMint() is called and hook call this function to update the swap fee - /// 3) other use case where the hook might want to on an ad-hoc basis increase/reduce swap fee - function updateDynamicSwapFee(PoolKey memory key, uint24 newDynamicSwapFee) external; + /// 1) when hook#beforeSwap() is called and hook call this function to update the lp fee + /// 2) For BinPool only, when hook#beforeMint() is called and hook call this function to update the lp fee + /// 3) other use case where the hook might want to on an ad-hoc basis increase/reduce lp fee + function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external; } diff --git a/src/interfaces/IProtocolFeeController.sol b/src/interfaces/IProtocolFeeController.sol index b259cc90..b99ef031 100644 --- a/src/interfaces/IProtocolFeeController.sol +++ b/src/interfaces/IProtocolFeeController.sol @@ -7,5 +7,5 @@ interface IProtocolFeeController { /// @notice Returns the protocol fee for a pool given the conditions of this contract /// @param poolKey The pool key to identify the pool. The controller may want to use attributes on the pool /// to determine the protocol fee, hence the entire key is needed. - function protocolFeeForPool(PoolKey memory poolKey) external view returns (uint16); + function protocolFeeForPool(PoolKey memory poolKey) external view returns (uint24); } diff --git a/src/interfaces/IFees.sol b/src/interfaces/IProtocolFees.sol similarity index 60% rename from src/interfaces/IFees.sol rename to src/interfaces/IProtocolFees.sol index 14078dd6..3d472dd0 100644 --- a/src/interfaces/IFees.sol +++ b/src/interfaces/IProtocolFees.sol @@ -3,25 +3,29 @@ pragma solidity ^0.8.24; import {Currency} from "../types/Currency.sol"; import {IProtocolFeeController} from "./IProtocolFeeController.sol"; +import {PoolId} from "../types/PoolId.sol"; +import {PoolKey} from "../types/PoolKey.sol"; -interface IFees { - /// @notice Thrown when the protocol fee denominator is less than 4. Also thrown when the static or dynamic fee on a pool exceeds the upper limit. +interface IProtocolFees { + /// @notice Thrown when the protocol fee exceeds the upper limit. error FeeTooLarge(); /// @notice Thrown when not enough gas is provided to look up the protocol fee error ProtocolFeeCannotBeFetched(); - /// @notice Thrown when user not authorized to collect protocol fee - error InvalidProtocolFeeCollector(); - /// @notice Thrown when the call to fetch the protocol fee reverts or returns invalid data. - error ProtocolFeeControllerCallFailedOrInvalidResult(); + /// @notice Thrown when user not authorized to set or collect protocol fee + error InvalidCaller(); - event ProtocolFeeControllerUpdated(address protocolFeeController); + /// @notice Emitted when protocol fee is updated + /// @dev The event is emitted even if the updated protocolFee is the same as previous protocolFee + event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFee); - /// @notice Returns the minimum denominator for the protocol fee, which restricts it to a maximum of 25% - function MIN_PROTOCOL_FEE_DENOMINATOR() external view returns (uint8); + event ProtocolFeeControllerUpdated(address protocolFeeController); /// @notice Given a currency address, returns the protocol fees accrued in that currency function protocolFeesAccrued(Currency) external view returns (uint256); + /// @notice Sets the protocol's swap fee for the given pool + function setProtocolFee(PoolKey memory key, uint24 newProtocolFee) external; + /// @notice Update the protocol fee controller, called by the owner function setProtocolFeeController(IProtocolFeeController controller) external; diff --git a/src/libraries/Hooks.sol b/src/libraries/Hooks.sol index f1e9be5b..279998e3 100644 --- a/src/libraries/Hooks.sol +++ b/src/libraries/Hooks.sol @@ -5,13 +5,13 @@ pragma solidity ^0.8.24; import {IHooks} from "../interfaces/IHooks.sol"; import {PoolKey} from "../types/PoolKey.sol"; import {Encoded} from "./math/Encoded.sol"; -import {SwapFeeLibrary} from "./SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "./LPFeeLibrary.sol"; import {ParametersHelper} from "./math/ParametersHelper.sol"; library Hooks { using Encoded for bytes32; using ParametersHelper for bytes32; - using SwapFeeLibrary for uint24; + using LPFeeLibrary for uint24; bytes4 constant NO_OP_SELECTOR = bytes4(keccak256(abi.encodePacked("NoOp"))); @@ -34,7 +34,7 @@ library Hooks { if (address(poolKey.hooks) == address(0)) { /// @notice If the hooks address is 0, then the bitmap must be 0, /// in the same time, the dynamic fee should be disabled as well - if (bitmapInParameters == 0 && !poolKey.fee.isDynamicSwapFee()) { + if (bitmapInParameters == 0 && !poolKey.fee.isDynamicLPFee()) { return; } revert HookConfigValidationError(); diff --git a/src/libraries/LPFeeLibrary.sol b/src/libraries/LPFeeLibrary.sol new file mode 100644 index 00000000..770812bd --- /dev/null +++ b/src/libraries/LPFeeLibrary.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2024 PancakeSwap +pragma solidity ^0.8.24; + +/// @dev Library for handling lp fee setting from `PoolKey.fee` +/// It can be either static or dynamic, and upper 4 bits are used to store the flag: +/// 1. if the flag is set, then the fee is dynamic, it can be set and updated by hook +/// 2. otherwise if the flag is not set, then the fee is static, and the lower 20 bits are used to store the fee +library LPFeeLibrary { + using LPFeeLibrary for uint24; + + /// @notice Thrown when the static/dynamic fee on a pool exceeds 100%. + error FeeTooLarge(); + + /// @dev the flag and mask + uint24 public constant STATIC_FEE_MASK = 0x0FFFFF; + uint24 public constant DYNAMIC_FEE_FLAG = 0x800000; + + /// @dev the fee is represented in hundredths of a bip + /// @dev the max fee for cl pool is 100% and for bin, it is 10% + uint24 public constant ONE_HUNDRED_PERCENT_FEE = 1_000_000; + uint24 public constant TEN_PERCENT_FEE = 100_000; + + function isDynamicLPFee(uint24 self) internal pure returns (bool) { + return self & DYNAMIC_FEE_FLAG != 0; + } + + function validate(uint24 self, uint24 maxFee) internal pure { + if (self > maxFee) revert FeeTooLarge(); + } + + /// @return lpFee initial lp fee for the pool. For dynamic fee pool, zero is returned + function getInitialLPFee(uint24 self) internal pure returns (uint24 lpFee) { + // the initial fee for a dynamic fee pool is 0 + if (self.isDynamicLPFee()) return 0; + lpFee = self & STATIC_FEE_MASK; + } +} diff --git a/src/libraries/ProtocolFeeLibrary.sol b/src/libraries/ProtocolFeeLibrary.sol new file mode 100644 index 00000000..9597a36b --- /dev/null +++ b/src/libraries/ProtocolFeeLibrary.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright (C) 2024 PancakeSwap +pragma solidity ^0.8.24; + +import "./math/UnsafeMath.sol"; + +library ProtocolFeeLibrary { + // Max protocol fee is 0.1% (1000 pips) + uint16 public constant MAX_PROTOCOL_FEE = 1000; + + // the protocol fee is represented in hundredths of a bip + uint256 internal constant PIPS_DENOMINATOR = 1_000_000; + + function getZeroForOneFee(uint24 self) internal pure returns (uint16) { + return uint16(self & (4096 - 1)); + } + + function getOneForZeroFee(uint24 self) internal pure returns (uint16) { + return uint16(self >> 12); + } + + function validate(uint24 self) internal pure returns (bool) { + if (self != 0) { + uint16 fee0 = getZeroForOneFee(self); + uint16 fee1 = getOneForZeroFee(self); + // The fee is represented in pips and it cannot be greater than the MAX_PROTOCOL_FEE. + if ((fee0 > MAX_PROTOCOL_FEE) || (fee1 > MAX_PROTOCOL_FEE)) { + return false; + } + } + return true; + } + + // The protocol fee is taken from the input amount first and then the LP fee is taken from the remaining + // The swap fee is capped at 100% + // equivalent to protocolFee + lpFee(1_000_000 - protocolFee) / 1_000_000 + function calculateSwapFee(uint24 self, uint24 lpFee) internal pure returns (uint24) { + unchecked { + uint256 numerator = uint256(self) * uint256(lpFee); + return uint24(uint256(self) + lpFee - UnsafeMath.divRoundingUp(numerator, PIPS_DENOMINATOR)); + } + } +} diff --git a/src/libraries/SwapFeeLibrary.sol b/src/libraries/SwapFeeLibrary.sol deleted file mode 100644 index 4f9203af..00000000 --- a/src/libraries/SwapFeeLibrary.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -// Copyright (C) 2024 PancakeSwap -pragma solidity ^0.8.24; - -/// @dev Library for parsing swap fee info from PoolKey.fee: -/// 24 bits (upper 4 bits are used to store flag, if swap fee is static, parse lower 20 bits to get swap fee) -/// 1. flag to indicate the activation of dynamic swap fee, otherwise static swap fee is used -/// - if dynamic swap fee is activated, then the swap fee can be updated by hook -/// - if dynamic swap fee is not activated, then the swap fee is controlled by PoolKey.fee itself -/// 2. protocol fee is controlled by protocolFeeController, not PoolKey.fee -/// - protocol fee is controlled by IProtocolFeeController(hook).protocolFeeForPool() -library SwapFeeLibrary { - using SwapFeeLibrary for uint24; - - /// @dev swap fee is stored in PoolKey as uint24 - uint24 public constant STATIC_FEE_MASK = 0x0FFFFF; - uint24 public constant DYNAMIC_FEE_FLAG = 0x800000; // 1000 - - /// @dev used as max swap fee for a pool. for CL, its 100%, for bin, its 10% - uint24 public constant ONE_HUNDRED_PERCENT_FEE = 1_000_000; - uint24 public constant TEN_PERCENT_FEE = 100_000; - - // swap fee for LP - function isDynamicSwapFee(uint24 self) internal pure returns (bool) { - return self & DYNAMIC_FEE_FLAG != 0; - } - - function isSwapFeeTooLarge(uint24 self, uint24 maxFee) internal pure returns (bool) { - return self > maxFee; - } - - /// @return swapFee initial swap fee for the pool. For dynamic fee pool, query - /// poolManager.getSlot0(poolId) to get the current swapFee instead - function getInitialSwapFee(uint24 self) internal pure returns (uint24 swapFee) { - // the initial fee for a dynamic fee pool is 0 - if (self.isDynamicSwapFee()) return 0; - swapFee = self & STATIC_FEE_MASK; - } -} diff --git a/src/pool-cl/libraries/UnsafeMath.sol b/src/libraries/math/UnsafeMath.sol similarity index 100% rename from src/pool-cl/libraries/UnsafeMath.sol rename to src/libraries/math/UnsafeMath.sol diff --git a/src/pool-bin/BinPoolManager.sol b/src/pool-bin/BinPoolManager.sol index 4f673523..90027597 100644 --- a/src/pool-bin/BinPoolManager.sol +++ b/src/pool-bin/BinPoolManager.sol @@ -2,7 +2,7 @@ // Copyright (C) 2024 PancakeSwap pragma solidity ^0.8.24; -import {Fees} from "../Fees.sol"; +import {ProtocolFees} from "../ProtocolFees.sol"; import {Hooks} from "../libraries/Hooks.sol"; import {BinPool} from "./libraries/BinPool.sol"; import {BinPoolParametersHelper} from "./libraries/BinPoolParametersHelper.sol"; @@ -15,18 +15,18 @@ import {PoolKey} from "../types/PoolKey.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../types/BalanceDelta.sol"; import {IVault} from "../interfaces/IVault.sol"; import {BinPosition} from "./libraries/BinPosition.sol"; -import {SwapFeeLibrary} from "../libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; import {PackedUint128Math} from "./libraries/math/PackedUint128Math.sol"; import {Extsload} from "../Extsload.sol"; import "./interfaces/IBinHooks.sol"; /// @notice Holds the state for all bin pools -contract BinPoolManager is IBinPoolManager, Fees, Extsload { +contract BinPoolManager is IBinPoolManager, ProtocolFees, Extsload { using PoolIdLibrary for PoolKey; using BinPool for *; using BinPosition for mapping(bytes32 => BinPosition.Info); using BinPoolParametersHelper for bytes32; - using SwapFeeLibrary for uint24; + using LPFeeLibrary for uint24; using PackedUint128Math for bytes32; using Hooks for bytes32; @@ -38,7 +38,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { mapping(PoolId id => BinPool.State) public pools; - constructor(IVault vault, uint256 controllerGasLimit) Fees(vault, controllerGasLimit) {} + constructor(IVault vault, uint256 controllerGasLimit) ProtocolFees(vault, controllerGasLimit) {} /// @notice pool manager specified in the pool key must match current contract modifier poolManagerMatch(address poolManager) { @@ -51,10 +51,10 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { } /// @inheritdoc IBinPoolManager - function getSlot0(PoolId id) external view override returns (uint24 activeId, uint16 protocolFee, uint24 swapFee) { + function getSlot0(PoolId id) external view override returns (uint24 activeId, uint24 protocolFee, uint24 lpFee) { BinPool.Slot0 memory slot0 = pools[id].slot0; - return (slot0.activeId, slot0.protocolFee, slot0.swapFee); + return (slot0.activeId, slot0.protocolFee, slot0.lpFee); } /// @inheritdoc IBinPoolManager @@ -102,9 +102,9 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { Hooks.validateHookConfig(key); _validateHookNoOp(key); - /// @notice init value for dynamic swap fee is 0, but hook can still set it in afterInitialize - uint24 swapFee = key.fee.getInitialSwapFee(); - if (swapFee.isSwapFeeTooLarge(SwapFeeLibrary.TEN_PERCENT_FEE)) revert FeeTooLarge(); + /// @notice init value for dynamic lp fee is 0, but hook can still set it in afterInitialize + uint24 lpFee = key.fee.getInitialLPFee(); + lpFee.validate(LPFeeLibrary.TEN_PERCENT_FEE); if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET, hooks)) { if (hooks.beforeInitialize(msg.sender, key, activeId, hookData) != IBinHooks.beforeInitialize.selector) { @@ -114,8 +114,8 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { PoolId id = key.toId(); - (, uint16 protocolFee) = _fetchProtocolFee(key); - pools[id].initialize(activeId, protocolFee, swapFee); + (, uint24 protocolFee) = _fetchProtocolFee(key); + pools[id].initialize(activeId, protocolFee, lpFee); /// @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); @@ -151,23 +151,23 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @dev fix stack too deep { - bytes32 feeForProtocol; - uint24 activeId; - uint24 swapFee; - (delta, feeForProtocol, activeId, swapFee) = + BinPool.SwapState memory state; + (delta, state) = pools[id].swap(BinPool.SwapParams({swapForY: swapForY, binStep: key.parameters.getBinStep()}), amountIn); vault.accountPoolBalanceDelta(key, delta, msg.sender); unchecked { - if (feeForProtocol > 0) { - protocolFeesAccrued[key.currency0] += feeForProtocol.decodeX(); - protocolFeesAccrued[key.currency1] += feeForProtocol.decodeY(); + if (state.feeForProtocol > 0) { + protocolFeesAccrued[key.currency0] += state.feeForProtocol.decodeX(); + protocolFeesAccrued[key.currency1] += state.feeForProtocol.decodeY(); } } /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one - emit Swap(id, msg.sender, delta.amount0(), delta.amount1(), activeId, swapFee, feeForProtocol); + emit Swap( + id, msg.sender, delta.amount0(), delta.amount1(), state.activeId, state.swapFee, state.protocolFee + ); } if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET, hooks)) { @@ -187,20 +187,19 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { PoolId id = key.toId(); _checkPoolInitialized(id); - uint24 totalSwapFee; - if (key.fee.isDynamicSwapFee()) { - totalSwapFee = IBinDynamicFeeManager(address(key.hooks)).getFeeForSwapInSwapOut( + uint24 lpFee; + if (key.fee.isDynamicLPFee()) { + lpFee = IBinDynamicFeeManager(address(key.hooks)).getFeeForSwapInSwapOut( msg.sender, key, swapForY, 0, amountOut ); - if (totalSwapFee > SwapFeeLibrary.TEN_PERCENT_FEE) revert FeeTooLarge(); } else { // clear the top 4 bits since they may be flagged - totalSwapFee = key.fee.getInitialSwapFee(); + lpFee = key.fee.getInitialLPFee(); } + lpFee.validate(LPFeeLibrary.TEN_PERCENT_FEE); (amountIn, amountOutLeft, fee) = pools[id].getSwapIn( - BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), fee: totalSwapFee}), - amountOut + BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), lpFee: lpFee}), amountOut ); } @@ -214,18 +213,17 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { PoolId id = key.toId(); _checkPoolInitialized(id); - uint24 totalSwapFee; - if (key.fee.isDynamicSwapFee()) { - totalSwapFee = + uint24 lpFee; + if (key.fee.isDynamicLPFee()) { + lpFee = IBinDynamicFeeManager(address(key.hooks)).getFeeForSwapInSwapOut(msg.sender, key, swapForY, amountIn, 0); - if (totalSwapFee > SwapFeeLibrary.TEN_PERCENT_FEE) revert FeeTooLarge(); } else { - totalSwapFee = key.fee.getInitialSwapFee(); + lpFee = key.fee.getInitialLPFee(); } + lpFee.validate(LPFeeLibrary.TEN_PERCENT_FEE); (amountInLeft, amountOut, fee) = pools[id].getSwapOut( - BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), fee: totalSwapFee}), - amountIn + BinPool.SwapViewParams({swapForY: swapForY, binStep: key.parameters.getBinStep(), lpFee: lpFee}), amountIn ); } @@ -354,14 +352,6 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { } } - function setProtocolFee(PoolKey memory key) external override { - (bool success, uint16 newProtocolFee) = _fetchProtocolFee(key); - if (!success) revert ProtocolFeeControllerCallFailedOrInvalidResult(); - PoolId id = key.toId(); - pools[id].setProtocolFee(newProtocolFee); - emit ProtocolFeeUpdated(id, newProtocolFee); - } - /// @inheritdoc IBinPoolManager function setMaxBinStep(uint16 maxBinStep) external override onlyOwner { if (maxBinStep <= MIN_BIN_STEP) revert MaxBinStepTooSmall(maxBinStep); @@ -371,13 +361,17 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { } /// @inheritdoc IPoolManager - function updateDynamicSwapFee(PoolKey memory key, uint24 newDynamicSwapFee) external override { - if (!key.fee.isDynamicSwapFee() || msg.sender != address(key.hooks)) revert UnauthorizedDynamicSwapFeeUpdate(); - if (newDynamicSwapFee.isSwapFeeTooLarge(SwapFeeLibrary.TEN_PERCENT_FEE)) revert FeeTooLarge(); + function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external override { + if (!key.fee.isDynamicLPFee() || msg.sender != address(key.hooks)) revert UnauthorizedDynamicLPFeeUpdate(); + newDynamicLPFee.validate(LPFeeLibrary.TEN_PERCENT_FEE); PoolId id = key.toId(); - pools[id].setSwapFee(newDynamicSwapFee); - emit DynamicSwapFeeUpdated(id, newDynamicSwapFee); + pools[id].setLPFee(newDynamicLPFee); + emit DynamicLPFeeUpdated(id, newDynamicLPFee); + } + + function _setProtocolFee(PoolId id, uint24 newProtocolFee) internal override { + pools[id].setProtocolFee(newProtocolFee); } function _checkPoolInitialized(PoolId id) internal view { diff --git a/src/pool-bin/interfaces/IBinPoolManager.sol b/src/pool-bin/interfaces/IBinPoolManager.sol index f9a8d713..457d0fb8 100644 --- a/src/pool-bin/interfaces/IBinPoolManager.sol +++ b/src/pool-bin/interfaces/IBinPoolManager.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.24; import {Currency} from "../../types/Currency.sol"; -import {IFees} from "../../interfaces/IFees.sol"; +import {IProtocolFees} from "../../interfaces/IProtocolFees.sol"; import {PoolId} from "../../types/PoolId.sol"; import {PoolKey} from "../../types/PoolKey.sol"; import {BalanceDelta} from "../../types/BalanceDelta.sol"; @@ -11,7 +11,7 @@ import {IExtsload} from "../../interfaces/IExtsload.sol"; import {IBinHooks} from "./IBinHooks.sol"; import {BinPosition, BinPool} from "../libraries/BinPool.sol"; -interface IBinPoolManager is IFees, IPoolManager, IExtsload { +interface IBinPoolManager is IProtocolFees, IPoolManager, IExtsload { /// @notice PoolManagerMismatch is thrown when pool manager specified in the pool key does not match current contract error PoolManagerMismatch(); @@ -36,7 +36,7 @@ interface IBinPoolManager is IFees, IPoolManager, IExtsload { /// @param id The abi encoded hash of the pool key struct for the new pool /// @param currency0 The first currency of the pool by address sort order /// @param currency1 The second currency of the pool by address sort order - /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param fee The lp fee collected upon every swap in the pool, denominated in hundredths of a bip /// @param binStep The bin step in basis point, used to calculate log(1 + binStep / 10_000) /// @param hooks The hooks contract address for the pool, or address(0) if none event Initialize( @@ -54,8 +54,8 @@ interface IBinPoolManager is IFees, IPoolManager, IExtsload { /// @param amount0 The delta of the currency0 balance of the pool /// @param amount1 The delta of the currency1 balance of the pool /// @param activeId The activeId of the pool after the swap - /// @param fee Total swap fee - 10_000 = 1% - /// @param pFee Protocol fee from the swap: token0 and token1 amount + /// @param fee The fee collected upon every swap in the pool (including protocol fee and LP fee), denominated in hundredths of a bip + /// @param protocolFee Protocol fee from the swap, also denominated in hundredths of a bip event Swap( PoolId indexed id, address indexed sender, @@ -63,7 +63,7 @@ interface IBinPoolManager is IFees, IPoolManager, IExtsload { int128 amount1, uint24 activeId, uint24 fee, - bytes32 pFee + uint24 protocolFee ); /// @notice Emitted when liquidity is added @@ -114,7 +114,7 @@ interface IBinPoolManager is IFees, IPoolManager, IExtsload { } /// @notice Get the current value in slot0 of the given pool - function getSlot0(PoolId id) external view returns (uint24 activeId, uint16 protocolFee, uint24 swapFee); + function getSlot0(PoolId id) external view returns (uint24 activeId, uint24 protocolFee, uint24 lpFee); /// @notice Returns the reserves of a bin /// @param id The id of the bin diff --git a/src/pool-bin/libraries/BinHelper.sol b/src/pool-bin/libraries/BinHelper.sol index fc202f15..37b6e4e0 100644 --- a/src/pool-bin/libraries/BinHelper.sol +++ b/src/pool-bin/libraries/BinHelper.sol @@ -11,6 +11,7 @@ import {Constants} from "./Constants.sol"; import {BinPoolParametersHelper} from "./BinPoolParametersHelper.sol"; import {FeeHelper} from "./FeeHelper.sol"; import {PriceHelper} from "./PriceHelper.sol"; +import {ProtocolFeeLibrary} from "../../libraries/ProtocolFeeLibrary.sol"; /// @notice This library contains functions to help interaction with bins. library BinHelper { @@ -21,6 +22,7 @@ library BinHelper { using SafeCast for uint256; using BinPoolParametersHelper for bytes32; using FeeHelper for uint128; + using ProtocolFeeLibrary for uint24; error BinHelper__CompositionFactorFlawed(uint24 id); error BinHelper__LiquidityOverflow(); @@ -148,31 +150,42 @@ library BinHelper { /// @dev Returns the composition fees when adding liquidity to the active bin with a different /// composition factor than the bin's one, as it does an implicit swap /// @param binReserves The reserves of the bin - /// @param fee 100 = 0.01%, 1000 = 0.1% + /// @param protocolFee 100 = 0.01%, 1000 = 0.1% + /// @param lpFee 100 = 0.01%, 1000 = 0.1% /// @param amountsIn The amounts of tokens to add /// @param totalSupply The total supply of the liquidity book /// @param shares The share of the liquidity book that the user will receive - /// @return fees The encoded fees that will be charged + /// @return fees The encoded fees that will be charged (including protocol and LP fee) + //// @return feeForProtocol The encoded protocol fee that will be charged function getCompositionFees( bytes32 binReserves, - uint24 fee, // fee: 100 = 0.01% + uint24 protocolFee, // fee: 100 = 0.01% + uint24 lpFee, bytes32 amountsIn, uint256 totalSupply, uint256 shares - ) internal pure returns (bytes32 fees) { - if (shares == 0) return 0; + ) internal pure returns (bytes32 fees, bytes32 feeForProtocol) { + if (shares == 0) return (0, 0); (uint128 amountX, uint128 amountY) = amountsIn.decode(); (uint128 receivedAmountX, uint128 receivedAmountY) = getAmountOutOfBin(binReserves.add(amountsIn), shares, totalSupply + shares).decode(); - // if received more X than given X + // if received more X than given X, then swap some Y for X if (receivedAmountX > amountX) { - uint128 feeY = (amountY - receivedAmountY).getCompositionFee(fee); - fees = feeY.encodeSecond(); + protocolFee = protocolFee.getOneForZeroFee(); + uint24 swapFee = uint24(protocolFee).calculateSwapFee(lpFee); + + uint128 amtSwapped = amountY - receivedAmountY; + fees = amtSwapped.getCompositionFee(swapFee).encodeSecond(); + feeForProtocol = amtSwapped.getCompositionFee(protocolFee).encodeSecond(); } else if (receivedAmountY > amountY) { - uint128 feeX = (amountX - receivedAmountX).getCompositionFee(fee); - fees = feeX.encodeFirst(); + protocolFee = protocolFee.getZeroForOneFee(); + uint24 swapFee = uint24(protocolFee).calculateSwapFee(lpFee); + + uint128 amtSwapped = amountX - receivedAmountX; + fees = amtSwapped.getCompositionFee(swapFee).encodeFirst(); + feeForProtocol = amtSwapped.getCompositionFee(protocolFee).encodeFirst(); } } diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index 9e4c9618..b6c36d31 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -13,6 +13,7 @@ import {BinPosition} from "./BinPosition.sol"; import {SafeCast} from "./math/SafeCast.sol"; import {Constants} from "./Constants.sol"; import {FeeHelper} from "./FeeHelper.sol"; +import {ProtocolFeeLibrary} from "../../libraries/ProtocolFeeLibrary.sol"; library BinPool { using BinHelper for bytes32; @@ -28,6 +29,7 @@ library BinPool { using SafeCast for uint128; using FeeHelper for uint128; using BinPool for State; + using ProtocolFeeLibrary for uint24; error PoolNotInitialized(); error PoolAlreadyInitialized(); @@ -44,21 +46,13 @@ library BinPool { struct Slot0 { // the current activeId uint24 activeId; - /// @dev Fee is involved when: - // 1. During mint() incur composition fee when amountIn to active bin doesn't match the composition of asset in bin - // 2. During swap() - // protocol swap fee represented as integer denominator (1/x), taken as a % of the LP swap fee - // upper 8 bits are for 1->0, and the lower 8 are for 0->1 - // the minimum permitted denominator is 4 - meaning the maximum protocol fee is 25% - // granularity is increments of 0.38% (100/type(uint8).max) - /// bits 16 14 12 10 8 6 4 2 0 - /// | swap | - /// ┌───────────┬───────────┬ - /// protocolFee : | 1->0 | 0 -> 1 | - /// └───────────┴───────────┴ - uint16 protocolFee; - // used for the swap fee, either static at initialize or dynamic via hook - uint24 swapFee; + // protocol fee, expressed in hundredths of a bip + // upper 12 bits are for 1->0, and the lower 12 are for 0->1 + // the maximum is 1000 - meaning the maximum protocol fee is 0.1% + // the protocolFee is taken from the input first, then the lpFee is taken from the remaining input + uint24 protocolFee; + // lp fee, either static at initialize or dynamic via hook + uint24 lpFee; } /// @dev The state of a pool @@ -78,29 +72,29 @@ library BinPool { mapping(bytes32 => bytes32) level2; } - function initialize(State storage self, uint24 activeId, uint16 protocolFee, uint24 swapFee) internal { + function initialize(State storage self, uint24 activeId, uint24 protocolFee, uint24 lpFee) internal { /// An initialized pool will not have activeId: 0 if (self.slot0.activeId != 0) revert PoolAlreadyInitialized(); - self.slot0 = Slot0({activeId: activeId, protocolFee: protocolFee, swapFee: swapFee}); + self.slot0 = Slot0({activeId: activeId, protocolFee: protocolFee, lpFee: lpFee}); } - function setProtocolFee(State storage self, uint16 protocolFee) internal { + function setProtocolFee(State storage self, uint24 protocolFee) internal { if (self.isNotInitialized()) revert PoolNotInitialized(); self.slot0.protocolFee = protocolFee; } /// @notice Only dynamic fee pools may update the swap fee. - function setSwapFee(State storage self, uint24 swapFee) internal { + function setLPFee(State storage self, uint24 lpFee) internal { if (self.isNotInitialized()) revert PoolNotInitialized(); - self.slot0.swapFee = swapFee; + self.slot0.lpFee = lpFee; } struct SwapViewParams { bool swapForY; uint16 binStep; - uint24 fee; + uint24 lpFee; } function getSwapIn(State storage self, SwapViewParams memory params, uint128 amountOut) @@ -108,10 +102,15 @@ library BinPool { view returns (uint128 amountIn, uint128 amountOutLeft, uint128 fee) { + Slot0 memory slot0Cache = self.slot0; + uint24 id = slot0Cache.activeId; bool swapForY = params.swapForY; - uint24 id = self.slot0.activeId; amountOutLeft = amountOut; + uint24 protocolFee = + swapForY ? slot0Cache.protocolFee.getOneForZeroFee() : slot0Cache.protocolFee.getZeroForOneFee(); + uint24 swapFee = protocolFee.calculateSwapFee(params.lpFee); + while (true) { uint128 binReserves = self.reserveOfBin[id].decode(!swapForY); if (binReserves > 0) { @@ -125,7 +124,7 @@ library BinPool { : uint256(amountOutOfBin).mulShiftRoundUp(price, Constants.SCALE_OFFSET) ); - uint128 feeAmount = amountInWithoutFee.getFeeAmount(params.fee); + uint128 feeAmount = amountInWithoutFee.getFeeAmount(swapFee); amountIn += amountInWithoutFee + feeAmount; amountOutLeft -= amountOutOfBin; @@ -148,15 +147,23 @@ library BinPool { view returns (uint128 amountInLeft, uint128 amountOut, uint128 fee) { + Slot0 memory slot0Cache = self.slot0; + uint24 id = slot0Cache.activeId; bool swapForY = params.swapForY; - uint24 id = self.slot0.activeId; bytes32 amountsInLeft = amountIn.encode(swapForY); + uint24 swapFee; + { + uint24 protocolFee = + swapForY ? slot0Cache.protocolFee.getOneForZeroFee() : slot0Cache.protocolFee.getZeroForOneFee(); + swapFee = protocolFee.calculateSwapFee(params.lpFee); + } + while (true) { bytes32 binReserves = self.reserveOfBin[id]; if (!binReserves.isEmpty(!swapForY)) { (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) = - binReserves.getAmounts(params.fee, params.binStep, swapForY, id, amountsInLeft); + binReserves.getAmounts(swapFee, params.binStep, swapForY, id, amountsInLeft); if (amountsInWithFees > 0) { amountsInLeft = amountsInLeft.sub(amountsInWithFees); @@ -184,51 +191,64 @@ library BinPool { uint16 binStep; } + struct SwapState { + uint24 activeId; + uint24 protocolFee; + uint24 swapFee; + bytes32 feeForProtocol; + } + function swap(State storage self, SwapParams memory params, uint128 amountIn) internal - returns (BalanceDelta result, bytes32 feeForProtocol, uint24 activeId, uint24 swapFee) + returns (BalanceDelta result, SwapState memory swapState) { if (amountIn == 0) revert BinPool__InsufficientAmountIn(); - activeId = self.slot0.activeId; + Slot0 memory slot0Cache = self.slot0; + swapState.activeId = slot0Cache.activeId; bool swapForY = params.swapForY; + swapState.protocolFee = + swapForY ? slot0Cache.protocolFee.getOneForZeroFee() : slot0Cache.protocolFee.getZeroForOneFee(); bytes32 amountsLeft = swapForY ? amountIn.encodeFirst() : amountIn.encodeSecond(); bytes32 amountsOut; - swapFee = self.slot0.swapFee; + + /// @dev swap fee includes protocolFee (charged first) and lpFee + swapState.swapFee = swapState.protocolFee.calculateSwapFee(slot0Cache.lpFee); while (true) { - bytes32 binReserves = self.reserveOfBin[activeId]; + bytes32 binReserves = self.reserveOfBin[swapState.activeId]; if (!binReserves.isEmpty(!swapForY)) { - (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) = - binReserves.getAmounts(swapFee, params.binStep, swapForY, activeId, amountsLeft); + (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFee) = + binReserves.getAmounts(swapState.swapFee, params.binStep, swapForY, swapState.activeId, amountsLeft); if (amountsInWithFees > 0) { amountsLeft = amountsLeft.sub(amountsInWithFees); amountsOut = amountsOut.add(amountsOutOfBin); - bytes32 pFee = totalFees.getExternalFeeAmt(self.slot0.protocolFee); + /// @dev calc protocol fee for current bin, totalFee * protocolFee / (protocolFee + lpFee) + bytes32 pFee = totalFee.getExternalFeeAmt(slot0Cache.protocolFee, swapState.swapFee); if (pFee != 0) { - feeForProtocol = feeForProtocol.add(pFee); + swapState.feeForProtocol = swapState.feeForProtocol.add(pFee); amountsInWithFees = amountsInWithFees.sub(pFee); } - self.reserveOfBin[activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); + self.reserveOfBin[swapState.activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); } } if (amountsLeft == 0) { break; } else { - uint24 nextId = getNextNonEmptyBin(self, swapForY, activeId); + uint24 nextId = getNextNonEmptyBin(self, swapForY, swapState.activeId); if (nextId == 0 || nextId == type(uint24).max) revert BinPool__OutOfLiquidity(); - activeId = nextId; + swapState.activeId = nextId; } } if (amountsOut == 0) revert BinPool__InsufficientAmountOut(); - self.slot0.activeId = activeId; + self.slot0.activeId = swapState.activeId; if (swapForY) { uint128 consumed = amountIn - amountsLeft.decodeX(); @@ -416,7 +436,8 @@ library BinPool { bytes32 compositionFee ) { - uint24 activeId = self.slot0.activeId; + Slot0 memory slot0Cache = self.slot0; + uint24 activeId = slot0Cache.activeId; bytes32 binReserves = self.reserveOfBin[id]; uint256 price = id.getPriceFromId(params.binStep); @@ -428,7 +449,9 @@ library BinPool { if (id == activeId) { // Fees happens when user try to add liquidity in active bin but with different ratio of (x, y) /// eg. current bin is 40/60 (a,b) but user tries to add liquidity with 50/50 ratio - bytes32 fees = binReserves.getCompositionFees(self.slot0.swapFee, amountsIn, supply, shares); + bytes32 fees; + (fees, feeForProtocol) = + binReserves.getCompositionFees(slot0Cache.protocolFee, slot0Cache.lpFee, amountsIn, supply, shares); compositionFee = fees; if (fees != 0) { { @@ -437,11 +460,8 @@ library BinPool { shares = userLiquidity.mulDivRoundDown(supply, binLiquidity); } - { - feeForProtocol = fees.getExternalFeeAmt(self.slot0.protocolFee); - if (feeForProtocol != 0) { - amountsInToBin = amountsInToBin.sub(feeForProtocol); - } + if (feeForProtocol != 0) { + amountsInToBin = amountsInToBin.sub(feeForProtocol); } } } else { diff --git a/src/pool-bin/libraries/math/PackedUint128Math.sol b/src/pool-bin/libraries/math/PackedUint128Math.sol index 421c6d58..fd90fd4a 100644 --- a/src/pool-bin/libraries/math/PackedUint128Math.sol +++ b/src/pool-bin/libraries/math/PackedUint128Math.sol @@ -3,10 +3,13 @@ pragma solidity ^0.8.24; import {Constants} from "../Constants.sol"; +import {ProtocolFeeLibrary} from "../../../libraries/ProtocolFeeLibrary.sol"; /// @notice This library contains functions to encode and decode two uint128 into a single bytes32 /// and interact with the encoded bytes32. library PackedUint128Math { + using ProtocolFeeLibrary for uint24; + error PackedUint128Math__AddOverflow(); error PackedUint128Math__SubUnderflow(); @@ -209,17 +212,22 @@ library PackedUint128Math { /// @dev given amount and protocolFee, calculate and return external protocol fee amt /// @param amount encoded bytes with (x, y) - /// @param fee protocolFee - function getExternalFeeAmt(bytes32 amount, uint16 fee) internal pure returns (bytes32 z) { - if (fee == 0) return 0; + /// @param protocolFee Protocol fee from the swap, also denominated in hundredths of a bip + /// @param swapFee The fee collected upon every swap in the pool (including protocol fee and LP fee), denominated in hundredths of a bip + function getExternalFeeAmt(bytes32 amount, uint24 protocolFee, uint24 swapFee) internal pure returns (bytes32 z) { + if (protocolFee == 0 || swapFee == 0) return 0; (uint128 amountX, uint128 amountY) = decode(amount); - - uint16 fee0 = fee % 256; - uint16 fee1 = fee >> 8; - - uint128 feeForX = fee0 == 0 ? 0 : amountX / fee0; - uint128 feeForY = fee1 == 0 ? 0 : amountY / fee1; + uint16 fee0 = protocolFee.getZeroForOneFee(); + uint16 fee1 = protocolFee.getOneForZeroFee(); + + uint128 feeForX; + uint128 feeForY; + // todo: double check on this unchecked condition + unchecked { + feeForX = fee0 == 0 ? 0 : uint128(uint256(amountX) * fee0 / swapFee); + feeForY = fee1 == 0 ? 0 : uint128(uint256(amountY) * fee1 / swapFee); + } return encode(feeForX, feeForY); } diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index 5eb7cf36..54b0b572 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.24; import "./interfaces/ICLHooks.sol"; -import {Fees} from "../Fees.sol"; +import {ProtocolFees} from "../ProtocolFees.sol"; import {ICLPoolManager} from "./interfaces/ICLPoolManager.sol"; import {IVault} from "../interfaces/IVault.sol"; import {PoolId, PoolIdLibrary} from "../types/PoolId.sol"; @@ -14,18 +14,18 @@ import {IPoolManager} from "../interfaces/IPoolManager.sol"; import {Hooks} from "../libraries/Hooks.sol"; import {Tick} from "./libraries/Tick.sol"; import {CLPoolParametersHelper} from "./libraries/CLPoolParametersHelper.sol"; -import {SwapFeeLibrary} from "../libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; 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 { +contract CLPoolManager is ICLPoolManager, ProtocolFees, Extsload { using SafeCast for int256; using PoolIdLibrary for PoolKey; using Hooks for bytes32; - using SwapFeeLibrary for uint24; + using LPFeeLibrary for uint24; using CLPoolParametersHelper for bytes32; using CLPool for *; using CLPosition for mapping(bytes32 => CLPosition.Info); @@ -39,7 +39,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { mapping(PoolId id => CLPool.State) public pools; - constructor(IVault _vault, uint256 controllerGasLimit) Fees(_vault, controllerGasLimit) {} + constructor(IVault _vault, uint256 controllerGasLimit) ProtocolFees(_vault, controllerGasLimit) {} /// @notice pool manager specified in the pool key must match current contract modifier poolManagerMatch(address poolManager) { @@ -52,10 +52,10 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { external view override - returns (uint160 sqrtPriceX96, int24 tick, uint16 protocolFee, uint24 swapFee) + returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee) { CLPool.Slot0 memory slot0 = pools[id].slot0; - return (slot0.sqrtPriceX96, slot0.tick, slot0.protocolFee, slot0.swapFee); + return (slot0.sqrtPriceX96, slot0.tick, slot0.protocolFee, slot0.lpFee); } /// @inheritdoc ICLPoolManager @@ -99,9 +99,9 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { Hooks.validateHookConfig(key); _validateHookNoOp(key); - /// @notice init value for dynamic swap fee is 0, but hook can still set it in afterInitialize - uint24 swapFee = key.fee.getInitialSwapFee(); - if (swapFee.isSwapFeeTooLarge(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE)) revert FeeTooLarge(); + /// @notice init value for dynamic lp fee is 0, but hook can still set it in afterInitialize + uint24 lpFee = key.fee.getInitialLPFee(); + lpFee.validate(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE); if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET, hooks)) { if (hooks.beforeInitialize(msg.sender, key, sqrtPriceX96, hookData) != ICLHooks.beforeInitialize.selector) { @@ -110,8 +110,8 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { } PoolId id = key.toId(); - (, uint16 protocolFee) = _fetchProtocolFee(key); - tick = pools[id].initialize(sqrtPriceX96, protocolFee, swapFee); + (, uint24 protocolFee) = _fetchProtocolFee(key); + tick = pools[id].initialize(sqrtPriceX96, protocolFee, lpFee); /// @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); @@ -234,8 +234,8 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { vault.accountPoolBalanceDelta(key, delta, msg.sender); unchecked { - if (state.protocolFee > 0) { - protocolFeesAccrued[params.zeroForOne ? key.currency0 : key.currency1] += state.protocolFee; + if (state.feeForProtocol > 0) { + protocolFeesAccrued[params.zeroForOne ? key.currency0 : key.currency1] += state.feeForProtocol; } } @@ -312,22 +312,17 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { } /// @inheritdoc IPoolManager - function setProtocolFee(PoolKey memory key) external { - (bool success, uint16 newProtocolFee) = _fetchProtocolFee(key); - if (!success) revert ProtocolFeeControllerCallFailedOrInvalidResult(); + function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external override { + if (!key.fee.isDynamicLPFee() || msg.sender != address(key.hooks)) revert UnauthorizedDynamicLPFeeUpdate(); + newDynamicLPFee.validate(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE); + PoolId id = key.toId(); - pools[id].setProtocolFee(newProtocolFee); - emit ProtocolFeeUpdated(id, newProtocolFee); + pools[id].setLPFee(newDynamicLPFee); + emit DynamicLPFeeUpdated(id, newDynamicLPFee); } - /// @inheritdoc IPoolManager - function updateDynamicSwapFee(PoolKey memory key, uint24 newDynamicSwapFee) external override { - if (!key.fee.isDynamicSwapFee() || msg.sender != address(key.hooks)) revert UnauthorizedDynamicSwapFeeUpdate(); - if (newDynamicSwapFee.isSwapFeeTooLarge(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE)) revert FeeTooLarge(); - - PoolId id = key.toId(); - pools[id].setSwapFee(newDynamicSwapFee); - emit DynamicSwapFeeUpdated(id, newDynamicSwapFee); + function _setProtocolFee(PoolId id, uint24 newProtocolFee) internal override { + pools[id].setProtocolFee(newProtocolFee); } function _checkPoolInitialized(PoolId id) internal view { diff --git a/src/pool-cl/interfaces/ICLPoolManager.sol b/src/pool-cl/interfaces/ICLPoolManager.sol index 2ab1dc1a..ff92ac4f 100644 --- a/src/pool-cl/interfaces/ICLPoolManager.sol +++ b/src/pool-cl/interfaces/ICLPoolManager.sol @@ -5,14 +5,14 @@ import {Currency} from "../../types/Currency.sol"; import {PoolKey} from "../../types/PoolKey.sol"; import {CLPool} from "../libraries/CLPool.sol"; import {ICLHooks} from "./ICLHooks.sol"; -import {IFees} from "../../interfaces/IFees.sol"; +import {IProtocolFees} from "../../interfaces/IProtocolFees.sol"; import {BalanceDelta} from "../../types/BalanceDelta.sol"; import {PoolId} from "../../types/PoolId.sol"; import {CLPosition} from "../libraries/CLPosition.sol"; import {IPoolManager} from "../../interfaces/IPoolManager.sol"; import {IExtsload} from "../../interfaces/IExtsload.sol"; -interface ICLPoolManager is IFees, IPoolManager, IExtsload { +interface ICLPoolManager is IProtocolFees, IPoolManager, IExtsload { /// @notice PoolManagerMismatch is thrown when pool manager specified in the pool key does not match current contract error PoolManagerMismatch(); /// @notice Pools are limited to type(int16).max tickSpacing in #initialize, to prevent overflow @@ -26,7 +26,7 @@ interface ICLPoolManager is IFees, IPoolManager, IExtsload { /// @param id The abi encoded hash of the pool key struct for the new pool /// @param currency0 The first currency of the pool by address sort order /// @param currency1 The second currency of the pool by address sort order - /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip + /// @param fee The lp fee collected upon every swap in the pool, denominated in hundredths of a bip /// @param tickSpacing The minimum number of ticks between initialized ticks /// @param hooks The hooks contract address for the pool, or address(0) if none event Initialize( @@ -56,8 +56,8 @@ interface ICLPoolManager is IFees, IPoolManager, IExtsload { /// @param sqrtPriceX96 The sqrt(price) of the pool after the swap, as a Q64.96 /// @param liquidity The liquidity of the pool after the swap /// @param tick The log base 1.0001 of the price of the pool after the swap - /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip - /// @param protocolFee Protocol fee from the swap, and it is only on the input currency + /// @param fee The fee collected upon every swap in the pool (including protocol fee and LP fee), denominated in hundredths of a bip + /// @param protocolFee Protocol fee from the swap, also denominated in hundredths of a bip event Swap( PoolId indexed id, address indexed sender, @@ -67,7 +67,7 @@ interface ICLPoolManager is IFees, IPoolManager, IExtsload { uint128 liquidity, int24 tick, uint24 fee, - uint256 protocolFee + uint24 protocolFee ); /// @notice Emitted when donate happen @@ -88,7 +88,7 @@ interface ICLPoolManager is IFees, IPoolManager, IExtsload { function getSlot0(PoolId id) external view - returns (uint160 sqrtPriceX96, int24 tick, uint16 protocolFee, uint24 swapFee); + returns (uint160 sqrtPriceX96, int24 tick, uint24 protocolFee, uint24 lpFee); /// @notice Get the current value of liquidity of the given pool function getLiquidity(PoolId id) external view returns (uint128 liquidity); diff --git a/src/pool-cl/libraries/CLPool.sol b/src/pool-cl/libraries/CLPool.sol index bfa0ac49..5f10defd 100644 --- a/src/pool-cl/libraries/CLPool.sol +++ b/src/pool-cl/libraries/CLPool.sol @@ -13,6 +13,8 @@ import {FixedPoint128} from "./FixedPoint128.sol"; import {FullMath} from "./FullMath.sol"; import {SwapMath} from "./SwapMath.sol"; import {LiquidityMath} from "./LiquidityMath.sol"; +import {ProtocolFeeLibrary} from "../../libraries/ProtocolFeeLibrary.sol"; +import {LPFeeLibrary} from "../../libraries/LPFeeLibrary.sol"; library CLPool { using SafeCast for int256; @@ -23,6 +25,7 @@ library CLPool { using CLPosition for CLPosition.Info; using LiquidityMath for uint128; using CLPool for State; + using ProtocolFeeLibrary for uint24; /// @notice Thrown when trying to initalize an already initialized pool error PoolAlreadyInitialized(); @@ -33,6 +36,9 @@ library CLPool { /// @notice Thrown when trying to swap amount of 0 error SwapAmountCannotBeZero(); + /// @notice Thrown when trying to swap with max lp fee and specifying an output amount + error InvalidFeeForExactOut(); + /// @notice Thrown when sqrtPriceLimitX96 is out of range /// @param sqrtPriceCurrentX96 current price in the pool /// @param sqrtPriceLimitX96 The price limit specified by user @@ -46,23 +52,18 @@ library CLPool { uint160 sqrtPriceX96; // the current tick int24 tick; - // protocol swap fee represented as integer denominator (1/x), taken as a % of the LP swap fee - // upper 8 bits are for 1->0, and the lower 8 are for 0->1 - // the minimum permitted denominator is 4 - meaning the maximum protocol fee is 25% - // granularity is increments of 0.38% (100/type(uint8).max) - /// bits 16 14 12 10 8 6 4 2 0 - /// | swap | - /// ┌───────────┬───────────┬ - /// protocolFee : | 1->0 | 0 -> 1 | - /// └───────────┴───────────┴ - uint16 protocolFee; - // used for the swap fee, either static at initialize or dynamic via hook - uint24 swapFee; + // protocol fee, expressed in hundredths of a bip + // upper 12 bits are for 1->0, and the lower 12 are for 0->1 + // the maximum is 1000 - meaning the maximum protocol fee is 0.1% + // the protocolFee is taken from the input first, then the lpFee is taken from the remaining input + uint24 protocolFee; + // used for the lp fee, either static at initialize or dynamic via hook + uint24 lpFee; } struct State { Slot0 slot0; - /// @dev swap fees + /// @dev accumulated lp fees uint256 feeGrowthGlobal0X128; uint256 feeGrowthGlobal1X128; /// @dev current active liquidity @@ -72,7 +73,7 @@ library CLPool { mapping(bytes32 => CLPosition.Info) positions; } - function initialize(State storage self, uint160 sqrtPriceX96, uint16 protocolFee, uint24 swapFee) + function initialize(State storage self, uint160 sqrtPriceX96, uint24 protocolFee, uint24 lpFee) internal returns (int24 tick) { @@ -80,7 +81,7 @@ library CLPool { tick = TickMath.getTickAtSqrtRatio(sqrtPriceX96); - self.slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick, protocolFee: protocolFee, swapFee: swapFee}); + self.slot0 = Slot0({sqrtPriceX96: sqrtPriceX96, tick: tick, protocolFee: protocolFee, lpFee: lpFee}); } struct ModifyLiquidityParams { @@ -147,13 +148,6 @@ library CLPool { feeDelta = toBalanceDelta(feesOwed0.toInt128(), feesOwed1.toInt128()); } - struct SwapCache { - // liquidity at the beginning of the swap - uint128 liquidityStart; - // the protocol fee for the input token - uint8 protocolFee; - } - // the top level state of the swap, the results of which are recorded in storage at the end struct SwapState { // the amount remaining to be swapped in/out of the input/output asset @@ -164,12 +158,14 @@ library CLPool { uint160 sqrtPriceX96; // the tick associated with the current price int24 tick; - // the swapFee + // the swapFee (the total percentage charged within a swap, including the protocol fee and the LP fee) uint24 swapFee; + // the protocol fee for the swap + uint24 protocolFee; // the global fee growth of the input token uint256 feeGrowthGlobalX128; // amount of input token paid as protocol fee - uint256 protocolFee; + uint256 feeForProtocol; // the current liquidity in range uint128 liquidity; } @@ -216,24 +212,32 @@ library CLPool { 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: zeroForOne ? uint8(slot0Start.protocolFee % 256) : uint8(slot0Start.protocolFee >> 8) - }); - + // liquidity at the beginning of the swap + uint128 liquidityStart = self.liquidity; bool exactInput = params.amountSpecified > 0; - state = SwapState({ - amountSpecifiedRemaining: params.amountSpecified, - amountCalculated: 0, - sqrtPriceX96: slot0Start.sqrtPriceX96, - tick: slot0Start.tick, - swapFee: slot0Start.swapFee, - feeGrowthGlobalX128: zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128, - protocolFee: 0, - liquidity: cache.liquidityStart - }); + { + uint16 protocolFee = + zeroForOne ? slot0Start.protocolFee.getZeroForOneFee() : slot0Start.protocolFee.getOneForZeroFee(); + + state = SwapState({ + amountSpecifiedRemaining: params.amountSpecified, + amountCalculated: 0, + sqrtPriceX96: slot0Start.sqrtPriceX96, + tick: slot0Start.tick, + swapFee: protocolFee == 0 ? slot0Start.lpFee : uint24(protocolFee).calculateSwapFee(slot0Start.lpFee), + protocolFee: protocolFee, + feeGrowthGlobalX128: zeroForOne ? self.feeGrowthGlobal0X128 : self.feeGrowthGlobal1X128, + feeForProtocol: 0, + liquidity: liquidityStart + }); + } + + /// @dev If amountSpecified is the output, also given amountSpecified cant be 0, + /// then the tx will always revert if the swap fee is 100% + if (!exactInput && (state.swapFee == LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE)) { + revert InvalidFeeForExactOut(); + } StepComputations memory step; // continue swapping as long as we haven't used the entire input/output and haven't reached the price limit @@ -280,11 +284,15 @@ library CLPool { } /// @dev if the protocol fee is on, calculate how much is owed, decrement feeAmount, and increment protocolFee - if (cache.protocolFee > 0) { - uint256 delta = step.feeAmount / cache.protocolFee; + if (state.protocolFee > 0) { unchecked { + // protocol fee is charged on input token first + uint256 delta = + (step.amountIn + step.feeAmount) * state.protocolFee / ProtocolFeeLibrary.PIPS_DENOMINATOR; + + // subtract it from the total fee then left over is the LP fee step.feeAmount -= delta; - state.protocolFee += delta; + state.feeForProtocol += delta; } } @@ -331,7 +339,7 @@ library CLPool { } // update liquidity if it changed - if (cache.liquidityStart != state.liquidity) self.liquidity = state.liquidity; + if (liquidityStart != state.liquidity) self.liquidity = state.liquidity; // update fee growth global if (zeroForOne) { @@ -444,17 +452,17 @@ library CLPool { } } - function setProtocolFee(State storage self, uint16 protocolFee) internal { + function setProtocolFee(State storage self, uint24 protocolFee) internal { if (self.isNotInitialized()) revert PoolNotInitialized(); self.slot0.protocolFee = protocolFee; } - /// @notice Only dynamic fee pools may update the swap fee. - function setSwapFee(State storage self, uint24 swapFee) internal { + /// @notice Only dynamic fee pools may update the lp fee. + function setLPFee(State storage self, uint24 lpFee) internal { if (self.isNotInitialized()) revert PoolNotInitialized(); - self.slot0.swapFee = swapFee; + self.slot0.lpFee = lpFee; } function isNotInitialized(State storage self) internal view returns (bool) { diff --git a/src/pool-cl/libraries/SqrtPriceMath.sol b/src/pool-cl/libraries/SqrtPriceMath.sol index a31bc315..41163549 100644 --- a/src/pool-cl/libraries/SqrtPriceMath.sol +++ b/src/pool-cl/libraries/SqrtPriceMath.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.24; import {SafeCast} from "../../libraries/SafeCast.sol"; import {FullMath} from "./FullMath.sol"; -import {UnsafeMath} from "./UnsafeMath.sol"; +import {UnsafeMath} from "../../libraries/math/UnsafeMath.sol"; import {FixedPoint96} from "./FixedPoint96.sol"; /// @title Functions based on Q64.96 sqrt price and liquidity diff --git a/src/pool-cl/libraries/SwapMath.sol b/src/pool-cl/libraries/SwapMath.sol index 4c4865db..9bd83ea4 100644 --- a/src/pool-cl/libraries/SwapMath.sol +++ b/src/pool-cl/libraries/SwapMath.sol @@ -83,7 +83,7 @@ library SwapMath { // we didn't reach the target, so take the remainder of the maximum input as fee feeAmount = uint256(amountRemaining) - amountIn; } else { - feeAmount = FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); + feeAmount = feePips == 1e6 ? amountIn : FullMath.mulDivRoundingUp(amountIn, feePips, 1e6 - feePips); } } } diff --git a/src/test/MockFeePoolManager.sol b/src/test/MockFeePoolManager.sol index 4756f405..cd36f924 100644 --- a/src/test/MockFeePoolManager.sol +++ b/src/test/MockFeePoolManager.sol @@ -5,14 +5,15 @@ import {IVault} from "../interfaces/IVault.sol"; import {PoolId, PoolIdLibrary} from "../types/PoolId.sol"; import {PoolKey} from "../types/PoolKey.sol"; import {BalanceDelta} from "../types/BalanceDelta.sol"; -import {Fees} from "../Fees.sol"; -import {SwapFeeLibrary} from "../libraries/SwapFeeLibrary.sol"; +import {ProtocolFees} from "../ProtocolFees.sol"; +import {LPFeeLibrary} from "../libraries/LPFeeLibrary.sol"; +import {ProtocolFeeLibrary} from "../libraries/ProtocolFeeLibrary.sol"; import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol"; /** * @dev A MockFeePoolManager meant to test Fees functionality */ -contract MockFeePoolManager is Fees { +contract MockFeePoolManager is ProtocolFees { using PoolIdLibrary for PoolKey; using FixedPointMathLib for uint256; @@ -20,15 +21,15 @@ contract MockFeePoolManager is Fees { mapping(PoolId id => Slot0) public pools; struct Slot0 { - uint16 protocolFee; + uint24 protocolFee; } - constructor(IVault vault, uint256 controllerGasLimit) Fees(vault, controllerGasLimit) {} + constructor(IVault vault, uint256 controllerGasLimit) ProtocolFees(vault, controllerGasLimit) {} function initialize(PoolKey memory key, bytes calldata) external { PoolId id = key.toId(); - (, uint16 protocolFee) = _fetchProtocolFee(key); + (, uint24 protocolFee) = _fetchProtocolFee(key); pools[id] = Slot0({protocolFee: protocolFee}); } @@ -59,24 +60,28 @@ contract MockFeePoolManager is Fees { Slot0 memory slot0 = pools[id]; // Similar to uni-v4 logic (deduct protocolFee portion first) - uint16 protocolFee = isSwap ? slot0.protocolFee : 0; + uint24 protocolFee = isSwap ? slot0.protocolFee : 0; if (protocolFee > 0) { - if ((protocolFee % 256) > 0) { - protocolFee0 = amt0Fee / (protocolFee % 256); + if ((protocolFee % 4096) > 0) { + protocolFee0 = amt0Fee * (protocolFee % 4096) / ProtocolFeeLibrary.PIPS_DENOMINATOR; amt0Fee -= protocolFee0; protocolFeesAccrued[key.currency0] += protocolFee0; } - if ((protocolFee >> 8) > 0) { - protocolFee1 = amt1Fee / (protocolFee >> 8); + if ((protocolFee >> 12) > 0) { + protocolFee1 = amt1Fee * (protocolFee >> 12) / ProtocolFeeLibrary.PIPS_DENOMINATOR; amt1Fee -= protocolFee1; protocolFeesAccrued[key.currency1] += protocolFee1; } } } - function getProtocolFee(PoolKey memory key) external view returns (uint16) { + function getProtocolFee(PoolKey memory key) external view returns (uint24) { return pools[key.toId()].protocolFee; } + + function _setProtocolFee(PoolId id, uint24 newProtocolFee) internal override { + pools[id].protocolFee = newProtocolFee; + } } diff --git a/src/test/fee/MockFeeManagerHook.sol b/src/test/fee/MockFeeManagerHook.sol index 0a6395cd..6127c8b8 100644 --- a/src/test/fee/MockFeeManagerHook.sol +++ b/src/test/fee/MockFeeManagerHook.sol @@ -43,7 +43,7 @@ contract MockFeeManagerHook is IHooks, IBinDynamicFeeManager { // swap fee for dynamic fee pool is 0 by default, so we need to update it after pool initialization function afterInitialize(address, PoolKey calldata key, uint24, bytes calldata) external returns (bytes4) { - IBinPoolManager(msg.sender).updateDynamicSwapFee(key, swapfee); + IBinPoolManager(msg.sender).updateDynamicLPFee(key, swapfee); return MockFeeManagerHook.afterInitialize.selector; } } diff --git a/src/test/fee/MockProtocolFeeController.sol b/src/test/fee/MockProtocolFeeController.sol index b034bf12..d6febdf1 100644 --- a/src/test/fee/MockProtocolFeeController.sol +++ b/src/test/fee/MockProtocolFeeController.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import {IProtocolFeeController} from "../../interfaces/IProtocolFeeController.sol"; import {PoolId, PoolIdLibrary} from "../../types/PoolId.sol"; import {PoolKey} from "../../types/PoolKey.sol"; +import {ProtocolFeeLibrary} from "../../libraries/ProtocolFeeLibrary.sol"; /** * @dev A MockProtocolFeeController meant to test Fees functionality @@ -11,14 +12,14 @@ import {PoolKey} from "../../types/PoolKey.sol"; contract MockProtocolFeeController is IProtocolFeeController { using PoolIdLibrary for PoolKey; - mapping(PoolId id => uint16 fee) public protocolFee; + mapping(PoolId id => uint24 fee) public protocolFee; - function setProtocolFeeForPool(PoolKey memory key, uint16 fee) public { + function setProtocolFeeForPool(PoolKey memory key, uint24 fee) public { PoolId id = key.toId(); protocolFee[id] = fee; } - function protocolFeeForPool(PoolKey memory key) external view returns (uint16) { + function protocolFeeForPool(PoolKey memory key) external view returns (uint24) { PoolId id = key.toId(); return protocolFee[id]; } @@ -26,25 +27,24 @@ contract MockProtocolFeeController is IProtocolFeeController { /// @notice Reverts on call contract RevertingMockProtocolFeeController is IProtocolFeeController { - function protocolFeeForPool(PoolKey memory /* key */ ) external pure returns (uint16) { + function protocolFeeForPool(PoolKey memory /* key */ ) external pure returns (uint24) { revert(); } } /// @notice Returns an out of bounds protocol fee contract OutOfBoundsMockProtocolFeeController is IProtocolFeeController { - function protocolFeeForPool(PoolKey memory /* key */ ) external pure returns (uint16) { - // set swap fee to 1, which is less than MIN_PROTOCOL_FEE_DENOMINATOR - return 0x0001; + function protocolFeeForPool(PoolKey memory /* key */ ) external pure returns (uint24) { + return ProtocolFeeLibrary.MAX_PROTOCOL_FEE + 1; } } -/// @notice Return a value that overflows a uint16 +/// @notice Return a value that overflows a uint24 contract OverflowMockProtocolFeeController is IProtocolFeeController { - function protocolFeeForPool(PoolKey memory /* key */ ) external pure returns (uint16) { + function protocolFeeForPool(PoolKey memory /* key */ ) external pure returns (uint24) { assembly { let ptr := mload(0x40) - mstore(ptr, 0xFFFFAAA001) + mstore(ptr, 0xFFFFFFFFAAA001) return(ptr, 0x20) } } @@ -52,7 +52,7 @@ contract OverflowMockProtocolFeeController is IProtocolFeeController { /// @notice Returns data that is larger than a word contract InvalidReturnSizeMockProtocolFeeController is IProtocolFeeController { - function protocolFeeForPool(PoolKey memory /* key */ ) external view returns (uint16) { + function protocolFeeForPool(PoolKey memory /* key */ ) external view returns (uint24) { address a = address(this); assembly { let ptr := mload(0x40) diff --git a/src/types/PoolKey.sol b/src/types/PoolKey.sol index 9eb8d942..818812a0 100644 --- a/src/types/PoolKey.sol +++ b/src/types/PoolKey.sol @@ -15,7 +15,7 @@ struct PoolKey { IHooks hooks; /// @notice The pool manager of the pool IPoolManager poolManager; - /// @notice The pool swap fee, capped at 1_000_000. The upper 4 bits determine if the hook sets any fees. + /// @notice The pool lp fee, capped at 1_000_000. The upper 4 bits determine if the hook sets any fees. uint24 fee; /// @notice Hooks callback and pool specific parameters, i.e. tickSpacing for CL, binStep for bin bytes32 parameters; diff --git a/test/Fees.t.sol b/test/ProtocolFees.t.sol similarity index 75% rename from test/Fees.t.sol rename to test/ProtocolFees.t.sol index 6538f29f..73043224 100644 --- a/test/Fees.t.sol +++ b/test/ProtocolFees.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import "solmate/test/utils/mocks/MockERC20.sol"; import "../src/test/MockFeePoolManager.sol"; import "../src/test/fee/MockFeeManagerHook.sol"; @@ -13,14 +13,14 @@ import { InvalidReturnSizeMockProtocolFeeController } from "../src/test/fee/MockProtocolFeeController.sol"; import "../src/test/MockVault.sol"; -import "../src/Fees.sol"; -import "../src/interfaces/IFees.sol"; +import "../src/ProtocolFees.sol"; +import "../src/interfaces/IProtocolFees.sol"; import "../src/interfaces/IVault.sol"; import "../src/interfaces/IPoolManager.sol"; import "../src/interfaces/IHooks.sol"; -import "../src/libraries/SwapFeeLibrary.sol"; +import "../src/libraries/LPFeeLibrary.sol"; -contract FeesTest is Test { +contract ProtocolFeesTest is Test { MockFeePoolManager poolManager; MockProtocolFeeController feeController; RevertingMockProtocolFeeController revertingFeeController; @@ -90,7 +90,7 @@ contract FeesTest is Test { }); poolManagerWithLowControllerGasLimit.setProtocolFeeController(feeController); - vm.expectRevert(IFees.ProtocolFeeCannotBeFetched.selector); + vm.expectRevert(IProtocolFees.ProtocolFeeCannotBeFetched.selector); poolManagerWithLowControllerGasLimit.initialize{gas: 2000_000}(_key, new bytes(0)); } @@ -125,7 +125,7 @@ contract FeesTest is Test { assertEq(poolManager.getProtocolFee(key), 0); } - function testInitFuzz(uint16 fee) public { + function testInitFuzz(uint24 fee) public { poolManager.setProtocolFeeController(feeController); vm.mockCall( @@ -137,13 +137,10 @@ contract FeesTest is Test { poolManager.initialize(key, new bytes(0)); if (fee != 0) { - uint16 fee0 = fee % 256; - uint16 fee1 = fee >> 8; + uint24 fee0 = fee % 4096; + uint24 fee1 = fee >> 12; - if ( - (fee0 != 0 && fee0 < poolManager.MIN_PROTOCOL_FEE_DENOMINATOR()) - || (fee1 != 0 && fee1 < poolManager.MIN_PROTOCOL_FEE_DENOMINATOR()) - ) { + if (fee0 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE || fee1 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) { // invalid fee, fallback to 0 assertEq(poolManager.getProtocolFee(key), 0); } else { @@ -152,67 +149,86 @@ contract FeesTest is Test { } } - function testSwap_OnlyProtocolFee() public { - // set protocolFee as 10% of fee - uint16 protocolFee = _buildSwapFee(10, 10); // 10% - feeController.setProtocolFeeForPool(key, protocolFee); + function testSetProtocolFee() public { + poolManager.initialize(key, new bytes(0)); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - poolManager.initialize(key, new bytes(0)); - (uint256 protocolFee0, uint256 protocolFee1) = poolManager.swap(key, 1e18, 1e18); - assertEq(protocolFee0, 1e17); - assertEq(protocolFee1, 1e17); + assertEq(poolManager.getProtocolFee(key), 0); + + { + uint24 protocolFee = _buildProtocolFee(100, 100); + vm.prank(address(feeController)); + poolManager.setProtocolFee(key, protocolFee); + assertEq(poolManager.getProtocolFee(key), protocolFee); + } + + { + vm.expectRevert(IProtocolFees.InvalidCaller.selector); + uint24 protocolFee = _buildProtocolFee(100, 100); + poolManager.setProtocolFee(key, protocolFee); + } + + { + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); + uint24 protocolFee = _buildProtocolFee(ProtocolFeeLibrary.MAX_PROTOCOL_FEE + 1, 100); + vm.prank(address(feeController)); + poolManager.setProtocolFee(key, protocolFee); + } } - function test_CheckProtocolFee_SwapFee() public { - uint16 protocolFee = _buildSwapFee(3, 3); // 25% is the limit, 3 = amt/3 = 33% + function testSwap_OnlyProtocolFee() public { + // set protocolFee as 0.1% of fee + uint24 protocolFee = _buildProtocolFee(ProtocolFeeLibrary.MAX_PROTOCOL_FEE, ProtocolFeeLibrary.MAX_PROTOCOL_FEE); feeController.setProtocolFeeForPool(key, protocolFee); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - // wont revert but set protocolFee as 0 poolManager.initialize(key, new bytes(0)); - assertEq(poolManager.getProtocolFee(key), 0); + (uint256 protocolFee0, uint256 protocolFee1) = poolManager.swap(key, 1e18, 1e18); + assertEq(protocolFee0, 1e15); + assertEq(protocolFee1, 1e15); } function test_CollectProtocolFee_OnlyOwnerOrFeeController() public { - vm.expectRevert(IFees.InvalidProtocolFeeCollector.selector); + vm.expectRevert(IProtocolFees.InvalidCaller.selector); vm.prank(address(alice)); poolManager.collectProtocolFees(alice, Currency.wrap(address(token0)), 1e18); } function test_CollectProtocolFee() public { - // set protocolFee as 10% of fee - feeController.setProtocolFeeForPool(key, _buildSwapFee(10, 10)); + // set protocolFee as 0.1% of fee + uint24 protocolFee = _buildProtocolFee(ProtocolFeeLibrary.MAX_PROTOCOL_FEE, ProtocolFeeLibrary.MAX_PROTOCOL_FEE); + feeController.setProtocolFeeForPool(key, protocolFee); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); poolManager.initialize(key, new bytes(0)); (uint256 protocolFee0, uint256 protocolFee1) = poolManager.swap(key, 1e18, 1e18); - assertEq(protocolFee0, 1e17); - assertEq(protocolFee1, 1e17); + assertEq(protocolFee0, 1e15); + assertEq(protocolFee1, 1e15); // send some token to vault as poolManager.swap doesn't have tokens - token0.mint(address(vault), 1e17); - token1.mint(address(vault), 1e17); + token0.mint(address(vault), 1e15); + token1.mint(address(vault), 1e15); // before collect assertEq(token0.balanceOf(alice), 0); assertEq(token1.balanceOf(alice), 0); - assertEq(token0.balanceOf(address(vault)), 1e17); - assertEq(token1.balanceOf(address(vault)), 1e17); + assertEq(token0.balanceOf(address(vault)), 1e15); + assertEq(token1.balanceOf(address(vault)), 1e15); // collect vm.prank(address(feeController)); - poolManager.collectProtocolFees(alice, Currency.wrap(address(token0)), 1e17); - poolManager.collectProtocolFees(alice, Currency.wrap(address(token1)), 1e17); + poolManager.collectProtocolFees(alice, Currency.wrap(address(token0)), 1e15); + poolManager.collectProtocolFees(alice, Currency.wrap(address(token1)), 1e15); // after collect - assertEq(token0.balanceOf(alice), 1e17); - assertEq(token1.balanceOf(alice), 1e17); + assertEq(token0.balanceOf(alice), 1e15); + assertEq(token1.balanceOf(alice), 1e15); assertEq(token0.balanceOf(address(vault)), 0); assertEq(token1.balanceOf(address(vault)), 0); } - function _buildSwapFee(uint16 fee0, uint16 fee1) public pure returns (uint16) { - return fee0 + (fee1 << 8); + function _buildProtocolFee(uint24 fee0, uint24 fee1) public pure returns (uint24) { + // max fee is 1000 pips = 0.1% + return fee0 + (fee1 << 12); } } diff --git a/test/libraries/FeeLibrary.sol b/test/libraries/FeeLibrary.sol deleted file mode 100644 index 8ebee853..00000000 --- a/test/libraries/FeeLibrary.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: Unlicense -pragma solidity ^0.8.24; - -import {Test} from "forge-std/Test.sol"; - -import {SwapFeeLibrary} from "../../src/libraries/SwapFeeLibrary.sol"; - -contract SwapFeeLibraryTest is Test { - using SwapFeeLibrary for uint24; - - function testisDynamicSwapFee() public { - // 1000 0000 0000 0000 0000 0000 - assertEq(SwapFeeLibrary.isDynamicSwapFee(0x800000), true); - - // 0100 0000 0000 0000 0000 0000 - assertEq(SwapFeeLibrary.isDynamicSwapFee(0x400000), false); - - // 0010 0000 0000 0000 0000 0000 - assertEq(SwapFeeLibrary.isDynamicSwapFee(0x200000), false); - - // 0001 0000 0000 0000 0000 0000 - assertEq(SwapFeeLibrary.isDynamicSwapFee(0x100000), false); - - // 1111 1111 1111 1111 1111 1111 - assertEq(SwapFeeLibrary.isDynamicSwapFee(0xFFFFFF), true); - - // 0111 1111 1111 1111 1111 1111 - assertEq(SwapFeeLibrary.isDynamicSwapFee(0x7FFFF), false); - } - - function testGetSwapFee() public { - // static - assertEq(SwapFeeLibrary.getInitialSwapFee(0x000001), 0x000001); - assertEq(SwapFeeLibrary.getInitialSwapFee(0x000002), 0x000002); - assertEq(SwapFeeLibrary.getInitialSwapFee(0x0F0003), 0x0F0003); - assertEq(SwapFeeLibrary.getInitialSwapFee(0x001004), 0x001004); - assertEq(SwapFeeLibrary.getInitialSwapFee(0x111020), 0x011020); - assertEq(SwapFeeLibrary.getInitialSwapFee(0x101020), 0x001020); - - // dynamic - assertEq(SwapFeeLibrary.getInitialSwapFee(0xF00F05), 0); - assertEq(SwapFeeLibrary.getInitialSwapFee(0x800310), 0); - } - - function testFuzzIsStaicFeeTooLarge(uint24 self, uint24 maxFee) public { - assertEq(self.getInitialSwapFee() > maxFee, self.getInitialSwapFee().isSwapFeeTooLarge(maxFee)); - } -} diff --git a/test/libraries/Hooks/Hooks.t.sol b/test/libraries/Hooks/Hooks.t.sol index 7fb583a2..25bece44 100644 --- a/test/libraries/Hooks/Hooks.t.sol +++ b/test/libraries/Hooks/Hooks.t.sol @@ -8,7 +8,7 @@ import {IPoolManager} from "../../../src/interfaces/IPoolManager.sol"; import {Currency} from "../../../src/types/Currency.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {HooksContract} from "./HooksContract.sol"; -import {SwapFeeLibrary} from "../../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; contract HooksTest is Test { /// @dev trick to convert poolKey to calldata @@ -64,7 +64,7 @@ contract HooksTest is Test { bitmap := and(parameters, 0xFFFF) } - if (bitmap != 0 || SwapFeeLibrary.isDynamicSwapFee(fee)) { + if (bitmap != 0 || LPFeeLibrary.isDynamicLPFee(fee)) { vm.expectRevert(Hooks.HookConfigValidationError.selector); } diff --git a/test/libraries/LPFeeLibrary.t.sol b/test/libraries/LPFeeLibrary.t.sol new file mode 100644 index 00000000..dd1acff1 --- /dev/null +++ b/test/libraries/LPFeeLibrary.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.24; + +import {Test} from "forge-std/Test.sol"; + +import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; + +contract LPFeeLibraryTest is Test { + using LPFeeLibrary for uint24; + + function testIsDynamicLPFee() public { + // 1000 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isDynamicLPFee(0x800000), true); + + // 0100 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isDynamicLPFee(0x400000), false); + + // 0010 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isDynamicLPFee(0x200000), false); + + // 0001 0000 0000 0000 0000 0000 + assertEq(LPFeeLibrary.isDynamicLPFee(0x100000), false); + + // 1111 1111 1111 1111 1111 1111 + assertEq(LPFeeLibrary.isDynamicLPFee(0xFFFFFF), true); + + // 0111 1111 1111 1111 1111 1111 + assertEq(LPFeeLibrary.isDynamicLPFee(0x7FFFF), false); + } + + function testGetInitialLPFee() public { + // static + assertEq(LPFeeLibrary.getInitialLPFee(0x000001), 0x000001); + assertEq(LPFeeLibrary.getInitialLPFee(0x000002), 0x000002); + assertEq(LPFeeLibrary.getInitialLPFee(0x0F0003), 0x0F0003); + assertEq(LPFeeLibrary.getInitialLPFee(0x001004), 0x001004); + assertEq(LPFeeLibrary.getInitialLPFee(0x111020), 0x011020); + assertEq(LPFeeLibrary.getInitialLPFee(0x101020), 0x001020); + + // dynamic + assertEq(LPFeeLibrary.getInitialLPFee(0xF00F05), 0); + assertEq(LPFeeLibrary.getInitialLPFee(0x800310), 0); + } + + function testFuzzValidate(uint24 self, uint24 maxFee) public { + if (self > maxFee) { + vm.expectRevert(LPFeeLibrary.FeeTooLarge.selector); + } + LPFeeLibrary.validate(self, maxFee); + } +} diff --git a/test/pool-cl/libraries/UnsafeMath.t.sol b/test/libraries/math/UnsafeMath.t.sol similarity index 85% rename from test/pool-cl/libraries/UnsafeMath.t.sol rename to test/libraries/math/UnsafeMath.t.sol index cad5d784..b02609ab 100644 --- a/test/pool-cl/libraries/UnsafeMath.t.sol +++ b/test/libraries/math/UnsafeMath.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; -import {UnsafeMath} from "../../../src/pool-cl/libraries/UnsafeMath.sol"; +import {UnsafeMath} from "../../../src/libraries/math/UnsafeMath.sol"; contract UnsafeMathTest is Test { function testDivRoundingUpFuzz(uint256 x, uint256 d) external { diff --git a/test/pool-bin/BinPoolManager.t.sol b/test/pool-bin/BinPoolManager.t.sol index 3521fe76..9e9aa38e 100644 --- a/test/pool-bin/BinPoolManager.t.sol +++ b/test/pool-bin/BinPoolManager.t.sol @@ -4,9 +4,9 @@ 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 {SwapFeeLibrary} from "../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; import {IVault} from "../../src/interfaces/IVault.sol"; -import {IFees} from "../../src/interfaces/IFees.sol"; +import {IProtocolFees} from "../../src/interfaces/IProtocolFees.sol"; import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; import {IBinPoolManager} from "../../src/pool-bin/interfaces/IBinPoolManager.sol"; import {IProtocolFeeController} from "../../src/interfaces/IProtocolFeeController.sol"; @@ -74,12 +74,12 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { int128 amount1, uint24 activeId, uint24 fee, - bytes32 pFees + uint24 pFees ); event Donate(PoolId indexed id, address indexed sender, int128 amount0, int128 amount1, uint24 binId); - event ProtocolFeeUpdated(PoolId indexed id, uint16 protocolFees); + event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFees); event SetMaxBinStep(uint16 maxBinStep); - event DynamicSwapFeeUpdated(PoolId indexed id, uint24 dynamicSwapFee); + event DynamicLPFeeUpdated(PoolId indexed id, uint24 dynamicSwapFee); Vault public vault; BinPoolManager public poolManager; @@ -163,7 +163,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { } function testInitializeDynamicFeeTooLarge(uint24 dynamicSwapFee) public { - dynamicSwapFee = uint24(bound(dynamicSwapFee, SwapFeeLibrary.TEN_PERCENT_FEE + 1, type(uint24).max)); + dynamicSwapFee = uint24(bound(dynamicSwapFee, LPFeeLibrary.TEN_PERCENT_FEE + 1, type(uint24).max)); uint16 bitMap = 0x0040; // 0000 0000 0100 0000 (before swap call) BinFeeManagerHook binFeeManagerHook = new BinFeeManagerHook(poolManager); @@ -174,19 +174,19 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { currency1: currency1, hooks: IHooks(address(binFeeManagerHook)), poolManager: IPoolManager(address(poolManager)), - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% parameters: bytes32(uint256(bitMap)).setBinStep(10) }); binFeeManagerHook.setFee(dynamicSwapFee); vm.prank(address(binFeeManagerHook)); - vm.expectRevert(IFees.FeeTooLarge.selector); - poolManager.updateDynamicSwapFee(key, dynamicSwapFee); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); + poolManager.updateDynamicLPFee(key, dynamicSwapFee); } function testInitializeSwapFeeTooLarge() public { - uint24 swapFee = SwapFeeLibrary.TEN_PERCENT_FEE + 1; + uint24 swapFee = LPFeeLibrary.TEN_PERCENT_FEE + 1; key = PoolKey({ currency0: currency0, @@ -197,7 +197,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { parameters: poolParam.setBinStep(1) // binStep }); - vm.expectRevert(IFees.FeeTooLarge.selector); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); poolManager.initialize(key, activeId, ""); } @@ -479,8 +479,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { BinSwapHelper.TestSettings memory testSettings = BinSwapHelper.TestSettings({withdrawTokens: true, settleUsingTransfer: true}); vm.expectEmit(); - bytes32 pFee = uint128(0).encode(uint128(0)); - emit Swap(key.toId(), address(binSwapHelper), 1 ether, -((1 ether * 997) / 1000), activeId, key.fee, pFee); + emit Swap(key.toId(), address(binSwapHelper), 1 ether, -((1 ether * 997) / 1000), activeId, key.fee, 0); snapStart("BinPoolManagerTest#testGasSwapSingleBin"); binSwapHelper.swap(key, true, 1 ether, testSettings, ""); @@ -649,35 +648,48 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { assertEq(ativeIdExtsload, activeIdLoad); } + function testSetProtocolFeePoolNotOwner() public { + MockProtocolFeeController feeController = new MockProtocolFeeController(); + poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); + + uint24 protocolFee = feeController.protocolFeeForPool(key); + + vm.expectRevert(IProtocolFees.InvalidCaller.selector); + poolManager.setProtocolFee(key, protocolFee); + } + function testSetProtocolFeePoolNotInitialized() public { MockProtocolFeeController feeController = new MockProtocolFeeController(); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); + uint24 protocolFee = feeController.protocolFeeForPool(key); + vm.expectRevert(PoolNotInitialized.selector); - poolManager.setProtocolFee(key); + vm.prank(address(feeController)); + poolManager.setProtocolFee(key, protocolFee); } function testSetProtocolFee() public { // initialize the pool and asset protocolFee is 0 poolManager.initialize(key, activeId, new bytes(0)); - (, uint16 protocolFee,) = poolManager.getSlot0(key.toId()); + (, uint24 protocolFee,) = poolManager.getSlot0(key.toId()); assertEq(protocolFee, 0); // set up feeController MockProtocolFeeController feeController = new MockProtocolFeeController(); - uint16 newSwapFee = _getSwapFee(10, 10); // 10% - feeController.setProtocolFeeForPool(key, newSwapFee); + uint24 newProtocolFee = _getSwapFee(1000, 1000); // 0.1% poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); // Call setProtocolFee, verify event and state updated vm.expectEmit(); - emit ProtocolFeeUpdated(key.toId(), newSwapFee); + emit ProtocolFeeUpdated(key.toId(), newProtocolFee); snapStart("BinPoolManagerTest#testSetProtocolFee"); - poolManager.setProtocolFee(key); + vm.prank(address(feeController)); + poolManager.setProtocolFee(key, newProtocolFee); snapEnd(); (, protocolFee,) = poolManager.getSlot0(key.toId()); - assertEq(protocolFee, newSwapFee); + assertEq(protocolFee, newProtocolFee); } function testFuzz_SetMaxBinStep(uint16 binStep) public { @@ -702,7 +714,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { poolManager.setMaxBinStep(100); } - function testUpdateDynamicSwapFee_FeeTooLarge() public { + function testUpdateDynamicLPFee_FeeTooLarge() public { uint16 bitMap = 0x0004; // 0000 0000 0000 0100 (before mint call) BinFeeManagerHook binFeeManagerHook = new BinFeeManagerHook(poolManager); binFeeManagerHook.setHooksRegistrationBitmap(bitMap); @@ -712,18 +724,18 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { currency1: currency1, hooks: IHooks(address(binFeeManagerHook)), poolManager: IPoolManager(address(poolManager)), - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% parameters: bytes32(uint256(bitMap)).setBinStep(10) }); - binFeeManagerHook.setFee(SwapFeeLibrary.TEN_PERCENT_FEE + 1); + binFeeManagerHook.setFee(LPFeeLibrary.TEN_PERCENT_FEE + 1); - vm.expectRevert(IFees.FeeTooLarge.selector); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); vm.prank(address(binFeeManagerHook)); - poolManager.updateDynamicSwapFee(key, SwapFeeLibrary.TEN_PERCENT_FEE + 1); + poolManager.updateDynamicLPFee(key, LPFeeLibrary.TEN_PERCENT_FEE + 1); } - function testUpdateDynamicSwapFee_FeeNotDynamic() public { + function testUpdateDynamicLPFee_FeeNotDynamic() public { key = PoolKey({ currency0: currency0, currency1: currency1, @@ -733,12 +745,12 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { parameters: poolParam }); - vm.expectRevert(IPoolManager.UnauthorizedDynamicSwapFeeUpdate.selector); - poolManager.updateDynamicSwapFee(key, 3000); + vm.expectRevert(IPoolManager.UnauthorizedDynamicLPFeeUpdate.selector); + poolManager.updateDynamicLPFee(key, 3000); } - function testFuzzUpdateDynamicSwapFee(uint24 _swapFee) public { - _swapFee = uint24(bound(_swapFee, 0, SwapFeeLibrary.TEN_PERCENT_FEE)); + function testFuzzUpdateDynamicLPFee(uint24 _lpFee) public { + _lpFee = uint24(bound(_lpFee, 0, LPFeeLibrary.TEN_PERCENT_FEE)); uint16 bitMap = 0x0004; // 0000 0000 0000 0100 (before mint call) BinFeeManagerHook binFeeManagerHook = new BinFeeManagerHook(poolManager); @@ -749,23 +761,23 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { currency1: currency1, hooks: IHooks(address(binFeeManagerHook)), poolManager: IPoolManager(address(poolManager)), - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% parameters: bytes32(uint256(bitMap)).setBinStep(10) }); poolManager.initialize(key, activeId, new bytes(0)); - binFeeManagerHook.setFee(_swapFee); + binFeeManagerHook.setFee(_lpFee); vm.expectEmit(); - emit DynamicSwapFeeUpdated(key.toId(), _swapFee); + emit DynamicLPFeeUpdated(key.toId(), _lpFee); - snapStart("BinPoolManagerTest#testFuzzUpdateDynamicSwapFee"); + snapStart("BinPoolManagerTest#testFuzzUpdateDynamicLPFee"); vm.prank(address(binFeeManagerHook)); - poolManager.updateDynamicSwapFee(key, _swapFee); + poolManager.updateDynamicLPFee(key, _lpFee); snapEnd(); (,, uint24 swapFee) = poolManager.getSlot0(key.toId()); - assertEq(swapFee, _swapFee); + assertEq(swapFee, _lpFee); } function testSwap_WhenPaused() public { @@ -857,7 +869,7 @@ contract BinPoolManagerTest is Test, GasSnapshot, BinTestHelper { return true; } - function _getSwapFee(uint16 fee0, uint16 fee1) internal pure returns (uint16) { - return fee0 + (fee1 << 8); + function _getSwapFee(uint24 fee0, uint24 fee1) internal pure returns (uint24) { + return fee0 + (fee1 << 12); } } diff --git a/test/pool-bin/helpers/BinFeeManagerHook.sol b/test/pool-bin/helpers/BinFeeManagerHook.sol index eb3b089d..c2bc87c5 100644 --- a/test/pool-bin/helpers/BinFeeManagerHook.sol +++ b/test/pool-bin/helpers/BinFeeManagerHook.sol @@ -51,7 +51,7 @@ contract BinFeeManagerHook is BaseBinTestHook, IBinDynamicFeeManager { (bool _update, uint24 _fee) = abi.decode(hookData, (bool, uint24)); if (_update) { fee = _fee; - binManager.updateDynamicSwapFee(key, _fee); + binManager.updateDynamicLPFee(key, _fee); } } @@ -67,7 +67,7 @@ contract BinFeeManagerHook is BaseBinTestHook, IBinDynamicFeeManager { (bool _update, uint24 _fee) = abi.decode(hookData, (bool, uint24)); if (_update) { fee = _fee; - binManager.updateDynamicSwapFee(key, _fee); + binManager.updateDynamicLPFee(key, _fee); } } diff --git a/test/pool-bin/libraries/BinHelper.t.sol b/test/pool-bin/libraries/BinHelper.t.sol index b351f4e2..507d608f 100644 --- a/test/pool-bin/libraries/BinHelper.t.sol +++ b/test/pool-bin/libraries/BinHelper.t.sol @@ -10,7 +10,7 @@ import {BinHelper} from "../../../src/pool-bin/libraries/BinHelper.sol"; import {BinPoolParametersHelper} from "../../../src/pool-bin/libraries/BinPoolParametersHelper.sol"; import {PriceHelper} from "../../../src/pool-bin/libraries/PriceHelper.sol"; import {FeeHelper} from "../../../src/pool-bin/libraries/FeeHelper.sol"; -import {SwapFeeLibrary} from "../../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; contract BinHelperTest is BinTestHelper { using BinHelper for bytes32; @@ -165,7 +165,7 @@ contract BinHelperTest is BinTestHelper { amountXIn = uint128(bound(amountXIn, 1, type(uint128).max)); amountYIn = uint128(bound(amountYIn, 1, type(uint128).max)); price = uint256(bound(price, 1, type(uint256).max / amountXIn)); - fee = uint24(bound(fee, 0, SwapFeeLibrary.TEN_PERCENT_FEE)); + fee = uint24(bound(fee, 0, LPFeeLibrary.TEN_PERCENT_FEE)); ///@dev temp fix for "The `vm.assume` cheatcode rejected too many inputs" /// dont see a clear way to rewrite this with bound @@ -202,7 +202,7 @@ contract BinHelperTest is BinTestHelper { (amountXIn, amountYIn) = amountsIn.decode(); - bytes32 compositionFees = binReserves.getCompositionFees(fee, amountsIn, totalSupply, shares); + (bytes32 compositionFees,) = binReserves.getCompositionFees(0, fee, amountsIn, totalSupply, shares); uint256 binC = reserveX | reserveY == 0 ? 0 : (uint256(reserveY) << 128) / (uint256(reserveX) + reserveY); uint256 userC = amountXIn | amountYIn == 0 ? 0 : (uint256(amountYIn) << 128) / (uint256(amountXIn) + amountYIn); @@ -229,7 +229,7 @@ contract BinHelperTest is BinTestHelper { uint128 amountIn, uint24 fee ) external { - fee = uint24(bound(fee, 0, SwapFeeLibrary.TEN_PERCENT_FEE)); + fee = uint24(bound(fee, 0, LPFeeLibrary.TEN_PERCENT_FEE)); uint24 activeId = uint24(uint256(int256(uint256(ID_ONE)) + deltaId)); uint256 price = PriceHelper.getPriceFromId(activeId, DEFAULT_BIN_STEP); @@ -281,7 +281,7 @@ contract BinHelperTest is BinTestHelper { uint128 amountIn, uint24 fee ) external { - fee = uint24(bound(fee, 0, SwapFeeLibrary.TEN_PERCENT_FEE)); + fee = uint24(bound(fee, 0, LPFeeLibrary.TEN_PERCENT_FEE)); uint24 activeId = uint24(uint256(int256(uint256(ID_ONE)) + deltaId)); uint256 price = PriceHelper.getPriceFromId(activeId, DEFAULT_BIN_STEP); diff --git a/test/pool-bin/libraries/BinPoolFee.t.sol b/test/pool-bin/libraries/BinPoolFee.t.sol index c630ed47..b23e361b 100644 --- a/test/pool-bin/libraries/BinPoolFee.t.sol +++ b/test/pool-bin/libraries/BinPoolFee.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.24; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; -import {IFees} from "../../../src/interfaces/IFees.sol"; +import {IProtocolFees} from "../../../src/interfaces/IProtocolFees.sol"; import {IVault} from "../../../src/interfaces/IVault.sol"; import {IHooks} from "../../../src/interfaces/IHooks.sol"; import {IPoolManager} from "../../../src/interfaces/IPoolManager.sol"; @@ -21,7 +21,7 @@ import {SafeCast} from "../../../src/pool-bin/libraries/math/SafeCast.sol"; import {LiquidityConfigurations} from "../../../src/pool-bin/libraries/math/LiquidityConfigurations.sol"; import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager.sol"; import {BinPoolParametersHelper} from "../../../src/pool-bin/libraries/BinPoolParametersHelper.sol"; -import {SwapFeeLibrary} from "../../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; import {BinTestHelper} from "../helpers/BinTestHelper.sol"; import {BinFeeManagerHook} from "../helpers/BinFeeManagerHook.sol"; import {HOOKS_AFTER_INITIALIZE_OFFSET, HOOKS_BEFORE_MINT_OFFSET} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; @@ -52,7 +52,7 @@ contract BinPoolFeeTest is BinTestHelper { int128 amount1, uint24 activeId, uint24 fee, - bytes32 pFees + uint24 pFees ); MockVault public vault; @@ -126,7 +126,7 @@ contract BinPoolFeeTest is BinTestHelper { } function testFuzz_Mint_WithDynamicFeeTooLarge(uint24 swapFee) external { - swapFee = uint24(bound(swapFee, SwapFeeLibrary.TEN_PERCENT_FEE + 1, type(uint24).max)); + swapFee = uint24(bound(swapFee, LPFeeLibrary.TEN_PERCENT_FEE + 1, type(uint24).max)); // 0000 0000 0000 0100, beforeMint uint16 bitMap = 0x0004; @@ -137,14 +137,14 @@ contract BinPoolFeeTest is BinTestHelper { currency1: currency1, hooks: IHooks(address(binFeeManagerHook)), poolManager: IPoolManager(address(poolManager)), - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(10_000), // 10_000 = 1% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(10_000), // 10_000 = 1% parameters: bytes32(uint256(bitMap)).setBinStep(10) }); uint24 activeId = ID_ONE; // where token price are the same poolManager.initialize(key, activeId, new bytes(0)); - vm.expectRevert(IFees.FeeTooLarge.selector); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); bytes memory data = abi.encode(true, uint24(swapFee)); addLiquidityToBin(key, poolManager, bob, activeId, 10_000 ether, 10_000 ether, 1e18, 1e18, data); } @@ -157,7 +157,7 @@ contract BinPoolFeeTest is BinTestHelper { hooks: IHooks(address(mockFeeManagerHook)), poolManager: IPoolManager(address(poolManager)), /// @dev dynamic swap fee is 0 when pool is initialized, hence 0.3% will be ignored - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), parameters: BinPoolParametersHelper.setBinStep( bytes32(uint256(mockFeeManagerHook.getHooksRegistrationBitmap())), 10 ) @@ -192,8 +192,8 @@ contract BinPoolFeeTest is BinTestHelper { } function test_MintCompositionFee_WithProtocolFee() external { - // set protocolFee as 10% of fee - uint16 pFee = _getSwapFee(10, 10); // 10% + // set protocolFee as 0.1% of fee + uint24 pFee = _getSwapFee(1000, 1000); feeController.setProtocolFeeForPool(key, pFee); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); @@ -205,10 +205,11 @@ contract BinPoolFeeTest is BinTestHelper { // first mint: 5:5 ratio, will never incur composition fee for first mint addLiquidityToBin(key, poolManager, bob, binId, amountX, amountY, 1e18, 1e18, ""); - // lpFee is 90% while protocolFee = 10% - bytes32 lpFee = uint128(0).encode(uint128(93382758620689655)); - bytes32 protocolFee = uint128(0).encode(uint128(10375862068965517)); - bytes32 expectedFee = lpFee.add(protocolFee); + // protocol fee: 0.1% of fee + // lp fee 0.3% * (1 - 0.1%) = 0.297% roughly 3 times of protocol fee + // hence swap fee roughly 4 times of protocol fee + bytes32 protocolFee = uint128(0).encode(uint128(34517241379310344)); + bytes32 expectedFee = uint128(0).encode(uint128(138378483068965517)); bytes32 expectedAmtInBin = uint128(400e18).encode(uint128(500e18)).sub(protocolFee); uint256[] memory ids = new uint256[](1); bytes32[] memory amounts = new bytes32[](1); @@ -262,8 +263,7 @@ contract BinPoolFeeTest is BinTestHelper { vm.startPrank(bob); vm.expectEmit(); - bytes32 pFee = uint128(0).encode(uint128(0)); - emit Swap(key.toId(), bob, 1e18, -((1e18 * 997) / 1000), activeId, 3000, pFee); + emit Swap(key.toId(), bob, 1e18, -((1e18 * 997) / 1000), activeId, 3000, 0); // swap: 1e18 X for Y. pool is 0.3% fee BalanceDelta delta = poolManager.swap(key, true, 1e18, "0x"); @@ -279,7 +279,7 @@ contract BinPoolFeeTest is BinTestHelper { } function test_Swap_WithDynamicFee(uint24 poolFee) external { - poolFee = uint24(bound(poolFee, 0, SwapFeeLibrary.TEN_PERCENT_FEE - 1)); + poolFee = uint24(bound(poolFee, 0, LPFeeLibrary.TEN_PERCENT_FEE - 1)); // 0000 0000 0100 0000, beforeSwap uint16 bitMap = 0x0040; @@ -290,7 +290,7 @@ contract BinPoolFeeTest is BinTestHelper { currency1: currency1, hooks: IHooks(address(binFeeManagerHook)), poolManager: IPoolManager(address(poolManager)), - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + poolFee, + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + poolFee, // parameters: poolParam // binStep parameters: bytes32(uint256(bitMap)).setBinStep(10) }); @@ -303,7 +303,7 @@ contract BinPoolFeeTest is BinTestHelper { // overwrite fee to 2% binFeeManagerHook.setFee(20_000); vm.prank(address(binFeeManagerHook)); - poolManager.updateDynamicSwapFee(key, 20_000); + poolManager.updateDynamicLPFee(key, 20_000); // Call getSwapIn and getSwapOut (, uint128 getSwapOutAmtOut,) = poolManager.getSwapOut(key, true, 1e18); @@ -320,7 +320,7 @@ contract BinPoolFeeTest is BinTestHelper { } function testFuzz_Swap_WithDynamicFeeTooLarge(uint24 swapFee) external { - swapFee = uint24(bound(swapFee, SwapFeeLibrary.TEN_PERCENT_FEE + 1, type(uint24).max)); + swapFee = uint24(bound(swapFee, LPFeeLibrary.TEN_PERCENT_FEE + 1, type(uint24).max)); // 0000 0000 0100 0000, beforeSwap uint16 bitMap = 0x0040; @@ -331,7 +331,7 @@ contract BinPoolFeeTest is BinTestHelper { currency1: currency1, hooks: IHooks(address(binFeeManagerHook)), poolManager: IPoolManager(address(poolManager)), - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(10_000), // 10_000 = 1% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(10_000), // 10_000 = 1% parameters: bytes32(uint256(bitMap)).setBinStep(10) }); @@ -340,13 +340,13 @@ contract BinPoolFeeTest is BinTestHelper { poolManager.initialize(key, activeId, new bytes(0)); addLiquidityToBin(key, poolManager, bob, activeId, 10_000e18, 10_000e18, 1e18, 1e18, ""); - vm.expectRevert(IFees.FeeTooLarge.selector); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); bytes memory data = abi.encode(true, uint24(swapFee)); poolManager.swap(key, true, 1e18, data); } - function _getSwapFee(uint16 fee0, uint16 fee1) internal pure returns (uint16) { - return fee0 + (fee1 << 8); + function _getSwapFee(uint24 fee0, uint24 fee1) internal pure returns (uint24) { + return fee0 + (fee1 << 12); } /** diff --git a/test/pool-bin/libraries/BinPoolSwap.t.sol b/test/pool-bin/libraries/BinPoolSwap.t.sol index d2a15cd4..c4e354a9 100644 --- a/test/pool-bin/libraries/BinPoolSwap.t.sol +++ b/test/pool-bin/libraries/BinPoolSwap.t.sol @@ -96,9 +96,9 @@ contract BinPoolSwapTest is BinTestHelper { } function test_SwapSingleBinWithProtocolFee() public { - // Pre-req: set protocol fee at 10% + // Pre-req: set protocol fee at 0.1% MockProtocolFeeController feeController = new MockProtocolFeeController(); - uint16 protocolFee = _getSwapFee(10, 10); // 10% + uint24 protocolFee = _getSwapFee(1000, 1000); feeController.setProtocolFeeForPool(key, protocolFee); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); @@ -107,23 +107,24 @@ contract BinPoolSwapTest is BinTestHelper { addLiquidityToBin(key, poolManager, bob, activeId, 1e18, 1e18, 1e18, 1e18, ""); (uint128 amountIn,, uint128 fee1) = poolManager.getSwapIn(key, true, 1e18); - assertEq(fee1, 3009027081243732); + // total fee should be roughly 0.1% + 0.3% (1 - 0.1%) = 0.3997% + assertApproxEqRel(fee1, 1e18 * 0.003997, 0.01e18); (,, uint128 fee2) = poolManager.getSwapOut(key, true, amountIn); assertEq(fee2, fee1); - // Swap and verify protocol fee is 10% + // Swap and verify protocol fee is 0.1% assertEq(poolManager.protocolFeesAccrued(key.currency0), 0); assertEq(poolManager.protocolFeesAccrued(key.currency1), 0); poolManager.swap(key, true, amountIn, ""); - assertEq(poolManager.protocolFeesAccrued(key.currency0), fee1 / 10); + assertApproxEqRel(poolManager.protocolFeesAccrued(key.currency0), fee1 / 4, 0.001e18); assertEq(poolManager.protocolFeesAccrued(key.currency1), 0); } function test_SwapMultipleBinWithProtocolFee() public { - // Pre-req: set protocol fee at 10% + // Pre-req: set protocol fee at 0.1% MockProtocolFeeController feeController = new MockProtocolFeeController(); - uint16 protocolFee = _getSwapFee(10, 10); // 10% + uint24 protocolFee = _getSwapFee(1000, 1000); // 0.1% feeController.setProtocolFeeForPool(key, protocolFee); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); @@ -132,17 +133,18 @@ contract BinPoolSwapTest is BinTestHelper { addLiquidity(key, poolManager, bob, activeId, 1e18, 1e18, 10, 10); (uint128 amountIn,, uint128 fee1) = poolManager.getSwapIn(key, true, 1e18); - assertEq(fee1, 3022603874699769); + assertEq(fee1, 4031147042767755); (,, uint128 fee2) = poolManager.getSwapOut(key, true, amountIn); assertEq(fee2, fee1); - // Swap and verify protocol fee is 10% + // Swap and verify protocol fee is 0.1% assertEq(poolManager.protocolFeesAccrued(key.currency0), 0); assertEq(poolManager.protocolFeesAccrued(key.currency1), 0); poolManager.swap(key, true, amountIn, ""); - // should be very close to 1/10 of fee. add 0.1% approxEq due to math - assertApproxEqRel(poolManager.protocolFeesAccrued(key.currency0), fee1 / 10, 0.001e18); + + // should be very close to 1/4 of fee. add 0.1% approxEq due to math + assertApproxEqRel(poolManager.protocolFeesAccrued(key.currency0), fee1 / 4, 0.001e18); assertEq(poolManager.protocolFeesAccrued(key.currency1), 0); } @@ -270,7 +272,7 @@ contract BinPoolSwapTest is BinTestHelper { poolManager.swap(key, false, amountIn, "0x"); } - function _getSwapFee(uint16 fee0, uint16 fee1) internal pure returns (uint16) { - return fee0 + (fee1 << 8); + function _getSwapFee(uint24 fee0, uint24 fee1) internal pure returns (uint24) { + return fee0 + (fee1 << 12); } } diff --git a/test/pool-bin/libraries/FeeHelper.t.sol b/test/pool-bin/libraries/FeeHelper.t.sol index 54336fae..389bd85b 100644 --- a/test/pool-bin/libraries/FeeHelper.t.sol +++ b/test/pool-bin/libraries/FeeHelper.t.sol @@ -4,14 +4,14 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; import {FeeHelper} from "../../../src/pool-bin/libraries/FeeHelper.sol"; import {Uint256x256Math} from "../../../src/pool-bin/libraries/math/Uint256x256Math.sol"; -import {SwapFeeLibrary} from "../../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; contract FeeHelperTest is Test { using FeeHelper for uint128; using Uint256x256Math for uint256; function testFuzz_GetFeeAmountFrom(uint128 amountWithFee, uint24 feeBips) external { - feeBips = uint24(bound(feeBips, 0, SwapFeeLibrary.TEN_PERCENT_FEE)); + feeBips = uint24(bound(feeBips, 0, LPFeeLibrary.TEN_PERCENT_FEE)); uint128 fee = uint128(feeBips) * 1e12; uint256 expectedFeeAmount = (uint256(amountWithFee) * fee + 1e18 - 1) / 1e18; @@ -21,7 +21,7 @@ contract FeeHelperTest is Test { } function testFuzz_GetFeeAmount(uint128 amount, uint24 feeBips) external { - feeBips = uint24(bound(feeBips, 0, SwapFeeLibrary.TEN_PERCENT_FEE)); + feeBips = uint24(bound(feeBips, 0, LPFeeLibrary.TEN_PERCENT_FEE)); uint128 fee = uint128(feeBips) * 1e12; uint128 denominator = 1e18 - fee; @@ -33,7 +33,7 @@ contract FeeHelperTest is Test { } function testFuzz_GetCompositionFee(uint128 amountWithFee, uint24 feeBips) external { - feeBips = uint24(bound(feeBips, 0, SwapFeeLibrary.TEN_PERCENT_FEE)); + feeBips = uint24(bound(feeBips, 0, LPFeeLibrary.TEN_PERCENT_FEE)); uint128 fee = uint128(feeBips) * 1e12; uint256 denominator = 1e36; diff --git a/test/pool-bin/libraries/math/PackedUint128Math.t.sol b/test/pool-bin/libraries/math/PackedUint128Math.t.sol index 3dcdae21..3e038f5f 100644 --- a/test/pool-bin/libraries/math/PackedUint128Math.t.sol +++ b/test/pool-bin/libraries/math/PackedUint128Math.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.24; import "forge-std/Test.sol"; import {Constants} from "../../../../src/pool-bin/libraries/Constants.sol"; import {PackedUint128Math} from "../../../../src/pool-bin/libraries/math/PackedUint128Math.sol"; +import {ProtocolFeeLibrary} from "../../../../src/libraries/ProtocolFeeLibrary.sol"; contract PackedUint128MathTest is Test { using PackedUint128Math for bytes32; @@ -147,18 +148,21 @@ contract PackedUint128MathTest is Test { assertEq(x.gt(y), x1 > y1 || x2 > y2, "testFuzz_GreaterThan::1"); } - function testFuzz_getExternalFeeAmt(bytes32 x, uint16 fee) external { + function testFuzz_getExternalFeeAmt(bytes32 x, uint24 protocolFee, uint24 swapFee) external { + protocolFee = uint24(bound(protocolFee, 0, 1000_000)); + swapFee = uint24(bound(swapFee, protocolFee, 1000_000)); + (uint128 x1, uint128 x2) = x.decode(); - if (fee == 0) { - assertEq(x.getExternalFeeAmt(fee), 0); + if (protocolFee == 0 || swapFee == 0) { + assertEq(x.getExternalFeeAmt(protocolFee, swapFee), 0); } else { - uint16 fee0 = fee % 256; - uint16 fee1 = fee >> 8; + uint24 fee0 = protocolFee % 4096; + uint24 fee1 = protocolFee >> 12; - uint128 x1Fee = fee0 > 0 ? x1 / fee0 : 0; - uint128 x2Fee = fee1 > 0 ? x2 / fee1 : 0; - assertEq(x.getExternalFeeAmt(fee), uint128(x1Fee).encode(uint128(x2Fee))); + uint128 x1Fee = fee0 > 0 ? uint128(uint256(x1) * fee0 / swapFee) : 0; + uint128 x2Fee = fee1 > 0 ? uint128(uint256(x2) * fee1 / swapFee) : 0; + assertEq(x.getExternalFeeAmt(protocolFee, swapFee), uint128(x1Fee).encode(uint128(x2Fee))); } } @@ -166,22 +170,27 @@ contract PackedUint128MathTest is Test { { // 0% fee bytes32 x = uint128(100).encode(uint128(100)); - uint16 fee = (0 << 8) + 0; // amt / 0 = 0% - assertEq(x.getExternalFeeAmt(fee), 0); + uint24 fee = (0 << 12) + 0; // amt / 0 = 0% + assertEq(x.getExternalFeeAmt(fee, 0), 0); } { - // 20% fee - bytes32 x = uint128(100).encode(uint128(100)); - uint16 fee = (5 << 8) + 5; // amt / 5 = 20% - assertEq(x.getExternalFeeAmt(fee), uint128(20).encode(uint128(20))); + // 0.01% fee + bytes32 x = uint128(10000).encode(uint128(10000)); + uint24 fee = (100 << 12) + 100; + + // lpFee 0% + assertEq(x.getExternalFeeAmt(fee, 100), uint128(10000).encode(uint128(10000))); } { - // 100% fee - bytes32 x = uint128(100).encode(uint128(100)); - uint16 fee = (1 << 8) + 1; // amt / 1 = 100% - assertEq(x.getExternalFeeAmt(fee), x); + // 0.1% fee + bytes32 x = uint128(10000).encode(uint128(10000)); + uint24 fee = (1000 << 12) + 1000; + + // 0.3% lp fee => swap fee 0.3997% + uint24 swapFee = ProtocolFeeLibrary.calculateSwapFee(1000, 3000); + assertEq(x.getExternalFeeAmt(fee, swapFee), uint128(2501).encode(uint128(2501))); } } } diff --git a/test/pool-cl/CLFees.t.sol b/test/pool-cl/CLFees.t.sol index ca02a3b4..5866fb2c 100644 --- a/test/pool-cl/CLFees.t.sol +++ b/test/pool-cl/CLFees.t.sol @@ -5,9 +5,9 @@ import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {IHooks} from "../../src/interfaces/IHooks.sol"; import {Hooks} from "../../src/libraries/Hooks.sol"; -import {SwapFeeLibrary} from "../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; -import {IFees} from "../../src/interfaces/IFees.sol"; +import {IProtocolFees} from "../../src/interfaces/IProtocolFees.sol"; import {ICLPoolManager} from "../../src/pool-cl/interfaces/ICLPoolManager.sol"; import {CLPoolManager} from "../../src/pool-cl/CLPoolManager.sol"; import {TickMath} from "../../src/pool-cl/libraries/TickMath.sol"; @@ -22,10 +22,11 @@ import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; import {CLPoolManagerRouter} from "./helpers/CLPoolManagerRouter.sol"; import {ProtocolFeeControllerTest} from "./helpers/ProtocolFeeControllerTest.sol"; import {IProtocolFeeController} from "../../src/interfaces/IProtocolFeeController.sol"; -import {Fees} from "../../src/Fees.sol"; +import {ProtocolFees} from "../../src/ProtocolFees.sol"; import {BalanceDelta} from "../../src/types/BalanceDelta.sol"; import {PoolKey} from "../../src/types/PoolKey.sol"; import {IVault} from "../../src/interfaces/IVault.sol"; +import {ProtocolFeeLibrary} from "../../src/libraries/ProtocolFeeLibrary.sol"; contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { using Hooks for IHooks; @@ -72,42 +73,40 @@ contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { manager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); } - function testSetProtocolFeeControllerFuzz(uint16 protocolSwapFee) public { - vm.assume(protocolSwapFee < 2 ** 16); - + function testSetProtocolFeeControllerFuzz(uint24 protocolFee) public { (CLPool.Slot0 memory slot0,,,) = manager.pools(key.toId()); assertEq(slot0.protocolFee, 0); - protocolFeeController.setSwapFeeForPool(key.toId(), protocolSwapFee); manager.setProtocolFeeController(IProtocolFeeController(protocolFeeController)); - uint16 protocolSwapFee0 = protocolSwapFee % 256; - uint16 protocolSwapFee1 = protocolSwapFee >> 8; + uint24 protocolFee0 = protocolFee % 4096; + uint24 protocolFee1 = protocolFee >> 12; - if ((protocolSwapFee1 != 0 && protocolSwapFee1 < 4) || (protocolSwapFee0 != 0 && protocolSwapFee0 < 4)) { - vm.expectRevert(IFees.ProtocolFeeControllerCallFailedOrInvalidResult.selector); - manager.setProtocolFee(key); + if (protocolFee0 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE || protocolFee1 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) { + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); + vm.prank(address(protocolFeeController)); + manager.setProtocolFee(key, protocolFee); return; } - manager.setProtocolFee(key); - (slot0,,,) = manager.pools(key.toId()); + vm.prank(address(protocolFeeController)); + manager.setProtocolFee(key, protocolFee); - assertEq(slot0.protocolFee, protocolSwapFee); + (slot0,,,) = manager.pools(key.toId()); + assertEq(slot0.protocolFee, protocolFee); } - function testNoProtocolFee(uint16 protocolSwapFee) public { + function testNoProtocolFee(uint24 protocolFee) public { // Early return instead of vm.assume (too many input rejected) - if (protocolSwapFee >= 2 ** 16) return; - if (protocolSwapFee >> 8 < 4) return; - if (protocolSwapFee % 256 < 4) return; + if (protocolFee % 4096 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) return; + if (protocolFee >> 12 > ProtocolFeeLibrary.MAX_PROTOCOL_FEE) return; - protocolFeeController.setSwapFeeForPool(key.toId(), protocolSwapFee); manager.setProtocolFeeController(IProtocolFeeController(protocolFeeController)); - manager.setProtocolFee(key); + vm.prank(address(protocolFeeController)); + manager.setProtocolFee(key, protocolFee); (CLPool.Slot0 memory slot0,,,) = manager.pools(key.toId()); - assertEq(slot0.protocolFee, protocolSwapFee); + assertEq(slot0.protocolFee, protocolFee); int256 liquidityDelta = 10000; ICLPoolManager.ModifyLiquidityParams memory params = @@ -122,12 +121,6 @@ contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { ICLPoolManager.ModifyLiquidityParams(-60, 60, -liquidityDelta); router.modifyPosition(key, params2, ZERO_BYTES); - uint16 protocolSwapFee1 = (protocolSwapFee >> 8); - - // No fees should accrue bc there is no hook so the protocol cant take withdraw fees. - assertEq(manager.protocolFeesAccrued(currency0), 0); - assertEq(manager.protocolFeesAccrued(currency1), 0); - // add larger liquidity params = ICLPoolManager.ModifyLiquidityParams(-60, 60, 10e18); router.modifyPosition(key, params, ZERO_BYTES); @@ -139,19 +132,17 @@ contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { CLPoolManagerRouter.SwapTestSettings(true, true), ZERO_BYTES ); - // key3 pool is 30 bps => 10000 * 0.003 (.3%) = 30 - uint256 expectedSwapFeeAccrued = 30; - uint256 expectedProtocolAmount1 = protocolSwapFee1 == 0 ? 0 : expectedSwapFeeAccrued / protocolSwapFee1; + uint256 expectedProtocolAmount1 = 10000 * (protocolFee >> 12) / ProtocolFeeLibrary.PIPS_DENOMINATOR; assertEq(manager.protocolFeesAccrued(currency0), 0); assertEq(manager.protocolFeesAccrued(currency1), expectedProtocolAmount1); } function testCollectFees() public { - uint16 protocolFee = _computeFee(_oneForZero, 10); // 10% on 1 to 0 swaps - protocolFeeController.setSwapFeeForPool(key.toId(), protocolFee); + uint24 protocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE | (uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12); // 0.1% protocol fee manager.setProtocolFeeController(IProtocolFeeController(protocolFeeController)); - manager.setProtocolFee(key); + vm.prank(address(protocolFeeController)); + manager.setProtocolFee(key, protocolFee); (CLPool.Slot0 memory slot0,,,) = manager.pools(key.toId()); assertEq(slot0.protocolFee, protocolFee); @@ -162,23 +153,14 @@ contract CLFeesTest is Test, Deployers, TokenFixture, GasSnapshot { MockERC20(Currency.unwrap(currency1)).approve(address(router), type(uint256).max); router.swap( key, - ICLPoolManager.SwapParams(false, 10000, TickMath.MAX_SQRT_RATIO - 1), + ICLPoolManager.SwapParams(false, 1000000, TickMath.MAX_SQRT_RATIO - 1), CLPoolManagerRouter.SwapTestSettings(true, true), ZERO_BYTES ); - uint256 expectedProtocolFees = 3; // 10% of 30 is 3 + uint256 expectedProtocolFees = 1000000 * 0.001; vm.prank(address(protocolFeeController)); manager.collectProtocolFees(address(protocolFeeController), currency1, 0); assertEq(currency1.balanceOf(address(protocolFeeController)), expectedProtocolFees); } - - // If zeroForOne is true, then value is set on the lower bits. If zeroForOne is false, then value is set on the higher bits. - function _computeFee(bool zeroForOne, uint16 value) internal pure returns (uint16 fee) { - if (zeroForOne) { - fee = value % 256; - } else { - fee = value << 8; - } - } } diff --git a/test/pool-cl/CLHookSkipCallback.t.sol b/test/pool-cl/CLHookSkipCallback.t.sol index f3bbadfa..d019e445 100644 --- a/test/pool-cl/CLHookSkipCallback.t.sol +++ b/test/pool-cl/CLHookSkipCallback.t.sol @@ -18,7 +18,7 @@ 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 {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; import {CLPoolParametersHelper} from "../../src/pool-cl/libraries/CLPoolParametersHelper.sol"; import {ParametersHelper} from "../../src/libraries/math/ParametersHelper.sol"; import {CLSkipCallbackHook} from "./helpers/CLSkipCallbackHook.sol"; @@ -28,7 +28,7 @@ contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { using CurrencyLibrary for Currency; using CLPoolParametersHelper for bytes32; using ParametersHelper for bytes32; - using SwapFeeLibrary for uint24; + using LPFeeLibrary for uint24; PoolKey key; IVault public vault; diff --git a/test/pool-cl/CLPoolManager.t.sol b/test/pool-cl/CLPoolManager.t.sol index e6ac669a..5c8a4253 100644 --- a/test/pool-cl/CLPoolManager.t.sol +++ b/test/pool-cl/CLPoolManager.t.sol @@ -15,7 +15,7 @@ import {PoolKey} from "../../src/types/PoolKey.sol"; import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; import {IHooks} from "../../src/interfaces/IHooks.sol"; import {TickMath} from "../../src/pool-cl/libraries/TickMath.sol"; -import {IFees} from "../../src/interfaces/IFees.sol"; +import {IProtocolFees} from "../../src/interfaces/IProtocolFees.sol"; import {ICLHooks, HOOKS_AFTER_INITIALIZE_OFFSET} from "../../src/pool-cl/interfaces/ICLHooks.sol"; import {Hooks} from "../../src/libraries/Hooks.sol"; import {CLPoolManagerRouter} from "./helpers/CLPoolManagerRouter.sol"; @@ -25,7 +25,7 @@ import {CLPosition} from "../../src/pool-cl/libraries/CLPosition.sol"; import {Deployers} from "./helpers/Deployers.sol"; import {TokenFixture, MockERC20} from "../helpers/TokenFixture.sol"; import {MockHooks} from "./helpers/MockHooks.sol"; -import {SwapFeeLibrary} from "../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; import {CLPoolParametersHelper} from "../../src/pool-cl/libraries/CLPoolParametersHelper.sol"; import {ParametersHelper} from "../../src/libraries/math/ParametersHelper.sol"; import {BalanceDelta, BalanceDeltaLibrary} from "../../src/types/BalanceDelta.sol"; @@ -34,6 +34,7 @@ import {ProtocolFeeControllerTest} from "./helpers/ProtocolFeeControllerTest.sol import {IProtocolFeeController} from "../../src/interfaces/IProtocolFeeController.sol"; import {CLFeeManagerHook} from "./helpers/CLFeeManagerHook.sol"; import {CLNoOpTestHook} from "./helpers/CLNoOpTestHook.sol"; +import {ProtocolFeeLibrary} from "../../src/libraries/ProtocolFeeLibrary.sol"; import {SafeCast} from "../../src/libraries/SafeCast.sol"; contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { @@ -41,7 +42,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { using CurrencyLibrary for Currency; using CLPoolParametersHelper for bytes32; using ParametersHelper for bytes32; - using SwapFeeLibrary for uint24; + using LPFeeLibrary for uint24; event Initialize( PoolId indexed id, @@ -63,12 +64,12 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { uint128 liquidity, int24 tick, uint24 fee, - uint256 protocolFee + uint24 protocolFee ); event Transfer(address caller, address indexed from, address indexed to, Currency indexed currency, uint256 amount); - event ProtocolFeeUpdated(PoolId indexed id, uint16 protocolFees); - event DynamicSwapFeeUpdated(PoolId indexed id, uint24 dynamicSwapFee); + event ProtocolFeeUpdated(PoolId indexed id, uint24 protocolFees); + event DynamicLPFeeUpdated(PoolId indexed id, uint24 dynamicLPFee); event Donate(PoolId indexed id, address indexed sender, uint256 amount0, uint256 amount1, int24 tick); IVault public vault; @@ -161,7 +162,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { parameters: bytes32(uint256(0xa0000)) }); - vm.expectRevert(IFees.FeeTooLarge.selector); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); poolManager.initialize(key, TickMath.MIN_SQRT_RATIO, new bytes(0)); } } @@ -309,8 +310,8 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { } else if (!_validateHookConfig(key)) { vm.expectRevert(abi.encodeWithSelector(Hooks.HookConfigValidationError.selector)); poolManager.initialize(key, sqrtPriceX96, ZERO_BYTES); - } else if (key.fee & SwapFeeLibrary.STATIC_FEE_MASK > SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE) { - vm.expectRevert(abi.encodeWithSelector(IFees.FeeTooLarge.selector)); + } else if (key.fee & LPFeeLibrary.STATIC_FEE_MASK > LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE) { + vm.expectRevert(abi.encodeWithSelector(IProtocolFees.FeeTooLarge.selector)); poolManager.initialize(key, sqrtPriceX96, ZERO_BYTES); } else { vm.expectEmit(true, true, true, true); @@ -606,13 +607,13 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { } function test_initialize_failsIDynamicFeeTooLarge(uint24 dynamicSwapFee) public { - dynamicSwapFee = uint24(bound(dynamicSwapFee, SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1, type(uint24).max)); + dynamicSwapFee = uint24(bound(dynamicSwapFee, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1, type(uint24).max)); clFeeManagerHook.setHooksRegistrationBitmap(uint16(1 << HOOKS_AFTER_INITIALIZE_OFFSET)); PoolKey memory key = PoolKey({ currency0: currency0, currency1: currency1, - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% hooks: IHooks(address(clFeeManagerHook)), poolManager: poolManager, parameters: CLPoolParametersHelper.setTickSpacing( @@ -622,7 +623,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { clFeeManagerHook.setFee(dynamicSwapFee); - vm.expectRevert(IFees.FeeTooLarge.selector); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); } @@ -1468,7 +1469,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { PoolKey memory key = PoolKey({ currency0: currency0, currency1: currency1, - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 0.3% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 0.3% hooks: IHooks(address(clFeeManagerHook)), poolManager: poolManager, parameters: bytes32(uint256((60 << 16) | clFeeManagerHook.getHooksRegistrationBitmap())) @@ -1497,6 +1498,56 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { router.swap(key, params, testSettings, data); } + function testSwap_succeeds_withBothFeeEnabled() public { + PoolKey memory key = PoolKey({ + currency0: currency0, + currency1: currency1, + // 0.3% + fee: 3000, + hooks: IHooks(address(0)), + poolManager: poolManager, + parameters: bytes32(uint256(60) << 16) + }); + + poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); + poolManager.setProtocolFeeController(feeController); + + // 0.1% + vm.prank(address(feeController)); + poolManager.setProtocolFee( + key, ProtocolFeeLibrary.MAX_PROTOCOL_FEE | (uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12) + ); + + ICLPoolManager.ModifyLiquidityParams memory modifyPositionParams = + ICLPoolManager.ModifyLiquidityParams({tickLower: -60, tickUpper: 60, liquidityDelta: 1 ether}); + + router.modifyPosition(key, modifyPositionParams, ZERO_BYTES); + + // 0.1% protocol fee first then 0.3% lp fee charged for the remaining i.e. 0.3997% in total + vm.expectEmit(true, true, true, true); + emit Swap( + key.toId(), + address(router), + // amt0 = 3013394245478362 and amt1 = -2995354955910780 if it goes without protocol fee + 3016410656134496, + -2995354955910780, + 56022770974786139918731938227, + 0, + -6932, + 3997, + 1000 + ); + + // sell base token(x) for quote token(y), pricea(y / x) decreases + ICLPoolManager.SwapParams memory params = + ICLPoolManager.SwapParams({zeroForOne: true, amountSpecified: 0.1 ether, sqrtPriceLimitX96: SQRT_RATIO_1_2}); + + CLPoolManagerRouter.SwapTestSettings memory testSettings = + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}); + + router.swap(key, params, testSettings, ZERO_BYTES); + } + function testSwap_succeedsWithNativeTokensIfInitialized() public { PoolKey memory key = PoolKey({ currency0: Currency.wrap(address(0)), @@ -2167,7 +2218,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { } function testSetProtocolFee_updatesProtocolFeeForInitializedPool() public { - uint16 protocolFee = 4; + uint24 protocolFee = 4; PoolKey memory key = PoolKey({ currency0: currency0, @@ -2182,15 +2233,16 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); assertEq(slot0.protocolFee, 0); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - feeController.setSwapFeeForPool(key.toId(), uint16(protocolFee)); + feeController.setProtocolFeeForPool(key.toId(), protocolFee); vm.expectEmit(false, false, false, true); emit ProtocolFeeUpdated(key.toId(), protocolFee); - poolManager.setProtocolFee(key); + vm.prank(address(feeController)); + poolManager.setProtocolFee(key, protocolFee); } function testCollectProtocolFees_initializesWithProtocolFeeIfCalled() public { - uint16 protocolFee = 1028; // 00000100 00000100 i.e. 25% + uint24 protocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE | (uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12); PoolKey memory key = PoolKey({ currency0: currency0, currency1: currency1, @@ -2200,7 +2252,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { parameters: bytes32(uint256(10) << 16) }); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - feeController.setSwapFeeForPool(key.toId(), uint16(protocolFee)); + feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); @@ -2208,20 +2260,22 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { } function testCollectProtocolFees_ERC20_allowsOwnerToAccumulateFees() public { - uint16 protocolFee = 1028; // 00000100 00000100 i.e. 25% - // swap fee i.e. 0.3% * protocol fee i.e. 25% * input amount i.e. 10000 = 0.075% - uint256 expectedFees = uint256(10000) * 3000 / 1000000 * 25 / 100; + // protocol fee 0.1% + uint24 protocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE | (uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12); + uint256 expectedProtocolFees = + uint256(10000) * ProtocolFeeLibrary.MAX_PROTOCOL_FEE / ProtocolFeeLibrary.PIPS_DENOMINATOR; PoolKey memory key = PoolKey({ currency0: currency0, currency1: currency1, + // 0.3% lp fee fee: 3000, hooks: IHooks(address(0)), poolManager: poolManager, parameters: bytes32(uint256(10) << 16) }); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - feeController.setSwapFeeForPool(key.toId(), uint16(protocolFee)); + feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); @@ -2236,18 +2290,19 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { ZERO_BYTES ); - assertEq(poolManager.protocolFeesAccrued(currency0), expectedFees); + assertEq(poolManager.protocolFeesAccrued(currency0), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(currency1), 0); assertEq(currency0.balanceOf(address(1)), 0); - poolManager.collectProtocolFees(address(1), currency0, expectedFees); - assertEq(currency0.balanceOf(address(1)), expectedFees); + poolManager.collectProtocolFees(address(1), currency0, expectedProtocolFees); + assertEq(currency0.balanceOf(address(1)), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(currency0), 0); } function testCollectProtocolFees_ERC20_returnsAllFeesIf0IsProvidedAsParameter() public { - uint16 protocolFee = 1028; // 00000100 00000100 i.e. 25% - // swap fee i.e. 0.3% * protocol fee i.e. 25% * input amount i.e. 10000 = 0.075% - uint256 expectedFees = uint256(10000) * 3000 / 1000000 * 25 / 100; + // protocol fee 0.1% + uint24 protocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE | (uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12); + uint256 expectedProtocolFees = + uint256(10000) * ProtocolFeeLibrary.MAX_PROTOCOL_FEE / ProtocolFeeLibrary.PIPS_DENOMINATOR; PoolKey memory key = PoolKey({ currency0: currency0, @@ -2258,7 +2313,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { parameters: bytes32(uint256(10) << 16) }); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - feeController.setSwapFeeForPool(key.toId(), uint16(protocolFee)); + feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); @@ -2273,18 +2328,19 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { ZERO_BYTES ); - assertEq(poolManager.protocolFeesAccrued(currency0), expectedFees); + assertEq(poolManager.protocolFeesAccrued(currency0), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(currency1), 0); assertEq(currency0.balanceOf(address(1)), 0); poolManager.collectProtocolFees(address(1), currency0, 0); - assertEq(currency0.balanceOf(address(1)), expectedFees); + assertEq(currency0.balanceOf(address(1)), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(currency0), 0); } function testCollectProtocolFees_nativeToken_allowsOwnerToAccumulateFees() public { - uint16 protocolFee = 1028; // 00000100 00000100 i.e. 25% - // swap fee i.e. 0.3% * protocol fee i.e. 25% * input amount i.e. 10000 = 0.075% - uint256 expectedFees = uint256(10000) * 3000 / 1000000 * 25 / 100; + // protocol fee 0.1% + uint24 protocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE | (uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12); + uint256 expectedProtocolFees = + uint256(10000) * ProtocolFeeLibrary.MAX_PROTOCOL_FEE / ProtocolFeeLibrary.PIPS_DENOMINATOR; Currency nativeCurrency = Currency.wrap(address(0)); PoolKey memory key = PoolKey({ @@ -2296,7 +2352,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { parameters: bytes32(uint256(10) << 16) }); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - feeController.setSwapFeeForPool(key.toId(), uint16(protocolFee)); + feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); @@ -2311,18 +2367,19 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { ZERO_BYTES ); - assertEq(poolManager.protocolFeesAccrued(nativeCurrency), expectedFees); + assertEq(poolManager.protocolFeesAccrued(nativeCurrency), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(currency1), 0); assertEq(nativeCurrency.balanceOf(address(1)), 0); - poolManager.collectProtocolFees(address(1), nativeCurrency, expectedFees); - assertEq(nativeCurrency.balanceOf(address(1)), expectedFees); + poolManager.collectProtocolFees(address(1), nativeCurrency, expectedProtocolFees); + assertEq(nativeCurrency.balanceOf(address(1)), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(nativeCurrency), 0); } function testCollectProtocolFees_nativeToken_returnsAllFeesIf0IsProvidedAsParameter() public { - uint16 protocolFee = 1028; // 00000100 00000100 i.e. 25% - // swap fee i.e. 0.3% * protocol fee i.e. 25% * input amount i.e. 10000 = 0.075% - uint256 expectedFees = uint256(10000) * 3000 / 1000000 * 25 / 100; + // protocol fee 0.1% + uint24 protocolFee = ProtocolFeeLibrary.MAX_PROTOCOL_FEE | (uint24(ProtocolFeeLibrary.MAX_PROTOCOL_FEE) << 12); + uint256 expectedProtocolFees = + uint256(10000) * ProtocolFeeLibrary.MAX_PROTOCOL_FEE / ProtocolFeeLibrary.PIPS_DENOMINATOR; Currency nativeCurrency = Currency.wrap(address(0)); PoolKey memory key = PoolKey({ @@ -2334,7 +2391,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { parameters: bytes32(uint256(10) << 16) }); poolManager.setProtocolFeeController(IProtocolFeeController(address(feeController))); - feeController.setSwapFeeForPool(key.toId(), uint16(protocolFee)); + feeController.setProtocolFeeForPool(key.toId(), protocolFee); poolManager.initialize(key, SQRT_RATIO_1_1, ZERO_BYTES); (CLPool.Slot0 memory slot0,,,) = poolManager.pools(key.toId()); @@ -2349,32 +2406,32 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { ZERO_BYTES ); - assertEq(poolManager.protocolFeesAccrued(nativeCurrency), expectedFees); + assertEq(poolManager.protocolFeesAccrued(nativeCurrency), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(currency1), 0); assertEq(nativeCurrency.balanceOf(address(1)), 0); poolManager.collectProtocolFees(address(1), nativeCurrency, 0); - assertEq(nativeCurrency.balanceOf(address(1)), expectedFees); + assertEq(nativeCurrency.balanceOf(address(1)), expectedProtocolFees); assertEq(poolManager.protocolFeesAccrued(nativeCurrency), 0); } - function testUpdateDynamicSwapFee_FeeTooLarge() public { + function testUpdateDynamicLPFee_FeeTooLarge() public { PoolKey memory key = PoolKey({ currency0: currency0, currency1: currency1, - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% hooks: IHooks(address(clFeeManagerHook)), poolManager: poolManager, parameters: bytes32(uint256(10) << 16) }); - clFeeManagerHook.setFee(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); + clFeeManagerHook.setFee(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); - vm.expectRevert(IFees.FeeTooLarge.selector); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); vm.prank(address(clFeeManagerHook)); - poolManager.updateDynamicSwapFee(key, SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); + poolManager.updateDynamicLPFee(key, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); } - function testUpdateDynamicSwapFee_FeeNotDynamic() public { + function testUpdateDynamicLPFee_FeeNotDynamic() public { PoolKey memory key = PoolKey({ currency0: currency0, currency1: currency1, @@ -2384,12 +2441,12 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { parameters: bytes32(uint256(10) << 16) }); - vm.expectRevert(IPoolManager.UnauthorizedDynamicSwapFeeUpdate.selector); - poolManager.updateDynamicSwapFee(key, 3000); + vm.expectRevert(IPoolManager.UnauthorizedDynamicLPFeeUpdate.selector); + poolManager.updateDynamicLPFee(key, 3000); } - function testFuzzUpdateDynamicSwapFee(uint24 _swapFee) public { - vm.assume(_swapFee < SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE); + function testFuzzUpdateDynamicLPFee(uint24 _swapFee) public { + vm.assume(_swapFee < LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE); uint16 bitMap = 0x0010; // 0000 0000 0001 0000 (before swap call) clFeeManagerHook.setHooksRegistrationBitmap(bitMap); @@ -2397,7 +2454,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { PoolKey memory key = PoolKey({ currency0: currency0, currency1: currency1, - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG + uint24(3000), // 3000 = 0.3% hooks: IHooks(address(clFeeManagerHook)), poolManager: poolManager, parameters: bytes32(uint256((10 << 16) | clFeeManagerHook.getHooksRegistrationBitmap())) @@ -2408,11 +2465,11 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { clFeeManagerHook.setFee(_swapFee); vm.expectEmit(); - emit DynamicSwapFeeUpdated(key.toId(), _swapFee); + emit DynamicLPFeeUpdated(key.toId(), _swapFee); - snapStart("CLPoolManagerTest#testFuzzUpdateDynamicSwapFee"); + snapStart("CLPoolManagerTest#testFuzzUpdateDynamicLPFee"); vm.prank(address(clFeeManagerHook)); - poolManager.updateDynamicSwapFee(key, _swapFee); + poolManager.updateDynamicLPFee(key, _swapFee); snapEnd(); (,,, uint24 swapFee) = poolManager.getSlot0(key.toId()); @@ -2593,7 +2650,7 @@ contract CLPoolManagerTest is Test, Deployers, TokenFixture, GasSnapshot { function _validateHookConfig(PoolKey memory poolKey) internal view returns (bool) { uint16 bitmapInParameters = poolKey.parameters.getHooksRegistrationBitmap(); if (address(poolKey.hooks) == address(0)) { - if (bitmapInParameters == 0 && !poolKey.fee.isDynamicSwapFee()) { + if (bitmapInParameters == 0 && !poolKey.fee.isDynamicLPFee()) { return true; } return false; diff --git a/test/pool-cl/helpers/CLFeeManagerHook.sol b/test/pool-cl/helpers/CLFeeManagerHook.sol index e312a2c2..6502f0a2 100644 --- a/test/pool-cl/helpers/CLFeeManagerHook.sol +++ b/test/pool-cl/helpers/CLFeeManagerHook.sol @@ -42,7 +42,7 @@ contract CLFeeManagerHook is BaseCLTestHook { override returns (bytes4) { - clManager.updateDynamicSwapFee(key, fee); + clManager.updateDynamicLPFee(key, fee); return ICLHooks.afterInitialize.selector; } @@ -55,7 +55,7 @@ contract CLFeeManagerHook is BaseCLTestHook { (bool _update, uint24 _fee) = abi.decode(hookData, (bool, uint24)); if (_update) { fee = _fee; - clManager.updateDynamicSwapFee(key, _fee); + clManager.updateDynamicLPFee(key, _fee); } } diff --git a/test/pool-cl/helpers/Deployers.sol b/test/pool-cl/helpers/Deployers.sol index f865bcaf..43313d40 100644 --- a/test/pool-cl/helpers/Deployers.sol +++ b/test/pool-cl/helpers/Deployers.sol @@ -8,7 +8,7 @@ import {IHooks} from "../../../src/interfaces/IHooks.sol"; import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; import {CLPoolManager} from "../../../src/pool-cl/CLPoolManager.sol"; import {PoolId, PoolIdLibrary} from "../../../src/types/PoolId.sol"; -import {SwapFeeLibrary} from "../../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {Constants} from "./Constants.sol"; import {SortTokens} from "../../helpers/SortTokens.sol"; @@ -16,7 +16,7 @@ import {Vault} from "../../../src/Vault.sol"; import {IVault} from "../../../src/interfaces/IVault.sol"; contract Deployers { - using SwapFeeLibrary for uint24; + using LPFeeLibrary for uint24; using PoolIdLibrary for PoolKey; bytes constant ZERO_BYTES = new bytes(0); @@ -58,7 +58,7 @@ contract Deployers { hooks, manager, fee, - fee.isDynamicSwapFee() + fee.isDynamicLPFee() ? bytes32(uint256((60 << 16) | 0x00ff)) : bytes32(uint256(((fee / 100 * 2) << 16) | 0x00ff)) ); diff --git a/test/pool-cl/helpers/ProtocolFeeControllerTest.sol b/test/pool-cl/helpers/ProtocolFeeControllerTest.sol index 8ec30599..7568fd65 100644 --- a/test/pool-cl/helpers/ProtocolFeeControllerTest.sol +++ b/test/pool-cl/helpers/ProtocolFeeControllerTest.sol @@ -8,14 +8,14 @@ import {PoolKey} from "../../../src/types/PoolKey.sol"; contract ProtocolFeeControllerTest is IProtocolFeeController { using PoolIdLibrary for PoolKey; - mapping(PoolId => uint16) public swapFeeForPool; + mapping(PoolId => uint24) public swapFeeForPool; - function protocolFeeForPool(PoolKey memory key) external view returns (uint16) { + function protocolFeeForPool(PoolKey memory key) external view returns (uint24) { return swapFeeForPool[key.toId()]; } // for tests to set pool protocol fees - function setSwapFeeForPool(PoolId id, uint16 fee) external { + function setProtocolFeeForPool(PoolId id, uint24 fee) external { swapFeeForPool[id] = fee; } } diff --git a/test/pool-cl/libraries/CLPool.t.sol b/test/pool-cl/libraries/CLPool.t.sol index 5ce73acb..d18f0006 100644 --- a/test/pool-cl/libraries/CLPool.t.sol +++ b/test/pool-cl/libraries/CLPool.t.sol @@ -12,7 +12,7 @@ import {Tick} from "../../../src/pool-cl/libraries/Tick.sol"; import {FixedPoint96} from "../../../src/pool-cl/libraries/FixedPoint96.sol"; import {SafeCast} from "../../../src/libraries/SafeCast.sol"; import {LiquidityAmounts} from "../helpers/LiquidityAmounts.sol"; -import {SwapFeeLibrary} from "../../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; import {FullMath} from "../../../src/pool-cl/libraries/FullMath.sol"; import {FixedPoint128} from "../../../src/pool-cl/libraries/FixedPoint128.sol"; @@ -35,7 +35,7 @@ contract PoolTest is Test { assertEq(state.slot0.tick, TickMath.getTickAtSqrtRatio(sqrtPriceX96)); assertLt(state.slot0.tick, TickMath.MAX_TICK); assertGt(state.slot0.tick, TickMath.MIN_TICK - 1); - assertEq(state.slot0.swapFee, swapFee); + assertEq(state.slot0.lpFee, swapFee); } } @@ -44,7 +44,7 @@ contract PoolTest is Test { { // Assumptions tested in PoolManager.t.sol params.tickSpacing = int24(bound(params.tickSpacing, 1, 32767)); - swapFee = uint24(bound(swapFee, 0, SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE - 1)); + swapFee = uint24(bound(swapFee, 0, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE - 1)); testPoolInitialize(sqrtPriceX96, 0, swapFee); diff --git a/test/pool-cl/libraries/CLPoolSwapFee.t.sol b/test/pool-cl/libraries/CLPoolSwapFee.t.sol index fa782d5f..c3988adb 100644 --- a/test/pool-cl/libraries/CLPoolSwapFee.t.sol +++ b/test/pool-cl/libraries/CLPoolSwapFee.t.sol @@ -6,13 +6,13 @@ import "forge-std/Test.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {TokenFixture} from "../../helpers/TokenFixture.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; -import {SwapFeeLibrary} from "../../../src/libraries/SwapFeeLibrary.sol"; +import {LPFeeLibrary} from "../../../src/libraries/LPFeeLibrary.sol"; import {CLFeeManagerHook} from "../helpers/CLFeeManagerHook.sol"; import {Deployers} from "../helpers/Deployers.sol"; import {Vault} from "../../../src/Vault.sol"; import {CLPoolManager} from "../../../src/pool-cl/CLPoolManager.sol"; import {CLPoolParametersHelper} from "../../../src/pool-cl/libraries/CLPoolParametersHelper.sol"; -import {IFees} from "../../../src/interfaces/IFees.sol"; +import {IProtocolFees} from "../../../src/interfaces/IProtocolFees.sol"; import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; import {CLPoolManagerRouter} from "../helpers/CLPoolManagerRouter.sol"; import {Currency} from "../../../src/types/Currency.sol"; @@ -42,7 +42,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { uint128 liquidity, int24 tick, uint24 fee, - uint256 protocolFee + uint24 protocolFee ); function setUp() public { @@ -62,7 +62,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { currency1: currency1, hooks: hook, poolManager: poolManager, - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG, + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG, parameters: CLPoolParametersHelper.setTickSpacing(bytes32(uint256(hook.getHooksRegistrationBitmap())), 1) }); @@ -73,30 +73,30 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { hooks: hook, poolManager: poolManager, // 50% - fee: SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2, + fee: LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2, parameters: CLPoolParametersHelper.setTickSpacing(bytes32(uint256(hook.getHooksRegistrationBitmap())), 1) }); } function testPoolInitializeFailsWithTooLargeFee() public { - vm.expectRevert(IFees.FeeTooLarge.selector); - staticFeeKey.fee = SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1; + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); + staticFeeKey.fee = LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1; poolManager.initialize(staticFeeKey, SQRT_RATIO_1_1, ZERO_BYTES); } function testUpdateFailsWithTooLargeFee() public { - hook.setFee(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2); + hook.setFee(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2); hook.setHooksRegistrationBitmap(uint16((1 << HOOKS_BEFORE_SWAP_OFFSET) | (1 << HOOKS_AFTER_INITIALIZE_OFFSET))); poolManager.initialize(dynamicFeeKey, SQRT_RATIO_1_1, ZERO_BYTES); - hook.setFee(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); - vm.expectRevert(IFees.FeeTooLarge.selector); + hook.setFee(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); + vm.expectRevert(IProtocolFees.FeeTooLarge.selector); vm.prank(address(dynamicFeeKey.hooks)); - poolManager.updateDynamicSwapFee(dynamicFeeKey, SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); + poolManager.updateDynamicLPFee(dynamicFeeKey, LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE + 1); } function testSwapWorks() public { - hook.setFee(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2); + hook.setFee(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2); // starts from price = 1 hook.setHooksRegistrationBitmap(uint16((1 << HOOKS_BEFORE_SWAP_OFFSET) | (1 << HOOKS_AFTER_INITIALIZE_OFFSET))); @@ -159,7 +159,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { } function testCacheDynamicFeeAndSwap() public { - hook.setFee(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2); + hook.setFee(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE / 2); hook.setHooksRegistrationBitmap(uint16((1 << HOOKS_BEFORE_SWAP_OFFSET) | (1 << HOOKS_AFTER_INITIALIZE_OFFSET))); // starts from price = 1 @@ -179,7 +179,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { CLPoolManagerRouter.SwapTestSettings memory testSettings = CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}); - bytes memory data = abi.encode(true, uint24(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE - 1)); + bytes memory data = abi.encode(true, uint24(LPFeeLibrary.ONE_HUNDRED_PERCENT_FEE - 1)); router.swap(dynamicFeeKey, params, testSettings, data); } @@ -189,7 +189,7 @@ contract CLPoolSwapFeeTest is Deployers, TokenFixture, Test { currency1: currency1, hooks: IHooks(address(0)), poolManager: poolManager, - fee: SwapFeeLibrary.DYNAMIC_FEE_FLAG, + fee: LPFeeLibrary.DYNAMIC_FEE_FLAG, parameters: CLPoolParametersHelper.setTickSpacing(bytes32(uint256(hook.getHooksRegistrationBitmap())), 1) }); diff --git a/test/vault/FakePoolManager.sol b/test/vault/FakePoolManager.sol index c0ad5bfe..b5177a6f 100644 --- a/test/vault/FakePoolManager.sol +++ b/test/vault/FakePoolManager.sol @@ -17,7 +17,5 @@ contract FakePoolManager is IPoolManager { vault.accountPoolBalanceDelta(poolKey, toBalanceDelta(delta0, delta1), msg.sender); } - function setProtocolFee(PoolKey memory key) external override {} - - function updateDynamicSwapFee(PoolKey memory key, uint24 newDynamicSwapFee) external override {} + function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicSwapFee) external override {} }