fix: address AI review findings for #706 recenterAccess removal

- DeployBase.sol: remove broken inline second recenter() (would always
  revert with 'recenter cooldown' in same Forge broadcast); replace with
  operator instructions to run the new BootstrapVWAPPhase2.s.sol script
  at least 60 s after deployment
- BootstrapVWAPPhase2.s.sol: new script for the second VWAP bootstrap
  recenter on Base mainnet deployments
- StrategyExecutor.sol: update stale docstring that still described the
  removed recenterAccess bypass; reflect permissionless model with vm.warp
- TestBase.sol: remove vestigial recenterCaller parameter from all four
  setupEnvironment* functions (parameter was silently ignored after
  setRecenterAccess was removed); update all callers across six test files
- bootstrap-common.sh: fix misleading retry recenter in
  seed_application_state() — add evm_increaseTime 61 before evm_mine so
  the recenter cooldown actually clears and the retry can succeed

All 210 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-14 09:15:48 +00:00
parent 9b53f409b7
commit 0d3aee15b4
11 changed files with 71 additions and 42 deletions

View file

@ -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=<deployed LiquidityManager 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();
}
}

View file

@ -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 <RPC> --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();
}

View file

@ -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.

View file

@ -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));

View file

@ -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);

View file

@ -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)
);

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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

View file

@ -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" \