From 8624ae1524155e29147a731319bab82a6452a281 Mon Sep 17 00:00:00 2001 From: ChefMist <133624774+ChefMist@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:57:26 +0800 Subject: [PATCH] feat: do not callback hook if caller is hook --- .../BinHookTest#testBurnSucceedsWithHook.snap | 2 +- ...inHookTest#testDonateSucceedsWithHook.snap | 2 +- ...okTest#testInitializeSucceedsWithHook.snap | 2 +- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- ...oolManagerTest#testBurnNativeCurrency.snap | 2 +- ...BinPoolManagerTest#testGasBurnHalfBin.snap | 2 +- ...inPoolManagerTest#testGasBurnNineBins.snap | 2 +- .../BinPoolManagerTest#testGasBurnOneBin.snap | 2 +- .../BinPoolManagerTest#testGasDonate.snap | 2 +- ...nPoolManagerTest#testGasMintNneBins-1.snap | 2 +- ...nPoolManagerTest#testGasMintNneBins-2.snap | 2 +- ...inPoolManagerTest#testGasMintOneBin-1.snap | 2 +- ...inPoolManagerTest#testGasMintOneBin-2.snap | 2 +- ...olManagerTest#testGasSwapMultipleBins.snap | 2 +- ...nagerTest#testGasSwapOverBigBinIdGate.snap | 2 +- ...nPoolManagerTest#testGasSwapSingleBin.snap | 2 +- ...oolManagerTest#testMintNativeCurrency.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Burn.snap | 2 +- ...BinPoolManagerTest#testNoOpGas_Donate.snap | 2 +- ...oolManagerTest#testNoOpGas_Initialize.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Mint.snap | 2 +- .../BinPoolManagerTest#testNoOpGas_Swap.snap | 2 +- ...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 +- ...CLPoolManagerTest#testNoOp_gas_Donate.snap | 2 +- ...olManagerTest#testNoOp_gas_Initialize.snap | 2 +- ...nagerTest#testNoOp_gas_ModifyPosition.snap | 2 +- .../CLPoolManagerTest#testNoOp_gas_Swap.snap | 2 +- src/libraries/Hooks.sol | 12 +- src/pool-bin/BinPoolManager.sol | 30 +- src/pool-cl/CLPoolManager.sol | 34 +- test/libraries/Hooks/Hooks.t.sol | 34 +- test/pool-bin/BinHookSkipCallback.t.sol | 175 +++++++++ test/pool-bin/helpers/BaseBinTestHook.sol | 43 +++ test/pool-bin/helpers/BinLiquidityHelper.sol | 2 +- test/pool-bin/helpers/BinSkipCallbackHook.sol | 297 ++++++++++++++ test/pool-bin/helpers/BinSwapHelper.sol | 2 +- test/pool-cl/CLHookSkipCallback.t.sol | 159 ++++++++ test/pool-cl/helpers/BaseCLTestHook.sol | 43 +++ test/pool-cl/helpers/CLPoolManagerRouter.sol | 6 +- test/pool-cl/helpers/CLSkipCallbackHook.sol | 365 ++++++++++++++++++ 55 files changed, 1190 insertions(+), 96 deletions(-) create mode 100644 test/pool-bin/BinHookSkipCallback.t.sol create mode 100644 test/pool-bin/helpers/BinSkipCallbackHook.sol create mode 100644 test/pool-cl/CLHookSkipCallback.t.sol create mode 100644 test/pool-cl/helpers/CLSkipCallbackHook.sol diff --git a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap index e39570f1..322fcc75 100644 --- a/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testBurnSucceedsWithHook.snap @@ -1 +1 @@ -143699 \ No newline at end of file +142950 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap index f1685993..8dbedb00 100644 --- a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap @@ -1 +1 @@ -135043 \ No newline at end of file +134297 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap index feed235a..980a2587 100644 --- a/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testInitializeSucceedsWithHook.snap @@ -1 +1 @@ -108914 \ No newline at end of file +108533 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 11adaddb..ee5f839f 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -301091 \ No newline at end of file +300345 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index b3a4b1df..b6149057 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -139136 \ No newline at end of file +138390 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap index f57e6954..7cbc479f 100644 --- a/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testBurnNativeCurrency.snap @@ -1 +1 @@ -90479 \ No newline at end of file +93346 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap index 0d077daa..a4d12d29 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnHalfBin.snap @@ -1 +1 @@ -65672 \ No newline at end of file +68539 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap index 636780b6..7b2cfa14 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnNineBins.snap @@ -1 +1 @@ -149227 \ No newline at end of file +152077 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap index 5d28fd52..af5c4805 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasBurnOneBin.snap @@ -1 +1 @@ -66851 \ No newline at end of file +69718 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap index b6666ed0..4f7d508c 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap @@ -1 +1 @@ -52444 \ No newline at end of file +55313 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index b87a958f..1d9494c7 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -968444 \ No newline at end of file +971345 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index 6d2afda6..de47e2ca 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -119958 \ No newline at end of file +122859 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index 634f8883..63036760 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -337634 \ No newline at end of file +340528 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index b635cba9..23e9deb3 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -54695 \ No newline at end of file +57589 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 1d4e2bf7..9b31843b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -89312 \ No newline at end of file +92098 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 2035602e..53edc1db 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -91297 \ No newline at end of file +94083 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index bd838801..9d50608b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -70741 \ No newline at end of file +73587 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index ff78ebe9..ee190d58 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -319341 \ No newline at end of file +322235 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap index 768aed05..a9d320e2 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Burn.snap @@ -1 +1 @@ -41503 \ No newline at end of file +44517 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap index ca967971..38017dac 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Donate.snap @@ -1 +1 @@ -19392 \ No newline at end of file +20863 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap index a238b0f8..8bf3bdbc 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Initialize.snap @@ -1 +1 @@ -37117 \ No newline at end of file +42510 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap index e12ba3b7..c43230aa 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Mint.snap @@ -1 +1 @@ -39136 \ No newline at end of file +42175 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap index e75d7399..4fb042f4 100644 --- a/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap +++ b/.forge-snapshots/BinPoolManagerTest#testNoOpGas_Swap.snap @@ -1 +1 @@ -22507 \ No newline at end of file +25539 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap index f40efcf6..7c3fc46d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap +++ b/.forge-snapshots/BinPoolManagerTest#testSetProtocolFee.snap @@ -1 +1 @@ -8148 \ No newline at end of file +8153 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap index ed36e4f9..5d0fae35 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromEmpty.snap @@ -1 +1 @@ -348848 \ No newline at end of file +351744 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap index 234f701a..89dea19e 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_fromNonEmpty.snap @@ -1 +1 @@ -59396 \ No newline at end of file +62292 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap index 2c5a90c6..6c431e01 100644 --- a/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#addLiquidity_nativeToken.snap @@ -1 +1 @@ -242441 \ No newline at end of file +245337 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap index 79604bfa..a232f935 100644 --- a/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap +++ b/.forge-snapshots/CLPoolManagerTest#donateBothTokens.snap @@ -1 +1 @@ -82514 \ No newline at end of file +85388 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap index 499386f7..fb638ead 100644 --- a/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap +++ b/.forge-snapshots/CLPoolManagerTest#gasDonateOneToken.snap @@ -1 +1 @@ -52476 \ No newline at end of file +55350 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap index 8812e0dc..776b1c34 100644 --- a/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#initializeWithoutHooks.snap @@ -1 +1 @@ -36547 \ No newline at end of file +41952 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap index 1e077618..a283a34a 100644 --- a/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap +++ b/.forge-snapshots/CLPoolManagerTest#removeLiquidity_toNonEmpty.snap @@ -1 +1 @@ -42374 \ No newline at end of file +45262 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap index 8470dee1..cce42307 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_againstLiquidity.snap @@ -1 +1 @@ -54672 \ No newline at end of file +57550 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap index a15d1b9b..962a085b 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_leaveSurplusTokenInVault.snap @@ -1 +1 @@ -100748 \ No newline at end of file +103626 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap index 4aee4462..9cb0cc27 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_runOutOfLiquidity.snap @@ -1 +1 @@ -25040458 \ No newline at end of file +25043336 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap index 8490e1e1..e44e7c53 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_simple.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_simple.snap @@ -1 +1 @@ -35872 \ No newline at end of file +38750 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap index 017f6b15..ae2086a9 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_useSurplusTokenAsInput.snap @@ -1 +1 @@ -101510 \ No newline at end of file +104388 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap index 8fbed506..eee3d6fd 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withHooks.snap @@ -1 +1 @@ -41522 \ No newline at end of file +44489 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap index b8acbea3..5acefc72 100644 --- a/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap +++ b/.forge-snapshots/CLPoolManagerTest#swap_withNative.snap @@ -1 +1 @@ -35875 \ No newline at end of file +38753 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap index cfb243eb..fd89c046 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Donate.snap @@ -1 +1 @@ -19218 \ No newline at end of file +22231 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap index 91c6b09a..345509f1 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Initialize.snap @@ -1 +1 @@ -37497 \ No newline at end of file +42902 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap index 692872a2..2995ee9b 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_ModifyPosition.snap @@ -1 +1 @@ -29405 \ No newline at end of file +32427 \ No newline at end of file diff --git a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap index c6b439c0..e966df5c 100644 --- a/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap +++ b/.forge-snapshots/CLPoolManagerTest#testNoOp_gas_Swap.snap @@ -1 +1 @@ -21667 \ No newline at end of file +24680 \ No newline at end of file diff --git a/src/libraries/Hooks.sol b/src/libraries/Hooks.sol index 892ebdcf..f1e9be5b 100644 --- a/src/libraries/Hooks.sol +++ b/src/libraries/Hooks.sol @@ -45,14 +45,22 @@ library Hooks { } } - function shouldCall(bytes32 parameters, uint8 offset) internal pure returns (bool) { + /// @return true if parameter has offset enabled + function hasOffsetEnabled(bytes32 parameters, uint8 offset) internal pure returns (bool) { return parameters.decodeBool(offset); } + /// @notice checks if hook should be called -- based on 2 factors: + /// 1. whether pool.parameters has the callback offset registered + /// 2. whether msg.sender is the hook itself + function shouldCall(bytes32 parameters, uint8 offset, IHooks hook) internal view returns (bool) { + return hasOffsetEnabled(parameters, offset) && address(hook) != msg.sender; + } + /// @dev Verify hook return value matches no-op when these 2 conditions are met /// 1) Hook have permission for no-op /// 2) Return value is no-op selector function isValidNoOpCall(bytes32 parameters, uint8 noOpOffset, bytes4 selector) internal pure returns (bool) { - return shouldCall(parameters, noOpOffset) && selector == NO_OP_SELECTOR; + return hasOffsetEnabled(parameters, noOpOffset) && selector == NO_OP_SELECTOR; } } diff --git a/src/pool-bin/BinPoolManager.sol b/src/pool-bin/BinPoolManager.sol index 64c725d0..71d394db 100644 --- a/src/pool-bin/BinPoolManager.sol +++ b/src/pool-bin/BinPoolManager.sol @@ -106,7 +106,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { uint24 swapFee = key.fee.getInitialSwapFee(); if (swapFee.isSwapFeeTooLarge(SwapFeeLibrary.TEN_PERCENT_FEE)) revert FeeTooLarge(); - if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET, key.hooks)) { if (hooks.beforeInitialize(msg.sender, key, activeId, hookData) != IBinHooks.beforeInitialize.selector) { revert Hooks.InvalidHookResponse(); } @@ -120,7 +120,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Initialize(id, key.currency0, key.currency1, key.fee, binStep, hooks); - if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET, key.hooks)) { if (hooks.afterInitialize(msg.sender, key, activeId, hookData) != IBinHooks.afterInitialize.selector) { revert Hooks.InvalidHookResponse(); } @@ -139,7 +139,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET, key.hooks)) { bytes4 selector = hooks.beforeSwap(msg.sender, key, swapForY, amountIn, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -170,7 +170,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { emit Swap(id, msg.sender, delta.amount0(), delta.amount1(), activeId, swapFee, feeForProtocol); } - if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET, key.hooks)) { if (hooks.afterSwap(msg.sender, key, swapForY, amountIn, delta, hookData) != IBinHooks.afterSwap.selector) { revert Hooks.InvalidHookResponse(); } @@ -241,7 +241,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_MINT_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_MINT_OFFSET, key.hooks)) { bytes4 selector = hooks.beforeMint(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -274,7 +274,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Mint(id, msg.sender, mintArray.ids, mintArray.amounts, compositionFee, feeForProtocol); - if (key.parameters.shouldCall(HOOKS_AFTER_MINT_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_MINT_OFFSET, key.hooks)) { if (hooks.afterMint(msg.sender, key, params, delta, hookData) != IBinHooks.afterMint.selector) { revert Hooks.InvalidHookResponse(); } @@ -292,7 +292,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_BURN_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_BURN_OFFSET, key.hooks)) { bytes4 selector = hooks.beforeBurn(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -312,7 +312,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Burn(id, msg.sender, binIds, amountRemoved); - if (key.parameters.shouldCall(HOOKS_AFTER_BURN_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_BURN_OFFSET, key.hooks)) { if (hooks.afterBurn(msg.sender, key, params, delta, hookData) != IBinHooks.afterBurn.selector) { revert Hooks.InvalidHookResponse(); } @@ -330,7 +330,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { _checkPoolInitialized(id); IBinHooks hooks = IBinHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET, key.hooks)) { bytes4 selector = hooks.beforeDonate(msg.sender, key, amount0, amount1, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -347,7 +347,7 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Donate(id, msg.sender, delta.amount0(), delta.amount1(), binId); - if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET, key.hooks)) { if (hooks.afterDonate(msg.sender, key, amount0, amount1, hookData) != IBinHooks.afterDonate.selector) { revert Hooks.InvalidHookResponse(); } @@ -386,12 +386,12 @@ contract BinPoolManager is IBinPoolManager, Fees, Extsload { function _validateHookNoOp(PoolKey memory key) internal pure { // if no-op is active for hook, there must be a before* hook active too - if (key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (key.parameters.hasOffsetEnabled(HOOKS_NO_OP_OFFSET)) { if ( - !key.parameters.shouldCall(HOOKS_BEFORE_MINT_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_BURN_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET) + !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_MINT_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_BURN_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_DONATE_OFFSET) ) { revert Hooks.NoOpHookMissingBeforeCall(); } diff --git a/src/pool-cl/CLPoolManager.sol b/src/pool-cl/CLPoolManager.sol index 18e26f3d..13776cce 100644 --- a/src/pool-cl/CLPoolManager.sol +++ b/src/pool-cl/CLPoolManager.sol @@ -101,7 +101,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { uint24 swapFee = key.fee.getInitialSwapFee(); if (swapFee.isSwapFeeTooLarge(SwapFeeLibrary.ONE_HUNDRED_PERCENT_FEE)) revert FeeTooLarge(); - if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_INITIALIZE_OFFSET, key.hooks)) { if (hooks.beforeInitialize(msg.sender, key, sqrtPriceX96, hookData) != ICLHooks.beforeInitialize.selector) { revert Hooks.InvalidHookResponse(); } @@ -114,7 +114,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Initialize(id, key.currency0, key.currency1, key.fee, tickSpacing, hooks); - if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_INITIALIZE_OFFSET, key.hooks)) { if ( hooks.afterInitialize(msg.sender, key, sqrtPriceX96, tick, hookData) != ICLHooks.afterInitialize.selector @@ -138,7 +138,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { ICLHooks hooks = ICLHooks(address(key.hooks)); - if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET)) { + if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET, key.hooks)) { bytes4 selector = hooks.beforeAddLiquidity(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -146,7 +146,9 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { } else if (selector != ICLHooks.beforeAddLiquidity.selector) { revert Hooks.InvalidHookResponse(); } - } else if (params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET)) { + } else if ( + params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET, key.hooks) + ) { bytes4 selector = hooks.beforeRemoveLiquidity(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -171,13 +173,15 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit ModifyLiquidity(id, msg.sender, params.tickLower, params.tickUpper, params.liquidityDelta); - if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_AFTER_ADD_LIQUIDITY_OFFSET)) { + if (params.liquidityDelta > 0 && key.parameters.shouldCall(HOOKS_AFTER_ADD_LIQUIDITY_OFFSET, key.hooks)) { if ( hooks.afterAddLiquidity(msg.sender, key, params, delta, hookData) != ICLHooks.afterAddLiquidity.selector ) { revert Hooks.InvalidHookResponse(); } - } else if (params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET)) { + } else if ( + params.liquidityDelta <= 0 && key.parameters.shouldCall(HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET, key.hooks) + ) { if ( hooks.afterRemoveLiquidity(msg.sender, key, params, delta, hookData) != ICLHooks.afterRemoveLiquidity.selector @@ -200,7 +204,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { ICLHooks hooks = ICLHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET, key.hooks)) { bytes4 selector = hooks.beforeSwap(msg.sender, key, params, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -243,7 +247,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { state.protocolFee ); - if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_SWAP_OFFSET, key.hooks)) { if (hooks.afterSwap(msg.sender, key, params, delta, hookData) != ICLHooks.afterSwap.selector) { revert Hooks.InvalidHookResponse(); } @@ -262,7 +266,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { _checkPoolInitialized(id); ICLHooks hooks = ICLHooks(address(key.hooks)); - if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET, key.hooks)) { bytes4 selector = hooks.beforeDonate(msg.sender, key, amount0, amount1, hookData); if (key.parameters.isValidNoOpCall(HOOKS_NO_OP_OFFSET, selector)) { // Sentinel return value used to signify that a NoOp occurred. @@ -279,7 +283,7 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { /// @notice Make sure the first event is noted, so that later events from afterHook won't get mixed up with this one emit Donate(id, msg.sender, amount0, amount1, tick); - if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET)) { + if (key.parameters.shouldCall(HOOKS_AFTER_DONATE_OFFSET, key.hooks)) { if (hooks.afterDonate(msg.sender, key, amount0, amount1, hookData) != ICLHooks.afterDonate.selector) { revert Hooks.InvalidHookResponse(); } @@ -319,12 +323,12 @@ contract CLPoolManager is ICLPoolManager, Fees, Extsload { function _validateHookNoOp(PoolKey memory key) internal pure { // if no-op is active for hook, there must be a before* hook active too - if (key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (key.parameters.hasOffsetEnabled(HOOKS_NO_OP_OFFSET)) { if ( - !key.parameters.shouldCall(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_SWAP_OFFSET) - && !key.parameters.shouldCall(HOOKS_BEFORE_DONATE_OFFSET) + !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_SWAP_OFFSET) + && !key.parameters.hasOffsetEnabled(HOOKS_BEFORE_DONATE_OFFSET) ) { revert Hooks.NoOpHookMissingBeforeCall(); } diff --git a/test/libraries/Hooks/Hooks.t.sol b/test/libraries/Hooks/Hooks.t.sol index e14a860d..7fb583a2 100644 --- a/test/libraries/Hooks/Hooks.t.sol +++ b/test/libraries/Hooks/Hooks.t.sol @@ -71,24 +71,24 @@ contract HooksTest is Test { this.toCallAsCalldata(poolKey); } - function testShouldCall() public { + function testhasOffsetEnabled() public { // 0b1010101010101010 - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 0), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 1), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 2), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 3), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 4), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 5), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 6), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 7), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 8), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 9), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 10), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 11), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 12), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 13), true); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 14), false); - assertEq(Hooks.shouldCall(bytes32(uint256(0xaaaa)), 15), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 0), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 1), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 2), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 3), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 4), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 5), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 6), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 7), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 8), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 9), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 10), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 11), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 12), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 13), true); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 14), false); + assertEq(Hooks.hasOffsetEnabled(bytes32(uint256(0xaaaa)), 15), true); } function testIsValidNoOpCall(bytes32 parameters, uint8 noOpOffset, bytes4 selector) public { diff --git a/test/pool-bin/BinHookSkipCallback.t.sol b/test/pool-bin/BinHookSkipCallback.t.sol new file mode 100644 index 00000000..b66d7dff --- /dev/null +++ b/test/pool-bin/BinHookSkipCallback.t.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; +import {IBinPoolManager} from "../../src/pool-bin/interfaces/IBinPoolManager.sol"; +import {Vault} from "../../src/Vault.sol"; +import {Currency} from "../../src/types/Currency.sol"; +import {PoolKey} from "../../src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../../src/types/BalanceDelta.sol"; +import {BinPoolManager} from "../../src/pool-bin/BinPoolManager.sol"; +import {BinPool} from "../../src/pool-bin/libraries/BinPool.sol"; +import {PackedUint128Math} from "../../src/pool-bin/libraries/math/PackedUint128Math.sol"; +import {SafeCast} from "../../src/pool-bin/libraries/math/SafeCast.sol"; +import {BinPoolParametersHelper} from "../../src/pool-bin/libraries/BinPoolParametersHelper.sol"; +import {Constants} from "../../src/pool-bin/libraries/Constants.sol"; +import {IBinHooks} from "../../src/pool-bin/interfaces/IBinHooks.sol"; +import {BinFeeManagerHook} from "./helpers/BinFeeManagerHook.sol"; +import {IHooks} from "../../src/interfaces/IHooks.sol"; +import {IBinHooks} from "../../src/pool-bin/interfaces/IBinHooks.sol"; +import {BinSwapHelper} from "./helpers/BinSwapHelper.sol"; +import {BinLiquidityHelper} from "./helpers/BinLiquidityHelper.sol"; +import {BinDonateHelper} from "./helpers/BinDonateHelper.sol"; +import {BinTestHelper} from "./helpers/BinTestHelper.sol"; +import {Hooks} from "../../src/libraries/Hooks.sol"; +import {BinSkipCallbackHook} from "./helpers/BinSkipCallbackHook.sol"; + +contract BinHookSkipCallbackTest is Test, GasSnapshot, BinTestHelper { + using PoolIdLibrary for PoolKey; + using SafeCast for uint256; + using PackedUint128Math for bytes32; + using PackedUint128Math for uint128; + using BinPoolParametersHelper for bytes32; + + Vault public vault; + BinPoolManager public poolManager; + BinSkipCallbackHook public binSkipCallbackHook; + + BinSwapHelper public binSwapHelper; + BinLiquidityHelper public binLiquidityHelper; + BinDonateHelper public binDonateHelper; + + uint24 activeId = 2 ** 23; // where token0 and token1 price is the same + + PoolKey key; + bytes32 poolParam; + MockERC20 token0; + MockERC20 token1; + Currency currency0; + Currency currency1; + + function setUp() public { + vault = new Vault(); + poolManager = new BinPoolManager(IVault(address(vault)), 500000); + + vault.registerPoolManager(address(poolManager)); + + token0 = new MockERC20("TestA", "A", 18); + token1 = new MockERC20("TestB", "B", 18); + (token0, token1) = token0 < token1 ? (token0, token1) : (token1, token0); + currency0 = Currency.wrap(address(token0)); + currency1 = Currency.wrap(address(token1)); + + token0.mint(address(this), 1000 ether); + token1.mint(address(this), 1000 ether); + + IBinPoolManager iBinPoolManager = IBinPoolManager(address(poolManager)); + IVault iVault = IVault(address(vault)); + + binSwapHelper = new BinSwapHelper(iBinPoolManager, iVault); + binLiquidityHelper = new BinLiquidityHelper(iBinPoolManager, iVault); + binDonateHelper = new BinDonateHelper(iBinPoolManager, iVault); + token0.approve(address(binSwapHelper), 1000 ether); + token1.approve(address(binSwapHelper), 1000 ether); + token0.approve(address(binLiquidityHelper), 1000 ether); + token1.approve(address(binLiquidityHelper), 1000 ether); + token0.approve(address(binDonateHelper), 1000 ether); + token1.approve(address(binDonateHelper), 1000 ether); + + binSkipCallbackHook = new BinSkipCallbackHook(iVault, iBinPoolManager); + token0.approve(address(binSkipCallbackHook), 1000 ether); + token1.approve(address(binSkipCallbackHook), 1000 ether); + + key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: binSkipCallbackHook, + poolManager: IPoolManager(address(poolManager)), + fee: uint24(3000), // 3000 = 0.3% + parameters: bytes32(uint256(binSkipCallbackHook.getHooksRegistrationBitmap())).setBinStep(10) + }); + } + + function testInitialize_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testInitialize_NotfromHook() external { + poolManager.initialize(key, activeId, new bytes(0)); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testMint_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binLiquidityHelper.mint(key, mintParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testMint_NotFromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testBurn_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + IBinPoolManager.BurnParams memory burnParams = + _getSingleBinBurnLiquidityParams(key, poolManager, activeId, address(binSkipCallbackHook), 100); + + binSkipCallbackHook.burn(key, burnParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testBurn_NotFromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binLiquidityHelper.mint(key, mintParams, ""); + + IBinPoolManager.BurnParams memory burnParams = + _getSingleBinBurnLiquidityParams(key, poolManager, activeId, address(binLiquidityHelper), 100); + + binLiquidityHelper.burn(key, burnParams, ""); + + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 4); + } + + function testDonate_FromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + binSkipCallbackHook.donate(key, 10 ether, 10 ether, ""); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testDonate_NotFromHook() external { + binSkipCallbackHook.initialize(key, activeId, new bytes(0)); + + IBinPoolManager.MintParams memory mintParams = _getSingleBinMintParams(activeId, 1 ether, 1 ether); + binSkipCallbackHook.mint(key, mintParams, ""); + + binDonateHelper.donate(key, 10 ether, 10 ether, ""); + assertEq(binSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + receive() external payable {} +} diff --git a/test/pool-bin/helpers/BaseBinTestHook.sol b/test/pool-bin/helpers/BaseBinTestHook.sol index 58d30334..3b1085c9 100644 --- a/test/pool-bin/helpers/BaseBinTestHook.sol +++ b/test/pool-bin/helpers/BaseBinTestHook.sol @@ -1,6 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; +import { + HOOKS_BEFORE_INITIALIZE_OFFSET, + HOOKS_AFTER_INITIALIZE_OFFSET, + HOOKS_BEFORE_MINT_OFFSET, + HOOKS_AFTER_MINT_OFFSET, + HOOKS_BEFORE_BURN_OFFSET, + HOOKS_AFTER_BURN_OFFSET, + HOOKS_BEFORE_SWAP_OFFSET, + HOOKS_AFTER_SWAP_OFFSET, + HOOKS_BEFORE_DONATE_OFFSET, + HOOKS_AFTER_DONATE_OFFSET, + HOOKS_NO_OP_OFFSET +} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; import {IBinHooks} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; @@ -9,6 +22,20 @@ import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager. contract BaseBinTestHook is IBinHooks { error HookNotImplemented(); + struct Permissions { + bool beforeInitialize; + bool afterInitialize; + bool beforeMint; + bool afterMint; + bool beforeBurn; + bool afterBurn; + bool beforeSwap; + bool afterSwap; + bool beforeDonate; + bool afterDonate; + bool noOp; + } + function getHooksRegistrationBitmap() external view virtual returns (uint16) { revert HookNotImplemented(); } @@ -80,4 +107,20 @@ contract BaseBinTestHook is IBinHooks { { revert HookNotImplemented(); } + + function _hooksRegistrationBitmapFrom(Permissions memory permissions) internal pure returns (uint16) { + return uint16( + (permissions.beforeInitialize ? 1 << HOOKS_BEFORE_INITIALIZE_OFFSET : 0) + | (permissions.afterInitialize ? 1 << HOOKS_AFTER_INITIALIZE_OFFSET : 0) + | (permissions.beforeMint ? 1 << HOOKS_BEFORE_MINT_OFFSET : 0) + | (permissions.afterMint ? 1 << HOOKS_AFTER_MINT_OFFSET : 0) + | (permissions.beforeBurn ? 1 << HOOKS_BEFORE_BURN_OFFSET : 0) + | (permissions.afterBurn ? 1 << HOOKS_AFTER_BURN_OFFSET : 0) + | (permissions.beforeSwap ? 1 << HOOKS_BEFORE_SWAP_OFFSET : 0) + | (permissions.afterSwap ? 1 << HOOKS_AFTER_SWAP_OFFSET : 0) + | (permissions.beforeDonate ? 1 << HOOKS_BEFORE_DONATE_OFFSET : 0) + | (permissions.afterDonate ? 1 << HOOKS_AFTER_DONATE_OFFSET : 0) + | (permissions.noOp ? 1 << HOOKS_NO_OP_OFFSET : 0) + ); + } } diff --git a/test/pool-bin/helpers/BinLiquidityHelper.sol b/test/pool-bin/helpers/BinLiquidityHelper.sol index 17e203fd..b1c86634 100644 --- a/test/pool-bin/helpers/BinLiquidityHelper.sol +++ b/test/pool-bin/helpers/BinLiquidityHelper.sol @@ -88,7 +88,7 @@ contract BinLiquidityHelper { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); diff --git a/test/pool-bin/helpers/BinSkipCallbackHook.sol b/test/pool-bin/helpers/BinSkipCallbackHook.sol new file mode 100644 index 00000000..25557968 --- /dev/null +++ b/test/pool-bin/helpers/BinSkipCallbackHook.sol @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IVault} from "../../../src/interfaces/IVault.sol"; +import {Currency, CurrencyLibrary} from "../../../src/types/Currency.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import {PoolKey} from "../../../src/types/PoolKey.sol"; +import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; +import {IBinHooks} from "../../../src/pool-bin/interfaces/IBinHooks.sol"; +import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager.sol"; +import {Hooks} from "../../../src/libraries/Hooks.sol"; +import {BaseBinTestHook} from "./BaseBinTestHook.sol"; + +/// @notice CL hook which does a callback +contract BinSkipCallbackHook is BaseBinTestHook { + error InvalidAction(); + + using CurrencyLibrary for Currency; + using Hooks for bytes32; + + IBinPoolManager public immutable poolManager; + IVault public immutable vault; + ActionType public actionType; + + enum ActionType { + Mint, + Burn, + Swap, + Donate + } + + uint16 bitmap; + uint256 public hookCounterCallbackCount; + + constructor(IVault _vault, IBinPoolManager _poolManager) { + vault = _vault; + poolManager = _poolManager; + } + + function getHooksRegistrationBitmap() external pure override returns (uint16) { + return _hooksRegistrationBitmapFrom( + Permissions({ + beforeInitialize: true, + afterInitialize: true, + beforeMint: true, + afterMint: true, + beforeBurn: true, + afterBurn: true, + beforeSwap: true, + afterSwap: true, + beforeDonate: true, + afterDonate: true, + noOp: false + }) + ); + } + + struct CallbackData { + bytes action; + bytes rawCallbackData; + } + + struct BurnCallbackData { + address sender; + PoolKey key; + IBinPoolManager.BurnParams params; + bytes hookData; + } + + function burn(PoolKey memory key, IBinPoolManager.BurnParams memory params, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + BurnCallbackData memory data = BurnCallbackData(msg.sender, key, params, hookData); + actionType = ActionType.Burn; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + } + + struct MintCallbackData { + address sender; + PoolKey key; + IBinPoolManager.MintParams params; + bytes hookData; + } + + function mint(PoolKey memory key, IBinPoolManager.MintParams memory params, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + MintCallbackData memory data = MintCallbackData(msg.sender, key, params, hookData); + actionType = ActionType.Mint; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + } + + struct SwapCallbackData { + address sender; + TestSettings testSettings; + PoolKey key; + bool swapForY; + uint128 amountIn; + bytes hookData; + } + + struct TestSettings { + bool withdrawTokens; + bool settleUsingTransfer; + } + + function swap( + PoolKey memory key, + bool swapForY, + uint128 amountIn, + TestSettings memory testSettings, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + SwapCallbackData memory data = SwapCallbackData(msg.sender, testSettings, key, swapForY, amountIn, hookData); + actionType = ActionType.Swap; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + struct DonateCallbackData { + address sender; + PoolKey key; + uint128 amount0; + uint128 amount1; + bytes hookData; + } + + function donate(PoolKey memory key, uint128 amount0, uint128 amount1, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + DonateCallbackData memory data = DonateCallbackData(msg.sender, key, amount0, amount1, hookData); + actionType = ActionType.Donate; + + delta = abi.decode(vault.lock(abi.encode(data)), (BalanceDelta)); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + function lockAcquired(bytes calldata callbackData) external returns (bytes memory) { + require(msg.sender == address(vault)); + BalanceDelta delta; + PoolKey memory key; + address sender; + + if (actionType == ActionType.Burn) { + BurnCallbackData memory data = abi.decode(callbackData, (BurnCallbackData)); + + key = data.key; + sender = data.sender; + delta = poolManager.burn(data.key, data.params, data.hookData); + } else if (actionType == ActionType.Mint) { + MintCallbackData memory data = abi.decode(callbackData, (MintCallbackData)); + + key = data.key; + sender = data.sender; + (delta,) = poolManager.mint(data.key, data.params, data.hookData); + } else if (actionType == ActionType.Swap) { + SwapCallbackData memory data = abi.decode(callbackData, (SwapCallbackData)); + + key = data.key; + sender = data.sender; + delta = poolManager.swap(data.key, data.swapForY, data.amountIn, data.hookData); + } else if (actionType == ActionType.Donate) { + DonateCallbackData memory data = abi.decode(callbackData, (DonateCallbackData)); + + key = data.key; + sender = data.sender; + (delta,) = poolManager.donate(data.key, data.amount0, data.amount1, data.hookData); + } + + if (delta.amount0() > 0) { + if (key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(key.currency0); + } else { + IERC20(Currency.unwrap(key.currency0)).transferFrom(sender, address(vault), uint128(delta.amount0())); + vault.settle(key.currency0); + } + } + + if (delta.amount1() > 0) { + if (key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(key.currency1); + } else { + IERC20(Currency.unwrap(key.currency1)).transferFrom(sender, address(vault), uint128(delta.amount1())); + vault.settle(key.currency1); + } + } + + if (delta.amount0() < 0) { + vault.take(key.currency0, sender, uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + vault.take(key.currency1, sender, uint128(-delta.amount1())); + } + + return abi.encode(delta); + } + + function initialize(PoolKey memory key, uint24 activeId, bytes memory hookData) external { + poolManager.initialize(key, activeId, hookData); + } + + function beforeInitialize(address, PoolKey calldata, uint24, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeInitialize.selector; + } + + function afterInitialize(address, PoolKey calldata, uint24, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterInitialize.selector; + } + + function beforeMint(address, PoolKey calldata, IBinPoolManager.MintParams calldata, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeMint.selector; + } + + function afterMint(address, PoolKey calldata, IBinPoolManager.MintParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterMint.selector; + } + + function beforeBurn(address, PoolKey calldata, IBinPoolManager.BurnParams calldata, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeBurn.selector; + } + + function afterBurn(address, PoolKey calldata, IBinPoolManager.BurnParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterBurn.selector; + } + + function beforeSwap(address, PoolKey calldata, bool, uint128, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeSwap.selector; + } + + function afterSwap(address, PoolKey calldata, bool, uint128, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterSwap.selector; + } + + function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.beforeDonate.selector; + } + + function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return BinSkipCallbackHook.afterDonate.selector; + } +} diff --git a/test/pool-bin/helpers/BinSwapHelper.sol b/test/pool-bin/helpers/BinSwapHelper.sol index fc75b53b..9a764e1e 100644 --- a/test/pool-bin/helpers/BinSwapHelper.sol +++ b/test/pool-bin/helpers/BinSwapHelper.sol @@ -63,7 +63,7 @@ contract BinSwapHelper { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); diff --git a/test/pool-cl/CLHookSkipCallback.t.sol b/test/pool-cl/CLHookSkipCallback.t.sol new file mode 100644 index 00000000..f3bbadfa --- /dev/null +++ b/test/pool-cl/CLHookSkipCallback.t.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import "forge-std/Test.sol"; +import {GasSnapshot} from "forge-gas-snapshot/GasSnapshot.sol"; +import {IVault} from "../../src/interfaces/IVault.sol"; +import {Vault} from "../../src/Vault.sol"; +import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; +import {ICLPoolManager} from "../../src/pool-cl/interfaces/ICLPoolManager.sol"; +import {CLPoolManager} from "../../src/pool-cl/CLPoolManager.sol"; +import {CLPool} from "../../src/pool-cl/libraries/CLPool.sol"; +import {Currency, CurrencyLibrary} from "../../src/types/Currency.sol"; +import {PoolKey} from "../../src/types/PoolKey.sol"; +import {PoolId, PoolIdLibrary} from "../../src/types/PoolId.sol"; +import {IHooks} from "../../src/interfaces/IHooks.sol"; +import {Hooks} from "../../src/libraries/Hooks.sol"; +import {CLPoolManagerRouter} from "./helpers/CLPoolManagerRouter.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Deployers} from "./helpers/Deployers.sol"; +import {TokenFixture} from "../helpers/TokenFixture.sol"; +import {SwapFeeLibrary} from "../../src/libraries/SwapFeeLibrary.sol"; +import {CLPoolParametersHelper} from "../../src/pool-cl/libraries/CLPoolParametersHelper.sol"; +import {ParametersHelper} from "../../src/libraries/math/ParametersHelper.sol"; +import {CLSkipCallbackHook} from "./helpers/CLSkipCallbackHook.sol"; + +contract CLHookSkipCallbackTest is Test, Deployers, TokenFixture, GasSnapshot { + using PoolIdLibrary for PoolKey; + using CurrencyLibrary for Currency; + using CLPoolParametersHelper for bytes32; + using ParametersHelper for bytes32; + using SwapFeeLibrary for uint24; + + PoolKey key; + IVault public vault; + CLPoolManager public poolManager; + CLPoolManagerRouter public router; + // hook with all callback registered + CLSkipCallbackHook public clSkipCallbackHook; + + function setUp() public { + initializeTokens(); + (vault, poolManager) = createFreshManager(); + + router = new CLPoolManagerRouter(vault, poolManager); + clSkipCallbackHook = new CLSkipCallbackHook(vault, poolManager); + + IERC20(Currency.unwrap(currency0)).approve(address(router), 1000 ether); + IERC20(Currency.unwrap(currency1)).approve(address(router), 1000 ether); + IERC20(Currency.unwrap(currency0)).approve(address(clSkipCallbackHook), 1000 ether); + IERC20(Currency.unwrap(currency1)).approve(address(clSkipCallbackHook), 1000 ether); + + key = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: clSkipCallbackHook, + poolManager: poolManager, + fee: uint24(3000), + parameters: bytes32(uint256(clSkipCallbackHook.getHooksRegistrationBitmap())).setTickSpacing(10) + }); + } + + function testInitialize_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testInitialize_NotFromHook() external { + poolManager.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testModifyPosition_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Add and remove liquidity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18}), "" + ); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testModifyPosition_NotFromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Add and remove liquidity + router.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + router.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: -1e18}), "" + ); + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 4); + } + + function testSwap_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + clSkipCallbackHook.swap( + key, + ICLPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000, sqrtPriceLimitX96: SQRT_RATIO_1_2}), + CLSkipCallbackHook.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + "" + ); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testSwap_NotFromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + router.swap( + key, + ICLPoolManager.SwapParams({zeroForOne: true, amountSpecified: 1000, sqrtPriceLimitX96: SQRT_RATIO_1_2}), + CLPoolManagerRouter.SwapTestSettings({withdrawTokens: true, settleUsingTransfer: true}), + "" + ); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 2); + } + + function testDonate_FromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + clSkipCallbackHook.donate(key, 100, 200, ZERO_BYTES); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 0); + } + + function testDonate_NotFromHook() external { + clSkipCallbackHook.initialize(key, SQRT_RATIO_1_1, new bytes(0)); + + // Pre-req add some liqudiity + clSkipCallbackHook.modifyPosition( + key, ICLPoolManager.ModifyLiquidityParams({tickLower: -100, tickUpper: 100, liquidityDelta: 1e18}), "" + ); + + router.donate(key, 100, 200, ZERO_BYTES); + + assertEq(clSkipCallbackHook.hookCounterCallbackCount(), 2); + } +} diff --git a/test/pool-cl/helpers/BaseCLTestHook.sol b/test/pool-cl/helpers/BaseCLTestHook.sol index 84917e84..ebc52da4 100644 --- a/test/pool-cl/helpers/BaseCLTestHook.sol +++ b/test/pool-cl/helpers/BaseCLTestHook.sol @@ -1,6 +1,19 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; +import { + HOOKS_BEFORE_INITIALIZE_OFFSET, + HOOKS_AFTER_INITIALIZE_OFFSET, + HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET, + HOOKS_AFTER_ADD_LIQUIDITY_OFFSET, + HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET, + HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET, + HOOKS_BEFORE_SWAP_OFFSET, + HOOKS_AFTER_SWAP_OFFSET, + HOOKS_BEFORE_DONATE_OFFSET, + HOOKS_AFTER_DONATE_OFFSET, + HOOKS_NO_OP_OFFSET +} from "../../../src/pool-cl/interfaces/ICLHooks.sol"; import {PoolKey} from "../../../src/types/PoolKey.sol"; import {BalanceDelta} from "../../../src/types/BalanceDelta.sol"; import {ICLHooks} from "../../../src/pool-cl/interfaces/ICLHooks.sol"; @@ -9,6 +22,20 @@ import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol contract BaseCLTestHook is ICLHooks { error HookNotImplemented(); + struct Permissions { + bool beforeInitialize; + bool afterInitialize; + bool beforeAddLiquidity; + bool afterAddLiquidity; + bool beforeRemoveLiquidity; + bool afterRemoveLiquidity; + bool beforeSwap; + bool afterSwap; + bool beforeDonate; + bool afterDonate; + bool noOp; + } + function getHooksRegistrationBitmap() external view virtual returns (uint16) { revert HookNotImplemented(); } @@ -94,4 +121,20 @@ contract BaseCLTestHook is ICLHooks { { revert HookNotImplemented(); } + + function _hooksRegistrationBitmapFrom(Permissions memory permissions) internal pure returns (uint16) { + return uint16( + (permissions.beforeInitialize ? 1 << HOOKS_BEFORE_INITIALIZE_OFFSET : 0) + | (permissions.afterInitialize ? 1 << HOOKS_AFTER_INITIALIZE_OFFSET : 0) + | (permissions.beforeAddLiquidity ? 1 << HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET : 0) + | (permissions.afterAddLiquidity ? 1 << HOOKS_AFTER_ADD_LIQUIDITY_OFFSET : 0) + | (permissions.beforeRemoveLiquidity ? 1 << HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET : 0) + | (permissions.afterRemoveLiquidity ? 1 << HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET : 0) + | (permissions.beforeSwap ? 1 << HOOKS_BEFORE_SWAP_OFFSET : 0) + | (permissions.afterSwap ? 1 << HOOKS_AFTER_SWAP_OFFSET : 0) + | (permissions.beforeDonate ? 1 << HOOKS_BEFORE_DONATE_OFFSET : 0) + | (permissions.afterDonate ? 1 << HOOKS_AFTER_DONATE_OFFSET : 0) + | (permissions.noOp ? 1 << HOOKS_NO_OP_OFFSET : 0) + ); + } } diff --git a/test/pool-cl/helpers/CLPoolManagerRouter.sol b/test/pool-cl/helpers/CLPoolManagerRouter.sol index 08e423dc..a5113328 100644 --- a/test/pool-cl/helpers/CLPoolManagerRouter.sol +++ b/test/pool-cl/helpers/CLPoolManagerRouter.sol @@ -65,7 +65,7 @@ contract CLPoolManagerRouter { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); @@ -139,7 +139,7 @@ contract CLPoolManagerRouter { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); @@ -231,7 +231,7 @@ contract CLPoolManagerRouter { if (delta == BalanceDeltaLibrary.MAXIMUM_DELTA) { // check if the hook has permission to no-op, if true, return early - if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET)) { + if (!data.key.parameters.shouldCall(HOOKS_NO_OP_OFFSET, data.key.hooks)) { revert HookMissingNoOpPermission(); } return abi.encode(delta); diff --git a/test/pool-cl/helpers/CLSkipCallbackHook.sol b/test/pool-cl/helpers/CLSkipCallbackHook.sol new file mode 100644 index 00000000..dfc84be5 --- /dev/null +++ b/test/pool-cl/helpers/CLSkipCallbackHook.sol @@ -0,0 +1,365 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IVault} from "../../../src/interfaces/IVault.sol"; +import {Hooks} from "../../../src/libraries/Hooks.sol"; +import {ICLPoolManager} from "../../../src/pool-cl/interfaces/ICLPoolManager.sol"; +import {PoolKey} from "../../../src/types/PoolKey.sol"; +import {Currency, CurrencyLibrary} from "../../../src/types/Currency.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {BalanceDelta, BalanceDeltaLibrary} from "../../../src/types/BalanceDelta.sol"; +import {BaseCLTestHook} from "./BaseCLTestHook.sol"; + +/// @notice CL hook which does a callback +contract CLSkipCallbackHook is BaseCLTestHook { + error InvalidAction(); + + using CurrencyLibrary for Currency; + using Hooks for bytes32; + + IVault public immutable vault; + ICLPoolManager public immutable poolManager; + + uint16 bitmap; + uint256 public hookCounterCallbackCount; + + constructor(IVault _vault, ICLPoolManager _poolManager) { + vault = _vault; + poolManager = _poolManager; + } + + function getHooksRegistrationBitmap() external pure override returns (uint16) { + return _hooksRegistrationBitmapFrom( + Permissions({ + beforeInitialize: true, + afterInitialize: true, + beforeAddLiquidity: true, + afterAddLiquidity: true, + beforeRemoveLiquidity: true, + afterRemoveLiquidity: true, + beforeSwap: true, + afterSwap: true, + beforeDonate: true, + afterDonate: true, + noOp: false + }) + ); + } + + struct CallbackData { + bytes action; + bytes rawCallbackData; + } + + function initialize(PoolKey memory key, uint160 sqrtPriceX96, bytes memory hookData) external { + poolManager.initialize(key, sqrtPriceX96, hookData); + } + + struct ModifyPositionCallbackData { + address sender; + PoolKey key; + ICLPoolManager.ModifyLiquidityParams params; + bytes hookData; + } + + function modifyPosition( + PoolKey memory key, + ICLPoolManager.ModifyLiquidityParams memory params, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + delta = abi.decode( + vault.lock( + abi.encode("modifyPosition", abi.encode(ModifyPositionCallbackData(msg.sender, key, params, hookData))) + ), + (BalanceDelta) + ); + + // if any ethers left + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + function modifyPositionCallback(bytes memory rawData) private returns (bytes memory) { + ModifyPositionCallbackData memory data = abi.decode(rawData, (ModifyPositionCallbackData)); + + BalanceDelta delta = poolManager.modifyLiquidity(data.key, data.params, data.hookData); + if (delta.amount0() > 0) { + if (data.key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + } else { + IERC20(Currency.unwrap(data.key.currency0)).transferFrom( + data.sender, address(vault), uint128(delta.amount0()) + ); + vault.settle(data.key.currency0); + } + } + if (delta.amount1() > 0) { + if (data.key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(data.key.currency1); + } else { + IERC20(Currency.unwrap(data.key.currency1)).transferFrom( + data.sender, address(vault), uint128(delta.amount1()) + ); + vault.settle(data.key.currency1); + } + } + + if (delta.amount0() < 0) { + vault.take(data.key.currency0, data.sender, uint128(-delta.amount0())); + } + if (delta.amount1() < 0) { + vault.take(data.key.currency1, data.sender, uint128(-delta.amount1())); + } + + return abi.encode(delta); + } + + struct SwapTestSettings { + bool withdrawTokens; + bool settleUsingTransfer; + } + + struct SwapCallbackData { + address sender; + SwapTestSettings testSettings; + PoolKey key; + ICLPoolManager.SwapParams params; + bytes hookData; + } + + function swap( + PoolKey memory key, + ICLPoolManager.SwapParams memory params, + SwapTestSettings memory testSettings, + bytes memory hookData + ) external payable returns (BalanceDelta delta) { + delta = abi.decode( + vault.lock( + abi.encode("swap", abi.encode(SwapCallbackData(msg.sender, testSettings, key, params, hookData))) + ), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + + function swapCallback(bytes memory rawData) private returns (bytes memory) { + SwapCallbackData memory data = abi.decode(rawData, (SwapCallbackData)); + + BalanceDelta delta = poolManager.swap(data.key, data.params, data.hookData); + if (data.params.zeroForOne) { + if (delta.amount0() > 0) { + if (data.testSettings.settleUsingTransfer) { + if (data.key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + } else { + IERC20(Currency.unwrap(data.key.currency0)).transferFrom( + data.sender, address(vault), uint128(delta.amount0()) + ); + vault.settle(data.key.currency0); + } + } else { + // the received hook on this transfer will burn the tokens + vault.transferFrom(data.sender, address(this), data.key.currency0, uint128(delta.amount0())); + vault.burn(address(this), data.key.currency0, uint128(delta.amount0())); + } + } + if (delta.amount1() < 0) { + if (data.testSettings.withdrawTokens) { + vault.take(data.key.currency1, data.sender, uint128(-delta.amount1())); + } else { + vault.mint(data.sender, data.key.currency1, uint128(-delta.amount1())); + } + } + } else { + if (delta.amount1() > 0) { + if (data.testSettings.settleUsingTransfer) { + if (data.key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(data.key.currency1); + } else { + IERC20(Currency.unwrap(data.key.currency1)).transferFrom( + data.sender, address(vault), uint128(delta.amount1()) + ); + vault.settle(data.key.currency1); + } + } else { + // the received hook on this transfer will burn the tokens + vault.transferFrom(data.sender, address(this), data.key.currency1, uint128(delta.amount1())); + vault.burn(address(this), data.key.currency1, uint128(delta.amount1())); + } + } + if (delta.amount0() < 0) { + if (data.testSettings.withdrawTokens) { + vault.take(data.key.currency0, data.sender, uint128(-delta.amount0())); + } else { + vault.mint(data.sender, data.key.currency0, uint128(-delta.amount0())); + } + } + } + + return abi.encode(delta); + } + + struct DonateCallbackData { + address sender; + PoolKey key; + uint256 amount0; + uint256 amount1; + bytes hookData; + } + + function donate(PoolKey memory key, uint256 amount0, uint256 amount1, bytes memory hookData) + external + payable + returns (BalanceDelta delta) + { + delta = abi.decode( + vault.lock( + abi.encode("donate", abi.encode(DonateCallbackData(msg.sender, key, amount0, amount1, hookData))) + ), + (BalanceDelta) + ); + + uint256 ethBalance = address(this).balance; + if (ethBalance > 0) { + CurrencyLibrary.NATIVE.transfer(msg.sender, ethBalance); + } + } + + function donateCallback(bytes memory rawData) private returns (bytes memory) { + DonateCallbackData memory data = abi.decode(rawData, (DonateCallbackData)); + + BalanceDelta delta = poolManager.donate(data.key, data.amount0, data.amount1, data.hookData); + if (delta.amount0() > 0) { + if (data.key.currency0.isNative()) { + vault.settle{value: uint128(delta.amount0())}(data.key.currency0); + } else { + IERC20(Currency.unwrap(data.key.currency0)).transferFrom( + data.sender, address(vault), uint128(delta.amount0()) + ); + vault.settle(data.key.currency0); + } + } + if (delta.amount1() > 0) { + if (data.key.currency1.isNative()) { + vault.settle{value: uint128(delta.amount1())}(data.key.currency1); + } else { + IERC20(Currency.unwrap(data.key.currency1)).transferFrom( + data.sender, address(vault), uint128(delta.amount1()) + ); + vault.settle(data.key.currency1); + } + } + + return abi.encode(delta); + } + + function lockAcquired(bytes calldata data) external returns (bytes memory) { + (bytes memory action, bytes memory rawCallbackData) = abi.decode(data, (bytes, bytes)); + + if (keccak256(action) == keccak256("modifyPosition")) { + return modifyPositionCallback(rawCallbackData); + } else if (keccak256(action) == keccak256("swap")) { + return swapCallback(rawCallbackData); + } else if (keccak256(action) == keccak256("donate")) { + return donateCallback(rawCallbackData); + } else { + revert InvalidAction(); + } + } + + function beforeInitialize(address, PoolKey calldata, uint160, bytes calldata) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeInitialize.selector; + } + + function afterInitialize(address, PoolKey calldata, uint160, int24, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterInitialize.selector; + } + + function beforeAddLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeAddLiquidity.selector; + } + + function afterAddLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterAddLiquidity.selector; + } + + function beforeRemoveLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeRemoveLiquidity.selector; + } + + function afterRemoveLiquidity( + address, + PoolKey calldata, + ICLPoolManager.ModifyLiquidityParams calldata, + BalanceDelta, + bytes calldata + ) external override returns (bytes4) { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterRemoveLiquidity.selector; + } + + function beforeSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeSwap.selector; + } + + function afterSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterSwap.selector; + } + + function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.beforeDonate.selector; + } + + function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) + external + override + returns (bytes4) + { + hookCounterCallbackCount++; + return CLSkipCallbackHook.afterDonate.selector; + } +}