// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol"; import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol"; import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol"; import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol"; import {Currency} from "@uniswap/v4-core/src/types/Currency.sol"; import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol"; import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol"; import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol"; import {PoolModifyLiquidityTest} from "@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol"; import {HookMiner} from "v4-periphery/src/utils/HookMiner.sol"; import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol"; import {MockToken} from "../src/mocks/MockToken.sol"; import {MaasHook} from "../src/hooks/MaasHook.sol"; import {PriceMath} from "../src/libraries/PriceMath.sol"; import {PriceSource} from "./lib/PriceSource.sol"; /// @notice Deploys MaasHook (UUPS proxy at all-flags address) on the existing spot stack from /// backend spot.json, registers + inits + seeds BTC/ETH/HYPE pools, rewrites client+backend spot.json. /// Env: PYTH (hook oracle + seed price, see PriceSource). contract DeployHook is PriceSource { using PoolIdLibrary for PoolKey; int24 constant TICK_SPACING = 60; uint256 constant SEED_USDC = 1_000_000e6; string constant SPOT = "../backend/config/spot.json"; struct Spot { IPoolManager manager; address usdc; address[3] bases; // WBTC, WETH, HYPE = feeds()[0..2] address swapRouter; PoolModifyLiquidityTest liquidityRouter; } function run() external { Spot memory s = readSpot(); IPyth pyth = IPyth(vm.envAddress("PYTH")); vm.startBroadcast(); (, address deployer,) = vm.readCallers(); (MaasHook hook, PoolKey[3] memory keys) = deploy(s, pyth, deployer); vm.stopBroadcast(); writeJson(s, hook, keys, "../client/src/shared/config/spot.json"); writeJson(s, hook, keys, SPOT); } function readSpot() internal view returns (Spot memory s) { string memory j = vm.readFile(SPOT); s.manager = IPoolManager(vm.parseJsonAddress(j, ".poolManager")); s.usdc = vm.parseJsonAddress(j, ".tokens.USDC"); s.bases = [ vm.parseJsonAddress(j, ".tokens.WBTC"), vm.parseJsonAddress(j, ".tokens.WETH"), vm.parseJsonAddress(j, ".tokens.HYPE") ]; s.swapRouter = vm.parseJsonAddress(j, ".swapRouter"); s.liquidityRouter = PoolModifyLiquidityTest(vm.parseJsonAddress(j, ".liquidityRouter")); } /// @dev `owner` must be the caller; owns hook, mints + seeds liquidity (tracked to `owner`). function deploy(Spot memory s, IPyth pyth, address owner) public returns (MaasHook hook, PoolKey[3] memory keys) { hook = deployHook(s.manager, pyth, owner); hook.setRouter(address(s.liquidityRouter), true); Feed[4] memory f = feeds(); for (uint256 i; i < 3; i++) { keys[i] = createPool(s, hook, s.bases[i], f[i], owner); } } function deployHook(IPoolManager manager, IPyth pyth, address owner) internal returns (MaasHook) { address impl = address(new MaasHook(manager)); bytes memory args = abi.encode(impl, abi.encodeCall(MaasHook.initialize, (owner, pyth))); bytes memory code = type(ERC1967Proxy).creationCode; (address hook, bytes32 salt) = HookMiner.find(CREATE2_FACTORY, uint160(Hooks.ALL_HOOK_MASK), code, args); (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(salt, code, args)); require(ok && hook.code.length > 0, "hook deploy failed"); return MaasHook(hook); } function createPool(Spot memory s, MaasHook hook, address base, Feed memory f, address owner) internal returns (PoolKey memory key) { uint8 dec = IERC20Metadata(base).decimals(); bool baseIs0 = base < s.usdc; (Currency b, Currency u) = (Currency.wrap(base), Currency.wrap(s.usdc)); key = PoolKey( baseIs0 ? b : u, baseIs0 ? u : b, LPFeeLibrary.DYNAMIC_FEE_FLAG, TICK_SPACING, IHooks(address(hook)) ); hook.setPool(key, f.id, baseIs0, dec); uint256 price = price6(f); uint160 sqrtPrice = PriceMath.toSqrtPriceX96(price, dec, baseIs0); s.manager.initialize(key, sqrtPrice); uint256 baseAmt = SEED_USDC * 10 ** dec / price; seed(s.liquidityRouter, key, sqrtPrice, baseIs0 ? baseAmt : SEED_USDC, baseIs0 ? SEED_USDC : baseAmt, owner); } function seed( PoolModifyLiquidityTest lr, PoolKey memory key, uint160 sqrtPrice, uint256 a0, uint256 a1, address owner ) internal { int24 lo = TickMath.minUsableTick(TICK_SPACING); int24 hi = TickMath.maxUsableTick(TICK_SPACING); uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts( sqrtPrice, TickMath.getSqrtPriceAtTick(lo), TickMath.getSqrtPriceAtTick(hi), a0, a1 ); // +1 wei: core rounds amounts up MockToken t0 = MockToken(Currency.unwrap(key.currency0)); MockToken t1 = MockToken(Currency.unwrap(key.currency1)); t0.mint(owner, a0 + 1); t1.mint(owner, a1 + 1); t0.approve(address(lr), type(uint256).max); t1.approve(address(lr), type(uint256).max); lr.modifyLiquidity(key, ModifyLiquidityParams(lo, hi, int256(uint256(liquidity)), 0), abi.encode(owner)); } function writeJson(Spot memory s, MaasHook hook, PoolKey[3] memory keys, string memory path) internal { Feed[4] memory f = feeds(); string[3] memory sym = ["WBTC", "WETH", "HYPE"]; string memory pools; string memory tokens = vm.serializeAddress("tokens", "USDC", s.usdc); for (uint256 i; i < 3; i++) { string memory p = f[i].symbol; vm.serializeAddress(p, "base", s.bases[i]); vm.serializeBytes32(p, "poolId", PoolId.unwrap(keys[i].toId())); vm.serializeAddress(p, "currency0", Currency.unwrap(keys[i].currency0)); vm.serializeAddress(p, "currency1", Currency.unwrap(keys[i].currency1)); vm.serializeUint(p, "fee", keys[i].fee); vm.serializeInt(p, "tickSpacing", keys[i].tickSpacing); pools = vm.serializeString("pools", p, vm.serializeAddress(p, "hooks", address(hook))); tokens = vm.serializeAddress("tokens", sym[i], s.bases[i]); } string memory o = "root"; vm.serializeUint(o, "chainId", block.chainid); vm.serializeAddress(o, "poolManager", address(s.manager)); vm.serializeAddress(o, "hook", address(hook)); vm.serializeAddress(o, "swapRouter", s.swapRouter); vm.serializeAddress(o, "liquidityRouter", address(s.liquidityRouter)); vm.serializeString(o, "tokens", tokens); vm.writeJson(vm.serializeString(o, "pools", pools), path); } }