// SPDX-License-Identifier: MIT pragma solidity ^0.8.26; import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol"; import {PoolManager} from "@uniswap/v4-core/src/PoolManager.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 {PoolSwapTest} from "@uniswap/v4-core/src/test/PoolSwapTest.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 {VaultFeeHook} from "../src/hooks/VaultFeeHook.sol"; import {PriceMath} from "../src/libraries/PriceMath.sol"; import {PriceSource} from "./lib/PriceSource.sol"; /// @notice Deploys mock tokens, PoolManager, VaultFeeHook, 4 dynamic-fee pools vs USDC, seeds liquidity. /// Seed prices: PRICE_ override > live onchain Pyth (env PYTH) > local default (tests only). See PriceSource. contract DeploySpot is PriceSource { using PoolIdLibrary for PoolKey; int24 constant TICK_SPACING = 60; uint256 constant SEED_USDC = 1_000_000e6; struct Market { string name; string symbol; uint8 decimals; uint256 price; } struct Deployment { IPoolManager manager; VaultFeeHook hook; PoolSwapTest swapRouter; PoolModifyLiquidityTest liquidityRouter; MockToken usdc; MockToken[4] bases; PoolKey[4] keys; } function run() external { vm.startBroadcast(); (, address deployer,) = vm.readCallers(); Deployment memory d = deploy(deployer); vm.stopBroadcast(); writeJson(d, "../client/src/shared/config/spot.json"); writeJson(d, "../backend/config/spot.json"); } function markets() public view returns (Market[4] memory m) { Feed[4] memory f = feeds(); (string[4] memory sym, uint8[4] memory dec) = (["WBTC", "WETH", "HYPE", "ZEC"], [8, 18, 18, 8]); for (uint256 i; i < 4; i++) { m[i] = Market(f[i].symbol, sym[i], dec[i], price6(f[i])); } } /// @dev `owner` must be the account making these calls; it owns hook and holds seed tokens. function deploy(address owner) public returns (Deployment memory d) { d.manager = IPoolManager(new PoolManager(owner)); d.hook = deployHook(d.manager, owner); d.swapRouter = new PoolSwapTest(d.manager); d.liquidityRouter = new PoolModifyLiquidityTest(d.manager); d.usdc = new MockToken("USDC", "USDC", 6); Market[4] memory m = markets(); for (uint256 i; i < 4; i++) { (d.bases[i], d.keys[i]) = createPool(d, m[i], owner); } } function deployHook(IPoolManager manager, address owner) internal returns (VaultFeeHook) { bytes memory args = abi.encode(manager, owner); bytes memory code = type(VaultFeeHook).creationCode; (address hook, bytes32 salt) = HookMiner.find(CREATE2_FACTORY, uint160(Hooks.BEFORE_SWAP_FLAG), code, args); (bool ok,) = CREATE2_FACTORY.call(abi.encodePacked(salt, code, args)); require(ok && hook.code.length > 0, "hook deploy failed"); return VaultFeeHook(hook); } function createPool(Deployment memory d, Market memory m, address owner) internal returns (MockToken base, PoolKey memory key) { base = new MockToken(m.symbol, m.symbol, m.decimals); bool baseIs0 = address(base) < address(d.usdc); (address c0, address c1) = baseIs0 ? (address(base), address(d.usdc)) : (address(d.usdc), address(base)); key = PoolKey({ currency0: Currency.wrap(c0), currency1: Currency.wrap(c1), fee: LPFeeLibrary.DYNAMIC_FEE_FLAG, tickSpacing: TICK_SPACING, hooks: IHooks(address(d.hook)) }); uint160 sqrtPrice = PriceMath.toSqrtPriceX96(m.price, m.decimals, baseIs0); d.manager.initialize(key, sqrtPrice); uint256 baseAmt = SEED_USDC * 10 ** m.decimals / m.price; (uint256 a0, uint256 a1) = baseIs0 ? (baseAmt, SEED_USDC) : (SEED_USDC, baseAmt); seed(d, key, sqrtPrice, a0, a1, owner); } function seed(Deployment memory d, 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 for (uint256 i; i < 2; i++) { MockToken t = MockToken(Currency.unwrap(i == 0 ? key.currency0 : key.currency1)); t.mint(owner, (i == 0 ? a0 : a1) + 1); t.approve(address(d.liquidityRouter), type(uint256).max); t.approve(address(d.swapRouter), type(uint256).max); } ModifyLiquidityParams memory params = ModifyLiquidityParams({tickLower: lo, tickUpper: hi, liquidityDelta: int256(uint256(liquidity)), salt: 0}); d.liquidityRouter.modifyLiquidity(key, params, ""); } function writeJson(Deployment memory d, string memory path) internal { Market[4] memory m = markets(); string memory pools; for (uint256 i; i < 4; i++) { string memory p = m[i].name; vm.serializeAddress(p, "base", address(d.bases[i])); vm.serializeBytes32(p, "poolId", PoolId.unwrap(d.keys[i].toId())); vm.serializeAddress(p, "currency0", Currency.unwrap(d.keys[i].currency0)); vm.serializeAddress(p, "currency1", Currency.unwrap(d.keys[i].currency1)); vm.serializeUint(p, "fee", d.keys[i].fee); vm.serializeInt(p, "tickSpacing", d.keys[i].tickSpacing); pools = vm.serializeString("pools", p, vm.serializeAddress(p, "hooks", address(d.hook))); } string memory tokens = vm.serializeAddress("tokens", "USDC", address(d.usdc)); for (uint256 i; i < 4; i++) { tokens = vm.serializeAddress("tokens", m[i].symbol, address(d.bases[i])); } string memory o = "root"; vm.serializeUint(o, "chainId", block.chainid); vm.serializeAddress(o, "poolManager", address(d.manager)); vm.serializeAddress(o, "hook", address(d.hook)); vm.serializeAddress(o, "swapRouter", address(d.swapRouter)); vm.serializeAddress(o, "liquidityRouter", address(d.liquidityRouter)); vm.serializeString(o, "tokens", tokens); vm.writeJson(vm.serializeString(o, "pools", pools), path); } }