// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.20; import "forge-std/Test.sol"; import {MockERC20} from "solmate/src/test/utils/mocks/MockERC20.sol"; import {Hooks} from "../../src/libraries/Hooks.sol"; import {Currency, CurrencyLibrary} from "../../src/types/Currency.sol"; import {IHooks} from "../../src/interfaces/IHooks.sol"; import {IPoolManager} from "../../src/interfaces/IPoolManager.sol"; import {PoolManager} from "../../src/PoolManager.sol"; import {ModifyLiquidityParams, SwapParams} from "../../src/types/PoolOperation.sol"; import {PoolId} from "../../src/types/PoolId.sol"; import {LPFeeLibrary} from "../../src/libraries/LPFeeLibrary.sol"; import {PoolKey} from "../../src/types/PoolKey.sol"; import {BalanceDelta} from "../../src/types/BalanceDelta.sol"; import {TickMath} from "../../src/libraries/TickMath.sol"; import {Constants} from "../utils/Constants.sol"; import {SortTokens} from "./SortTokens.sol"; import {PoolModifyLiquidityTest} from "../../src/test/PoolModifyLiquidityTest.sol"; import {PoolModifyLiquidityTestNoChecks} from "../../src/test/PoolModifyLiquidityTestNoChecks.sol"; import {PoolSwapTest} from "../../src/test/PoolSwapTest.sol"; import {SwapRouterNoChecks} from "../../src/test/SwapRouterNoChecks.sol"; import {PoolDonateTest} from "../../src/test/PoolDonateTest.sol"; import {PoolNestedActionsTest} from "../../src/test/PoolNestedActionsTest.sol"; import {PoolTakeTest} from "../../src/test/PoolTakeTest.sol"; import {PoolClaimsTest} from "../../src/test/PoolClaimsTest.sol"; import {ActionsRouter} from "../../src/test/ActionsRouter.sol"; import {LiquidityAmounts} from "../../test/utils/LiquidityAmounts.sol"; import {StateLibrary} from "../../src/libraries/StateLibrary.sol"; contract Deployers is Test { using LPFeeLibrary for uint24; using StateLibrary for IPoolManager; // Helpful test constants bytes constant ZERO_BYTES = Constants.ZERO_BYTES; uint160 constant SQRT_PRICE_1_1 = Constants.SQRT_PRICE_1_1; uint160 constant SQRT_PRICE_1_2 = Constants.SQRT_PRICE_1_2; uint160 constant SQRT_PRICE_2_1 = Constants.SQRT_PRICE_2_1; uint160 constant SQRT_PRICE_1_4 = Constants.SQRT_PRICE_1_4; uint160 constant SQRT_PRICE_4_1 = Constants.SQRT_PRICE_4_1; uint160 public constant MIN_PRICE_LIMIT = TickMath.MIN_SQRT_PRICE + 1; uint160 public constant MAX_PRICE_LIMIT = TickMath.MAX_SQRT_PRICE - 1; ModifyLiquidityParams public LIQUIDITY_PARAMS = ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: 1e18, salt: 0}); ModifyLiquidityParams public REMOVE_LIQUIDITY_PARAMS = ModifyLiquidityParams({tickLower: -120, tickUpper: 120, liquidityDelta: -1e18, salt: 0}); SwapParams public SWAP_PARAMS = SwapParams({zeroForOne: true, amountSpecified: -100, sqrtPriceLimitX96: SQRT_PRICE_1_2}); // Global variables Currency internal currency0; Currency internal currency1; IPoolManager manager; PoolModifyLiquidityTest modifyLiquidityRouter; PoolModifyLiquidityTestNoChecks modifyLiquidityNoChecks; SwapRouterNoChecks swapRouterNoChecks; PoolSwapTest swapRouter; PoolDonateTest donateRouter; PoolTakeTest takeRouter; ActionsRouter actionsRouter; PoolClaimsTest claimsRouter; PoolNestedActionsTest nestedActionRouter; address feeController; PoolKey key; PoolKey nativeKey; PoolKey uninitializedKey; PoolKey uninitializedNativeKey; // Update this value when you add a new hook flag. uint160 hookPermissionCount = 14; uint160 clearAllHookPermissionsMask = ~uint160(0) << (hookPermissionCount); modifier noIsolate() { if (msg.sender != address(this)) { (bool success,) = address(this).call(msg.data); require(success); } else { _; } } function deployFreshManager() internal virtual { manager = new PoolManager(address(this)); } function deployFreshManagerAndRouters() internal { deployFreshManager(); swapRouter = new PoolSwapTest(manager); swapRouterNoChecks = new SwapRouterNoChecks(manager); modifyLiquidityRouter = new PoolModifyLiquidityTest(manager); modifyLiquidityNoChecks = new PoolModifyLiquidityTestNoChecks(manager); donateRouter = new PoolDonateTest(manager); takeRouter = new PoolTakeTest(manager); claimsRouter = new PoolClaimsTest(manager); nestedActionRouter = new PoolNestedActionsTest(manager); feeController = makeAddr("feeController"); actionsRouter = new ActionsRouter(manager); manager.setProtocolFeeController(feeController); } // You must have first initialised the routers with deployFreshManagerAndRouters // If you only need the currencies (and not approvals) call deployAndMint2Currencies function deployMintAndApprove2Currencies() internal returns (Currency, Currency) { Currency _currencyA = deployMintAndApproveCurrency(); Currency _currencyB = deployMintAndApproveCurrency(); (currency0, currency1) = SortTokens.sort(MockERC20(Currency.unwrap(_currencyA)), MockERC20(Currency.unwrap(_currencyB))); return (currency0, currency1); } function deployMintAndApproveCurrency() internal returns (Currency currency) { MockERC20 token = deployTokens(1, 2 ** 255)[0]; address[9] memory toApprove = [ address(swapRouter), address(swapRouterNoChecks), address(modifyLiquidityRouter), address(modifyLiquidityNoChecks), address(donateRouter), address(takeRouter), address(claimsRouter), address(nestedActionRouter.executor()), address(actionsRouter) ]; for (uint256 i = 0; i < toApprove.length; i++) { token.approve(toApprove[i], Constants.MAX_UINT256); } return Currency.wrap(address(token)); } function deployAndMint2Currencies() internal returns (Currency, Currency) { MockERC20[] memory tokens = deployTokens(2, 2 ** 255); return SortTokens.sort(tokens[0], tokens[1]); } function deployTokens(uint8 count, uint256 totalSupply) internal returns (MockERC20[] memory tokens) { tokens = new MockERC20[](count); for (uint8 i = 0; i < count; i++) { tokens[i] = new MockERC20("TEST", "TEST", 18); tokens[i].mint(address(this), totalSupply); } } function initPool(Currency _currency0, Currency _currency1, IHooks hooks, uint24 fee, uint160 sqrtPriceX96) internal returns (PoolKey memory _key, PoolId id) { _key = PoolKey(_currency0, _currency1, fee, fee.isDynamicFee() ? int24(60) : int24(fee / 100 * 2), hooks); id = _key.toId(); manager.initialize(_key, sqrtPriceX96); } function initPool( Currency _currency0, Currency _currency1, IHooks hooks, uint24 fee, int24 tickSpacing, uint160 sqrtPriceX96 ) internal returns (PoolKey memory _key, PoolId id) { _key = PoolKey(_currency0, _currency1, fee, tickSpacing, hooks); id = _key.toId(); manager.initialize(_key, sqrtPriceX96); } function initPoolAndAddLiquidity( Currency _currency0, Currency _currency1, IHooks hooks, uint24 fee, uint160 sqrtPriceX96 ) internal returns (PoolKey memory _key, PoolId id) { (_key, id) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96); modifyLiquidityRouter.modifyLiquidity{value: msg.value}(_key, LIQUIDITY_PARAMS, ZERO_BYTES); } function initPoolAndAddLiquidityETH( Currency _currency0, Currency _currency1, IHooks hooks, uint24 fee, uint160 sqrtPriceX96, uint256 msgValue ) internal returns (PoolKey memory _key, PoolId id) { (_key, id) = initPool(_currency0, _currency1, hooks, fee, sqrtPriceX96); modifyLiquidityRouter.modifyLiquidity{value: msgValue}(_key, LIQUIDITY_PARAMS, ZERO_BYTES); } // Deploys the manager, all test routers, and sets up 2 pools: with and without native function initializeManagerRoutersAndPoolsWithLiq(IHooks hooks) internal { deployFreshManagerAndRouters(); // sets the global currencies and key deployMintAndApprove2Currencies(); (key,) = initPoolAndAddLiquidity(currency0, currency1, hooks, 3000, SQRT_PRICE_1_1); nestedActionRouter.executor().setKey(key); (nativeKey,) = initPoolAndAddLiquidityETH(CurrencyLibrary.ADDRESS_ZERO, currency1, hooks, 3000, SQRT_PRICE_1_1, 1 ether); uninitializedKey = key; uninitializedNativeKey = nativeKey; uninitializedKey.fee = 100; uninitializedNativeKey.fee = 100; } /// @notice Helper function for a simple ERC20 swaps that allows for unlimited price impact function swap(PoolKey memory _key, bool zeroForOne, int256 amountSpecified, bytes memory hookData) internal returns (BalanceDelta) { // allow native input for exact-input, guide users to the `swapNativeInput` function bool isNativeInput = zeroForOne && _key.currency0.isAddressZero(); if (isNativeInput) require(0 > amountSpecified, "Use swapNativeInput() for native-token exact-output swaps"); uint256 value = isNativeInput ? uint256(-amountSpecified) : 0; return swapRouter.swap{value: value}( _key, SwapParams({ zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT }), PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}), hookData ); } /// @notice Helper function to increase balance of pool manager. /// Uses default LIQUIDITY_PARAMS range. function seedMoreLiquidity(PoolKey memory _key, uint256 amount0, uint256 amount1) internal { (uint160 sqrtPriceX96,,,) = manager.getSlot0(_key.toId()); uint128 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts( sqrtPriceX96, TickMath.getSqrtPriceAtTick(LIQUIDITY_PARAMS.tickLower), TickMath.getSqrtPriceAtTick(LIQUIDITY_PARAMS.tickUpper), amount0, amount1 ); ModifyLiquidityParams memory params = ModifyLiquidityParams({ tickLower: LIQUIDITY_PARAMS.tickLower, tickUpper: LIQUIDITY_PARAMS.tickUpper, liquidityDelta: int128(liquidityDelta), salt: 0 }); modifyLiquidityRouter.modifyLiquidity(_key, params, ZERO_BYTES); } /// @notice Helper function for a simple Native-token swap that allows for unlimited price impact function swapNativeInput( PoolKey memory _key, bool zeroForOne, int256 amountSpecified, bytes memory hookData, uint256 msgValue ) internal returns (BalanceDelta) { require(_key.currency0.isAddressZero(), "currency0 is not native. Use swap() instead"); if (zeroForOne == false) require(msgValue == 0, "msgValue must be 0 for oneForZero swaps"); return swapRouter.swap{value: msgValue}( _key, SwapParams({ zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: zeroForOne ? MIN_PRICE_LIMIT : MAX_PRICE_LIMIT }), PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}), hookData ); } // to receive refunds of spare eth from test helpers receive() external payable {} }