fix: address review feedback on mainnet-bootstrap runbook (#728)

- docs/mainnet-bootstrap.md: fix Step 4c to use SwapRouter02 7-field
  struct (no deadline field); the 8-field ABI was for SwapRouter v1 but
  the address is SwapRouter02
- docs/mainnet-bootstrap.md: correct Step 1 to no longer falsely claim
  that pre-bootstrap transactions succeed when Forge aborts on simulation
  failure; Step 1 now reflects the try/catch behaviour added below
- docs/mainnet-bootstrap.md: Step 6 drops --private-key flag (Foundry
  ignores it when vm.startBroadcast(privateKey) is called internally)
  and documents that the .secret seed-phrase file must be present
- docs/mainnet-bootstrap.md: remove no-op `export LM_ADDRESS="$LM_ADDRESS"`
- docs/mainnet-bootstrap.md: cite exact line range (101-145) in
  Troubleshooting workaround instead of informal marker description
- onchain/script/DeployBase.sol: wrap liquidityManager.recenter() and
  seed buy in try/catch so a fresh-pool TWAP revert skips the inline
  bootstrap with a warning rather than aborting the entire simulation
- onchain/script/DeployBase.sol: fix --fork-url to --rpc-url in the
  post-deploy console.log hint

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
openhands 2026-03-14 18:35:48 +00:00
parent 973caff062
commit 4d16a51650
2 changed files with 28 additions and 21 deletions

View file

@ -39,7 +39,7 @@ export DEPLOYER_ADDRESS="$(cast wallet address --private-key $DEPLOYER_KEY)"
## Step 1 — Deploy contracts (pool init)
Run the mainnet deploy script. The inline bootstrap section in `DeployBase.sol` will revert on a fresh pool — that is expected. All contract deployments that precede the bootstrap attempt will succeed and be confirmed on-chain.
Run the mainnet deploy script. `DeployBase.sol` wraps the inline `recenter()` call in a try/catch, so if the pool is too fresh for the TWAP oracle the bootstrap is skipped with a warning and the deployment still succeeds. The deploy script then prints instructions directing you to complete the bootstrap manually.
```bash
cd onchain
@ -53,7 +53,7 @@ forge script script/DeployBaseMainnet.sol \
--private-key $DEPLOYER_KEY
```
> **Note:** If Forge aborts the simulation before broadcast due to the `recenter()` revert, see [Troubleshooting](#troubleshooting) below for how to separate the deploy from the bootstrap.
> **Note:** If the script still aborts during simulation (e.g., due to an older version of `DeployBase.sol` without the try/catch), see [Troubleshooting](#troubleshooting) for how to separate the deploy from the bootstrap.
After the broadcast completes, record the addresses from the console output:
@ -155,12 +155,11 @@ cast send $WETH \
--private-key $DEPLOYER_KEY
# Step 4c — Seed buy: swap 0.005 WETH → KRAIKEN via the 1 % pool
# Struct fields: tokenIn, tokenOut, fee, recipient, deadline, amountIn, amountOutMinimum, sqrtPriceLimitX96
DEADLINE=$(( $(cast block latest --rpc-url $BASE_RPC --field timestamp) + 1800 ))
# SwapRouter02 exactInputSingle struct (7 fields — no deadline):
# tokenIn, tokenOut, fee, recipient, amountIn, amountOutMinimum, sqrtPriceLimitX96
cast send $SWAP_ROUTER \
"exactInputSingle((address,address,uint24,address,uint256,uint256,uint256,uint160))(uint256)" \
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDRESS,$DEADLINE,5000000000000000,0,0)" \
"exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))(uint256)" \
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDRESS,5000000000000000,0,0)" \
--rpc-url $BASE_RPC \
--private-key $DEPLOYER_KEY
@ -199,13 +198,14 @@ done
The second `recenter()` hits the bootstrap path inside `LiquidityManager`: `cumulativeVolume == 0` and `ethFee > 0`, so it records the VWAP price anchor and sets `cumulativeVolume > 0`, permanently closing the bootstrap window.
```bash
export LM_ADDRESS="$LM_ADDRESS" # must be set
# LM_ADDRESS must already be set from Step 1.
# BootstrapVWAPPhase2.s.sol reads the broadcaster key from the .secret
# seed-phrase file in onchain/ (same as DeployBase.sol). Ensure that file
# is present; the --private-key CLI flag is NOT used by this script.
forge script script/BootstrapVWAPPhase2.s.sol \
--tc BootstrapVWAPPhase2 \
--rpc-url $BASE_RPC \
--broadcast \
--private-key $DEPLOYER_KEY
--broadcast
```
The script asserts `cumulativeVolume > 0` and will fail with an explicit message if the bootstrap did not succeed.
@ -240,7 +240,7 @@ cast balance $LM_ADDRESS --rpc-url $BASE_RPC
Foundry simulates the entire `run()` function before broadcasting anything. If the inline bootstrap in `DeployBase.sol` causes the simulation to fail, no transactions are broadcast.
**Workaround:** Comment out the bootstrap block (lines marked `VWAP Bootstrap`) in `DeployBase.sol` locally before running the deploy script, then restore it afterward. The bootstrap is then performed manually using Steps 36 above.
**Workaround:** Comment out the bootstrap block in `DeployBase.sol` locally (lines 101145, from `// =====================================================================` through `seedSwapper.executeSeedBuy{ value: SEED_SWAP_ETH }(sender);`) before running the deploy script, then restore it afterward. The bootstrap is then performed manually using Steps 36 above.
### `recenter()` reverts with "price deviated from oracle"

View file

@ -128,17 +128,24 @@ contract DeployBase is Script {
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.
// 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");
liquidityManager.recenter();
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
@ -153,7 +160,7 @@ contract DeployBase is Script {
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 --fork-url <RPC> --broadcast");
console.log(" 2. Run: forge script script/BootstrapVWAPPhase2.s.sol --tc BootstrapVWAPPhase2 --rpc-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.");