diff --git a/script/02_DeployQaSwapRouter.s.sol b/script/02_DeployQaSwapRouter.s.sol new file mode 100644 index 0000000..0a1e7b5 --- /dev/null +++ b/script/02_DeployQaSwapRouter.s.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.15; + +import "forge-std/console2.sol"; +import "forge-std/Script.sol"; +import {IVault} from "infinity-core/src/interfaces/IVault.sol"; +import {ICLPoolManager} from "infinity-core/src/pool-cl/interfaces/ICLPoolManager.sol"; +import {IBinPoolManager} from "infinity-core/src/pool-bin/interfaces/IBinPoolManager.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; + +import {QaSwapRouter} from "../src/QaSwapRouter.sol"; + +/** + * Step 1: Deploy + * forge script script/02_DeployQaSwapRouter.s.sol:DeployQaSwapRouter -vvv \ + * --rpc-url $RPC_URL \ + * --broadcast \ + * --slow \ + * --verify + */ +contract DeployQaSwapRouter is Script { + IVault vault = IVault(0x2CdB3EC82EE13d341Dc6E73637BE0Eab79cb79dD); + ICLPoolManager clPoolManager = ICLPoolManager(0x36A12c70c9Cf64f24E89ee132BF93Df2DCD199d4); + IBinPoolManager binPoolManager = IBinPoolManager(0xe71d2e0230cE0765be53A8A1ee05bdACF30F296B); + IAllowanceTransfer permit2 = IAllowanceTransfer(0x31c2F6fcFf4F8759b3Bd5Bf0e1084A055615c768); + + function run() public { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + QaSwapRouter router = new QaSwapRouter(vault, clPoolManager, binPoolManager, permit2); + console2.log("QaSwapRouter :", address(router)); + + vm.stopBroadcast(); + } +} diff --git a/src/QaSwapRouter.sol b/src/QaSwapRouter.sol new file mode 100644 index 0000000..33347aa --- /dev/null +++ b/src/QaSwapRouter.sol @@ -0,0 +1,210 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity 0.8.26; + +import {IVault} from "infinity-core/src/interfaces/IVault.sol"; +import {ICLPoolManager} from "infinity-core/src/pool-cl/interfaces/ICLPoolManager.sol"; +import {IBinPoolManager} from "infinity-core/src/pool-bin/interfaces/IBinPoolManager.sol"; +import {Currency} from "infinity-core/src/types/Currency.sol"; +import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; +import {PoolId} from "infinity-core/src/types/PoolId.sol"; +import {BalanceDelta} from "infinity-core/src/types/BalanceDelta.sol"; +import {TickMath} from "infinity-core/src/pool-cl/libraries/TickMath.sol"; +import {SafeCast} from "infinity-core/src/pool-bin/libraries/math/SafeCast.sol"; +import {IHooks} from "infinity-core/src/interfaces/IHooks.sol"; +import {IPoolManager} from "infinity-core/src/interfaces/IPoolManager.sol"; + +import {InfinityRouter} from "infinity-periphery/src/InfinityRouter.sol"; +import {ICLRouterBase} from "infinity-periphery/src/pool-cl/interfaces/ICLRouterBase.sol"; +import {IBinRouterBase} from "infinity-periphery/src/pool-bin/interfaces/IBinRouterBase.sol"; +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {ActionConstants} from "infinity-periphery/src/libraries/ActionConstants.sol"; +import {DeltaResolver} from "infinity-periphery/src/base/DeltaResolver.sol"; +import {CLCalldataDecoder} from "infinity-periphery/src/pool-cl/libraries/CLCalldataDecoder.sol"; +import {BinCalldataDecoder} from "infinity-periphery/src/pool-bin/libraries/BinCalldataDecoder.sol"; +import {IInfinityRouter} from "infinity-periphery/src/interfaces/IInfinityRouter.sol"; +import {SafeCastTemp} from "infinity-periphery/src/libraries/SafeCast.sol"; +import {ImmutableState} from "infinity-periphery/src/base/ImmutableState.sol"; + +/// @dev simple contract for internal use to perform a swap on testnet. +/// !!!!!! STRICTLY NOT for production use +contract QaSwapRouter is DeltaResolver { + using CLCalldataDecoder for bytes; + using BinCalldataDecoder for bytes; + using SafeCastTemp for *; + using SafeCast for *; + + error NotVault(); + + // IVault public vault; + ICLPoolManager public clPoolManager; + IBinPoolManager public binPoolManager; + IAllowanceTransfer public permit2; + + /// @notice Only allow calls from the Vault contract + modifier onlyByVault() { + if (msg.sender != address(vault)) revert NotVault(); + _; + } + + constructor( + IVault _vault, + ICLPoolManager _clPoolManager, + IBinPoolManager _binPoolManager, + IAllowanceTransfer _permit2 + ) ImmutableState(_vault) { + clPoolManager = _clPoolManager; + binPoolManager = _binPoolManager; + permit2 = _permit2; + } + + function clSwapExactInputSingle(ICLRouterBase.CLSwapExactInputSingleParams calldata params) external payable { + vault.lock(abi.encode("clSwapExactInputSingle", abi.encode(msg.sender, params))); + } + + function clSwapExactInputSingle( + PoolId poolId, + bool zeroForOne, + uint128 amountIn, + uint128 amountOutMinimum, + bytes memory hookData + ) external payable { + (Currency curr0, Currency curr1, IHooks hook, IPoolManager pm, uint24 fee, bytes32 param) = + clPoolManager.poolIdToPoolKey(poolId); + PoolKey memory poolKey = PoolKey(curr0, curr1, hook, pm, fee, param); + + ICLRouterBase.CLSwapExactInputSingleParams memory params = ICLRouterBase.CLSwapExactInputSingleParams({ + poolKey: poolKey, + zeroForOne: zeroForOne, + amountIn: amountIn, + amountOutMinimum: amountOutMinimum, + hookData: hookData + }); + vault.lock(abi.encode("clSwapExactInputSingle", abi.encode(msg.sender, params))); + } + + function poolKeyToPoolId(Currency curr0, Currency curr1, IHooks hook, IPoolManager pm, uint24 fee, bytes32 param) + external + pure + returns (bytes32) + { + PoolKey memory poolKey = PoolKey(curr0, curr1, hook, pm, fee, param); + return PoolId.unwrap(poolKey.toId()); + } + + function binSwapExactInputSingle(IBinRouterBase.BinSwapExactInputSingleParams calldata params) external payable { + vault.lock(abi.encode("binSwapExactInputSingle", abi.encode(msg.sender, params))); + } + + function binSwapExactInputSingle( + PoolId poolId, + bool swapForY, + uint128 amountIn, + uint128 amountOutMinimum, + bytes memory hookData + ) external payable { + (Currency curr0, Currency curr1, IHooks hook, IPoolManager pm, uint24 fee, bytes32 param) = + binPoolManager.poolIdToPoolKey(poolId); + PoolKey memory poolKey = PoolKey(curr0, curr1, hook, pm, fee, param); + + IBinRouterBase.BinSwapExactInputSingleParams memory params = IBinRouterBase.BinSwapExactInputSingleParams({ + poolKey: poolKey, + swapForY: swapForY, + amountIn: amountIn, + amountOutMinimum: amountOutMinimum, + hookData: hookData + }); + vault.lock(abi.encode("binSwapExactInputSingle", abi.encode(msg.sender, params))); + } + + function lockAcquired(bytes calldata callbackData) external onlyByVault returns (bytes memory) { + (bytes memory action, bytes memory rawCallbackData) = abi.decode(callbackData, (bytes, bytes)); + + if (keccak256(action) == keccak256("clSwapExactInputSingle")) { + (address sender, ICLRouterBase.CLSwapExactInputSingleParams memory params) = + abi.decode(rawCallbackData, (address, ICLRouterBase.CLSwapExactInputSingleParams)); + + _clSwapExactInputSingle(sender, params); + } else if (keccak256(action) == keccak256("binSwapExactInputSingle")) { + (address sender, IBinRouterBase.BinSwapExactInputSingleParams memory params) = + abi.decode(rawCallbackData, (address, IBinRouterBase.BinSwapExactInputSingleParams)); + + _binSwapExactInputSingle(sender, params); + } else { + revert("QaSwapRouter: invalid action"); + } + } + + /// @dev referenced from CLRouterBase.sol + function _clSwapExactInputSingle(address sender, ICLRouterBase.CLSwapExactInputSingleParams memory params) + internal + { + uint128 amountOut = _clSwapExactPrivate( + params.poolKey, params.zeroForOne, -int256(uint256(params.amountIn)), params.hookData + ).toUint128(); + if (amountOut < params.amountOutMinimum) { + revert IInfinityRouter.TooLittleReceived(params.amountOutMinimum, amountOut); + } + + (Currency inputCurrency, Currency outputCurrency) = params.zeroForOne + ? (params.poolKey.currency0, params.poolKey.currency1) + : (params.poolKey.currency1, params.poolKey.currency0); + + // pay and take + _settle(inputCurrency, sender, _getFullDebt(inputCurrency)); + _take(outputCurrency, sender, _getFullCredit(outputCurrency)); + } + + /// @dev referenced from CLRouterBase.sol + function _clSwapExactPrivate(PoolKey memory poolKey, bool zeroForOne, int256 amountSpecified, bytes memory hookData) + private + returns (int128 reciprocalAmount) + { + BalanceDelta delta = clPoolManager.swap( + poolKey, + ICLPoolManager.SwapParams( + zeroForOne, amountSpecified, zeroForOne ? TickMath.MIN_SQRT_RATIO + 1 : TickMath.MAX_SQRT_RATIO - 1 + ), + hookData + ); + + reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0(); + } + + /// @dev reference from BinRouterBase.sol + function _binSwapExactInputSingle(address sender, IBinRouterBase.BinSwapExactInputSingleParams memory params) + internal + { + uint128 amountOut = _swapBinExactPrivate( + params.poolKey, params.swapForY, -(params.amountIn.safeInt128()), params.hookData + ).toUint128(); + + if (amountOut < params.amountOutMinimum) { + revert IInfinityRouter.TooLittleReceived(params.amountOutMinimum, amountOut); + } + + (Currency inputCurrency, Currency outputCurrency) = params.swapForY + ? (params.poolKey.currency0, params.poolKey.currency1) + : (params.poolKey.currency1, params.poolKey.currency0); + + // pay and take + _settle(inputCurrency, sender, _getFullDebt(inputCurrency)); + _take(outputCurrency, sender, _getFullCredit(outputCurrency)); + } + + /// @dev referenced from BinRouterBase.sol + function _swapBinExactPrivate(PoolKey memory poolKey, bool swapForY, int128 amountSpecified, bytes memory hookData) + private + returns (int128 reciprocalAmount) + { + BalanceDelta delta = binPoolManager.swap(poolKey, swapForY, amountSpecified, hookData); + reciprocalAmount = (swapForY == amountSpecified < 0) ? delta.amount1() : delta.amount0(); + } + + function _pay(Currency currency, address payer, uint256 amount) internal override(DeltaResolver) { + if (payer == address(this)) { + currency.transfer(address(vault), amount); + } else { + permit2.transferFrom(payer, address(vault), uint160(amount), Currency.unwrap(currency)); + } + } +} diff --git a/test/QaSwapRouter.t.sol b/test/QaSwapRouter.t.sol new file mode 100644 index 0000000..b93c92b --- /dev/null +++ b/test/QaSwapRouter.t.sol @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.15; + +import "forge-std/Test.sol"; +import {ERC20} from "solmate/src/tokens/ERC20.sol"; +import {WETH} from "solmate/src/tokens/WETH.sol"; + +import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; +import {DeployPermit2} from "permit2/test/utils/DeployPermit2.sol"; + +import {Currency, CurrencyLibrary} from "infinity-core/src/types/Currency.sol"; +import {IHooks} from "infinity-core/src/interfaces/IHooks.sol"; +import {PoolKey} from "infinity-core/src/types/PoolKey.sol"; +import {PoolId} from "infinity-core/src/types/PoolId.sol"; +import {Currency} from "infinity-core/src/types/Currency.sol"; +import {IVault} from "infinity-core/src/interfaces/IVault.sol"; +import {Vault} from "infinity-core/src/Vault.sol"; +import {IBinPoolManager} from "infinity-core/src/pool-bin/interfaces/IBinPoolManager.sol"; +import {BinPoolManager} from "infinity-core/src/pool-bin/BinPoolManager.sol"; +import {BinPoolParametersHelper} from "infinity-core/src/pool-bin/libraries/BinPoolParametersHelper.sol"; +import {ICLPoolManager} from "infinity-core/src/pool-cl/interfaces/ICLPoolManager.sol"; +import {CLPoolManager} from "infinity-core/src/pool-cl/CLPoolManager.sol"; +import {CLPoolParametersHelper} from "infinity-core/src/pool-cl/libraries/CLPoolParametersHelper.sol"; +import {TickMath} from "infinity-core/src/pool-cl/libraries/TickMath.sol"; +import {FixedPoint96} from "infinity-core/src/pool-cl/libraries/FixedPoint96.sol"; + +import {Actions} from "infinity-periphery/src/libraries/Actions.sol"; +import {IWETH9} from "infinity-periphery/src/interfaces/external/IWETH9.sol"; +import {BinPositionManager} from "infinity-periphery/src/pool-bin/BinPositionManager.sol"; +import {CLPositionManager} from "infinity-periphery/src/pool-cl/CLPositionManager.sol"; +import {CLPositionDescriptorOffChain} from "infinity-periphery/src/pool-cl/CLPositionDescriptorOffChain.sol"; +import {Plan, Planner} from "infinity-periphery/src/libraries/Planner.sol"; +import {BinLiquidityHelper} from "infinity-periphery/test/pool-bin/helper/BinLiquidityHelper.sol"; +import {LiquidityAmounts} from "infinity-periphery/src/pool-cl/libraries/LiquidityAmounts.sol"; +import {IBinPositionManager} from "infinity-periphery/src/pool-bin/interfaces/IBinPositionManager.sol"; +import {ICLRouterBase} from "infinity-periphery/src/pool-cl/interfaces/ICLRouterBase.sol"; +import {IBinRouterBase} from "infinity-periphery/src/pool-bin/interfaces/IBinRouterBase.sol"; + +import {UniversalRouter} from "../src/UniversalRouter.sol"; +import {Dispatcher} from "../src/base/Dispatcher.sol"; +import {IUniversalRouter} from "../src/interfaces/IUniversalRouter.sol"; +import {Payments} from "../src/modules/Payments.sol"; +import {MockERC20} from "./mock/MockERC20.sol"; +import {BasePancakeSwapInfinity} from "./infinity/BasePancakeSwapInfinity.sol"; +import {QaSwapRouter} from "../src/QaSwapRouter.sol"; + +contract QaSwapRouterTest is BasePancakeSwapInfinity, BinLiquidityHelper { + using BinPoolParametersHelper for bytes32; + using CLPoolParametersHelper for bytes32; + using Planner for Plan; + + QaSwapRouter router; + MockERC20 erc20; + MockERC20 erc20_2; + + IVault public vault; + ICLPoolManager public clPoolManager; + CLPositionManager public clPositionManager; + IBinPoolManager public binPoolManager; + BinPositionManager public binPositionManager; + + WETH weth9 = new WETH(); + IAllowanceTransfer permit2; + + address alice = makeAddr("alice"); + uint160 constant SQRT_PRICE_1_1 = uint160(1 * FixedPoint96.Q96); // price 1 + uint24 constant ACTIVE_ID_1_1 = 2 ** 23; // where token0 and token1 price is the same + + MockERC20 token0; + MockERC20 token1; + + PoolKey public clPoolKey; + PoolKey public clNativePoolKey; + PoolKey public binPoolKey; + PoolKey public binNativePoolKey; + + function setUp() public { + initializeTokens(); + vm.label(Currency.unwrap(currency0), "token0"); + vm.label(Currency.unwrap(currency1), "token1"); + + token0 = MockERC20(Currency.unwrap(currency0)); + token1 = MockERC20(Currency.unwrap(currency1)); + + // pre-req: create vault + vault = IVault(new Vault()); + clPoolManager = new CLPoolManager(vault); + binPoolManager = new BinPoolManager(vault); + vault.registerApp(address(clPoolManager)); + vault.registerApp(address(binPoolManager)); + + permit2 = IAllowanceTransfer(deployPermit2()); + + CLPositionDescriptorOffChain pd = + new CLPositionDescriptorOffChain("https://pancakeswap.finance/infinity/pool-cl/positions/"); + clPositionManager = new CLPositionManager(vault, clPoolManager, permit2, 100_000, pd, IWETH9(address(weth9))); + binPositionManager = new BinPositionManager(vault, binPoolManager, permit2, IWETH9(address(weth9))); + _approvePermit2ForCurrency(address(this), currency0, address(clPositionManager), permit2); + _approvePermit2ForCurrency(address(this), currency1, address(clPositionManager), permit2); + _approvePermit2ForCurrency(address(this), currency0, address(binPositionManager), permit2); + _approvePermit2ForCurrency(address(this), currency1, address(binPositionManager), permit2); + + router = new QaSwapRouter(vault, clPoolManager, binPoolManager, permit2); + _approvePermit2ForCurrency(alice, currency0, address(router), permit2); + _approvePermit2ForCurrency(alice, currency1, address(router), permit2); + + clPoolKey = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: IHooks(address(0)), + poolManager: clPoolManager, + fee: uint24(3000), + parameters: bytes32(0).setTickSpacing(10) + }); + clPoolManager.initialize(clPoolKey, SQRT_PRICE_1_1); + _mintCl(clPoolKey); + + clNativePoolKey = PoolKey({ + currency0: CurrencyLibrary.NATIVE, + currency1: currency1, + hooks: IHooks(address(0)), + poolManager: clPoolManager, + fee: uint24(3000), + parameters: bytes32(0).setTickSpacing(10) + }); + clPoolManager.initialize(clNativePoolKey, SQRT_PRICE_1_1); + _mintCl(clNativePoolKey); + + binPoolKey = PoolKey({ + currency0: currency0, + currency1: currency1, + hooks: IHooks(address(0)), + poolManager: binPoolManager, + fee: uint24(3000), + parameters: bytes32(0).setBinStep(10) + }); + binPoolManager.initialize(binPoolKey, ACTIVE_ID_1_1); + _mintBin(binPoolKey); + + binNativePoolKey = PoolKey({ + currency0: CurrencyLibrary.NATIVE, + currency1: currency1, + hooks: IHooks(address(0)), + poolManager: binPoolManager, + fee: uint24(3000), + parameters: bytes32(0).setBinStep(10) + }); + binPoolManager.initialize(binNativePoolKey, ACTIVE_ID_1_1); + _mintBin(binNativePoolKey); + } + + function test_clSwapExactInputSingle_ZeroForOne() external { + uint128 amountIn = 0.01 ether; + MockERC20(Currency.unwrap(currency0)).mint(alice, amountIn); + vm.startPrank(alice); + + ICLRouterBase.CLSwapExactInputSingleParams memory params = + ICLRouterBase.CLSwapExactInputSingleParams(clPoolKey, true, amountIn, 0, ""); + + // before + assertEq(token0.balanceOf(alice), 0.01 ether); + assertEq(token1.balanceOf(alice), 0 ether); + + router.clSwapExactInputSingle(params); + + // after + assertEq(token0.balanceOf(alice), 0); + assertEq(token1.balanceOf(alice), 9969940541342903); + } + + function test_clSwapExactInputSingle_OneForZero() external { + uint128 amountIn = 0.01 ether; + MockERC20(Currency.unwrap(currency1)).mint(alice, amountIn); + vm.startPrank(alice); + + ICLRouterBase.CLSwapExactInputSingleParams memory params = + ICLRouterBase.CLSwapExactInputSingleParams(clPoolKey, false, amountIn, 0, ""); + + // before + assertEq(token0.balanceOf(alice), 0 ether); + assertEq(token1.balanceOf(alice), 0.01 ether); + + router.clSwapExactInputSingle(params); + + // after + assertEq(token0.balanceOf(alice), 9969940541342903); + assertEq(token1.balanceOf(alice), 0); + } + + function test_clSwapExactInputSingle_NativePool() external { + uint128 amountIn = 0.01 ether; + vm.deal(alice, amountIn); + vm.startPrank(alice); + + ICLRouterBase.CLSwapExactInputSingleParams memory params = + ICLRouterBase.CLSwapExactInputSingleParams(clNativePoolKey, true, amountIn, 0, ""); + + // before + assertEq(alice.balance, 0.01 ether); + assertEq(token1.balanceOf(alice), 0 ether); + + router.clSwapExactInputSingle{value: amountIn}(params); + + // after + assertEq(alice.balance, 0); + assertEq(token1.balanceOf(alice), 9969940541342903); + } + + function test_clSwapExactInputSingle_NativePool_PoolId() external { + uint128 amountIn = 0.01 ether; + vm.deal(alice, amountIn); + vm.startPrank(alice); + + // before + assertEq(alice.balance, 0.01 ether); + assertEq(token1.balanceOf(alice), 0 ether); + + router.clSwapExactInputSingle{value: amountIn}(clNativePoolKey.toId(), true, amountIn, 0, ""); + + // after + assertEq(alice.balance, 0); + assertEq(token1.balanceOf(alice), 9969940541342903); + } + + function test_binSwapExactInputSingle_NativePool() external { + uint128 amountIn = 0.01 ether; + vm.deal(alice, amountIn); + vm.startPrank(alice); + + IBinRouterBase.BinSwapExactInputSingleParams memory params = + IBinRouterBase.BinSwapExactInputSingleParams(binNativePoolKey, true, amountIn, 0, ""); + + // before + assertEq(alice.balance, 0.01 ether); + assertEq(token1.balanceOf(alice), 0 ether); + + router.binSwapExactInputSingle{value: amountIn}(params); + + // after + assertEq(alice.balance, 0); + assertEq(token1.balanceOf(alice), 9970000000000000); // 0.01 eth * 0.997 + } + + function test_binSwapExactInputSingle_ZeroForOne() external { + uint128 amountIn = 0.01 ether; + MockERC20(Currency.unwrap(currency0)).mint(alice, amountIn); + vm.startPrank(alice); + + IBinRouterBase.BinSwapExactInputSingleParams memory params = + IBinRouterBase.BinSwapExactInputSingleParams(binPoolKey, true, amountIn, 0, ""); + + // before + assertEq(token0.balanceOf(alice), 0.01 ether); + assertEq(token1.balanceOf(alice), 0 ether); + + router.binSwapExactInputSingle(params); + + // after + assertEq(token0.balanceOf(alice), 0 ether); + assertEq(token1.balanceOf(alice), 9970000000000000); // 0.01 eth * 0.997 + } + + function test_binSwapExactInputSingle_OneForZero() external { + uint128 amountIn = 0.01 ether; + MockERC20(Currency.unwrap(currency1)).mint(alice, amountIn); + vm.startPrank(alice); + + IBinRouterBase.BinSwapExactInputSingleParams memory params = + IBinRouterBase.BinSwapExactInputSingleParams(binPoolKey, false, amountIn, 0, ""); + + // before + assertEq(token0.balanceOf(alice), 0 ether); + assertEq(token1.balanceOf(alice), 0.01 ether); + + router.binSwapExactInputSingle(params); + + // after + assertEq(token0.balanceOf(alice), 9970000000000000); + assertEq(token1.balanceOf(alice), 0 ether); // 0.01 eth * 0.997 + } + + function test_binSwapExactInputSingle_OneForZero_poolId() external { + uint128 amountIn = 0.01 ether; + MockERC20(Currency.unwrap(currency1)).mint(alice, amountIn); + vm.startPrank(alice); + + // before + assertEq(token0.balanceOf(alice), 0 ether); + assertEq(token1.balanceOf(alice), 0.01 ether); + + router.binSwapExactInputSingle(binPoolKey.toId(), false, amountIn, 0, ""); + + // after + assertEq(token0.balanceOf(alice), 9970000000000000); + assertEq(token1.balanceOf(alice), 0 ether); // 0.01 eth * 0.997 + } + + function test_poolKeyToPoolId() external { + bytes32 clPoolId = router.poolKeyToPoolId( + clPoolKey.currency0, + clPoolKey.currency1, + clPoolKey.hooks, + clPoolKey.poolManager, + clPoolKey.fee, + clPoolKey.parameters + ); + assertEq(clPoolId, PoolId.unwrap(clPoolKey.toId())); + + bytes32 binNativePoolId = router.poolKeyToPoolId( + binNativePoolKey.currency0, + binNativePoolKey.currency1, + binNativePoolKey.hooks, + binNativePoolKey.poolManager, + binNativePoolKey.fee, + binNativePoolKey.parameters + ); + assertEq(binNativePoolId, PoolId.unwrap(binNativePoolKey.toId())); + } + + /// @dev add 10 ether of token0, token1 at tick(-120, 120) to poolKey + function _mintCl(PoolKey memory key) private { + int24 tickLower = -120; + int24 tickUpper = 120; + + uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(tickLower); + uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(tickUpper); + uint256 liquidity = + LiquidityAmounts.getLiquidityForAmounts(SQRT_PRICE_1_1, sqrtRatioAX96, sqrtRatioBX96, 10 ether, 10 ether); // around 1671 e18 liquidity + + Plan memory mintPlan = Planner.init(); + mintPlan.add( + Actions.CL_MINT_POSITION, + abi.encode(key, tickLower, tickUpper, liquidity, type(uint128).max, type(uint128).max, address(this), "") + ); + + bytes memory calls = mintPlan.finalizeModifyLiquidityWithClose(key); + + if (key.currency0 == CurrencyLibrary.NATIVE) { + clPositionManager.modifyLiquidities{value: 10 ether}(calls, block.timestamp + 1); + } else { + clPositionManager.modifyLiquidities(calls, block.timestamp + 1); + } + } + + function _mintBin(PoolKey memory key) private { + uint24[] memory binIds = getBinIds(ACTIVE_ID_1_1, 1); + IBinPositionManager.BinAddLiquidityParams memory addParams; + addParams = _getAddParams(key, binIds, 10 ether, 10 ether, ACTIVE_ID_1_1, address(this)); + + Plan memory planner = Planner.init().add(Actions.BIN_ADD_LIQUIDITY, abi.encode(addParams)); + bytes memory payload = planner.finalizeModifyLiquidityWithClose(key); + + if (key.currency0 == CurrencyLibrary.NATIVE) { + binPositionManager.modifyLiquidities{value: 10 ether}(payload, block.timestamp + 1); + } else { + binPositionManager.modifyLiquidities(payload, block.timestamp + 1); + } + } +}