// 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 { string memory seedPhrase = vm.readFile(".secret"); uint256 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 // // 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. // // Fix: execute a small buy BEFORE handing control to users so that // cumulativeVolume>0 by the time the protocol is live. // // recenter() is now permissionless and always enforces TWAP stability. // For a fresh pool on Base mainnet this bootstrap must run at least // 300 seconds after pool initialisation (so the TWAP oracle has history). // If the pool was just created in this same script run, the first // recenter() will revert with "price deviated from oracle" — wait 5 min // and call the bootstrap as a separate transaction or script. // // 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. // NOTE: recenter() requires TWAP history (>= 300s since pool init). // On Base mainnet this call will revert if the pool is too fresh. (bool funded,) = address(liquidityManager).call{ value: SEED_LM_ETH }(""); require(funded, "Failed to fund LM for seed bootstrap"); 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"); // Step 4: Second recenter records VWAP (bootstrap path + ethFee > 0). // NOTE: Must be called >= 60s after the first recenter (cooldown) AND // >= 300s after pool init so TWAP has settled at post-buy price. // On Base mainnet, mine/wait ~5 min between Step 2 and Step 4. liquidityManager.recenter(); require(liquidityManager.cumulativeVolume() > 0, "VWAP bootstrap failed: cumulativeVolume is 0"); console.log("VWAP bootstrapped -> cumulativeVolume:", liquidityManager.cumulativeVolume()); 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. Fund LiquidityManager with operational ETH (VWAP already bootstrapped)"); console.log(" 2. recenter() is now permissionless - any address (e.g. txnBot) can call it."); vm.stopBroadcast(); } }