├── .gitignore ├── foundry.toml ├── .gitmodules ├── script └── Counter.s.sol ├── README.md ├── remappings.txt ├── .github └── workflows │ └── test.yml ├── LICENSE ├── src ├── pool-bin │ ├── BinCounterHook.sol │ └── BinBaseHook.sol └── pool-cl │ ├── CLCounterHook.sol │ └── CLBaseHook.sol └── test ├── pool-bin ├── BinCounterHook.t.sol └── utils │ └── BinTestUtils.sol └── pool-cl ├── CLCounterHook.t.sol └── utils └── CLTestUtils.sol /.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | cache/ 3 | crytic-export/ 4 | node_modules/ 5 | typechain/ 6 | foundry-out/ 7 | .vscode/ 8 | .DS_Store 9 | .idea 10 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'src' 3 | out = 'foundry-out' 4 | libs = ["lib"] 5 | solc_version = '0.8.26' 6 | evm_version = 'cancun' 7 | via_ir = true 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/infinity-universal-router"] 5 | path = lib/infinity-universal-router 6 | url = https://github.com/pancakeswap/infinity-universal-router 7 | -------------------------------------------------------------------------------- /script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console} from "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Infinity hooks template 2 | 3 | [`Use this Template`](https://github.com/new?owner=pancakeswap&template_name=infinity-hooks-template&template_owner=pancakeswap) 4 | 5 | ## Prerequisite 6 | 7 | 1. Install foundry, see https://book.getfoundry.sh/getting-started/installation 8 | 9 | ## Running test 10 | 11 | 1. Install dependencies with `forge install` 12 | 2. Run test with `forge test` 13 | 14 | ## Description 15 | 16 | This repository contains example counter hook for both CL and Bin pool types. 17 | -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | forge-std/=lib/forge-std/src/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | openzeppelin-contracts/=lib/infinity-universal-router/lib/infinity-periphery/lib/infinity-core/lib/openzeppelin-contracts/ 4 | solmate/=lib/infinity-universal-router/lib/infinity-periphery/lib/infinity-core/lib/solmate/ 5 | infinity-core/=lib/infinity-universal-router/lib/infinity-periphery/lib/infinity-core/ 6 | infinity-periphery/=lib/infinity-universal-router/lib/infinity-periphery/ 7 | infinity-universal-router/=lib/infinity-universal-router/ 8 | permit2/=lib/infinity-universal-router/lib/infinity-periphery/lib/permit2/ 9 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | env: 10 | FOUNDRY_PROFILE: ci 11 | 12 | jobs: 13 | check: 14 | strategy: 15 | fail-fast: true 16 | 17 | name: Foundry project 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: recursive 23 | 24 | - name: Install Foundry 25 | uses: foundry-rs/foundry-toolchain@v1 26 | with: 27 | version: nightly 28 | 29 | - name: Run Forge build 30 | run: | 31 | forge --version 32 | forge build 33 | id: build 34 | 35 | - name: Run Forge tests 36 | run: | 37 | forge test -vvv 38 | id: test 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 saucepoint 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/pool-bin/BinCounterHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 5 | import {BalanceDelta, BalanceDeltaLibrary} from "infinity-core/src/types/BalanceDelta.sol"; 6 | import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "infinity-core/src/types/BeforeSwapDelta.sol"; 7 | import {PoolId, PoolIdLibrary} from "infinity-core/src/types/PoolId.sol"; 8 | import {IBinPoolManager} from "infinity-core/src/pool-bin/interfaces/IBinPoolManager.sol"; 9 | import {BinBaseHook} from "./BinBaseHook.sol"; 10 | 11 | /// @notice BinCounterHook is a contract that counts the number of times a hook is called 12 | /// @dev note the code is not production ready, it is only to share how a hook looks like 13 | contract BinCounterHook is BinBaseHook { 14 | using PoolIdLibrary for PoolKey; 15 | 16 | mapping(PoolId => uint256 count) public beforeMintCount; 17 | mapping(PoolId => uint256 count) public afterMintCount; 18 | mapping(PoolId => uint256 count) public beforeSwapCount; 19 | mapping(PoolId => uint256 count) public afterSwapCount; 20 | 21 | constructor(IBinPoolManager _poolManager) BinBaseHook(_poolManager) {} 22 | 23 | function getHooksRegistrationBitmap() external pure override returns (uint16) { 24 | return _hooksRegistrationBitmapFrom( 25 | Permissions({ 26 | beforeInitialize: false, 27 | afterInitialize: false, 28 | beforeMint: true, 29 | afterMint: true, 30 | beforeBurn: false, 31 | afterBurn: false, 32 | beforeSwap: true, 33 | afterSwap: true, 34 | beforeDonate: false, 35 | afterDonate: false, 36 | beforeSwapReturnDelta: false, 37 | afterSwapReturnDelta: false, 38 | afterMintReturnDelta: false, 39 | afterBurnReturnDelta: false 40 | }) 41 | ); 42 | } 43 | 44 | function _beforeMint(address, PoolKey calldata key, IBinPoolManager.MintParams calldata, bytes calldata) 45 | internal 46 | override 47 | returns (bytes4, uint24) 48 | { 49 | beforeMintCount[key.toId()]++; 50 | return (this.beforeMint.selector, 0); 51 | } 52 | 53 | function _afterMint( 54 | address, 55 | PoolKey calldata key, 56 | IBinPoolManager.MintParams calldata, 57 | BalanceDelta, 58 | bytes calldata 59 | ) internal override returns (bytes4, BalanceDelta) { 60 | afterMintCount[key.toId()]++; 61 | return (this.afterMint.selector, BalanceDeltaLibrary.ZERO_DELTA); 62 | } 63 | 64 | function _beforeSwap(address, PoolKey calldata key, bool, int128, bytes calldata) 65 | internal 66 | override 67 | returns (bytes4, BeforeSwapDelta, uint24) 68 | { 69 | beforeSwapCount[key.toId()]++; 70 | return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); 71 | } 72 | 73 | function _afterSwap(address, PoolKey calldata key, bool, int128, BalanceDelta, bytes calldata) 74 | internal 75 | override 76 | returns (bytes4, int128) 77 | { 78 | afterSwapCount[key.toId()]++; 79 | return (this.afterSwap.selector, 0); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/pool-bin/BinCounterHook.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.24; 3 | 4 | import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | import {Currency} from "infinity-core/src/types/Currency.sol"; 7 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 8 | import {BinPoolParametersHelper} from "infinity-core/src/pool-bin/libraries/BinPoolParametersHelper.sol"; 9 | import {BinCounterHook} from "../../src/pool-bin/BinCounterHook.sol"; 10 | import {BinTestUtils} from "./utils/BinTestUtils.sol"; 11 | import {PoolIdLibrary} from "infinity-core/src/types/PoolId.sol"; 12 | import {IBinRouterBase} from "infinity-periphery/src/pool-bin/interfaces/IBinRouterBase.sol"; 13 | 14 | contract BinCounterHookTest is Test, BinTestUtils { 15 | using PoolIdLibrary for PoolKey; 16 | using BinPoolParametersHelper for bytes32; 17 | 18 | BinCounterHook counterHook; 19 | Currency currency0; 20 | Currency currency1; 21 | PoolKey key; 22 | uint24 ACTIVE_ID = 2 ** 23; 23 | 24 | function setUp() public { 25 | (currency0, currency1) = deployContractsWithTokens(); 26 | counterHook = new BinCounterHook(poolManager); 27 | 28 | // create the pool key 29 | key = PoolKey({ 30 | currency0: currency0, 31 | currency1: currency1, 32 | hooks: counterHook, 33 | poolManager: poolManager, 34 | fee: uint24(3000), 35 | // binstep: 10 = 0.1% price jump per bin 36 | parameters: bytes32(uint256(counterHook.getHooksRegistrationBitmap())).setBinStep(10) 37 | }); 38 | 39 | // initialize pool at 1:1 price point (assume stablecoin pair) 40 | poolManager.initialize(key, ACTIVE_ID); 41 | } 42 | 43 | function testLiquidityCallback() public { 44 | assertEq(counterHook.beforeMintCount(key.toId()), 0); 45 | assertEq(counterHook.afterMintCount(key.toId()), 0); 46 | 47 | MockERC20(Currency.unwrap(currency0)).mint(address(this), 1 ether); 48 | MockERC20(Currency.unwrap(currency1)).mint(address(this), 1 ether); 49 | addLiquidity(key, 1 ether, 1 ether, ACTIVE_ID, 3, address(this)); 50 | 51 | assertEq(counterHook.beforeMintCount(key.toId()), 1); 52 | assertEq(counterHook.afterMintCount(key.toId()), 1); 53 | } 54 | 55 | function testSwapCallback() public { 56 | MockERC20(Currency.unwrap(currency0)).mint(address(this), 1 ether); 57 | MockERC20(Currency.unwrap(currency1)).mint(address(this), 1 ether); 58 | addLiquidity(key, 1 ether, 1 ether, ACTIVE_ID, 3, address(this)); 59 | 60 | assertEq(counterHook.beforeSwapCount(key.toId()), 0); 61 | assertEq(counterHook.afterSwapCount(key.toId()), 0); 62 | 63 | MockERC20(Currency.unwrap(currency0)).mint(address(this), 0.1 ether); 64 | exactInputSingle( 65 | IBinRouterBase.BinSwapExactInputSingleParams({ 66 | poolKey: key, 67 | swapForY: true, 68 | amountIn: 0.1 ether, 69 | amountOutMinimum: 0, 70 | hookData: new bytes(0) 71 | }) 72 | ); 73 | 74 | assertEq(counterHook.beforeSwapCount(key.toId()), 1); 75 | assertEq(counterHook.afterSwapCount(key.toId()), 1); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/pool-cl/CLCounterHook.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.24; 3 | 4 | import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | import {Constants} from "infinity-core/test/pool-cl/helpers/Constants.sol"; 7 | import {Currency} from "infinity-core/src/types/Currency.sol"; 8 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 9 | import {CLPoolParametersHelper} from "infinity-core/src/pool-cl/libraries/CLPoolParametersHelper.sol"; 10 | import {CLCounterHook} from "../../src/pool-cl/CLCounterHook.sol"; 11 | import {CLTestUtils} from "./utils/CLTestUtils.sol"; 12 | import {CLPoolParametersHelper} from "infinity-core/src/pool-cl/libraries/CLPoolParametersHelper.sol"; 13 | import {PoolIdLibrary} from "infinity-core/src/types/PoolId.sol"; 14 | import {ICLRouterBase} from "infinity-periphery/src/pool-cl/interfaces/ICLRouterBase.sol"; 15 | 16 | contract CLCounterHookTest is Test, CLTestUtils { 17 | using PoolIdLibrary for PoolKey; 18 | using CLPoolParametersHelper for bytes32; 19 | 20 | CLCounterHook hook; 21 | Currency currency0; 22 | Currency currency1; 23 | PoolKey key; 24 | 25 | function setUp() public { 26 | (currency0, currency1) = deployContractsWithTokens(); 27 | hook = new CLCounterHook(poolManager); 28 | 29 | // create the pool key 30 | key = PoolKey({ 31 | currency0: currency0, 32 | currency1: currency1, 33 | hooks: hook, 34 | poolManager: poolManager, 35 | fee: uint24(3000), // 0.3% fee 36 | // tickSpacing: 10 37 | parameters: bytes32(uint256(hook.getHooksRegistrationBitmap())).setTickSpacing(10) 38 | }); 39 | 40 | // initialize pool at 1:1 price point (assume stablecoin pair) 41 | poolManager.initialize(key, Constants.SQRT_RATIO_1_1); 42 | } 43 | 44 | function testLiquidityCallback() public { 45 | assertEq(hook.beforeAddLiquidityCount(key.toId()), 0); 46 | assertEq(hook.afterAddLiquidityCount(key.toId()), 0); 47 | 48 | MockERC20(Currency.unwrap(currency0)).mint(address(this), 1 ether); 49 | MockERC20(Currency.unwrap(currency1)).mint(address(this), 1 ether); 50 | addLiquidity(key, 1 ether, 1 ether, -60, 60, address(this)); 51 | 52 | assertEq(hook.beforeAddLiquidityCount(key.toId()), 1); 53 | assertEq(hook.afterAddLiquidityCount(key.toId()), 1); 54 | } 55 | 56 | function testSwapCallback() public { 57 | MockERC20(Currency.unwrap(currency0)).mint(address(this), 1 ether); 58 | MockERC20(Currency.unwrap(currency1)).mint(address(this), 1 ether); 59 | addLiquidity(key, 1 ether, 1 ether, -60, 60, address(this)); 60 | 61 | assertEq(hook.beforeSwapCount(key.toId()), 0); 62 | assertEq(hook.afterSwapCount(key.toId()), 0); 63 | 64 | MockERC20(Currency.unwrap(currency0)).mint(address(this), 0.1 ether); 65 | exactInputSingle( 66 | ICLRouterBase.CLSwapExactInputSingleParams({ 67 | poolKey: key, 68 | zeroForOne: true, 69 | amountIn: 0.1 ether, 70 | amountOutMinimum: 0, 71 | hookData: new bytes(0) 72 | }) 73 | ); 74 | 75 | assertEq(hook.beforeSwapCount(key.toId()), 1); 76 | assertEq(hook.afterSwapCount(key.toId()), 1); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/pool-cl/CLCounterHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 5 | import {BalanceDelta, BalanceDeltaLibrary} from "infinity-core/src/types/BalanceDelta.sol"; 6 | import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "infinity-core/src/types/BeforeSwapDelta.sol"; 7 | import {PoolId, PoolIdLibrary} from "infinity-core/src/types/PoolId.sol"; 8 | import {ICLPoolManager} from "infinity-core/src/pool-cl/interfaces/ICLPoolManager.sol"; 9 | import {CLBaseHook} from "./CLBaseHook.sol"; 10 | 11 | /// @notice CLCounterHook is a contract that counts the number of times a hook is called 12 | /// @dev note the code is not production ready, it is only to share how a hook looks like 13 | contract CLCounterHook is CLBaseHook { 14 | using PoolIdLibrary for PoolKey; 15 | 16 | mapping(PoolId => uint256 count) public beforeAddLiquidityCount; 17 | mapping(PoolId => uint256 count) public afterAddLiquidityCount; 18 | mapping(PoolId => uint256 count) public beforeSwapCount; 19 | mapping(PoolId => uint256 count) public afterSwapCount; 20 | 21 | constructor(ICLPoolManager _poolManager) CLBaseHook(_poolManager) {} 22 | 23 | function getHooksRegistrationBitmap() external pure override returns (uint16) { 24 | return _hooksRegistrationBitmapFrom( 25 | Permissions({ 26 | beforeInitialize: false, 27 | afterInitialize: false, 28 | beforeAddLiquidity: true, 29 | afterAddLiquidity: true, 30 | beforeRemoveLiquidity: false, 31 | afterRemoveLiquidity: false, 32 | beforeSwap: true, 33 | afterSwap: true, 34 | beforeDonate: false, 35 | afterDonate: false, 36 | beforeSwapReturnDelta: false, 37 | afterSwapReturnDelta: false, 38 | afterAddLiquidityReturnDelta: false, 39 | afterRemoveLiquidityReturnDelta: false 40 | }) 41 | ); 42 | } 43 | 44 | function _beforeAddLiquidity( 45 | address, 46 | PoolKey calldata key, 47 | ICLPoolManager.ModifyLiquidityParams calldata, 48 | bytes calldata 49 | ) internal override returns (bytes4) { 50 | beforeAddLiquidityCount[key.toId()]++; 51 | return this.beforeAddLiquidity.selector; 52 | } 53 | 54 | function _afterAddLiquidity( 55 | address, 56 | PoolKey calldata key, 57 | ICLPoolManager.ModifyLiquidityParams calldata, 58 | BalanceDelta, 59 | BalanceDelta, 60 | bytes calldata 61 | ) internal override returns (bytes4, BalanceDelta) { 62 | afterAddLiquidityCount[key.toId()]++; 63 | return (this.afterAddLiquidity.selector, BalanceDeltaLibrary.ZERO_DELTA); 64 | } 65 | 66 | function _beforeSwap(address, PoolKey calldata key, ICLPoolManager.SwapParams calldata, bytes calldata) 67 | internal 68 | override 69 | returns (bytes4, BeforeSwapDelta, uint24) 70 | { 71 | beforeSwapCount[key.toId()]++; 72 | return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); 73 | } 74 | 75 | function _afterSwap(address, PoolKey calldata key, ICLPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) 76 | internal 77 | override 78 | returns (bytes4, int128) 79 | { 80 | afterSwapCount[key.toId()]++; 81 | return (this.afterSwap.selector, 0); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/pool-cl/utils/CLTestUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.24; 3 | 4 | import {IERC20} from "forge-std/interfaces/IERC20.sol"; 5 | import {Test, console} from "forge-std/Test.sol"; 6 | import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 7 | import {CLPoolManager} from "infinity-core/src/pool-cl/CLPoolManager.sol"; 8 | import {Vault} from "infinity-core/src/Vault.sol"; 9 | import {Currency} from "infinity-core/src/types/Currency.sol"; 10 | import {SortTokens} from "infinity-core/test/helpers/SortTokens.sol"; 11 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 12 | import {CLPositionManager} from "infinity-periphery/src/pool-cl/CLPositionManager.sol"; 13 | import {CLPositionDescriptorOffChain} from "infinity-periphery/src/pool-cl/CLPositionDescriptorOffChain.sol"; 14 | import {ICLPositionManager} from "infinity-periphery/src/pool-cl/interfaces/ICLPositionManager.sol"; 15 | import {ICLRouterBase} from "infinity-periphery/src/pool-cl/interfaces/ICLRouterBase.sol"; 16 | import {Planner, Plan} from "infinity-periphery/src/libraries/Planner.sol"; 17 | import {Actions} from "infinity-periphery/src/libraries/Actions.sol"; 18 | import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; 19 | import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; 20 | import {UniversalRouter, RouterParameters} from "infinity-universal-router/src/UniversalRouter.sol"; 21 | import {Commands} from "infinity-universal-router/src/libraries/Commands.sol"; 22 | import {ActionConstants} from "infinity-periphery/src/libraries/ActionConstants.sol"; 23 | import {LiquidityAmounts} from "infinity-periphery/src/pool-cl/libraries/LiquidityAmounts.sol"; 24 | import {TickMath} from "infinity-core/src/pool-cl/libraries/TickMath.sol"; 25 | import {PoolId, PoolIdLibrary} from "infinity-core/src/types/PoolId.sol"; 26 | import {IWETH9} from "infinity-periphery/src/interfaces/external/IWETH9.sol"; 27 | 28 | contract CLTestUtils is DeployPermit2 { 29 | using Planner for Plan; 30 | using PoolIdLibrary for PoolKey; 31 | 32 | Vault vault; 33 | CLPoolManager poolManager; 34 | CLPositionManager positionManager; 35 | IAllowanceTransfer permit2; 36 | UniversalRouter universalRouter; 37 | 38 | function deployContractsWithTokens() internal returns (Currency, Currency) { 39 | vault = new Vault(); 40 | poolManager = new CLPoolManager(vault); 41 | vault.registerApp(address(poolManager)); 42 | 43 | permit2 = IAllowanceTransfer(deployPermit2()); 44 | CLPositionDescriptorOffChain positionDescriptor = new CLPositionDescriptorOffChain("https://pancakeswap.finance/infinity/pool-cl/positions/"); 45 | positionManager = new CLPositionManager(vault, poolManager, permit2, 100_000, positionDescriptor, IWETH9(address(0))); 46 | 47 | RouterParameters memory params = RouterParameters({ 48 | permit2: address(permit2), 49 | weth9: address(0), 50 | v2Factory: address(0), 51 | v3Factory: address(0), 52 | v3Deployer: address(0), 53 | v2InitCodeHash: bytes32(0), 54 | v3InitCodeHash: bytes32(0), 55 | stableFactory: address(0), 56 | stableInfo: address(0), 57 | infiVault: address(vault), 58 | infiClPoolManager: address(poolManager), 59 | infiBinPoolManager: address(0), 60 | v3NFTPositionManager: address(0), 61 | infiClPositionManager: address(positionManager), 62 | infiBinPositionManager: address(0) 63 | }); 64 | universalRouter = new UniversalRouter(params); 65 | 66 | MockERC20 token0 = new MockERC20("token0", "T0", 18); 67 | MockERC20 token1 = new MockERC20("token1", "T1", 18); 68 | 69 | // approve permit2 contract to transfer our funds 70 | token0.approve(address(permit2), type(uint256).max); 71 | token1.approve(address(permit2), type(uint256).max); 72 | 73 | permit2.approve(address(token0), address(positionManager), type(uint160).max, type(uint48).max); 74 | permit2.approve(address(token1), address(positionManager), type(uint160).max, type(uint48).max); 75 | 76 | permit2.approve(address(token0), address(universalRouter), type(uint160).max, type(uint48).max); 77 | permit2.approve(address(token1), address(universalRouter), type(uint160).max, type(uint48).max); 78 | 79 | return SortTokens.sort(token0, token1); 80 | } 81 | 82 | function addLiquidity( 83 | PoolKey memory key, 84 | uint128 amount0Max, 85 | uint128 amount1Max, 86 | int24 tickLower, 87 | int24 tickUpper, 88 | address recipient 89 | ) internal returns (uint256 tokenId) { 90 | tokenId = positionManager.nextTokenId(); 91 | 92 | (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(key.toId()); 93 | uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts( 94 | sqrtPriceX96, 95 | TickMath.getSqrtRatioAtTick(tickLower), 96 | TickMath.getSqrtRatioAtTick(tickUpper), 97 | amount0Max, 98 | amount1Max 99 | ); 100 | Plan memory planner = Planner.init().add( 101 | Actions.CL_MINT_POSITION, 102 | abi.encode(key, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, new bytes(0)) 103 | ); 104 | bytes memory data = planner.finalizeModifyLiquidityWithClose(key); 105 | positionManager.modifyLiquidities(data, block.timestamp); 106 | } 107 | 108 | function decreaseLiquidity( 109 | uint256 tokenId, 110 | PoolKey memory key, 111 | uint128 amount0, 112 | uint128 amount1, 113 | int24 tickLower, 114 | int24 tickUpper 115 | ) internal { 116 | (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(key.toId()); 117 | uint256 liquidity = LiquidityAmounts.getLiquidityForAmounts( 118 | sqrtPriceX96, 119 | TickMath.getSqrtRatioAtTick(tickLower), 120 | TickMath.getSqrtRatioAtTick(tickUpper), 121 | amount0, 122 | amount1 123 | ); 124 | // amount0Min and amount1Min is 0 as some hook takes a fee from here 125 | Plan memory planner = 126 | Planner.init().add(Actions.CL_DECREASE_LIQUIDITY, abi.encode(tokenId, liquidity, 0, 0, new bytes(0))); 127 | bytes memory data = planner.finalizeModifyLiquidityWithClose(key); 128 | positionManager.modifyLiquidities(data, block.timestamp); 129 | } 130 | 131 | function exactInputSingle(ICLRouterBase.CLSwapExactInputSingleParams memory params) internal { 132 | Plan memory plan = Planner.init().add(Actions.CL_SWAP_EXACT_IN_SINGLE, abi.encode(params)); 133 | bytes memory data = params.zeroForOne 134 | ? plan.finalizeSwap(params.poolKey.currency0, params.poolKey.currency1, ActionConstants.MSG_SENDER) 135 | : plan.finalizeSwap(params.poolKey.currency1, params.poolKey.currency0, ActionConstants.MSG_SENDER); 136 | 137 | bytes memory commands = abi.encodePacked(bytes1(uint8(Commands.INFI_SWAP))); 138 | bytes[] memory inputs = new bytes[](1); 139 | inputs[0] = data; 140 | 141 | universalRouter.execute(commands, inputs); 142 | } 143 | 144 | function exactOutputSingle(ICLRouterBase.CLSwapExactOutputSingleParams memory params) internal { 145 | Plan memory plan = Planner.init().add(Actions.CL_SWAP_EXACT_OUT_SINGLE, abi.encode(params)); 146 | bytes memory data = params.zeroForOne 147 | ? plan.finalizeSwap(params.poolKey.currency0, params.poolKey.currency1, ActionConstants.MSG_SENDER) 148 | : plan.finalizeSwap(params.poolKey.currency1, params.poolKey.currency0, ActionConstants.MSG_SENDER); 149 | 150 | bytes memory commands = abi.encodePacked(bytes1(uint8(Commands.INFI_SWAP))); 151 | bytes[] memory inputs = new bytes[](1); 152 | inputs[0] = data; 153 | 154 | universalRouter.execute(commands, inputs); 155 | } 156 | 157 | /// @notice permit2 approve from user addr to contractToApprove for currency 158 | function permit2Approve(address userAddr, Currency currency, address contractToApprove) internal { 159 | vm.startPrank(userAddr); 160 | 161 | // If contractToApprove uses permit2, we must execute 2 permits/approvals. 162 | // 1. First, the caller must approve permit2 on the token. 163 | IERC20(Currency.unwrap(currency)).approve(address(permit2), type(uint256).max); 164 | 165 | // 2. Then, the caller must approve contractToApprove as a spender of permit2. 166 | permit2.approve(Currency.unwrap(currency), address(contractToApprove), type(uint160).max, type(uint48).max); 167 | vm.stopPrank(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /test/pool-bin/utils/BinTestUtils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.24; 3 | 4 | import {IERC20} from "forge-std/interfaces/IERC20.sol"; 5 | import {Test} from "forge-std/Test.sol"; 6 | import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; 7 | import {BinPoolManager} from "infinity-core/src/pool-bin/BinPoolManager.sol"; 8 | import {Vault} from "infinity-core/src/Vault.sol"; 9 | import {Currency} from "infinity-core/src/types/Currency.sol"; 10 | import {SortTokens} from "infinity-core/test/helpers/SortTokens.sol"; 11 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 12 | import {SafeCast} from "infinity-core/src/pool-bin/libraries/math/SafeCast.sol"; 13 | import {BinPositionManager} from "infinity-periphery/src/pool-bin/BinPositionManager.sol"; 14 | import {IBinPositionManager} from "infinity-periphery/src/pool-bin/interfaces/IBinPositionManager.sol"; 15 | import {IBinRouterBase} from "infinity-periphery/src/pool-bin/interfaces/IBinRouterBase.sol"; 16 | import {Planner, Plan} from "infinity-periphery/src/libraries/Planner.sol"; 17 | import {Actions} from "infinity-periphery/src/libraries/Actions.sol"; 18 | import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; 19 | import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; 20 | import {UniversalRouter, RouterParameters} from "infinity-universal-router/src/UniversalRouter.sol"; 21 | import {Commands} from "infinity-universal-router/src/libraries/Commands.sol"; 22 | import {ActionConstants} from "infinity-periphery/src/libraries/ActionConstants.sol"; 23 | import {IWETH9} from "infinity-periphery/src/interfaces/external/IWETH9.sol"; 24 | 25 | contract BinTestUtils is DeployPermit2 { 26 | using SafeCast for uint256; 27 | using Planner for Plan; 28 | 29 | Vault vault; 30 | BinPoolManager poolManager; 31 | BinPositionManager positionManager; 32 | IAllowanceTransfer permit2; 33 | UniversalRouter universalRouter; 34 | 35 | function deployContractsWithTokens() internal returns (Currency, Currency) { 36 | vault = new Vault(); 37 | poolManager = new BinPoolManager(vault); 38 | vault.registerApp(address(poolManager)); 39 | 40 | permit2 = IAllowanceTransfer(deployPermit2()); 41 | positionManager = new BinPositionManager(vault, poolManager, permit2, IWETH9(address(0))); 42 | 43 | RouterParameters memory params = RouterParameters({ 44 | permit2: address(permit2), 45 | weth9: address(0), 46 | v2Factory: address(0), 47 | v3Factory: address(0), 48 | v3Deployer: address(0), 49 | v2InitCodeHash: bytes32(0), 50 | v3InitCodeHash: bytes32(0), 51 | stableFactory: address(0), 52 | stableInfo: address(0), 53 | infiVault: address(vault), 54 | infiClPoolManager: address(0), 55 | infiBinPoolManager: address(poolManager), 56 | v3NFTPositionManager: address(0), 57 | infiClPositionManager: address(0), 58 | infiBinPositionManager: address(positionManager) 59 | }); 60 | universalRouter = new UniversalRouter(params); 61 | 62 | MockERC20 token0 = new MockERC20("token0", "T0", 18); 63 | MockERC20 token1 = new MockERC20("token1", "T1", 18); 64 | 65 | // approve permit2 contract to transfer our funds 66 | token0.approve(address(permit2), type(uint256).max); 67 | token1.approve(address(permit2), type(uint256).max); 68 | 69 | permit2.approve(address(token0), address(positionManager), type(uint160).max, type(uint48).max); 70 | permit2.approve(address(token1), address(positionManager), type(uint160).max, type(uint48).max); 71 | 72 | permit2.approve(address(token0), address(universalRouter), type(uint160).max, type(uint48).max); 73 | permit2.approve(address(token1), address(universalRouter), type(uint160).max, type(uint48).max); 74 | 75 | return SortTokens.sort(token0, token1); 76 | } 77 | 78 | /// @notice add liqudiity to pool key, 79 | function addLiquidity( 80 | PoolKey memory key, 81 | uint128 amountX, 82 | uint128 amountY, 83 | uint24 currentActiveId, 84 | uint24 numOfBins, 85 | address recipient 86 | ) internal { 87 | uint24[] memory binIds = new uint24[](numOfBins); 88 | uint24 startId = currentActiveId - (numOfBins / 2); 89 | for (uint256 i; i < numOfBins; i++) { 90 | binIds[i] = startId; 91 | startId++; 92 | } 93 | 94 | uint8 nbBinX; // num of bins to the right 95 | uint8 nbBinY; // num of bins to the left 96 | for (uint256 i; i < numOfBins; ++i) { 97 | if (binIds[i] >= currentActiveId) nbBinX++; 98 | if (binIds[i] <= currentActiveId) nbBinY++; 99 | } 100 | 101 | // Equal distribution across all binds 102 | uint256[] memory distribX = new uint256[](numOfBins); 103 | uint256[] memory distribY = new uint256[](numOfBins); 104 | for (uint256 i; i < numOfBins; ++i) { 105 | uint24 binId = binIds[i]; 106 | distribX[i] = binId >= currentActiveId && nbBinX > 0 ? uint256(1e18 / nbBinX).safe64() : 0; 107 | distribY[i] = binId <= currentActiveId && nbBinY > 0 ? uint256(1e18 / nbBinY).safe64() : 0; 108 | } 109 | 110 | IBinPositionManager.BinAddLiquidityParams memory params = IBinPositionManager.BinAddLiquidityParams({ 111 | poolKey: key, 112 | amount0: amountX, 113 | amount1: amountY, 114 | amount0Max: type(uint128).max, // note in real world, this should not be type(uint128).max 115 | amount1Max: type(uint128).max, // note in real world, this should not be type(uint128).max 116 | activeIdDesired: uint256(currentActiveId), 117 | idSlippage: 0, 118 | deltaIds: convertToRelative(binIds, currentActiveId), 119 | distributionX: distribX, 120 | distributionY: distribY, 121 | to: recipient, 122 | hookData: bytes("") 123 | }); 124 | 125 | Plan memory planner = Planner.init().add(Actions.BIN_ADD_LIQUIDITY, abi.encode(params)); 126 | bytes memory data = planner.finalizeModifyLiquidityWithClose(params.poolKey); 127 | positionManager.modifyLiquidities(data, block.timestamp); 128 | } 129 | 130 | function exactInputSingle(IBinRouterBase.BinSwapExactInputSingleParams memory params) internal { 131 | Plan memory plan = Planner.init().add(Actions.BIN_SWAP_EXACT_IN_SINGLE, abi.encode(params)); 132 | bytes memory data = params.swapForY 133 | ? plan.finalizeSwap(params.poolKey.currency0, params.poolKey.currency1, ActionConstants.MSG_SENDER) 134 | : plan.finalizeSwap(params.poolKey.currency1, params.poolKey.currency0, ActionConstants.MSG_SENDER); 135 | 136 | bytes memory commands = abi.encodePacked(bytes1(uint8(Commands.INFI_SWAP))); 137 | bytes[] memory inputs = new bytes[](1); 138 | inputs[0] = data; 139 | 140 | universalRouter.execute(commands, inputs); 141 | } 142 | 143 | function exactOutputSingle(IBinRouterBase.BinSwapExactOutputSingleParams memory params) internal { 144 | Plan memory plan = Planner.init().add(Actions.BIN_SWAP_EXACT_OUT_SINGLE, abi.encode(params)); 145 | bytes memory data = params.swapForY 146 | ? plan.finalizeSwap(params.poolKey.currency0, params.poolKey.currency1, ActionConstants.MSG_SENDER) 147 | : plan.finalizeSwap(params.poolKey.currency1, params.poolKey.currency0, ActionConstants.MSG_SENDER); 148 | 149 | bytes memory commands = abi.encodePacked(bytes1(uint8(Commands.INFI_SWAP))); 150 | bytes[] memory inputs = new bytes[](1); 151 | inputs[0] = data; 152 | 153 | universalRouter.execute(commands, inputs); 154 | } 155 | 156 | /// @dev Given list of binIds and activeIds, return the delta ids. 157 | // eg. given id: [100, 101, 102] and activeId: 101, return [-1, 0, 1] 158 | function convertToRelative(uint24[] memory absoluteIds, uint24 activeId) 159 | internal 160 | pure 161 | returns (int256[] memory relativeIds) 162 | { 163 | relativeIds = new int256[](absoluteIds.length); 164 | for (uint256 i = 0; i < absoluteIds.length; i++) { 165 | relativeIds[i] = int256(uint256(absoluteIds[i])) - int256(uint256(activeId)); 166 | } 167 | } 168 | 169 | /// @notice permit2 approve from user addr to contractToApprove for currency 170 | function permit2Approve(address userAddr, Currency currency, address contractToApprove) internal { 171 | vm.startPrank(userAddr); 172 | 173 | // If contractToApprove uses permit2, we must execute 2 permits/approvals. 174 | // 1. First, the caller must approve permit2 on the token. 175 | IERC20(Currency.unwrap(currency)).approve(address(permit2), type(uint256).max); 176 | 177 | // 2. Then, the caller must approve contractToApprove as a spender of permit2. 178 | permit2.approve(Currency.unwrap(currency), address(contractToApprove), type(uint160).max, type(uint48).max); 179 | vm.stopPrank(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/pool-bin/BinBaseHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.24; 3 | 4 | import { 5 | HOOKS_BEFORE_INITIALIZE_OFFSET, 6 | HOOKS_AFTER_INITIALIZE_OFFSET, 7 | HOOKS_BEFORE_MINT_OFFSET, 8 | HOOKS_AFTER_MINT_OFFSET, 9 | HOOKS_BEFORE_BURN_OFFSET, 10 | HOOKS_AFTER_BURN_OFFSET, 11 | HOOKS_BEFORE_SWAP_OFFSET, 12 | HOOKS_AFTER_SWAP_OFFSET, 13 | HOOKS_BEFORE_DONATE_OFFSET, 14 | HOOKS_AFTER_DONATE_OFFSET, 15 | HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET, 16 | HOOKS_AFTER_SWAP_RETURNS_DELTA_OFFSET, 17 | HOOKS_AFTER_MINT_RETURNS_DELTA_OFFSET, 18 | HOOKS_AFTER_BURN_RETURNS_DELTA_OFFSET 19 | } from "infinity-core/src/pool-bin/interfaces/IBinHooks.sol"; 20 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 21 | import {BalanceDelta} from "infinity-core/src/types/BalanceDelta.sol"; 22 | import {BeforeSwapDelta} from "infinity-core/src/types/BeforeSwapDelta.sol"; 23 | import {IHooks} from "infinity-core/src/interfaces/IHooks.sol"; 24 | import {IVault} from "infinity-core/src/interfaces/IVault.sol"; 25 | import {IBinHooks} from "infinity-core/src/pool-bin/interfaces/IBinHooks.sol"; 26 | import {IBinPoolManager} from "infinity-core/src/pool-bin/interfaces/IBinPoolManager.sol"; 27 | import {BinPoolManager} from "infinity-core/src/pool-bin/BinPoolManager.sol"; 28 | 29 | /// @notice BaseHook abstract contract for Bin pool hooks to inherit 30 | abstract contract BinBaseHook is IBinHooks { 31 | /// @notice The sender is not the pool manager 32 | error NotPoolManager(); 33 | 34 | /// @notice The sender is not the vault 35 | error NotVault(); 36 | 37 | /// @notice The sender is not this contract 38 | error NotSelf(); 39 | 40 | /// @notice The pool key does not include this hook 41 | error InvalidPool(); 42 | 43 | /// @notice The delegation of lockAcquired failed 44 | error LockFailure(); 45 | 46 | /// @notice The method is not implemented 47 | error HookNotImplemented(); 48 | 49 | struct Permissions { 50 | bool beforeInitialize; 51 | bool afterInitialize; 52 | bool beforeMint; 53 | bool afterMint; 54 | bool beforeBurn; 55 | bool afterBurn; 56 | bool beforeSwap; 57 | bool afterSwap; 58 | bool beforeDonate; 59 | bool afterDonate; 60 | bool beforeSwapReturnDelta; 61 | bool afterSwapReturnDelta; 62 | bool afterMintReturnDelta; 63 | bool afterBurnReturnDelta; 64 | } 65 | 66 | /// @notice The address of the pool manager 67 | IBinPoolManager public immutable poolManager; 68 | 69 | /// @notice The address of the vault 70 | IVault public immutable vault; 71 | 72 | constructor(IBinPoolManager _poolManager) { 73 | poolManager = _poolManager; 74 | vault = BinPoolManager(address(poolManager)).vault(); 75 | } 76 | 77 | /// @dev Only the pool manager may call this function 78 | modifier poolManagerOnly() { 79 | if (msg.sender != address(poolManager)) revert NotPoolManager(); 80 | _; 81 | } 82 | 83 | /// @dev Only the vault may call this function 84 | modifier vaultOnly() { 85 | if (msg.sender != address(vault)) revert NotVault(); 86 | _; 87 | } 88 | 89 | /// @dev Only this address may call this function 90 | modifier selfOnly() { 91 | if (msg.sender != address(this)) revert NotSelf(); 92 | _; 93 | } 94 | 95 | /// @dev Only pools with hooks set to this contract may call this function 96 | modifier onlyValidPools(IHooks hooks) { 97 | if (address(hooks) != address(this)) revert InvalidPool(); 98 | _; 99 | } 100 | 101 | /// @dev Delegate calls to corresponding methods according to callback data 102 | function lockAcquired(bytes calldata data) external virtual vaultOnly returns (bytes memory) { 103 | (bool success, bytes memory returnData) = address(this).call(data); 104 | if (success) return returnData; 105 | if (returnData.length == 0) revert LockFailure(); 106 | // if the call failed, bubble up the reason 107 | /// @solidity memory-safe-assembly 108 | assembly { 109 | revert(add(returnData, 32), mload(returnData)) 110 | } 111 | } 112 | 113 | /// @inheritdoc IBinHooks 114 | function beforeInitialize(address sender, PoolKey calldata key, uint24 activeId) 115 | external 116 | virtual 117 | poolManagerOnly 118 | returns (bytes4) 119 | { 120 | return _beforeInitialize(sender, key, activeId); 121 | } 122 | 123 | function _beforeInitialize(address, PoolKey calldata, uint24) internal virtual returns (bytes4) { 124 | revert HookNotImplemented(); 125 | } 126 | 127 | /// @inheritdoc IBinHooks 128 | function afterInitialize(address sender, PoolKey calldata key, uint24 activeId) 129 | external 130 | virtual 131 | poolManagerOnly 132 | returns (bytes4) 133 | { 134 | return _afterInitialize(sender, key, activeId); 135 | } 136 | 137 | function _afterInitialize(address, PoolKey calldata, uint24) internal virtual returns (bytes4) { 138 | revert HookNotImplemented(); 139 | } 140 | 141 | /// @inheritdoc IBinHooks 142 | function beforeMint( 143 | address sender, 144 | PoolKey calldata key, 145 | IBinPoolManager.MintParams calldata params, 146 | bytes calldata hookData 147 | ) external virtual poolManagerOnly returns (bytes4, uint24) { 148 | return _beforeMint(sender, key, params, hookData); 149 | } 150 | 151 | function _beforeMint(address, PoolKey calldata, IBinPoolManager.MintParams calldata, bytes calldata) 152 | internal 153 | virtual 154 | returns (bytes4, uint24) 155 | { 156 | revert HookNotImplemented(); 157 | } 158 | 159 | /// @inheritdoc IBinHooks 160 | function afterMint( 161 | address sender, 162 | PoolKey calldata key, 163 | IBinPoolManager.MintParams calldata params, 164 | BalanceDelta delta, 165 | bytes calldata hookData 166 | ) external virtual poolManagerOnly returns (bytes4, BalanceDelta) { 167 | return _afterMint(sender, key, params, delta, hookData); 168 | } 169 | 170 | function _afterMint(address, PoolKey calldata, IBinPoolManager.MintParams calldata, BalanceDelta, bytes calldata) 171 | internal 172 | virtual 173 | returns (bytes4, BalanceDelta) 174 | { 175 | revert HookNotImplemented(); 176 | } 177 | 178 | /// @inheritdoc IBinHooks 179 | function beforeBurn( 180 | address sender, 181 | PoolKey calldata key, 182 | IBinPoolManager.BurnParams calldata params, 183 | bytes calldata hookData 184 | ) external virtual poolManagerOnly returns (bytes4) { 185 | return _beforeBurn(sender, key, params, hookData); 186 | } 187 | 188 | function _beforeBurn(address, PoolKey calldata, IBinPoolManager.BurnParams calldata, bytes calldata) 189 | internal 190 | virtual 191 | returns (bytes4) 192 | { 193 | revert HookNotImplemented(); 194 | } 195 | 196 | /// @inheritdoc IBinHooks 197 | function afterBurn( 198 | address sender, 199 | PoolKey calldata key, 200 | IBinPoolManager.BurnParams calldata params, 201 | BalanceDelta delta, 202 | bytes calldata hookData 203 | ) external virtual poolManagerOnly returns (bytes4, BalanceDelta) { 204 | return _afterBurn(sender, key, params, delta, hookData); 205 | } 206 | 207 | function _afterBurn(address, PoolKey calldata, IBinPoolManager.BurnParams calldata, BalanceDelta, bytes calldata) 208 | internal 209 | virtual 210 | returns (bytes4, BalanceDelta) 211 | { 212 | revert HookNotImplemented(); 213 | } 214 | 215 | /// @inheritdoc IBinHooks 216 | function beforeSwap( 217 | address sender, 218 | PoolKey calldata key, 219 | bool swapForY, 220 | int128 amountSpecified, 221 | bytes calldata hookData 222 | ) external virtual poolManagerOnly returns (bytes4, BeforeSwapDelta, uint24) { 223 | return _beforeSwap(sender, key, swapForY, amountSpecified, hookData); 224 | } 225 | 226 | function _beforeSwap(address, PoolKey calldata, bool, int128, bytes calldata) 227 | internal 228 | virtual 229 | returns (bytes4, BeforeSwapDelta, uint24) 230 | { 231 | revert HookNotImplemented(); 232 | } 233 | 234 | /// @inheritdoc IBinHooks 235 | function afterSwap( 236 | address sender, 237 | PoolKey calldata key, 238 | bool swapForY, 239 | int128 amountSpecified, 240 | BalanceDelta delta, 241 | bytes calldata hookData 242 | ) external virtual poolManagerOnly returns (bytes4, int128) { 243 | return _afterSwap(sender, key, swapForY, amountSpecified, delta, hookData); 244 | } 245 | 246 | function _afterSwap(address, PoolKey calldata, bool, int128, BalanceDelta, bytes calldata) 247 | internal 248 | virtual 249 | returns (bytes4, int128) 250 | { 251 | revert HookNotImplemented(); 252 | } 253 | 254 | /// @inheritdoc IBinHooks 255 | function beforeDonate( 256 | address sender, 257 | PoolKey calldata key, 258 | uint256 amount0, 259 | uint256 amount1, 260 | bytes calldata hookData 261 | ) external virtual poolManagerOnly returns (bytes4) { 262 | return _beforeDonate(sender, key, amount0, amount1, hookData); 263 | } 264 | 265 | function _beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) 266 | internal 267 | virtual 268 | returns (bytes4) 269 | { 270 | revert HookNotImplemented(); 271 | } 272 | 273 | /// @inheritdoc IBinHooks 274 | function afterDonate( 275 | address sender, 276 | PoolKey calldata key, 277 | uint256 amount0, 278 | uint256 amount1, 279 | bytes calldata hookData 280 | ) external virtual poolManagerOnly returns (bytes4) { 281 | return _afterDonate(sender, key, amount0, amount1, hookData); 282 | } 283 | 284 | function _afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) 285 | internal 286 | virtual 287 | returns (bytes4) 288 | { 289 | revert HookNotImplemented(); 290 | } 291 | 292 | /// @dev Helper function to construct the hook registration map 293 | function _hooksRegistrationBitmapFrom(Permissions memory permissions) internal pure returns (uint16) { 294 | return uint16( 295 | (permissions.beforeInitialize ? 1 << HOOKS_BEFORE_INITIALIZE_OFFSET : 0) 296 | | (permissions.afterInitialize ? 1 << HOOKS_AFTER_INITIALIZE_OFFSET : 0) 297 | | (permissions.beforeMint ? 1 << HOOKS_BEFORE_MINT_OFFSET : 0) 298 | | (permissions.afterMint ? 1 << HOOKS_AFTER_MINT_OFFSET : 0) 299 | | (permissions.beforeBurn ? 1 << HOOKS_BEFORE_BURN_OFFSET : 0) 300 | | (permissions.afterBurn ? 1 << HOOKS_AFTER_BURN_OFFSET : 0) 301 | | (permissions.beforeSwap ? 1 << HOOKS_BEFORE_SWAP_OFFSET : 0) 302 | | (permissions.afterSwap ? 1 << HOOKS_AFTER_SWAP_OFFSET : 0) 303 | | (permissions.beforeDonate ? 1 << HOOKS_BEFORE_DONATE_OFFSET : 0) 304 | | (permissions.afterDonate ? 1 << HOOKS_AFTER_DONATE_OFFSET : 0) 305 | | (permissions.beforeSwapReturnDelta ? 1 << HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET : 0) 306 | | (permissions.afterSwapReturnDelta ? 1 << HOOKS_AFTER_SWAP_RETURNS_DELTA_OFFSET : 0) 307 | | (permissions.afterMintReturnDelta ? 1 << HOOKS_AFTER_MINT_RETURNS_DELTA_OFFSET : 0) 308 | | (permissions.afterBurnReturnDelta ? 1 << HOOKS_AFTER_BURN_RETURNS_DELTA_OFFSET : 0) 309 | ); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/pool-cl/CLBaseHook.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | import { 5 | HOOKS_BEFORE_INITIALIZE_OFFSET, 6 | HOOKS_AFTER_INITIALIZE_OFFSET, 7 | HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET, 8 | HOOKS_AFTER_ADD_LIQUIDITY_OFFSET, 9 | HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET, 10 | HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET, 11 | HOOKS_BEFORE_SWAP_OFFSET, 12 | HOOKS_AFTER_SWAP_OFFSET, 13 | HOOKS_BEFORE_DONATE_OFFSET, 14 | HOOKS_AFTER_DONATE_OFFSET, 15 | HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET, 16 | HOOKS_AFTER_SWAP_RETURNS_DELTA_OFFSET, 17 | HOOKS_AFTER_ADD_LIQUIDIY_RETURNS_DELTA_OFFSET, 18 | HOOKS_AFTER_REMOVE_LIQUIDIY_RETURNS_DELTA_OFFSET 19 | } from "infinity-core/src/pool-cl/interfaces/ICLHooks.sol"; 20 | import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; 21 | import {BalanceDelta} from "infinity-core/src/types/BalanceDelta.sol"; 22 | import {BeforeSwapDelta} from "infinity-core/src/types/BeforeSwapDelta.sol"; 23 | import {IHooks} from "infinity-core/src/interfaces/IHooks.sol"; 24 | import {IVault} from "infinity-core/src/interfaces/IVault.sol"; 25 | import {ICLHooks} from "infinity-core/src/pool-cl/interfaces/ICLHooks.sol"; 26 | import {ICLPoolManager} from "infinity-core/src/pool-cl/interfaces/ICLPoolManager.sol"; 27 | import {CLPoolManager} from "infinity-core/src/pool-cl/CLPoolManager.sol"; 28 | 29 | /// @notice BaseHook abstract contract for CL pool hooks to inherit 30 | abstract contract CLBaseHook is ICLHooks { 31 | /// @notice The sender is not the pool manager 32 | error NotPoolManager(); 33 | 34 | /// @notice The sender is not the vault 35 | error NotVault(); 36 | 37 | /// @notice The sender is not this contract 38 | error NotSelf(); 39 | 40 | /// @notice The pool key does not include this hook 41 | error InvalidPool(); 42 | 43 | /// @notice The delegation of lockAcquired failed 44 | error LockFailure(); 45 | 46 | /// @notice The method is not implemented 47 | error HookNotImplemented(); 48 | 49 | struct Permissions { 50 | bool beforeInitialize; 51 | bool afterInitialize; 52 | bool beforeAddLiquidity; 53 | bool afterAddLiquidity; 54 | bool beforeRemoveLiquidity; 55 | bool afterRemoveLiquidity; 56 | bool beforeSwap; 57 | bool afterSwap; 58 | bool beforeDonate; 59 | bool afterDonate; 60 | bool beforeSwapReturnDelta; 61 | bool afterSwapReturnDelta; 62 | bool afterAddLiquidityReturnDelta; 63 | bool afterRemoveLiquidityReturnDelta; 64 | } 65 | 66 | /// @notice The address of the pool manager 67 | ICLPoolManager public immutable poolManager; 68 | 69 | /// @notice The address of the vault 70 | IVault public immutable vault; 71 | 72 | constructor(ICLPoolManager _poolManager) { 73 | poolManager = _poolManager; 74 | vault = CLPoolManager(address(poolManager)).vault(); 75 | } 76 | 77 | /// @dev Only the pool manager may call this function 78 | modifier poolManagerOnly() { 79 | if (msg.sender != address(poolManager)) revert NotPoolManager(); 80 | _; 81 | } 82 | 83 | /// @dev Only the vault may call this function 84 | modifier vaultOnly() { 85 | if (msg.sender != address(vault)) revert NotVault(); 86 | _; 87 | } 88 | 89 | /// @dev Only this address may call this function 90 | modifier selfOnly() { 91 | if (msg.sender != address(this)) revert NotSelf(); 92 | _; 93 | } 94 | 95 | /// @dev Only pools with hooks set to this contract may call this function 96 | modifier onlyValidPools(IHooks hooks) { 97 | if (address(hooks) != address(this)) revert InvalidPool(); 98 | _; 99 | } 100 | 101 | /// @dev Delegate calls to corresponding methods according to callback data 102 | function lockAcquired(bytes calldata data) external virtual vaultOnly returns (bytes memory) { 103 | (bool success, bytes memory returnData) = address(this).call(data); 104 | if (success) return returnData; 105 | if (returnData.length == 0) revert LockFailure(); 106 | // if the call failed, bubble up the reason 107 | /// @solidity memory-safe-assembly 108 | assembly { 109 | revert(add(returnData, 32), mload(returnData)) 110 | } 111 | } 112 | 113 | /// @inheritdoc ICLHooks 114 | function beforeInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96) 115 | external 116 | virtual 117 | poolManagerOnly 118 | returns (bytes4) 119 | { 120 | return _beforeInitialize(sender, key, sqrtPriceX96); 121 | } 122 | 123 | function _beforeInitialize(address, PoolKey calldata, uint160) internal virtual returns (bytes4) { 124 | revert HookNotImplemented(); 125 | } 126 | 127 | /// @inheritdoc ICLHooks 128 | function afterInitialize(address sender, PoolKey calldata key, uint160 sqrtPriceX96, int24 tick) 129 | external 130 | virtual 131 | poolManagerOnly 132 | returns (bytes4) 133 | { 134 | return _afterInitialize(sender, key, sqrtPriceX96, tick); 135 | } 136 | 137 | function _afterInitialize(address, PoolKey calldata, uint160, int24) internal virtual returns (bytes4) { 138 | revert HookNotImplemented(); 139 | } 140 | 141 | /// @inheritdoc ICLHooks 142 | function beforeAddLiquidity( 143 | address sender, 144 | PoolKey calldata key, 145 | ICLPoolManager.ModifyLiquidityParams calldata params, 146 | bytes calldata hookData 147 | ) external virtual poolManagerOnly returns (bytes4) { 148 | return _beforeAddLiquidity(sender, key, params, hookData); 149 | } 150 | 151 | function _beforeAddLiquidity( 152 | address, 153 | PoolKey calldata, 154 | ICLPoolManager.ModifyLiquidityParams calldata, 155 | bytes calldata 156 | ) internal virtual returns (bytes4) { 157 | revert HookNotImplemented(); 158 | } 159 | 160 | /// @inheritdoc ICLHooks 161 | function afterAddLiquidity( 162 | address sender, 163 | PoolKey calldata key, 164 | ICLPoolManager.ModifyLiquidityParams calldata params, 165 | BalanceDelta delta, 166 | BalanceDelta feesAccrued, 167 | bytes calldata hookData 168 | ) external virtual poolManagerOnly returns (bytes4, BalanceDelta) { 169 | return _afterAddLiquidity(sender, key, params, delta, feesAccrued, hookData); 170 | } 171 | 172 | function _afterAddLiquidity( 173 | address, 174 | PoolKey calldata, 175 | ICLPoolManager.ModifyLiquidityParams calldata, 176 | BalanceDelta, 177 | BalanceDelta, 178 | bytes calldata 179 | ) internal virtual returns (bytes4, BalanceDelta) { 180 | revert HookNotImplemented(); 181 | } 182 | 183 | /// @inheritdoc ICLHooks 184 | function beforeRemoveLiquidity( 185 | address sender, 186 | PoolKey calldata key, 187 | ICLPoolManager.ModifyLiquidityParams calldata params, 188 | bytes calldata hookData 189 | ) external virtual poolManagerOnly returns (bytes4) { 190 | return _beforeRemoveLiquidity(sender, key, params, hookData); 191 | } 192 | 193 | function _beforeRemoveLiquidity( 194 | address, 195 | PoolKey calldata, 196 | ICLPoolManager.ModifyLiquidityParams calldata, 197 | bytes calldata 198 | ) internal virtual returns (bytes4) { 199 | revert HookNotImplemented(); 200 | } 201 | 202 | /// @inheritdoc ICLHooks 203 | function afterRemoveLiquidity( 204 | address sender, 205 | PoolKey calldata key, 206 | ICLPoolManager.ModifyLiquidityParams calldata params, 207 | BalanceDelta delta, 208 | BalanceDelta feesAccrued, 209 | bytes calldata hookData 210 | ) external virtual poolManagerOnly returns (bytes4, BalanceDelta) { 211 | return _afterRemoveLiquidity(sender, key, params, delta, feesAccrued, hookData); 212 | } 213 | 214 | function _afterRemoveLiquidity( 215 | address, 216 | PoolKey calldata, 217 | ICLPoolManager.ModifyLiquidityParams calldata, 218 | BalanceDelta, 219 | BalanceDelta, 220 | bytes calldata 221 | ) internal virtual poolManagerOnly returns (bytes4, BalanceDelta) { 222 | revert HookNotImplemented(); 223 | } 224 | 225 | /// @inheritdoc ICLHooks 226 | function beforeSwap( 227 | address sender, 228 | PoolKey calldata key, 229 | ICLPoolManager.SwapParams calldata params, 230 | bytes calldata hookData 231 | ) external virtual poolManagerOnly returns (bytes4, BeforeSwapDelta, uint24) { 232 | return _beforeSwap(sender, key, params, hookData); 233 | } 234 | 235 | function _beforeSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, bytes calldata) 236 | internal 237 | virtual 238 | returns (bytes4, BeforeSwapDelta, uint24) 239 | { 240 | revert HookNotImplemented(); 241 | } 242 | 243 | /// @inheritdoc ICLHooks 244 | function afterSwap( 245 | address sender, 246 | PoolKey calldata key, 247 | ICLPoolManager.SwapParams calldata params, 248 | BalanceDelta delta, 249 | bytes calldata hookData 250 | ) external virtual poolManagerOnly returns (bytes4, int128) { 251 | return _afterSwap(sender, key, params, delta, hookData); 252 | } 253 | 254 | function _afterSwap(address, PoolKey calldata, ICLPoolManager.SwapParams calldata, BalanceDelta, bytes calldata) 255 | internal 256 | virtual 257 | returns (bytes4, int128) 258 | { 259 | revert HookNotImplemented(); 260 | } 261 | 262 | /// @inheritdoc ICLHooks 263 | function beforeDonate( 264 | address sender, 265 | PoolKey calldata key, 266 | uint256 amount0, 267 | uint256 amount1, 268 | bytes calldata hookData 269 | ) external virtual poolManagerOnly returns (bytes4) { 270 | return _beforeDonate(sender, key, amount0, amount1, hookData); 271 | } 272 | 273 | function _beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) 274 | internal 275 | virtual 276 | returns (bytes4) 277 | { 278 | revert HookNotImplemented(); 279 | } 280 | 281 | /// @inheritdoc ICLHooks 282 | function afterDonate( 283 | address sender, 284 | PoolKey calldata key, 285 | uint256 amount0, 286 | uint256 amount1, 287 | bytes calldata hookData 288 | ) external virtual poolManagerOnly returns (bytes4) { 289 | return _afterDonate(sender, key, amount0, amount1, hookData); 290 | } 291 | 292 | function _afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata) 293 | internal 294 | virtual 295 | returns (bytes4) 296 | { 297 | revert HookNotImplemented(); 298 | } 299 | 300 | /// @dev Helper function to construct the hook registration map 301 | function _hooksRegistrationBitmapFrom(Permissions memory permissions) internal pure returns (uint16) { 302 | return uint16( 303 | (permissions.beforeInitialize ? 1 << HOOKS_BEFORE_INITIALIZE_OFFSET : 0) 304 | | (permissions.afterInitialize ? 1 << HOOKS_AFTER_INITIALIZE_OFFSET : 0) 305 | | (permissions.beforeAddLiquidity ? 1 << HOOKS_BEFORE_ADD_LIQUIDITY_OFFSET : 0) 306 | | (permissions.afterAddLiquidity ? 1 << HOOKS_AFTER_ADD_LIQUIDITY_OFFSET : 0) 307 | | (permissions.beforeRemoveLiquidity ? 1 << HOOKS_BEFORE_REMOVE_LIQUIDITY_OFFSET : 0) 308 | | (permissions.afterRemoveLiquidity ? 1 << HOOKS_AFTER_REMOVE_LIQUIDITY_OFFSET : 0) 309 | | (permissions.beforeSwap ? 1 << HOOKS_BEFORE_SWAP_OFFSET : 0) 310 | | (permissions.afterSwap ? 1 << HOOKS_AFTER_SWAP_OFFSET : 0) 311 | | (permissions.beforeDonate ? 1 << HOOKS_BEFORE_DONATE_OFFSET : 0) 312 | | (permissions.afterDonate ? 1 << HOOKS_AFTER_DONATE_OFFSET : 0) 313 | | (permissions.beforeSwapReturnDelta ? 1 << HOOKS_BEFORE_SWAP_RETURNS_DELTA_OFFSET : 0) 314 | | (permissions.afterSwapReturnDelta ? 1 << HOOKS_AFTER_SWAP_RETURNS_DELTA_OFFSET : 0) 315 | | (permissions.afterAddLiquidityReturnDelta ? 1 << HOOKS_AFTER_ADD_LIQUIDIY_RETURNS_DELTA_OFFSET : 0) 316 | | (permissions.afterRemoveLiquidityReturnDelta ? 1 << HOOKS_AFTER_REMOVE_LIQUIDIY_RETURNS_DELTA_OFFSET : 0) 317 | ); 318 | } 319 | } 320 | --------------------------------------------------------------------------------