diff --git a/onchain/script/BootstrapVWAPPhase2.s.sol b/onchain/script/BootstrapVWAPPhase2.s.sol new file mode 100644 index 0000000..dcd7f0d --- /dev/null +++ b/onchain/script/BootstrapVWAPPhase2.s.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8.19; + +import { LiquidityManager } from "../src/LiquidityManager.sol"; +import "forge-std/Script.sol"; + +/** + * @title BootstrapVWAPPhase2 + * @notice Second phase of the VWAP bootstrap for Base mainnet deployments. + * + * Run this script >= 60 seconds after DeployBase (or DeployBaseMainnet/DeployBaseSepolia) + * finishes. The first recenter() sets lastRecenterTime; the 60-second cooldown must + * elapse before this second recenter() can succeed. + * + * What this does: + * - Calls liquidityManager.recenter() a second time. + * - At this point cumulativeVolume == 0 (bootstrap path) and the seed buy has + * generated ethFee > 0, so recenter() records the VWAP anchor. + * - Asserts cumulativeVolume > 0 to confirm bootstrap success. + * + * Usage: + * export LM_ADDRESS= + * forge script script/BootstrapVWAPPhase2.s.sol --tc BootstrapVWAPPhase2 \ + * --fork-url $BASE_RPC --broadcast + */ +contract BootstrapVWAPPhase2 is Script { + function run() public { + address lmAddress = vm.envAddress("LM_ADDRESS"); + LiquidityManager lm = LiquidityManager(payable(lmAddress)); + + string memory seedPhrase = vm.readFile(".secret"); + uint256 privateKey = vm.deriveKey(seedPhrase, 0); + vm.startBroadcast(privateKey); + + console.log("Running VWAP bootstrap phase 2 on LiquidityManager:", lmAddress); + + lm.recenter(); + + uint256 cumVol = lm.cumulativeVolume(); + require(cumVol > 0, "VWAP bootstrap failed: cumulativeVolume is still 0"); + console.log("VWAP bootstrapped successfully. cumulativeVolume:", cumVol); + + vm.stopBroadcast(); + } +} diff --git a/onchain/script/DeployBase.sol b/onchain/script/DeployBase.sol index 9565a66..5b524ef 100644 --- a/onchain/script/DeployBase.sol +++ b/onchain/script/DeployBase.sol @@ -137,12 +137,9 @@ contract DeployBase is Script { 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()); + // 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)); @@ -151,8 +148,11 @@ contract DeployBase is Script { 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."); + console.log(" 1. Wait >= 60 s after this script finishes."); + console.log(" 2. Run: forge script script/BootstrapVWAPPhase2.s.sol --tc BootstrapVWAPPhase2 --fork-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(); } diff --git a/onchain/script/backtesting/StrategyExecutor.sol b/onchain/script/backtesting/StrategyExecutor.sol index 14708f5..9b3dcc6 100644 --- a/onchain/script/backtesting/StrategyExecutor.sol +++ b/onchain/script/backtesting/StrategyExecutor.sol @@ -24,9 +24,9 @@ import { console2 } from "forge-std/console2.sol"; * notified on every block (for time-in-range) and on each successful recenter * (for position lifecycle and fee/IL accounting). * - * Access model: StrategyExecutor must be set as recenterAccess on the LM so that - * the cooldown and TWAP price-stability checks are bypassed in the simulation - * (vm.warp advances simulated time, not real oracle state). + * Access model: recenter() is permissionless — no special access grant is required. + * EventReplayer advances block.timestamp via vm.warp, so the 60-second cooldown and + * the 300-second TWAP window pass normally during simulation. * * TODO(#319): The negligible-impact assumption means we replay historical events * as-is without accounting for KrAIken's own liquidity affecting swap outcomes. diff --git a/onchain/test/EthScarcityAbundance.t.sol b/onchain/test/EthScarcityAbundance.t.sol index e540334..95f126e 100644 --- a/onchain/test/EthScarcityAbundance.t.sol +++ b/onchain/test/EthScarcityAbundance.t.sol @@ -41,7 +41,7 @@ contract EthScarcityAbundance is Test { // Default params: CI=50%, AS=50%, AW=50, DD=50% optimizer = new ConfigurableOptimizer(5e17, 5e17, 50, 5e17); - (factory, pool, weth, kraiken,, lm,, token0isWeth) = testEnv.setupEnvironmentWithExistingFactory(factory, true, fees, address(optimizer)); + (factory, pool, weth, kraiken,, lm,, token0isWeth) = testEnv.setupEnvironmentWithExistingFactory(factory, true, address(optimizer)); swapExecutor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm, true); @@ -231,7 +231,7 @@ contract EthScarcityAbundance is Test { // Bull optimizer: high anchorShare, wide anchor, deep discovery ConfigurableOptimizer bullOpt = new ConfigurableOptimizer(3e17, 8e17, 80, 8e17); - (,,,,, LiquidityManager bullLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, fees, address(bullOpt)); + (,,,,, LiquidityManager bullLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, address(bullOpt)); vm.deal(address(bullLm), 200 ether); vm.prank(address(bullLm)); @@ -252,7 +252,7 @@ contract EthScarcityAbundance is Test { // Bear optimizer: low anchorShare, moderate anchor, thin discovery ConfigurableOptimizer bearOpt = new ConfigurableOptimizer(8e17, 1e17, 40, 2e17); - (,,,,, LiquidityManager bearLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, fees, address(bearOpt)); + (,,,,, LiquidityManager bearLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, address(bearOpt)); vm.deal(address(bearLm), 200 ether); vm.prank(address(bearLm)); diff --git a/onchain/test/FuzzingAnalyzerBugs.t.sol b/onchain/test/FuzzingAnalyzerBugs.t.sol index e60772e..058741b 100644 --- a/onchain/test/FuzzingAnalyzerBugs.t.sol +++ b/onchain/test/FuzzingAnalyzerBugs.t.sol @@ -37,7 +37,7 @@ contract FuzzingAnalyzerBugs is Test { // Bear market params: CI=0.8e18, AS=0.1e18, AW=40, DD=0.2e18 optimizer = new ConfigurableOptimizer(8e17, 1e17, 40, 2e17); - (factory, pool, weth, kraiken,, lm,, token0isWeth) = testEnv.setupEnvironmentWithExistingFactory(factory, true, fees, address(optimizer)); + (factory, pool, weth, kraiken,, lm,, token0isWeth) = testEnv.setupEnvironmentWithExistingFactory(factory, true, address(optimizer)); swapExecutor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm, true); diff --git a/onchain/test/LiquidityManager.t.sol b/onchain/test/LiquidityManager.t.sol index 5997797..174c472 100644 --- a/onchain/test/LiquidityManager.t.sol +++ b/onchain/test/LiquidityManager.t.sol @@ -148,7 +148,7 @@ contract LiquidityManagerTest is UniSwapHelper { LiquidityManager _lm, Optimizer _optimizer, bool _token0isWeth - ) = testEnv.setupEnvironment(token0shouldBeWeth, RECENTER_CALLER); + ) = testEnv.setupEnvironment(token0shouldBeWeth); // Assign to state variables factory = _factory; @@ -1029,7 +1029,7 @@ contract LiquidityManagerTest is UniSwapHelper { function testOptimizerFallback() public { RevertingOptimizer revertingOpt = new RevertingOptimizer(); TestEnvironment env = new TestEnvironment(feeDestination); - (,,,,, LiquidityManager _lm,,) = env.setupEnvironmentWithOptimizer(DEFAULT_TOKEN0_IS_WETH, RECENTER_CALLER, address(revertingOpt)); + (,,,,, LiquidityManager _lm,,) = env.setupEnvironmentWithOptimizer(DEFAULT_TOKEN0_IS_WETH, address(revertingOpt)); // Recenter uses the fallback params from the catch block vm.prank(RECENTER_CALLER); @@ -1065,7 +1065,7 @@ contract LiquidityManagerTest is UniSwapHelper { LiquidityManager _lm, Optimizer _optimizer, bool _token0isWeth - ) = selfFeeEnv.setupEnvironmentWithSelfFeeDestination(DEFAULT_TOKEN0_IS_WETH, RECENTER_CALLER); + ) = selfFeeEnv.setupEnvironmentWithSelfFeeDestination(DEFAULT_TOKEN0_IS_WETH); // Wire state variables used by buy/sell/recenter helpers factory = _factory; @@ -1143,7 +1143,6 @@ contract LiquidityManagerTest is UniSwapHelper { , ) = clampTestEnv.setupEnvironmentWithOptimizer( DEFAULT_TOKEN0_IS_WETH, - RECENTER_CALLER, address(highWidthOptimizer) ); diff --git a/onchain/test/ReplayProfitableScenario.t.sol b/onchain/test/ReplayProfitableScenario.t.sol index 1b9c952..add766e 100644 --- a/onchain/test/ReplayProfitableScenario.t.sol +++ b/onchain/test/ReplayProfitableScenario.t.sol @@ -36,7 +36,7 @@ contract ReplayProfitableScenario is Test { BullMarketOptimizer optimizer = new BullMarketOptimizer(); // Use seed 1 setup (odd seed = false for first param) - (, pool, weth, kraiken, stake, lm,, token0isWeth) = testEnv.setupEnvironmentWithOptimizer(false, feeDestination, address(optimizer)); + (, pool, weth, kraiken, stake, lm,, token0isWeth) = testEnv.setupEnvironmentWithOptimizer(false, address(optimizer)); // Fund exactly as in the recorded scenario vm.deal(address(lm), 200 ether); diff --git a/onchain/test/SupplyCorruption.t.sol b/onchain/test/SupplyCorruption.t.sol index 41ccbaf..3117305 100644 --- a/onchain/test/SupplyCorruption.t.sol +++ b/onchain/test/SupplyCorruption.t.sol @@ -40,7 +40,7 @@ contract SupplyCorruptionTest is UniSwapHelper { LiquidityManager _lm, Optimizer _optimizer, bool _token0isWeth - ) = testEnv.setupEnvironment(false, RECENTER_CALLER); + ) = testEnv.setupEnvironment(false); factory = _factory; pool = _pool; diff --git a/onchain/test/VWAPFloorProtection.t.sol b/onchain/test/VWAPFloorProtection.t.sol index f25400f..f0ca3a5 100644 --- a/onchain/test/VWAPFloorProtection.t.sol +++ b/onchain/test/VWAPFloorProtection.t.sol @@ -34,7 +34,7 @@ contract VWAPFloorProtectionTest is UniSwapHelper { function setUp() public { testEnv = new TestEnvironment(feeDestination); (,pool, weth, harberg, , lm, , token0isWeth) = - testEnv.setupEnvironment(false, RECENTER_CALLER); + testEnv.setupEnvironment(false); vm.deal(address(lm), LM_ETH); diff --git a/onchain/test/helpers/TestBase.sol b/onchain/test/helpers/TestBase.sol index d3ab9b6..d78ec87 100644 --- a/onchain/test/helpers/TestBase.sol +++ b/onchain/test/helpers/TestBase.sol @@ -91,7 +91,6 @@ contract TestEnvironment is TestConstants { /** * @notice Deploy all contracts and set up the environment * @param token0shouldBeWeth Whether WETH should be token0 - * @param recenterCaller Address that will be granted recenter access * @return _factory The deployed Uniswap factory * @return _pool The created Uniswap pool * @return _weth The WETH token contract @@ -101,10 +100,7 @@ contract TestEnvironment is TestConstants { * @return _optimizer The optimizer contract * @return _token0isWeth Whether token0 is WETH */ - function setupEnvironment( - bool token0shouldBeWeth, - address recenterCaller - ) + function setupEnvironment(bool token0shouldBeWeth) external returns ( IUniswapV3Factory _factory, @@ -201,7 +197,6 @@ contract TestEnvironment is TestConstants { /** * @notice Setup environment with specific optimizer * @param token0shouldBeWeth Whether WETH should be token0 - * @param recenterCaller Address that will be granted recenter access * @param optimizerAddress Address of the optimizer to use * @return _factory The deployed Uniswap factory * @return _pool The created Uniswap pool @@ -212,11 +207,7 @@ contract TestEnvironment is TestConstants { * @return _optimizer The optimizer contract * @return _token0isWeth Whether token0 is WETH */ - function setupEnvironmentWithOptimizer( - bool token0shouldBeWeth, - address recenterCaller, - address optimizerAddress - ) + function setupEnvironmentWithOptimizer(bool token0shouldBeWeth, address optimizerAddress) external returns ( IUniswapV3Factory _factory, @@ -257,12 +248,8 @@ contract TestEnvironment is TestConstants { * _getOutstandingSupply skips the feeDestination KRK subtraction (already excluded * by outstandingSupply()). * @param token0shouldBeWeth Whether WETH should be token0 - * @param recenterCaller Address that will be granted recenter access */ - function setupEnvironmentWithSelfFeeDestination( - bool token0shouldBeWeth, - address recenterCaller - ) + function setupEnvironmentWithSelfFeeDestination(bool token0shouldBeWeth) external returns ( IUniswapV3Factory _factory, @@ -301,7 +288,6 @@ contract TestEnvironment is TestConstants { * @notice Setup environment with existing factory and specific optimizer * @param existingFactory The existing Uniswap factory to use * @param token0shouldBeWeth Whether WETH should be token0 - * @param recenterCaller Address that will be granted recenter access * @param optimizerAddress Address of the optimizer to use * @return _factory The existing Uniswap factory * @return _pool The created Uniswap pool @@ -315,7 +301,6 @@ contract TestEnvironment is TestConstants { function setupEnvironmentWithExistingFactory( IUniswapV3Factory existingFactory, bool token0shouldBeWeth, - address recenterCaller, address optimizerAddress ) external diff --git a/scripts/bootstrap-common.sh b/scripts/bootstrap-common.sh index 3898a2c..2ee8bf8 100755 --- a/scripts/bootstrap-common.sh +++ b/scripts/bootstrap-common.sh @@ -196,8 +196,8 @@ seed_application_state() { fi bootstrap_log "Swap returned 0 KRK — recentering and retrying" - # Mine a few blocks to advance time, then recenter - cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1 || true + # Advance 61 s to clear the 60-second recenter cooldown, then mine a block. + cast rpc --rpc-url "$ANVIL_RPC" evm_increaseTime 61 >>"$LOG_FILE" 2>&1 || true cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1 || true local recenter_pk="${TXNBOT_PRIVATE_KEY:-$DEPLOYER_PK}" cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \