bootstrap now discovers the Uniswap pool address from the factory contract and writes both LM_ADDRESS and POOL_ADDRESS to ponder .env.local. Without these, ponder watches hardcoded default addresses that dont exist on the local Anvil fork, causing all protocol stats except holder count to stay zero.
275 lines
12 KiB
Bash
Executable file
275 lines
12 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Shared bootstrap functions for local dev and CI.
|
|
# Source this file after setting these variables:
|
|
# ANVIL_RPC - Anvil JSON-RPC URL (required)
|
|
# DEPLOYER_PK - Deployer private key (defaults to Anvil account 0)
|
|
# DEPLOYER_ADDR - Deployer address (defaults to Anvil account 0)
|
|
# TXNBOT_ADDRESS - TxnBot wallet address (optional)
|
|
# TXNBOT_PRIVATE_KEY- TxnBot private key (optional)
|
|
# TXNBOT_FUND_VALUE - Amount to fund txnBot (default: 1ether)
|
|
# CONTRACT_ENV - Path to write contracts.env (required)
|
|
# LOG_FILE - Log file for cast/forge output (default: /dev/null)
|
|
# ONCHAIN_DIR - Path to onchain/ directory (required)
|
|
# KRAIKEN_LIB_DIR - Path to kraiken-lib/ directory (optional, for CI build)
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Constants ──────────────────────────────────────────────────────────
|
|
FEE_DEST=0xf6a3eef9088A255c32b6aD2025f83E57291D9011
|
|
WETH=0x4200000000000000000000000000000000000006
|
|
SWAP_ROUTER_SEPOLIA=0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4
|
|
SWAP_ROUTER_MAINNET=0x2626664c2603336E57B271c5C0b26F421741e481
|
|
SWAP_ROUTER="" # resolved lazily by detect_swap_router()
|
|
MAX_UINT=0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
|
|
|
|
DEFAULT_DEPLOYER_PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
|
|
DEFAULT_DEPLOYER_ADDR=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
|
|
|
|
DEFAULT_TXNBOT_PK=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
|
|
DEFAULT_TXNBOT_ADDR=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
|
|
|
|
# ── Defaults ───────────────────────────────────────────────────────────
|
|
DEPLOYER_PK=${DEPLOYER_PK:-$DEFAULT_DEPLOYER_PK}
|
|
DEPLOYER_ADDR=${DEPLOYER_ADDR:-$DEFAULT_DEPLOYER_ADDR}
|
|
TXNBOT_FUND_VALUE=${TXNBOT_FUND_VALUE:-1ether}
|
|
LOG_FILE=${LOG_FILE:-/dev/null}
|
|
|
|
# ── Helpers ────────────────────────────────────────────────────────────
|
|
bootstrap_log() {
|
|
echo "[bootstrap] $*"
|
|
}
|
|
|
|
# ── Functions ──────────────────────────────────────────────────────────
|
|
|
|
detect_swap_router() {
|
|
# Idempotency: only detect once
|
|
if [[ -n "$SWAP_ROUTER" ]]; then
|
|
return 0
|
|
fi
|
|
local chain_id
|
|
chain_id="$(cast chain-id --rpc-url "$ANVIL_RPC" 2>/dev/null || echo "")"
|
|
if [[ "$chain_id" == "8453" ]]; then
|
|
SWAP_ROUTER="$SWAP_ROUTER_MAINNET"
|
|
bootstrap_log "Detected Base mainnet (chain ID 8453) — using mainnet SwapRouter"
|
|
else
|
|
SWAP_ROUTER="$SWAP_ROUTER_SEPOLIA"
|
|
bootstrap_log "Using Base Sepolia SwapRouter (chain ID: ${chain_id:-unknown})"
|
|
fi
|
|
}
|
|
|
|
wait_for_rpc() {
|
|
for _ in {1..120}; do
|
|
if cast chain-id --rpc-url "$ANVIL_RPC" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
done
|
|
bootstrap_log "Timed out waiting for Anvil at $ANVIL_RPC"
|
|
return 1
|
|
}
|
|
|
|
run_forge_script() {
|
|
bootstrap_log "Deploying contracts to fork"
|
|
pushd "$ONCHAIN_DIR" >/dev/null
|
|
local _forge_log
|
|
_forge_log="$(mktemp)"
|
|
if ! forge script script/DeployLocal.sol --tc DeployLocal --fork-url "$ANVIL_RPC" --broadcast >"$_forge_log" 2>&1; then
|
|
bootstrap_log "forge script FAILED — output:"
|
|
cat "$_forge_log" >&2
|
|
rm -f "$_forge_log"
|
|
popd >/dev/null
|
|
return 1
|
|
fi
|
|
cat "$_forge_log" >>"$LOG_FILE" 2>/dev/null || true
|
|
rm -f "$_forge_log"
|
|
popd >/dev/null
|
|
}
|
|
|
|
extract_addresses() {
|
|
local run_file
|
|
run_file="$(ls -t "$ONCHAIN_DIR/broadcast/DeployLocal.sol"/*/run-latest.json 2>/dev/null | head -n1)"
|
|
if [[ -z "$run_file" ]]; then
|
|
bootstrap_log "Deployment artifact not found"
|
|
exit 1
|
|
fi
|
|
bootstrap_log "Using artifact $run_file"
|
|
LIQUIDITY_MANAGER="$(jq -r '.transactions[] | select(.contractName=="LiquidityManager") | .contractAddress' "$run_file" | head -n1)"
|
|
KRAIKEN="$(jq -r '.transactions[] | select(.contractName=="Kraiken") | .contractAddress' "$run_file" | head -n1)"
|
|
STAKE="$(jq -r '.transactions[] | select(.contractName=="Stake") | .contractAddress' "$run_file" | head -n1)"
|
|
OPTIMIZER_PROXY="$(jq -r '.transactions[] | select(.contractName=="ERC1967Proxy") | .contractAddress' "$run_file" | head -n1)"
|
|
DEPLOY_BLOCK="$(jq -r '.receipts[0].blockNumber' "$run_file" | xargs printf "%d")"
|
|
if [[ -z "$LIQUIDITY_MANAGER" || "$LIQUIDITY_MANAGER" == "null" ]]; then
|
|
bootstrap_log "LiquidityManager address missing"
|
|
exit 1
|
|
fi
|
|
|
|
# Discover Uniswap pool address from factory
|
|
detect_swap_router
|
|
local factory
|
|
factory=$(cast call --rpc-url "$ANVIL_RPC" "$SWAP_ROUTER" "factory()(address)" 2>/dev/null) || factory=""
|
|
if [[ -n "$factory" && "$factory" != "0x" ]]; then
|
|
POOL_ADDRESS=$(cast call --rpc-url "$ANVIL_RPC" "$factory" "getPool(address,address,uint24)(address)" "$WETH" "$KRAIKEN" 10000 2>/dev/null) || POOL_ADDRESS=""
|
|
fi
|
|
if [[ -z "$POOL_ADDRESS" || "$POOL_ADDRESS" == "0x0000000000000000000000000000000000000000" ]]; then
|
|
bootstrap_log "Warning: could not discover pool address"
|
|
POOL_ADDRESS=""
|
|
fi
|
|
}
|
|
|
|
write_contracts_env() {
|
|
cat >"$CONTRACT_ENV" <<EOCONTRACTS
|
|
LIQUIDITY_MANAGER=$LIQUIDITY_MANAGER
|
|
KRAIKEN=$KRAIKEN
|
|
STAKE=$STAKE
|
|
EOCONTRACTS
|
|
}
|
|
|
|
fund_liquidity_manager() {
|
|
bootstrap_log "Funding LiquidityManager"
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$LIQUIDITY_MANAGER" --value 10ether >>"$LOG_FILE" 2>&1
|
|
}
|
|
|
|
|
|
bootstrap_vwap() {
|
|
detect_swap_router
|
|
# WARNING: If the second recenter() call later in this function fails mid-sequence, the LM is left
|
|
# with positions deployed but cumulativeVolume == 0 (partial bootstrap).
|
|
# For mainnet recovery see docs/mainnet-bootstrap.md or scripts/recover-bootstrap.sh.
|
|
# Idempotency guard: if a previous run already bootstrapped VWAP, skip.
|
|
local cumvol
|
|
cumvol="$(cast call --rpc-url "$ANVIL_RPC" \
|
|
"$LIQUIDITY_MANAGER" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")"
|
|
if [[ "$cumvol" != "0" && -n "$cumvol" ]]; then
|
|
bootstrap_log "VWAP already bootstrapped (cumulativeVolume=$cumvol) -- skipping"
|
|
return 0
|
|
fi
|
|
|
|
local recenter_pk="${TXNBOT_PRIVATE_KEY:-$DEPLOYER_PK}"
|
|
|
|
# Fund LM with 1 ETH (thin bootstrap positions; 0.5 ETH seed swap moves >400 ticks)
|
|
bootstrap_log "Funding LM with 1 ETH for VWAP bootstrap..."
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$LIQUIDITY_MANAGER" --value 1ether >>"$LOG_FILE" 2>&1
|
|
|
|
# Advance Anvil time 301s so TWAP oracle has sufficient history for _isPriceStable()
|
|
cast rpc --rpc-url "$ANVIL_RPC" evm_increaseTime 301 >>"$LOG_FILE" 2>&1
|
|
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1
|
|
|
|
# First recenter: places initial bootstrap positions; no fees yet, cumulativeVolume stays 0
|
|
bootstrap_log "First recenter (places bootstrap positions)..."
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \
|
|
"$LIQUIDITY_MANAGER" "recenter()" >>"$LOG_FILE" 2>&1
|
|
|
|
# Seed buy: wrap 0.5 ETH to WETH and swap WETH->KRK
|
|
# Generates a non-zero WETH fee in the anchor position and moves price >400 ticks.
|
|
# sqrtPriceLimitX96 is direction-dependent: MIN+1 when WETH<KRK (token0), MAX-1 otherwise.
|
|
bootstrap_log "Executing seed buy (0.5 ETH WETH->KRK)..."
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$WETH" "deposit()" --value 0.5ether >>"$LOG_FILE" 2>&1
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$WETH" "approve(address,uint256)" "$SWAP_ROUTER" "$MAX_UINT" >>"$LOG_FILE" 2>&1
|
|
|
|
local weth_addr kraiken_addr sqrt_limit
|
|
weth_addr=$(echo "$WETH" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//')
|
|
kraiken_addr=$(echo "$KRAIKEN" | tr '[:upper:]' '[:lower:]' | sed 's/^0x//')
|
|
if [[ "$weth_addr" < "$kraiken_addr" ]]; then
|
|
sqrt_limit=4295128740 # WETH=token0, zeroForOne=true, price decreases
|
|
else
|
|
sqrt_limit=1461446703485210103287273052203988822378723970341 # WETH=token1, price increases
|
|
fi
|
|
|
|
cast send --legacy --gas-limit 300000 --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$SWAP_ROUTER" "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
|
|
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,500000000000000000,0,$sqrt_limit)" >>"$LOG_FILE" 2>&1
|
|
|
|
# Advance time 301s so TWAP settles at post-buy price and cooldown (60s) elapses
|
|
cast rpc --rpc-url "$ANVIL_RPC" evm_increaseTime 301 >>"$LOG_FILE" 2>&1
|
|
cast rpc --rpc-url "$ANVIL_RPC" evm_mine >>"$LOG_FILE" 2>&1
|
|
|
|
# Second recenter: cumulativeVolume==0 path fires (bootstrap), ethFee>0 -> records VWAP
|
|
bootstrap_log "Second recenter (records VWAP)..."
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$recenter_pk" \
|
|
"$LIQUIDITY_MANAGER" "recenter()" >>"$LOG_FILE" 2>&1
|
|
|
|
# Verify VWAP bootstrap succeeded
|
|
cumvol="$(cast call --rpc-url "$ANVIL_RPC" \
|
|
"$LIQUIDITY_MANAGER" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")"
|
|
if [[ "$cumvol" == "0" || -z "$cumvol" ]]; then
|
|
bootstrap_log "ERROR: VWAP bootstrap failed -- cumulativeVolume is 0"
|
|
return 1
|
|
fi
|
|
bootstrap_log "VWAP bootstrapped (cumulativeVolume=$cumvol)"
|
|
}
|
|
|
|
seed_application_state() {
|
|
detect_swap_router
|
|
bootstrap_log "Wrapping ETH to WETH"
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$WETH" "deposit()" --value 2ether >>"$LOG_FILE" 2>&1
|
|
bootstrap_log "Approving router"
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$WETH" "approve(address,uint256)" "$SWAP_ROUTER" "$MAX_UINT" >>"$LOG_FILE" 2>&1
|
|
|
|
# Swap with retry — recenter may not position liquidity at the right tick on first call
|
|
local swap_success=false
|
|
for attempt in 1 2 3; do
|
|
bootstrap_log "KRK swap attempt $attempt/3"
|
|
local balance_before balance_after
|
|
balance_before=$(cast call --rpc-url "$ANVIL_RPC" "$KRAIKEN" "balanceOf(address)(uint256)" "$DEPLOYER_ADDR" 2>/dev/null || echo "0")
|
|
|
|
cast send --legacy --gas-limit 300000 --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$SWAP_ROUTER" "exactInputSingle((address,address,uint24,address,uint256,uint256,uint160))" \
|
|
"($WETH,$KRAIKEN,10000,$DEPLOYER_ADDR,1000000000000000000,0,4295128740)" >>"$LOG_FILE" 2>&1 || true
|
|
|
|
balance_after=$(cast call --rpc-url "$ANVIL_RPC" "$KRAIKEN" "balanceOf(address)(uint256)" "$DEPLOYER_ADDR" 2>/dev/null || echo "0")
|
|
|
|
if [[ "$balance_after" != "$balance_before" && "$balance_after" != "0" ]]; then
|
|
bootstrap_log "Swap successful — got KRK tokens (balance: $balance_after)"
|
|
swap_success=true
|
|
break
|
|
fi
|
|
|
|
bootstrap_log "Swap returned 0 KRK — recentering and retrying"
|
|
# 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" \
|
|
"$LIQUIDITY_MANAGER" "recenter()" >>"$LOG_FILE" 2>&1 || true
|
|
done
|
|
|
|
if [[ "$swap_success" != "true" ]]; then
|
|
bootstrap_log "WARNING: All swap attempts returned 0 KRK. Pool may have no liquidity at current tick."
|
|
fi
|
|
}
|
|
|
|
fund_txn_bot_wallet() {
|
|
if [[ -z "${TXNBOT_ADDRESS:-}" ]]; then
|
|
return
|
|
fi
|
|
bootstrap_log "Funding txnBot wallet $TXNBOT_ADDRESS with $TXNBOT_FUND_VALUE"
|
|
cast send --rpc-url "$ANVIL_RPC" --private-key "$DEPLOYER_PK" \
|
|
"$TXNBOT_ADDRESS" --value "$TXNBOT_FUND_VALUE" >>"$LOG_FILE" 2>&1 || true
|
|
local wei hex
|
|
wei="$(cast --to-unit "$TXNBOT_FUND_VALUE" wei)"
|
|
hex="$(cast --to-hex "$wei")"
|
|
cast rpc --rpc-url "$ANVIL_RPC" anvil_setBalance "$TXNBOT_ADDRESS" "$hex" >>"$LOG_FILE" 2>&1
|
|
}
|
|
|
|
write_deployments_json() {
|
|
local target="${1:-$ONCHAIN_DIR/deployments-local.json}"
|
|
cat >"$target" <<EODEPLOYMENTS
|
|
{
|
|
"contracts": {
|
|
"Kraiken": "$KRAIKEN",
|
|
"Stake": "$STAKE",
|
|
"LiquidityManager": "$LIQUIDITY_MANAGER",
|
|
"OptimizerProxy": "$OPTIMIZER_PROXY"
|
|
},
|
|
"infrastructure": {
|
|
"weth": "$WETH"
|
|
}
|
|
}
|
|
EODEPLOYMENTS
|
|
}
|