harb/scripts/recover-bootstrap.sh

211 lines
9 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
# recover-bootstrap.sh — Diagnose and recover from a failed VWAP bootstrap.
#
# If the second recenter() reverts mid-sequence (e.g. "amplitude not reached"),
# the LiquidityManager is left with positions deployed but cumulativeVolume == 0.
# This script diagnoses the state and retries the bootstrap.
#
# See docs/mainnet-bootstrap.md "Recovery from failed mid-sequence bootstrap"
# for the full manual procedure.
#
# Usage:
# scripts/recover-bootstrap.sh --rpc-url <RPC> --private-key <KEY> --lm <LM_ADDRESS> [OPTIONS]
#
# Options:
# --rpc-url <url> RPC endpoint (required)
# --private-key <key> Deployer private key (required)
# --lm <address> LiquidityManager address (required)
# --kraiken <address> Kraiken token address (optional, for seed buy retry)
# --seed-eth <amount> Extra seed buy amount, e.g. "0.01ether" (default: skip seed buy)
# --fee-dest <address> Set feeDestination to this address if not already correct
# --dry-run Diagnose only, do not send transactions
# --help Show this help message
set -euo pipefail
# ── Defaults ──────────────────────────────────────────────────────────
RPC_URL=""
PRIVATE_KEY=""
LM_ADDRESS=""
KRAIKEN=""
SEED_ETH=""
FEE_DEST=""
DRY_RUN=false
WETH="0x4200000000000000000000000000000000000006"
SWAP_ROUTER_MAINNET="0x2626664c2603336E57B271c5C0b26F421741e481"
SWAP_ROUTER_SEPOLIA="0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4"
MAX_UINT="0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
# ── Argument parsing ──────────────────────────────────────────────────
usage() {
sed -n '/^# Usage:/,/^set -euo/p' "$0" | head -n -1 | sed 's/^# \?//'
exit 0
}
while [[ $# -gt 0 ]]; do
case "$1" in
--rpc-url) RPC_URL="$2"; shift 2 ;;
--private-key) PRIVATE_KEY="$2"; shift 2 ;;
--lm) LM_ADDRESS="$2"; shift 2 ;;
--kraiken) KRAIKEN="$2"; shift 2 ;;
--seed-eth) SEED_ETH="$2"; shift 2 ;;
--fee-dest) FEE_DEST="$2"; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
--help|-h) usage ;;
*) echo "Unknown option: $1"; usage ;;
esac
done
if [[ -z "$RPC_URL" || -z "$PRIVATE_KEY" || -z "$LM_ADDRESS" ]]; then
echo "Error: --rpc-url, --private-key, and --lm are required."
echo "Run with --help for usage."
exit 1
fi
# ── Helpers ───────────────────────────────────────────────────────────
info() { echo "[recover] $*"; }
warn() { echo "[recover] WARNING: $*"; }
error() { echo "[recover] ERROR: $*" >&2; }
detect_swap_router() {
local chain_id
chain_id="$(cast chain-id --rpc-url "$RPC_URL" 2>/dev/null || echo "")"
if [[ "$chain_id" == "8453" ]]; then
echo "$SWAP_ROUTER_MAINNET"
else
echo "$SWAP_ROUTER_SEPOLIA"
fi
}
# ── Step 1: Diagnose ─────────────────────────────────────────────────
info "Diagnosing LiquidityManager at $LM_ADDRESS ..."
CUMVOL="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "cumulativeVolume()(uint256)" 2>/dev/null || echo "ERROR")"
if [[ "$CUMVOL" == "ERROR" ]]; then
error "Cannot read cumulativeVolume — is LM_ADDRESS correct?"
exit 1
fi
FEE_DESTINATION="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "feeDestination()(address)" 2>/dev/null || echo "ERROR")"
FEE_LOCKED="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "feeDestinationLocked()(bool)" 2>/dev/null || echo "ERROR")"
LAST_RECENTER="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "lastRecenterTime()(uint256)" 2>/dev/null || echo "0")"
info " cumulativeVolume: $CUMVOL"
info " feeDestination: $FEE_DESTINATION"
info " feeDestinationLocked: $FEE_LOCKED"
info " lastRecenterTime: $LAST_RECENTER"
# ── Check: already bootstrapped? ─────────────────────────────────────
if [[ "$CUMVOL" != "0" ]]; then
info "VWAP bootstrap already complete (cumulativeVolume=$CUMVOL). Nothing to recover."
exit 0
fi
info "cumulativeVolume is 0 — bootstrap incomplete, recovery needed."
# ── Check: feeDestination ────────────────────────────────────────────
if [[ "$FEE_DESTINATION" == "0x0000000000000000000000000000000000000000" ]]; then
warn "feeDestination is not set (zero address)."
if [[ -n "$FEE_DEST" ]]; then
info "Will set feeDestination to $FEE_DEST"
else
warn "Pass --fee-dest <address> to set it during recovery."
fi
fi
if [[ -n "$FEE_DEST" && "$FEE_DESTINATION" != "$FEE_DEST" ]]; then
if [[ "$FEE_LOCKED" == "true" ]]; then
error "feeDestination is locked at $FEE_DESTINATION — cannot change to $FEE_DEST."
error "Manual intervention required."
exit 1
fi
if [[ "$DRY_RUN" == "true" ]]; then
info "[dry-run] Would set feeDestination to $FEE_DEST"
else
info "Setting feeDestination to $FEE_DEST ..."
cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \
"$LM_ADDRESS" "setFeeDestination(address)" "$FEE_DEST"
info "feeDestination updated."
fi
fi
# ── Check: cooldown ──────────────────────────────────────────────────
NOW="$(cast block latest --rpc-url "$RPC_URL" --field timestamp 2>/dev/null || echo "0")"
COOLDOWN_END=$(( LAST_RECENTER + 60 ))
if [[ "$NOW" -lt "$COOLDOWN_END" ]]; then
REMAINING=$(( COOLDOWN_END - NOW ))
warn "Recenter cooldown has not elapsed. ${REMAINING}s remaining."
warn "Wait and re-run this script."
if [[ "$DRY_RUN" == "true" ]]; then
exit 0
fi
info "Waiting ${REMAINING}s for cooldown ..."
sleep "$REMAINING"
fi
# ── Optional: extra seed buy ─────────────────────────────────────────
if [[ -n "$SEED_ETH" && -n "$KRAIKEN" ]]; then
SWAP_ROUTER="$(detect_swap_router)"
DEPLOYER_ADDR="$(cast wallet address --private-key "$PRIVATE_KEY")"
if [[ "$DRY_RUN" == "true" ]]; then
info "[dry-run] Would execute seed buy: $SEED_ETH WETH -> KRAIKEN"
else
info "Executing seed buy ($SEED_ETH WETH -> KRAIKEN) ..."
SEED_WEI="$(cast --to-unit "$SEED_ETH" wei)"
cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \
"$WETH" "deposit()" --value "$SEED_ETH"
cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \
"$WETH" "approve(address,uint256)" "$SWAP_ROUTER" "$MAX_UINT"
# Determine swap direction from token ordering
WETH_LOWER=$(echo "$WETH" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//')
KRAIKEN_LOWER=$(echo "$KRAIKEN" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//')
if [[ "$WETH_LOWER" < "$KRAIKEN_LOWER" ]]; then
SQRT_LIMIT=4295128740
else
SQRT_LIMIT=1461446703485210103287273052203988822378723970341
fi
cast send --legacy --gas-limit 300000 --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \
"$SWAP_ROUTER" "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,$SEED_WEI,0,$SQRT_LIMIT)"
info "Seed buy complete."
# Wait for cooldown after potential recenter trigger
info "Waiting 65s for recenter cooldown ..."
sleep 65
fi
fi
# ── Retry second recenter ────────────────────────────────────────────
if [[ "$DRY_RUN" == "true" ]]; then
info "[dry-run] Would call recenter() on $LM_ADDRESS"
info "[dry-run] Done. Re-run without --dry-run to execute."
exit 0
fi
info "Calling recenter() to complete VWAP bootstrap ..."
if ! cast send --rpc-url "$RPC_URL" --private-key "$PRIVATE_KEY" \
"$LM_ADDRESS" "recenter()" 2>&1; then
error "recenter() reverted. Check the revert reason above."
error "Common causes:"
error " - 'amplitude not reached' -> need larger seed buy (use --seed-eth with --kraiken)"
error " - 'price deviated from oracle' -> wait for TWAP history"
error " - 'recenter cooldown' -> wait 60s and retry"
exit 1
fi
# ── Verify ────────────────────────────────────────────────────────────
CUMVOL="$(cast call --rpc-url "$RPC_URL" "$LM_ADDRESS" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")"
if [[ "$CUMVOL" == "0" || -z "$CUMVOL" ]]; then
error "Recovery failed — cumulativeVolume is still 0."
error "The anchor position may have no fees. Try a larger seed buy:"
error " $0 --rpc-url $RPC_URL --private-key <KEY> --lm $LM_ADDRESS --kraiken <ADDR> --seed-eth 0.01ether"
exit 1
fi
info "Recovery successful! cumulativeVolume=$CUMVOL"
info "VWAP bootstrap is complete."