[
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test Suite\n\non:\n  workflow_dispatch:\n  # push:\n  # pull_request:\n\nenv:\n  FOUNDRY_PROFILE: ci\n\njobs:\n  test:\n    name: Foundry Project\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          submodules: recursive\n\n      - name: Install Foundry\n        uses: foundry-rs/foundry-toolchain@v1\n        with:\n          version: stable\n\n      - name: Build\n        run: |\n          forge --version\n          forge build --sizes\n        id: build\n\n      - name: Test\n        run: |\n          forge test -vvv\n        id: test\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiler files\ncache/\nout/\n\n# Ignores development (and goerli) broadcast logs\n!/broadcast\n/broadcast/*/31337/\n/broadcast/*/5/\n/broadcast/**/dry-run/\n\n# Docs\ndocs/\n\n# Dotenv file\n.env\n\n# Node modules\nnode_modules/"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"lib/forge-std\"]\n\tpath = lib/forge-std\n\turl = https://github.com/foundry-rs/forge-std\n[submodule \"lib/uniswap-hooks\"]\n\tpath = lib/uniswap-hooks\n\turl = https://github.com/openzeppelin/uniswap-hooks\n[submodule \"lib/hookmate\"]\n\tpath = lib/hookmate\n\turl = https://github.com/akshatmittal/hookmate"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"solidity.formatter\": \"forge\"\n}\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2023 saucepoint\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Uniswap v4 Hook Template\n\n**A template for writing Uniswap v4 Hooks 🦄**\n\n### Get Started\n\nThis template provides a starting point for writing Uniswap v4 Hooks, including a simple example and preconfigured test environment. Start by creating a new repository using the \"Use this template\" button at the top right of this page. Alternatively you can also click this link:\n\n[![Use this Template](https://img.shields.io/badge/Use%20this%20Template-101010?style=for-the-badge&logo=github)](https://github.com/uniswapfoundation/v4-template/generate)\n\n1. The example hook [Counter.sol](src/Counter.sol) demonstrates the `beforeSwap()` and `afterSwap()` hooks\n2. The test template [Counter.t.sol](test/Counter.t.sol) preconfigures the v4 pool manager, test tokens, and test liquidity.\n\n<details>\n<summary>Updating to v4-template:latest</summary>\n\nThis template is actively maintained -- you can update the v4 dependencies, scripts, and helpers:\n\n```bash\ngit remote add template https://github.com/uniswapfoundation/v4-template\ngit fetch template\ngit merge template/main <BRANCH> --allow-unrelated-histories\n```\n\n</details>\n\n### Requirements\n\nThis template is designed to work with Foundry (stable). If you are using Foundry Nightly, you may encounter compatibility issues. You can update your Foundry installation to the latest stable version by running:\n\n```\nfoundryup\n```\n\nTo set up the project, run the following commands in your terminal to install dependencies and run the tests:\n\n```\nforge install\nforge test\n```\n\n### Local Development\n\nOther than writing unit tests (recommended!), you can only deploy & test hooks on [anvil](https://book.getfoundry.sh/anvil/) locally. Scripts are available in the `script/` directory, which can be used to deploy hooks, create pools, provide liquidity and swap tokens. The scripts support both local `anvil` environment as well as running them directly on a production network.\n\n### Executing locally with using **Anvil**:\n\n1. Start Anvil (or fork a specific chain using anvil):\n\n```bash\nanvil\n```\n\nor\n\n```bash\nanvil --fork-url <YOUR_RPC_URL>\n```\n\n2. Execute scripts:\n\n```bash\nforge script script/00_DeployHook.s.sol \\\n    --rpc-url http://localhost:8545 \\\n    --private-key <PRIVATE_KEY> \\\n    --broadcast\n```\n\n### Using **RPC URLs** (actual transactions):\n\n:::info\nIt is best to not store your private key even in .env or enter it directly in the command line. Instead use the `--account` flag to select your private key from your keystore.\n:::\n\n### Follow these steps if you have not stored your private key in the keystore:\n\n<details>\n\n1. Add your private key to the keystore:\n\n```bash\ncast wallet import <SET_A_NAME_FOR_KEY> --interactive\n```\n\n2. You will prompted to enter your private key and set a password, fill and press enter:\n\n```\nEnter private key: <YOUR_PRIVATE_KEY>\nEnter keystore password: <SET_NEW_PASSWORD>\n```\n\nYou should see this:\n\n```\n`<YOUR_WALLET_PRIVATE_KEY_NAME>` keystore was saved successfully. Address: <YOUR_WALLET_ADDRESS>\n```\n\n::: warning\nUse `history -c` to clear your command history.\n:::\n\n</details>\n\n1. Execute scripts:\n\n```bash\nforge script script/00_DeployHook.s.sol \\\n    --rpc-url <YOUR_RPC_URL> \\\n    --account <YOUR_WALLET_PRIVATE_KEY_NAME> \\\n    --sender <YOUR_WALLET_ADDRESS> \\\n    --broadcast\n```\n\nYou will prompted to enter your wallet password, fill and press enter:\n\n```\nEnter keystore password: <YOUR_PASSWORD>\n```\n\n### Key Modifications to note:\n\n1. Update the `token0` and `token1` addresses in the `BaseScript.sol` file to match the tokens you want to use in the network of your choice for sepolia and mainnet deployments.\n2. Update the `token0Amount` and `token1Amount` in the `CreatePoolAndAddLiquidity.s.sol` file to match the amount of tokens you want to provide liquidity with.\n3. Update the `token0Amount` and `token1Amount` in the `AddLiquidity.s.sol` file to match the amount of tokens you want to provide liquidity with.\n4. Update the `amountIn` and `amountOutMin` in the `Swap.s.sol` file to match the amount of tokens you want to swap.\n\n### Verifying the hook contract\n\n```bash\nforge verify-contract \\\n  --rpc-url <URL> \\\n  --chain <CHAIN_NAME_OR_ID> \\\n  # Generally etherscan\n  --verifier <Verification_Provider> \\\n  # Use --etherscan-api-key <ETHERSCAN_API_KEY> if you are using etherscan\n  --verifier-api-key <Verification_Provider_API_KEY> \\\n  --constructor-args <ABI_ENCODED_ARGS> \\\n  --num-of-optimizations <OPTIMIZER_RUNS> \\\n  <Contract_Address> \\\n  <path/to/Contract.sol:ContractName>\n  --watch\n```\n\n### Troubleshooting\n\n<details>\n\n#### Permission Denied\n\nWhen installing dependencies with `forge install`, Github may throw a `Permission Denied` error\n\nTypically caused by missing Github SSH keys, and can be resolved by following the steps [here](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh)\n\nOr [adding the keys to your ssh-agent](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#adding-your-ssh-key-to-the-ssh-agent), if you have already uploaded SSH keys\n\n#### Anvil fork test failures\n\nSome versions of Foundry may limit contract code size to ~25kb, which could prevent local tests to fail. You can resolve this by setting the `code-size-limit` flag\n\n```\nanvil --code-size-limit 40000\n```\n\n#### Hook deployment failures\n\nHook deployment failures are caused by incorrect flags or incorrect salt mining\n\n1. Verify the flags are in agreement:\n   - `getHookCalls()` returns the correct flags\n   - `flags` provided to `HookMiner.find(...)`\n2. Verify salt mining is correct:\n   - In **forge test**: the _deployer_ for: `new Hook{salt: salt}(...)` and `HookMiner.find(deployer, ...)` are the same. This will be `address(this)`. If using `vm.prank`, the deployer will be the pranking address\n   - In **forge script**: the deployer must be the CREATE2 Proxy: `0x4e59b44847b379578588920cA78FbF26c0B4956C`\n     - If anvil does not have the CREATE2 deployer, your foundry may be out of date. You can update it with `foundryup`\n\n</details>\n\n### Additional Resources\n\n- [Uniswap v4 docs](https://docs.uniswap.org/contracts/v4/overview)\n- [v4-periphery](https://github.com/uniswap/v4-periphery)\n- [v4-core](https://github.com/uniswap/v4-core)\n- [v4-by-example](https://v4-by-example.org)\n"
  },
  {
    "path": "foundry.toml",
    "content": "[profile.default]\nbytecode_hash = \"none\"\nevm_version = \"cancun\"\nffi = true\nfs_permissions = [{access = \"read-write\", path = \".forge-snapshots/\"}]\nlibs = [\"lib\"]\nout = \"out\"\nsolc_version = \"0.8.30\"\nsrc = \"src\"\nvia_ir = false\n\n[lint]\nexclude_lints = [\"screaming-snake-case-immutable\", \"screaming-snake-case-const\"]\nlint_on_build = false\n"
  },
  {
    "path": "remappings.txt",
    "content": "forge-std/=lib/forge-std/src/\n\n@uniswap/v4-core/=lib/uniswap-hooks/lib/v4-core/\n@uniswap/v4-periphery/=lib/uniswap-hooks/lib/v4-periphery/\nv4-core/=lib/uniswap-hooks/lib/v4-core/\nv4-periphery/=lib/uniswap-hooks/lib/v4-periphery/\n\n@openzeppelin/uniswap-hooks/=lib/uniswap-hooks/\n@openzeppelin/contracts/=lib/uniswap-hooks/lib/v4-core/lib/openzeppelin-contracts/contracts/\n\nhookmate/=lib/hookmate/src/\npermit2/=lib/uniswap-hooks/lib/v4-periphery/lib/permit2/\nsolmate/=lib/uniswap-hooks/lib/v4-core/lib/solmate/"
  },
  {
    "path": "script/00_DeployHook.s.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {HookMiner} from \"@uniswap/v4-periphery/src/utils/HookMiner.sol\";\n\nimport {BaseScript} from \"./base/BaseScript.sol\";\n\nimport {Counter} from \"../src/Counter.sol\";\n\n/// @notice Mines the address and deploys the Counter.sol Hook contract\ncontract DeployHookScript is BaseScript {\n    function run() public {\n        // hook contracts must have specific flags encoded in the address\n        uint160 flags = uint160(\n            Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG\n                | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG\n        );\n\n        // Mine a salt that will produce a hook address with the correct flags\n        bytes memory constructorArgs = abi.encode(poolManager);\n        (address hookAddress, bytes32 salt) =\n            HookMiner.find(CREATE2_FACTORY, flags, type(Counter).creationCode, constructorArgs);\n\n        // Deploy the hook using CREATE2\n        vm.startBroadcast();\n        Counter counter = new Counter{salt: salt}(poolManager);\n        vm.stopBroadcast();\n\n        require(address(counter) == hookAddress, \"DeployHookScript: Hook Address Mismatch\");\n    }\n}\n"
  },
  {
    "path": "script/01_CreatePoolAndAddLiquidity.s.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {CurrencyLibrary, Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-core/test/utils/LiquidityAmounts.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\n\nimport {BaseScript} from \"./base/BaseScript.sol\";\nimport {LiquidityHelpers} from \"./base/LiquidityHelpers.sol\";\n\ncontract CreatePoolAndAddLiquidityScript is BaseScript, LiquidityHelpers {\n    using CurrencyLibrary for Currency;\n\n    /////////////////////////////////////\n    // --- Configure These ---\n    /////////////////////////////////////\n\n    uint24 lpFee = 3000; // 0.30%\n    int24 tickSpacing = 60;\n    uint160 startingPrice = 2 ** 96; // Starting price, sqrtPriceX96; floor(sqrt(1) * 2^96)\n\n    // --- liquidity position configuration --- //\n    uint256 public token0Amount = 100e18;\n    uint256 public token1Amount = 100e18;\n\n    // range of the position, must be a multiple of tickSpacing\n    int24 tickLower;\n    int24 tickUpper;\n    /////////////////////////////////////\n\n    function run() external {\n        PoolKey memory poolKey = PoolKey({\n            currency0: currency0,\n            currency1: currency1,\n            fee: lpFee,\n            tickSpacing: tickSpacing,\n            hooks: hookContract\n        });\n\n        bytes memory hookData = new bytes(0);\n\n        int24 currentTick = TickMath.getTickAtSqrtPrice(startingPrice);\n\n        tickLower = truncateTickSpacing((currentTick - 750 * tickSpacing), tickSpacing);\n        tickUpper = truncateTickSpacing((currentTick + 750 * tickSpacing), tickSpacing);\n\n        // Converts token amounts to liquidity units\n        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(\n            startingPrice,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            token0Amount,\n            token1Amount\n        );\n\n        // slippage limits\n        uint256 amount0Max = token0Amount + 1;\n        uint256 amount1Max = token1Amount + 1;\n\n        (bytes memory actions, bytes[] memory mintParams) = _mintLiquidityParams(\n            poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, deployerAddress, hookData\n        );\n\n        // multicall parameters\n        bytes[] memory params = new bytes[](2);\n\n        // Initialize Pool\n        params[0] = abi.encodeWithSelector(positionManager.initializePool.selector, poolKey, startingPrice, hookData);\n\n        // Mint Liquidity\n        params[1] = abi.encodeWithSelector(\n            positionManager.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 3600\n        );\n\n        // If the pool is an ETH pair, native tokens are to be transferred\n        uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0;\n\n        vm.startBroadcast();\n        tokenApprovals();\n\n        // Multicall to atomically create pool & add liquidity\n        positionManager.multicall{value: valueToPass}(params);\n        vm.stopBroadcast();\n    }\n}\n"
  },
  {
    "path": "script/02_AddLiquidity.s.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {CurrencyLibrary, Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-core/test/utils/LiquidityAmounts.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {StateLibrary} from \"@uniswap/v4-core/src/libraries/StateLibrary.sol\";\n\nimport {BaseScript} from \"./base/BaseScript.sol\";\nimport {LiquidityHelpers} from \"./base/LiquidityHelpers.sol\";\n\ncontract AddLiquidityScript is BaseScript, LiquidityHelpers {\n    using CurrencyLibrary for Currency;\n    using StateLibrary for IPoolManager;\n\n    /////////////////////////////////////\n    // --- Configure These ---\n    /////////////////////////////////////\n\n    uint24 lpFee = 3000; // 0.30%\n    int24 tickSpacing = 60;\n\n    // --- liquidity position configuration --- //\n    uint256 public token0Amount = 1e18;\n    uint256 public token1Amount = 1e18;\n\n    /////////////////////////////////////\n\n    int24 tickLower;\n    int24 tickUpper;\n\n    function run() external {\n        PoolKey memory poolKey = PoolKey({\n            currency0: currency0,\n            currency1: currency1,\n            fee: lpFee,\n            tickSpacing: tickSpacing,\n            hooks: hookContract\n        });\n        bytes memory hookData = new bytes(0);\n\n        (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey.toId());\n\n        int24 currentTick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);\n\n        tickLower = truncateTickSpacing((currentTick - 1000 * tickSpacing), tickSpacing);\n        tickUpper = truncateTickSpacing((currentTick + 1000 * tickSpacing), tickSpacing);\n\n        // Converts token amounts to liquidity units\n        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(\n            sqrtPriceX96,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            token0Amount,\n            token1Amount\n        );\n\n        // slippage limits\n        uint256 amount0Max = token0Amount + 1 wei;\n        uint256 amount1Max = token1Amount + 1 wei;\n\n        (bytes memory actions, bytes[] memory mintParams) = _mintLiquidityParams(\n            poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, deployerAddress, hookData\n        );\n\n        // multicall parameters\n        bytes[] memory params = new bytes[](1);\n\n        // Mint Liquidity\n        params[0] = abi.encodeWithSelector(\n            positionManager.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 60\n        );\n\n        // If the pool is an ETH pair, native tokens are to be transferred\n        uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0;\n\n        vm.startBroadcast();\n        tokenApprovals();\n\n        // Add liquidity to existing pool\n        positionManager.multicall{value: valueToPass}(params);\n        vm.stopBroadcast();\n    }\n}\n"
  },
  {
    "path": "script/03_Swap.s.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\n\nimport {BaseScript} from \"./base/BaseScript.sol\";\n\ncontract SwapScript is BaseScript {\n    function run() external {\n        PoolKey memory poolKey = PoolKey({\n            currency0: currency0,\n            currency1: currency1,\n            fee: 3000,\n            tickSpacing: 60,\n            hooks: hookContract // This must match the pool\n        });\n        bytes memory hookData = new bytes(0);\n\n        vm.startBroadcast();\n\n        // We'll approve both, just for testing.\n        token1.approve(address(swapRouter), type(uint256).max);\n        token0.approve(address(swapRouter), type(uint256).max);\n\n        // Execute swap\n        swapRouter.swapExactTokensForTokens({\n            amountIn: 1e18,\n            amountOutMin: 0, // Very bad, but we want to allow for unlimited price impact\n            zeroForOne: true,\n            poolKey: poolKey,\n            hookData: hookData,\n            receiver: address(this),\n            deadline: block.timestamp + 30\n        });\n\n        vm.stopBroadcast();\n    }\n}\n"
  },
  {
    "path": "script/base/BaseScript.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Script} from \"forge-std/Script.sol\";\nimport {IERC20} from \"forge-std/interfaces/IERC20.sol\";\n\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IPositionManager} from \"@uniswap/v4-periphery/src/interfaces/IPositionManager.sol\";\nimport {IPermit2} from \"permit2/src/interfaces/IPermit2.sol\";\n\nimport {IUniswapV4Router04} from \"hookmate/interfaces/router/IUniswapV4Router04.sol\";\nimport {AddressConstants} from \"hookmate/constants/AddressConstants.sol\";\n\nimport {Deployers} from \"test/utils/Deployers.sol\";\n\n/// @notice Shared configuration between scripts\ncontract BaseScript is Script, Deployers {\n    address immutable deployerAddress;\n\n    /////////////////////////////////////\n    // --- Configure These ---\n    /////////////////////////////////////\n    IERC20 internal constant token0 = IERC20(0x0165878A594ca255338adfa4d48449f69242Eb8F);\n    IERC20 internal constant token1 = IERC20(0xa513E6E4b8f2a923D98304ec87F64353C4D5C853);\n    IHooks constant hookContract = IHooks(address(0));\n    /////////////////////////////////////\n\n    Currency immutable currency0;\n    Currency immutable currency1;\n\n    constructor() {\n        // Make sure artifacts are available, either deploy or configure.\n        deployArtifacts();\n\n        deployerAddress = getDeployer();\n\n        (currency0, currency1) = getCurrencies();\n\n        vm.label(address(permit2), \"Permit2\");\n        vm.label(address(poolManager), \"V4PoolManager\");\n        vm.label(address(positionManager), \"V4PositionManager\");\n        vm.label(address(swapRouter), \"V4SwapRouter\");\n\n        vm.label(address(token0), \"Currency0\");\n        vm.label(address(token1), \"Currency1\");\n\n        vm.label(address(hookContract), \"HookContract\");\n    }\n\n    function _etch(address target, bytes memory bytecode) internal override {\n        if (block.chainid == 31337) {\n            vm.rpc(\"anvil_setCode\", string.concat('[\"', vm.toString(target), '\",', '\"', vm.toString(bytecode), '\"]'));\n        } else {\n            revert(\"Unsupported etch on this network\");\n        }\n    }\n\n    function getCurrencies() internal pure returns (Currency, Currency) {\n        require(address(token0) != address(token1));\n\n        if (token0 < token1) {\n            return (Currency.wrap(address(token0)), Currency.wrap(address(token1)));\n        } else {\n            return (Currency.wrap(address(token1)), Currency.wrap(address(token0)));\n        }\n    }\n\n    function getDeployer() internal returns (address) {\n        address[] memory wallets = vm.getWallets();\n\n        if (wallets.length > 0) {\n            return wallets[0];\n        } else {\n            return msg.sender;\n        }\n    }\n}\n"
  },
  {
    "path": "script/base/LiquidityHelpers.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {CurrencyLibrary, Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {Actions} from \"@uniswap/v4-periphery/src/libraries/Actions.sol\";\n\nimport {BaseScript} from \"./BaseScript.sol\";\n\ncontract LiquidityHelpers is BaseScript {\n    using CurrencyLibrary for Currency;\n\n    function _mintLiquidityParams(\n        PoolKey memory poolKey,\n        int24 _tickLower,\n        int24 _tickUpper,\n        uint256 liquidity,\n        uint256 amount0Max,\n        uint256 amount1Max,\n        address recipient,\n        bytes memory hookData\n    ) internal pure returns (bytes memory, bytes[] memory) {\n        bytes memory actions = abi.encodePacked(\n            uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR), uint8(Actions.SWEEP), uint8(Actions.SWEEP)\n        );\n\n        bytes[] memory params = new bytes[](4);\n        params[0] = abi.encode(poolKey, _tickLower, _tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);\n        params[1] = abi.encode(poolKey.currency0, poolKey.currency1);\n        params[2] = abi.encode(poolKey.currency0, recipient);\n        params[3] = abi.encode(poolKey.currency1, recipient);\n\n        return (actions, params);\n    }\n\n    function tokenApprovals() public {\n        if (!currency0.isAddressZero()) {\n            token0.approve(address(permit2), type(uint256).max);\n            permit2.approve(address(token0), address(positionManager), type(uint160).max, type(uint48).max);\n        }\n\n        if (!currency1.isAddressZero()) {\n            token1.approve(address(permit2), type(uint256).max);\n            permit2.approve(address(token1), address(positionManager), type(uint160).max, type(uint48).max);\n        }\n    }\n\n    function truncateTickSpacing(int24 tick, int24 tickSpacing) internal pure returns (int24) {\n        /// forge-lint: disable-next-line(divide-before-multiply)\n        return ((tick / tickSpacing) * tickSpacing);\n    }\n}\n"
  },
  {
    "path": "script/testing/00_DeployV4.s.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {HookMiner} from \"@uniswap/v4-periphery/src/utils/HookMiner.sol\";\n\nimport {console2} from \"forge-std/Script.sol\";\nimport {BaseScript} from \"../base/BaseScript.sol\";\n\ncontract DeployLocalV4 is BaseScript {\n    function run() public {\n        require(block.chainid == 31337, \"Local deployment only\");\n        /**\n         * Important:\n         *\n         * This script deploys the Uniswap V4 artifacts to local Anvil network.\n         * That said, scripts in this repo will NOT automatically use these deployments,\n         * unless you also change the addresses in the `Deployers.sol` file.\n         *\n         * You can override or modify the following functions with your own deployments:\n         * - deployPoolManager()\n         * - deployPositionManager()\n         * - deployRouter()\n         *\n         * Permit2 is always on the same address.\n         */\n\n        vm.startBroadcast();\n        deployArtifacts();\n        vm.stopBroadcast();\n\n        console2.log(\"Deployed Permit2 at:\", address(permit2));\n        console2.log(\"Deployed V4PoolManager at:\", address(poolManager));\n        console2.log(\"Deployed V4PositionManager at:\", address(positionManager));\n        console2.log(\"Deployed V4SwapRouter at:\", address(swapRouter));\n    }\n}\n"
  },
  {
    "path": "src/Counter.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {BaseHook} from \"@openzeppelin/uniswap-hooks/src/base/BaseHook.sol\";\n\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {IPoolManager, SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {PoolId, PoolIdLibrary} from \"@uniswap/v4-core/src/types/PoolId.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {BeforeSwapDelta, BeforeSwapDeltaLibrary} from \"@uniswap/v4-core/src/types/BeforeSwapDelta.sol\";\n\ncontract Counter is BaseHook {\n    using PoolIdLibrary for PoolKey;\n\n    // NOTE: ---------------------------------------------------------\n    // state variables should typically be unique to a pool\n    // a single hook contract should be able to service multiple pools\n    // ---------------------------------------------------------------\n\n    mapping(PoolId => uint256 count) public beforeSwapCount;\n    mapping(PoolId => uint256 count) public afterSwapCount;\n\n    mapping(PoolId => uint256 count) public beforeAddLiquidityCount;\n    mapping(PoolId => uint256 count) public beforeRemoveLiquidityCount;\n\n    constructor(IPoolManager _poolManager) BaseHook(_poolManager) {}\n\n    function getHookPermissions() public pure override returns (Hooks.Permissions memory) {\n        return Hooks.Permissions({\n            beforeInitialize: false,\n            afterInitialize: false,\n            beforeAddLiquidity: true,\n            afterAddLiquidity: false,\n            beforeRemoveLiquidity: true,\n            afterRemoveLiquidity: false,\n            beforeSwap: true,\n            afterSwap: true,\n            beforeDonate: false,\n            afterDonate: false,\n            beforeSwapReturnDelta: false,\n            afterSwapReturnDelta: false,\n            afterAddLiquidityReturnDelta: false,\n            afterRemoveLiquidityReturnDelta: false\n        });\n    }\n\n    // -----------------------------------------------\n    // NOTE: see IHooks.sol for function documentation\n    // -----------------------------------------------\n\n    function _beforeSwap(address, PoolKey calldata key, SwapParams calldata, bytes calldata)\n        internal\n        override\n        returns (bytes4, BeforeSwapDelta, uint24)\n    {\n        beforeSwapCount[key.toId()]++;\n        return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);\n    }\n\n    function _afterSwap(address, PoolKey calldata key, SwapParams calldata, BalanceDelta, bytes calldata)\n        internal\n        override\n        returns (bytes4, int128)\n    {\n        afterSwapCount[key.toId()]++;\n        return (BaseHook.afterSwap.selector, 0);\n    }\n\n    function _beforeAddLiquidity(address, PoolKey calldata key, ModifyLiquidityParams calldata, bytes calldata)\n        internal\n        override\n        returns (bytes4)\n    {\n        beforeAddLiquidityCount[key.toId()]++;\n        return BaseHook.beforeAddLiquidity.selector;\n    }\n\n    function _beforeRemoveLiquidity(address, PoolKey calldata key, ModifyLiquidityParams calldata, bytes calldata)\n        internal\n        override\n        returns (bytes4)\n    {\n        beforeRemoveLiquidityCount[key.toId()]++;\n        return BaseHook.beforeRemoveLiquidity.selector;\n    }\n}\n"
  },
  {
    "path": "test/Counter.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\n\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {PoolId, PoolIdLibrary} from \"@uniswap/v4-core/src/types/PoolId.sol\";\nimport {CurrencyLibrary, Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {StateLibrary} from \"@uniswap/v4-core/src/libraries/StateLibrary.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-core/test/utils/LiquidityAmounts.sol\";\nimport {IPositionManager} from \"@uniswap/v4-periphery/src/interfaces/IPositionManager.sol\";\nimport {Constants} from \"@uniswap/v4-core/test/utils/Constants.sol\";\n\nimport {EasyPosm} from \"./utils/libraries/EasyPosm.sol\";\n\nimport {Counter} from \"../src/Counter.sol\";\nimport {BaseTest} from \"./utils/BaseTest.sol\";\n\ncontract CounterTest is BaseTest {\n    using EasyPosm for IPositionManager;\n    using PoolIdLibrary for PoolKey;\n    using CurrencyLibrary for Currency;\n    using StateLibrary for IPoolManager;\n\n    Currency currency0;\n    Currency currency1;\n\n    PoolKey poolKey;\n\n    Counter hook;\n    PoolId poolId;\n\n    uint256 tokenId;\n    int24 tickLower;\n    int24 tickUpper;\n\n    function setUp() public {\n        // Deploys all required artifacts.\n        deployArtifactsAndLabel();\n\n        (currency0, currency1) = deployCurrencyPair();\n\n        // Deploy the hook to an address with the correct flags\n        address flags = address(\n            uint160(\n                Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG | Hooks.BEFORE_ADD_LIQUIDITY_FLAG\n                    | Hooks.BEFORE_REMOVE_LIQUIDITY_FLAG\n            ) ^ (0x4444 << 144) // Namespace the hook to avoid collisions\n        );\n        bytes memory constructorArgs = abi.encode(poolManager); // Add all the necessary constructor arguments from the hook\n        deployCodeTo(\"Counter.sol:Counter\", constructorArgs, flags);\n        hook = Counter(flags);\n\n        // Create the pool\n        poolKey = PoolKey(currency0, currency1, 3000, 60, IHooks(hook));\n        poolId = poolKey.toId();\n        poolManager.initialize(poolKey, Constants.SQRT_PRICE_1_1);\n\n        // Provide full-range liquidity to the pool\n        tickLower = TickMath.minUsableTick(poolKey.tickSpacing);\n        tickUpper = TickMath.maxUsableTick(poolKey.tickSpacing);\n\n        uint128 liquidityAmount = 100e18;\n\n        (uint256 amount0Expected, uint256 amount1Expected) = LiquidityAmounts.getAmountsForLiquidity(\n            Constants.SQRT_PRICE_1_1,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            liquidityAmount\n        );\n\n        (tokenId,) = positionManager.mint(\n            poolKey,\n            tickLower,\n            tickUpper,\n            liquidityAmount,\n            amount0Expected + 1,\n            amount1Expected + 1,\n            address(this),\n            block.timestamp,\n            Constants.ZERO_BYTES\n        );\n    }\n\n    function testCounterHooks() public {\n        // positions were created in setup()\n        assertEq(hook.beforeAddLiquidityCount(poolId), 1);\n        assertEq(hook.beforeRemoveLiquidityCount(poolId), 0);\n\n        assertEq(hook.beforeSwapCount(poolId), 0);\n        assertEq(hook.afterSwapCount(poolId), 0);\n\n        // Perform a test swap //\n        uint256 amountIn = 1e18;\n        BalanceDelta swapDelta = swapRouter.swapExactTokensForTokens({\n            amountIn: amountIn,\n            amountOutMin: 0, // Very bad, but we want to allow for unlimited price impact\n            zeroForOne: true,\n            poolKey: poolKey,\n            hookData: Constants.ZERO_BYTES,\n            receiver: address(this),\n            deadline: block.timestamp + 1\n        });\n        // ------------------- //\n\n        assertEq(int256(swapDelta.amount0()), -int256(amountIn));\n\n        assertEq(hook.beforeSwapCount(poolId), 1);\n        assertEq(hook.afterSwapCount(poolId), 1);\n    }\n\n    function testLiquidityHooks() public {\n        // positions were created in setup()\n        assertEq(hook.beforeAddLiquidityCount(poolId), 1);\n        assertEq(hook.beforeRemoveLiquidityCount(poolId), 0);\n\n        // remove liquidity\n        uint256 liquidityToRemove = 1e18;\n        positionManager.decreaseLiquidity(\n            tokenId,\n            liquidityToRemove,\n            0, // Max slippage, token0\n            0, // Max slippage, token1\n            address(this),\n            block.timestamp,\n            Constants.ZERO_BYTES\n        );\n\n        assertEq(hook.beforeAddLiquidityCount(poolId), 1);\n        assertEq(hook.beforeRemoveLiquidityCount(poolId), 1);\n    }\n}\n"
  },
  {
    "path": "test/utils/BaseTest.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\n\nimport {Deployers} from \"./Deployers.sol\";\n\ncontract BaseTest is Test, Deployers {\n    function deployArtifactsAndLabel() internal {\n        deployArtifacts();\n\n        vm.label(address(permit2), \"Permit2\");\n        vm.label(address(poolManager), \"V4PoolManager\");\n        vm.label(address(positionManager), \"V4PositionManager\");\n        vm.label(address(swapRouter), \"V4SwapRouter\");\n    }\n\n    function deployCurrencyPair() internal virtual override returns (Currency currency0, Currency currency1) {\n        (currency0, currency1) = super.deployCurrencyPair();\n\n        vm.label(Currency.unwrap(currency0), \"Currency0\");\n        vm.label(Currency.unwrap(currency1), \"Currency1\");\n    }\n\n    function _etch(address target, bytes memory bytecode) internal override {\n        vm.etch(target, bytecode);\n    }\n}\n"
  },
  {
    "path": "test/utils/Deployers.sol",
    "content": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.26;\n\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\n\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\n\nimport {IPermit2} from \"permit2/src/interfaces/IPermit2.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IPositionManager} from \"@uniswap/v4-periphery/src/interfaces/IPositionManager.sol\";\n\nimport {IUniswapV4Router04} from \"hookmate/interfaces/router/IUniswapV4Router04.sol\";\nimport {AddressConstants} from \"hookmate/constants/AddressConstants.sol\";\n\nimport {Permit2Deployer} from \"hookmate/artifacts/Permit2.sol\";\nimport {V4PoolManagerDeployer} from \"hookmate/artifacts/V4PoolManager.sol\";\nimport {V4PositionManagerDeployer} from \"hookmate/artifacts/V4PositionManager.sol\";\nimport {V4RouterDeployer} from \"hookmate/artifacts/V4Router.sol\";\n\n/**\n * Base Deployer Contract for Hook Testing\n *\n * Automatically does the following:\n * 1. Setup deployments for Permit2, PoolManager, PositionManager and V4SwapRouter.\n * 2. Check if chainId is 31337, is so, deploys local instances.\n * 3. If not, uses existing canonical deployments on the selected network.\n * 4. Provides utility functions to deploy tokens and currency pairs.\n *\n * This contract can be used for both local testing and fork testing.\n */\nabstract contract Deployers {\n    IPermit2 permit2;\n    IPoolManager poolManager;\n    IPositionManager positionManager;\n    IUniswapV4Router04 swapRouter;\n\n    function deployToken() internal returns (MockERC20 token) {\n        token = new MockERC20(\"Test Token\", \"TEST\", 18);\n        token.mint(address(this), 10_000_000 ether);\n\n        token.approve(address(permit2), type(uint256).max);\n        token.approve(address(swapRouter), type(uint256).max);\n\n        permit2.approve(address(token), address(positionManager), type(uint160).max, type(uint48).max);\n        permit2.approve(address(token), address(poolManager), type(uint160).max, type(uint48).max);\n    }\n\n    function deployCurrencyPair() internal virtual returns (Currency currency0, Currency currency1) {\n        MockERC20 token0 = deployToken();\n        MockERC20 token1 = deployToken();\n\n        if (token0 > token1) {\n            (token0, token1) = (token1, token0);\n        }\n\n        currency0 = Currency.wrap(address(token0));\n        currency1 = Currency.wrap(address(token1));\n    }\n\n    function deployPermit2() internal {\n        address permit2Address = AddressConstants.getPermit2Address();\n\n        if (permit2Address.code.length > 0) {\n            // Permit2 is already deployed, no need to etch it.\n        } else {\n            _etch(permit2Address, Permit2Deployer.deploy().code);\n        }\n\n        permit2 = IPermit2(permit2Address);\n    }\n\n    function deployPoolManager() internal virtual {\n        if (block.chainid == 31337) {\n            poolManager = IPoolManager(V4PoolManagerDeployer.deploy(address(0x4444)));\n        } else {\n            poolManager = IPoolManager(AddressConstants.getPoolManagerAddress(block.chainid));\n        }\n    }\n\n    function deployPositionManager() internal virtual {\n        if (block.chainid == 31337) {\n            positionManager = IPositionManager(\n                V4PositionManagerDeployer.deploy(\n                    address(poolManager), address(permit2), 300_000, address(0), address(0)\n                )\n            );\n        } else {\n            positionManager = IPositionManager(AddressConstants.getPositionManagerAddress(block.chainid));\n        }\n    }\n\n    function deployRouter() internal virtual {\n        if (block.chainid == 31337) {\n            swapRouter = IUniswapV4Router04(payable(V4RouterDeployer.deploy(address(poolManager), address(permit2))));\n        } else {\n            swapRouter = IUniswapV4Router04(payable(AddressConstants.getV4SwapRouterAddress(block.chainid)));\n        }\n    }\n\n    function _etch(address, bytes memory) internal virtual {\n        revert(\"Not implemented\");\n    }\n\n    function deployArtifacts() internal {\n        // Order matters.\n        deployPermit2();\n        deployPoolManager();\n        deployPositionManager();\n        deployRouter();\n    }\n}\n"
  },
  {
    "path": "test/utils/libraries/EasyPosm.sol",
    "content": "// SPDX-License-Identifier: GPL-2.0-or-later\npragma solidity ^0.8.21;\n\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {BalanceDelta, toBalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/src/types/Currency.sol\";\nimport {IPositionManager} from \"@uniswap/v4-periphery/src/interfaces/IPositionManager.sol\";\nimport {Actions} from \"@uniswap/v4-periphery/src/libraries/Actions.sol\";\nimport {SafeCast} from \"@uniswap/v4-core/src/libraries/SafeCast.sol\";\nimport {PositionInfo, PositionInfoLibrary} from \"@uniswap/v4-periphery/src/libraries/PositionInfoLibrary.sol\";\n\n/// @title Easy Position Manager\n/// @notice A library for abstracting Position Manager calldata\n/// @dev Useable onchain, but expensive because of encoding\nlibrary EasyPosm {\n    using CurrencyLibrary for Currency;\n    using SafeCast for uint256;\n    using SafeCast for int256;\n    using PositionInfoLibrary for PositionInfo;\n\n    /// @dev packing data to avoid stack too deep error\n    struct MintData {\n        uint256 balance0Before;\n        uint256 balance1Before;\n        bytes[] params;\n        bytes actions;\n    }\n\n    /// @dev This function supports sending native tokens (ETH), the amount-to-pay is determined by amount0Max.\n    ///      Any excess amount is NOT refunded since it is not encoding the SWEEP action\n    function mint(\n        IPositionManager posm,\n        PoolKey memory poolKey,\n        int24 tickLower,\n        int24 tickUpper,\n        uint256 liquidity,\n        uint256 amount0Max,\n        uint256 amount1Max,\n        address recipient,\n        uint256 deadline,\n        bytes memory hookData\n    ) internal returns (uint256 tokenId, BalanceDelta delta) {\n        (Currency currency0, Currency currency1) = (poolKey.currency0, poolKey.currency1);\n\n        MintData memory mintData = MintData({\n            balance0Before: currency0.balanceOf(address(this)),\n            balance1Before: currency1.balanceOf(address(this)),\n            actions: new bytes(0),\n            params: new bytes[](4)\n        });\n\n        mintData.actions = abi.encodePacked(\n            uint8(Actions.MINT_POSITION), uint8(Actions.SETTLE_PAIR), uint8(Actions.SWEEP), uint8(Actions.SWEEP)\n        );\n\n        mintData.params[0] =\n            abi.encode(poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);\n        mintData.params[1] = abi.encode(currency0, currency1);\n        mintData.params[2] = abi.encode(currency0, recipient);\n        mintData.params[3] = abi.encode(currency1, recipient);\n\n        // Mint Liquidity\n        tokenId = posm.nextTokenId();\n        uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0;\n        posm.modifyLiquidities{value: valueToPass}(abi.encode(mintData.actions, mintData.params), deadline);\n\n        delta = toBalanceDelta(\n            -(mintData.balance0Before - currency0.balanceOf(address(this))).toInt128(),\n            -(mintData.balance1Before - currency1.balanceOf(address(this))).toInt128()\n        );\n    }\n\n    function increaseLiquidity(\n        IPositionManager posm,\n        uint256 tokenId,\n        uint256 liquidityToAdd,\n        uint256 amount0Max,\n        uint256 amount1Max,\n        uint256 deadline,\n        bytes memory hookData\n    ) internal returns (BalanceDelta delta) {\n        (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId);\n\n        bytes[] memory params = new bytes[](3);\n        params[0] = abi.encode(tokenId, liquidityToAdd, amount0Max, amount1Max, hookData);\n        params[1] = abi.encode(currency0);\n        params[2] = abi.encode(currency1);\n\n        uint256 balance0Before = currency0.balanceOf(address(this));\n        uint256 balance1Before = currency1.balanceOf(address(this));\n\n        uint256 valueToPass = currency0.isAddressZero() ? amount0Max : 0;\n        posm.modifyLiquidities{value: valueToPass}(\n            abi.encode(\n                abi.encodePacked(\n                    uint8(Actions.INCREASE_LIQUIDITY), uint8(Actions.CLOSE_CURRENCY), uint8(Actions.CLOSE_CURRENCY)\n                ),\n                params\n            ),\n            deadline\n        );\n\n        delta = toBalanceDelta(\n            (currency0.balanceOf(address(this)).toInt256() - balance0Before.toInt256()).toInt128(),\n            (currency1.balanceOf(address(this)).toInt256() - balance1Before.toInt256()).toInt128()\n        );\n    }\n\n    function decreaseLiquidity(\n        IPositionManager posm,\n        uint256 tokenId,\n        uint256 liquidityToRemove,\n        uint256 amount0Min,\n        uint256 amount1Min,\n        address recipient,\n        uint256 deadline,\n        bytes memory hookData\n    ) internal returns (BalanceDelta delta) {\n        (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId);\n\n        bytes[] memory params = new bytes[](2);\n        params[0] = abi.encode(tokenId, liquidityToRemove, amount0Min, amount1Min, hookData);\n        params[1] = abi.encode(currency0, currency1, recipient);\n\n        uint256 balance0Before = currency0.balanceOf(address(this));\n        uint256 balance1Before = currency1.balanceOf(address(this));\n\n        posm.modifyLiquidities(\n            abi.encode(abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR)), params), deadline\n        );\n\n        delta = toBalanceDelta(\n            (currency0.balanceOf(address(this)) - balance0Before).toInt128(),\n            (currency1.balanceOf(address(this)) - balance1Before).toInt128()\n        );\n    }\n\n    function collect(\n        IPositionManager posm,\n        uint256 tokenId,\n        uint256 amount0Min,\n        uint256 amount1Min,\n        address recipient,\n        uint256 deadline,\n        bytes memory hookData\n    ) internal returns (BalanceDelta delta) {\n        (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId);\n\n        bytes[] memory params = new bytes[](2);\n        // collecting fees is achieved by decreasing liquidity with 0 liquidity removed\n        params[0] = abi.encode(tokenId, 0, amount0Min, amount1Min, hookData);\n        params[1] = abi.encode(currency0, currency1, recipient);\n\n        uint256 balance0Before = currency0.balanceOf(recipient);\n        uint256 balance1Before = currency1.balanceOf(recipient);\n\n        posm.modifyLiquidities(\n            abi.encode(abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR)), params), deadline\n        );\n\n        delta = toBalanceDelta(\n            (currency0.balanceOf(recipient) - balance0Before).toInt128(),\n            (currency1.balanceOf(recipient) - balance1Before).toInt128()\n        );\n    }\n\n    function burn(\n        IPositionManager posm,\n        uint256 tokenId,\n        uint256 amount0Min,\n        uint256 amount1Min,\n        address recipient,\n        uint256 deadline,\n        bytes memory hookData\n    ) internal returns (BalanceDelta delta) {\n        (Currency currency0, Currency currency1) = getCurrencies(posm, tokenId);\n\n        bytes[] memory params = new bytes[](2);\n        params[0] = abi.encode(tokenId, 0, amount0Min, amount1Min, hookData);\n        params[1] = abi.encode(currency0, currency1, recipient);\n\n        uint256 balance0Before = currency0.balanceOf(recipient);\n        uint256 balance1Before = currency1.balanceOf(recipient);\n\n        posm.modifyLiquidities(\n            abi.encode(abi.encodePacked(uint8(Actions.BURN_POSITION), uint8(Actions.TAKE_PAIR)), params), deadline\n        );\n\n        delta = toBalanceDelta(\n            (currency0.balanceOf(recipient) - balance0Before).toInt128(),\n            (currency1.balanceOf(recipient) - balance1Before).toInt128()\n        );\n    }\n\n    function getCurrencies(IPositionManager posm, uint256 tokenId)\n        internal\n        view\n        returns (Currency currency0, Currency currency1)\n    {\n        (PoolKey memory key,) = posm.getPoolAndPositionInfo(tokenId);\n        return (key.currency0, key.currency1);\n    }\n}\n"
  },
  {
    "path": "test/utils/libraries/EasyPosm.t.sol",
    "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {PoolIdLibrary} from \"@uniswap/v4-core/src/types/PoolId.sol\";\nimport {CurrencyLibrary, Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {StateLibrary} from \"@uniswap/v4-core/src/libraries/StateLibrary.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-core/test/utils/LiquidityAmounts.sol\";\nimport {IPositionManager} from \"@uniswap/v4-periphery/src/interfaces/IPositionManager.sol\";\nimport {Constants} from \"@uniswap/v4-core/test/utils/Constants.sol\";\n\nimport {EasyPosm} from \"./EasyPosm.sol\";\nimport {BaseTest} from \"../BaseTest.sol\";\n\ncontract EasyPosmTest is Test, BaseTest {\n    using EasyPosm for IPositionManager;\n    using PoolIdLibrary for PoolKey;\n    using CurrencyLibrary for Currency;\n    using StateLibrary for IPoolManager;\n\n    Currency currency0;\n    Currency currency1;\n\n    int24 tickLower;\n    int24 tickUpper;\n\n    PoolKey key;\n    PoolKey nativeKey;\n\n    function setUp() public {\n        deployArtifacts();\n\n        (currency0, currency1) = deployCurrencyPair();\n\n        // Create the pool\n        key = PoolKey(currency0, currency1, 3000, 60, IHooks(address(0)));\n        nativeKey = PoolKey(Currency.wrap(address(0)), currency1, 3000, 60, IHooks(address(0)));\n\n        poolManager.initialize(key, Constants.SQRT_PRICE_1_1);\n        poolManager.initialize(nativeKey, Constants.SQRT_PRICE_1_1);\n\n        // full-range liquidity\n        tickLower = TickMath.minUsableTick(key.tickSpacing);\n        tickUpper = TickMath.maxUsableTick(key.tickSpacing);\n    }\n\n    function test_mintLiquidity() public {\n        uint256 liquidityToMint = 100e18;\n        address recipient = address(this);\n\n        (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(\n            Constants.SQRT_PRICE_1_1,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            uint128(liquidityToMint)\n        );\n\n        (, BalanceDelta delta) = positionManager.mint(\n            key,\n            tickLower,\n            tickUpper,\n            liquidityToMint,\n            type(uint256).max,\n            type(uint256).max,\n            recipient,\n            block.timestamp + 1,\n            Constants.ZERO_BYTES\n        );\n        assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei)));\n        assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei)));\n    }\n\n    function test_mintLiquidityNative() public {\n        uint256 liquidityToMint = 100e18;\n        address recipient = address(this);\n\n        (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(\n            Constants.SQRT_PRICE_1_1,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            uint128(liquidityToMint)\n        );\n\n        vm.deal(address(this), amount0 + 1);\n        (, BalanceDelta delta) = positionManager.mint(\n            nativeKey,\n            tickLower,\n            tickUpper,\n            liquidityToMint,\n            amount0 + 1,\n            amount1 + 1,\n            recipient,\n            block.timestamp + 1,\n            Constants.ZERO_BYTES\n        );\n        assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei)));\n        assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei)));\n    }\n\n    function test_increaseLiquidity() public {\n        (uint256 tokenId,) = positionManager.mint(\n            key,\n            tickLower,\n            tickUpper,\n            100e18,\n            type(uint256).max,\n            type(uint256).max,\n            address(this),\n            block.timestamp + 1,\n            Constants.ZERO_BYTES\n        );\n\n        uint256 liquidityToAdd = 1e18;\n\n        (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(\n            Constants.SQRT_PRICE_1_1,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            uint128(liquidityToAdd)\n        );\n\n        BalanceDelta delta = positionManager.increaseLiquidity(\n            tokenId, liquidityToAdd, type(uint256).max, type(uint256).max, block.timestamp + 1, Constants.ZERO_BYTES\n        );\n        assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei)));\n        assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei)));\n    }\n\n    function test_increaseLiquidityNative() public {\n        uint256 liquidityToMint = 100e18;\n        address recipient = address(this);\n\n        (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(\n            Constants.SQRT_PRICE_1_1,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            uint128(liquidityToMint)\n        );\n\n        vm.deal(address(this), amount0 + 1);\n        (uint256 tokenId, BalanceDelta delta) = positionManager.mint(\n            nativeKey,\n            tickLower,\n            tickUpper,\n            liquidityToMint,\n            amount0 + 1,\n            amount1 + 1,\n            recipient,\n            block.timestamp + 1,\n            Constants.ZERO_BYTES\n        );\n\n        uint256 liquidityToIncrease = 1e18;\n\n        (amount0, amount1) = LiquidityAmounts.getAmountsForLiquidity(\n            Constants.SQRT_PRICE_1_1,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            uint128(liquidityToIncrease)\n        );\n\n        vm.deal(address(this), amount0 + 1);\n        delta = positionManager.increaseLiquidity(\n            tokenId, liquidityToIncrease, amount0 + 1, amount1 + 1, block.timestamp + 1, Constants.ZERO_BYTES\n        );\n        assertEq(delta.amount0(), -int128(uint128(amount0 + 1 wei)));\n        assertEq(delta.amount1(), -int128(uint128(amount1 + 1 wei)));\n    }\n\n    function test_decreaseLiquidity() public {\n        (uint256 tokenId,) = positionManager.mint(\n            key,\n            tickLower,\n            tickUpper,\n            100e18,\n            type(uint256).max,\n            type(uint256).max,\n            address(this),\n            block.timestamp + 1,\n            Constants.ZERO_BYTES\n        );\n\n        uint256 liquidityToRemove = 1e18;\n\n        (uint256 amount0, uint256 amount1) = LiquidityAmounts.getAmountsForLiquidity(\n            Constants.SQRT_PRICE_1_1,\n            TickMath.getSqrtPriceAtTick(tickLower),\n            TickMath.getSqrtPriceAtTick(tickUpper),\n            uint128(liquidityToRemove)\n        );\n\n        BalanceDelta delta = positionManager.decreaseLiquidity(\n            tokenId, liquidityToRemove, 0, 0, address(this), block.timestamp + 1, Constants.ZERO_BYTES\n        );\n        assertEq(delta.amount0(), int128(uint128(amount0)));\n        assertEq(delta.amount1(), int128(uint128(amount1)));\n    }\n\n    function test_burn() public {\n        (uint256 tokenId, BalanceDelta mintDelta) = positionManager.mint(\n            key,\n            tickLower,\n            tickUpper,\n            100e18,\n            type(uint256).max,\n            type(uint256).max,\n            address(this),\n            block.timestamp + 1,\n            Constants.ZERO_BYTES\n        );\n\n        BalanceDelta delta =\n            positionManager.burn(tokenId, 0, 0, address(this), block.timestamp + 1, Constants.ZERO_BYTES);\n        assertEq(delta.amount0(), -mintDelta.amount0() - 1 wei);\n        assertEq(delta.amount1(), -mintDelta.amount1() - 1 wei);\n    }\n\n    // This test requires a donateRouter, TODO\n    // function test_collect() public {\n    //     (uint256 tokenId,) = positionManager.mint(\n    //         key,\n    //         tickLower,\n    //         tickUpper,\n    //         100e18,\n    //         type(uint256).max,\n    //         type(uint256).max,\n    //         address(this),\n    //         block.timestamp + 1,\n    //         Constants.ZERO_BYTES\n    //     );\n\n    //     // donate to regenerate fee revenue\n    //     uint128 feeRevenue0 = 1e18;\n    //     uint128 feeRevenue1 = 0.1e18;\n\n    //     poolManager.donate(key, feeRevenue0, feeRevenue1, Constants.ZERO_BYTES);\n\n    //     // position collects half of the revenue since 50% of the liquidity is minted in setUp()\n    //     BalanceDelta delta =\n    //         positionManager.collect(tokenId, 0, 0, address(0x123), block.timestamp + 1, Constants.ZERO_BYTES);\n    //     assertEq(uint128(delta.amount0()), feeRevenue0 - 1 wei);\n    //     assertEq(uint128(delta.amount1()), feeRevenue1 - 1 wei);\n    // }\n}\n"
  }
]