// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import "../src/Kraiken.sol"; import { LiquidityManager } from "../src/LiquidityManager.sol"; import "../src/Optimizer.sol"; import "../src/Stake.sol"; import "../src/helpers/UniswapHelpers.sol"; import { ERC1967Proxy } from "@openzeppelin/proxy/ERC1967/ERC1967Proxy.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Factory.sol"; import "@uniswap-v3-core/interfaces/IUniswapV3Pool.sol"; import "forge-std/Script.sol"; import "./DeployCommon.sol"; uint24 constant FEE = uint24(10_000); contract DeployBase is Script { using UniswapHelpers for IUniswapV3Pool; bool public token0isWeth; address public feeDest; address public weth; address public v3Factory; address public optimizer; // Seed amounts for VWAP bootstrap. // Kept small: deployer only needs this ETH on top of gas. // With very thin bootstrap positions, even 0.005 ETH moves the price >400 ticks. uint256 internal constant SEED_LM_ETH = 0.01 ether; uint256 internal constant SEED_SWAP_ETH = 0.005 ether; // Deployed contracts Kraiken public kraiken; Stake public stake; LiquidityManager public liquidityManager; IUniswapV3Pool public pool; function run() public { // PRIVATE_KEY=0 / empty silently falls back to .secret (0 is an invalid secp256k1 key). uint256 privateKey = vm.envOr("PRIVATE_KEY", uint256(0)); if (privateKey == 0) { string memory seedPhrase = vm.readFile(".secret"); privateKey = vm.deriveKey(seedPhrase, 0); } vm.startBroadcast(privateKey); address sender = vm.addr(privateKey); console.log("Deploying from:", sender); // Deploy Kraiken token kraiken = new Kraiken("Kraiken", "KRK"); console.log("Kraiken deployed at:", address(kraiken)); // Determine token ordering token0isWeth = address(weth) < address(kraiken); console.log("token0isWeth:", token0isWeth); // Deploy Stake contract stake = new Stake(address(kraiken), feeDest); console.log("Stake deployed at:", address(stake)); // Set staking pool in Kraiken kraiken.setStakingPool(address(stake)); // Get or create Uniswap V3 pool IUniswapV3Factory factory = IUniswapV3Factory(v3Factory); address liquidityPool = factory.getPool(weth, address(kraiken), FEE); if (liquidityPool == address(0)) { liquidityPool = factory.createPool(weth, address(kraiken), FEE); console.log("Uniswap pool created at:", liquidityPool); } else { console.log("Using existing pool at:", liquidityPool); } pool = IUniswapV3Pool(liquidityPool); // Initialize pool at 1 cent price if not already initialized try pool.slot0() returns (uint160, int24, uint16, uint16, uint16, uint8, bool) { console.log("Pool already initialized"); } catch { pool.initializePoolFor1Cent(token0isWeth); console.log("Pool initialized"); } // Deploy Optimizer (if not already deployed) address optimizerAddress; if (optimizer == address(0)) { Optimizer optimizerImpl = new Optimizer(); bytes memory params = abi.encodeWithSignature("initialize(address,address)", address(kraiken), address(stake)); ERC1967Proxy proxy = new ERC1967Proxy(address(optimizerImpl), params); optimizerAddress = address(proxy); console.log("Optimizer deployed at:", optimizerAddress); } else { optimizerAddress = optimizer; console.log("Using existing optimizer at:", optimizerAddress); } // Deploy LiquidityManager liquidityManager = new LiquidityManager(v3Factory, weth, address(kraiken), optimizerAddress); console.log("LiquidityManager deployed at:", address(liquidityManager)); // Set liquidity manager in Kraiken kraiken.setLiquidityManager(address(liquidityManager)); // ===================================================================== // VWAP Bootstrap -> seed trade during deployment // // WARNING: On Base mainnet this inline attempt WILL REVERT when the pool // was created in this same script run. The Uniswap V3 TWAP oracle // requires >= 300 s of observation history before recenter() succeeds, // and the 60-second recenter cooldown prevents completing both recenters // in a single broadcast. // // Follow docs/mainnet-bootstrap.md for the correct two-phase manual // sequence: // Phase 1 — deploy contracts (this script, bootstrap section expected // to revert on fresh pool) // Phase 2 — wait >= 300 s, fund LM, first recenter(), seed buy // Phase 3 — wait >= 60 s, run BootstrapVWAPPhase2.s.sol // // The cumulativeVolume==0 path in recenter() records VWAP from whatever // price exists at the time of the first fee event. An attacker who // front-runs deployment with a whale buy inflates that anchor; executing // the seed buy before handing control to users closes that window. // // Deployer must have SEED_LM_ETH + SEED_SWAP_ETH available (≈0.015 ETH). // ===================================================================== console.log("\nBootstrapping VWAP with seed trade..."); // Step 1: Set the real feeDestination before any recenter. liquidityManager.setFeeDestination(feeDest); console.log("feeDestination set to", feeDest); // Step 2: Fund LM and place initial bootstrap positions. // recenter() requires TWAP history (>= 300s since pool init). // On a fresh mainnet pool this will revert; the try/catch allows the // deploy to succeed and the operator completes the bootstrap manually // by following docs/mainnet-bootstrap.md. (bool funded,) = address(liquidityManager).call{ value: SEED_LM_ETH }(""); require(funded, "Failed to fund LM for seed bootstrap"); try liquidityManager.recenter() { console.log("First recenter complete -> positions placed, cumulativeVolume still 0"); // Step 3: Seed buy -> generates a non-zero fee in the anchor position. SeedSwapper seedSwapper = new SeedSwapper(weth, address(pool), token0isWeth); seedSwapper.executeSeedBuy{ value: SEED_SWAP_ETH }(sender); console.log("Seed buy executed -> fee generated in anchor position"); } catch { console.log("WARNING: recenter() reverted - inline bootstrap skipped."); console.log(" Pool likely has < 300 s of TWAP history."); console.log(" Follow docs/mainnet-bootstrap.md (Phase 2 steps) to complete."); } // Step 4: Second recenter records VWAP (bootstrap path + ethFee > 0). // Cannot be called in the same Forge broadcast as Step 2 — recenter() enforces a // 60-second cooldown and there is no time-warp mechanism in a live broadcast. // Run BootstrapVWAPPhase2.s.sol at least 60 seconds after this script completes. console.log("\n=== Deployment Complete ==="); console.log("Kraiken:", address(kraiken)); console.log("Stake:", address(stake)); console.log("Pool:", address(pool)); console.log("LiquidityManager:", address(liquidityManager)); console.log("Optimizer:", optimizerAddress); console.log("\nPost-deploy steps:"); console.log(" 1. Wait >= 60 s after this script finishes."); console.log(" 2. Run: forge script script/BootstrapVWAPPhase2.s.sol --tc BootstrapVWAPPhase2 --rpc-url --broadcast"); console.log(" This performs the second recenter that records cumulativeVolume > 0."); console.log(" 3. Fund LiquidityManager with operational ETH."); console.log(" 4. recenter() is permissionless - any address (e.g. txnBot) can call it."); vm.stopBroadcast(); } }