#!/usr/bin/env bash # Lightweight bootstrap for red-team / evaluator use. # Starts only Anvil + deploys contracts. No ponder, no webapp, no txnbot. # # Environment overrides: # CANDIDATE Path to a .push3 file. When set, the push3-transpiler is # invoked to regenerate OptimizerV3Push3Lib.sol, then the # deployed Optimizer UUPS proxy is upgraded to OptimizerV3 # (which delegates to the regenerated lib). # RPC_URL Anvil RPC endpoint (default: http://localhost:8545) set -euo pipefail REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" ONCHAIN_DIR="$REPO_ROOT/onchain" RPC_URL="${RPC_URL:-http://localhost:8545}" CAST="$HOME/.foundry/bin/cast" FORGE="$HOME/.foundry/bin/forge" CANDIDATE="${CANDIDATE:-}" log() { echo "[bootstrap-light] $*"; } die() { log "ERROR: $*" >&2; exit 1; } # 1. Start Anvil (docker) log "Starting Anvil..." cd "$REPO_ROOT" sudo docker compose down -v 2>/dev/null || true sudo docker compose up -d anvil for i in $(seq 1 30); do $CAST chain-id --rpc-url "$RPC_URL" 2>/dev/null && break sleep 1 done $CAST chain-id --rpc-url "$RPC_URL" >/dev/null 2>&1 || die "Anvil not responding" log "Anvil running" # 2. Clear ERC-4337 code from well-known addresses (fork safety) DEPLOYER=$($CAST wallet address --mnemonic "test test test test test test test test test test test junk" 2>/dev/null) log "Clearing code from deployer ($DEPLOYER) + feeDest" $CAST rpc --rpc-url "$RPC_URL" anvil_setCode "$DEPLOYER" "0x" 2>/dev/null || true $CAST rpc --rpc-url "$RPC_URL" anvil_setCode "0xf6a3eef9088A255c32b6aD2025f83E57291D9011" "0x" 2>/dev/null || true # 3. Push3 candidate injection (optional) # When CANDIDATE is set, transpile the .push3 file into OptimizerV3Push3.sol, # then regenerate OptimizerV3Push3Lib.sol so that OptimizerV3 (which delegates # to the lib) will use the candidate logic after the proxy upgrade in step 6. if [[ -n "$CANDIDATE" ]]; then [[ -f "$CANDIDATE" ]] || die "CANDIDATE file not found: $CANDIDATE" log "Transpiling candidate: $CANDIDATE" TRANSPILER_DIR="$REPO_ROOT/tools/push3-transpiler" TRANSPILER_OUT="$ONCHAIN_DIR/src/OptimizerV3Push3.sol" # Ensure transpiler deps are installed if [[ ! -d "$TRANSPILER_DIR/node_modules" ]]; then (cd "$TRANSPILER_DIR" && npm install --silent) || die "transpiler npm install failed" fi # Transpile Push3 → standalone OptimizerV3Push3.sol (cd "$TRANSPILER_DIR" && npx tsx src/index.ts "$CANDIDATE" "$TRANSPILER_OUT") \ || die "push3-transpiler failed" # Regenerate OptimizerV3Push3Lib.sol from the transpiler output. # Extracts the calculateParams function body and wraps it in a library. python3 - "$TRANSPILER_OUT" "$ONCHAIN_DIR/src/OptimizerV3Push3Lib.sol" <<'PYEOF' || die "lib generation failed" import sys with open(sys.argv[1]) as f: push3 = f.read() # Extract function body (everything between the first { after calculateParams and its closing }) fn_start = push3.find("function calculateParams") if fn_start == -1: sys.exit("calculateParams not found in transpiler output") brace_start = push3.find("{", fn_start) sig_end = push3.index("\n", brace_start) + 1 lines = push3[sig_end:].split("\n") body_lines = [] depth = 1 for line in lines: depth += line.count("{") - line.count("}") if depth <= 0: break body_lines.append(line) else: sys.exit("closing brace not found") body = "\n".join(body_lines) lib = f"""// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import {{OptimizerInput}} from "./IOptimizer.sol"; /** * @title OptimizerV3Push3Lib * @notice Shared library containing the canonical Push3 transpiler output for * OptimizerV3 parameter calculation. Used by both OptimizerV3Push3 * (standalone) and OptimizerV3 (UUPS-upgradeable Optimizer) so that * future transpiler changes require only one edit. * @dev Auto-regenerated by bootstrap-light.sh from CANDIDATE env var. */ library OptimizerV3Push3Lib {{ function calculateParams(OptimizerInput[8] memory inputs) internal pure returns (uint256 ci, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {{ {body} }} }} """ with open(sys.argv[2], "w") as f: f.write(lib) PYEOF log "OptimizerV3Push3Lib.sol regenerated from candidate" fi # 4. Deploy contracts — capture output for addresses log "Deploying contracts..." cd "$ONCHAIN_DIR" # Fix ownership of forge artifacts (docker creates root-owned files) sudo chown -R "$(id -u):$(id -g)" cache out broadcast 2>/dev/null || true rm -f deployments-local.json # force fresh DEPLOY_OUT=$($FORGE script script/DeployLocal.sol --rpc-url "$RPC_URL" --broadcast 2>&1) echo "$DEPLOY_OUT" | grep -E "^\[|deployed|complete|Summary" || true # 5. Extract addresses from output and write deployments-local.json KRK=$(echo "$DEPLOY_OUT" | grep -oP 'Kraiken deployed: \K0x[a-fA-F0-9]+') [[ -n "$KRK" ]] || die "Could not extract Kraiken address from deploy output" STAKE=$(echo "$DEPLOY_OUT" | grep -oP 'Stake deployed: \K0x[a-fA-F0-9]+') [[ -n "$STAKE" ]] || die "Could not extract Stake address from deploy output" OPT=$(echo "$DEPLOY_OUT" | grep -oP 'Optimizer deployed: \K0x[a-fA-F0-9]+') [[ -n "$OPT" ]] || die "Could not extract Optimizer address from deploy output" LM=$(echo "$DEPLOY_OUT" | grep -oP 'LiquidityManager deployed: \K0x[a-fA-F0-9]+') [[ -n "$LM" ]] || die "Could not extract LiquidityManager address from deploy output" POOL=$(echo "$DEPLOY_OUT" | grep -oP 'Pool: \K0x[a-fA-F0-9]+' | head -1) [[ -n "$POOL" ]] || die "Could not extract Pool address from deploy output" # Base Sepolia Uniswap V3 Factory — must match v3Factory constant in DeployLocal.sol V3_FACTORY="0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24" WETH=0x4200000000000000000000000000000000000006 cat > "$ONCHAIN_DIR/deployments-local.json" << EOF { "contracts": { "Kraiken": "$KRK", "Stake": "$STAKE", "LiquidityManager": "$LM", "OptimizerProxy": "$OPT", "Pool": "$POOL", "V3Factory": "$V3_FACTORY" }, "infrastructure": { "weth": "$WETH" } } EOF # 6. Upgrade Optimizer proxy to OptimizerV3 (candidate injection) # DeployLocal deploys Optimizer (v1) behind a UUPS proxy. When a Push3 # candidate was transpiled in step 3, we deploy a fresh OptimizerV3 # implementation (which delegates to the regenerated OptimizerV3Push3Lib) # and upgrade the proxy so the candidate logic is live on-chain. if [[ -n "$CANDIDATE" ]]; then log "Upgrading Optimizer proxy to OptimizerV3 (candidate: $(basename "$CANDIDATE" .push3))..." # Deployer PK — Anvil account 0 (same key DeployLocal.sol uses via .secret.local) DEPLOYER_PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 # Deploy OptimizerV3 implementation V3_DEPLOY_OUT=$($FORGE create --rpc-url "$RPC_URL" --private-key "$DEPLOYER_PK" \ src/OptimizerV3.sol:OptimizerV3 2>&1) \ || die "OptimizerV3 deployment failed: $V3_DEPLOY_OUT" V3_IMPL=$(echo "$V3_DEPLOY_OUT" | grep -oP 'Deployed to: \K0x[a-fA-F0-9]+') [[ -n "$V3_IMPL" ]] || die "Could not extract OptimizerV3 address from forge create output" log "OptimizerV3 implementation: $V3_IMPL" # Upgrade the UUPS proxy $CAST send --rpc-url "$RPC_URL" --private-key "$DEPLOYER_PK" \ "$OPT" "upgradeTo(address)" "$V3_IMPL" >/dev/null 2>&1 \ || die "upgradeTo failed" log "Proxy upgraded to OptimizerV3" # Verify the upgrade by calling getLiquidityParams through the proxy VERIFY_OUT=$($CAST call --rpc-url "$RPC_URL" "$OPT" \ "getLiquidityParams()(uint256,uint256,uint24,uint256)" 2>/dev/null) || true log "Post-upgrade getLiquidityParams: $VERIFY_OUT" fi # 7. Verify VWAP=$($CAST call --rpc-url "$RPC_URL" "$LM" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0") log "LiquidityManager: $LM" log "cumulativeVolume: $VWAP" [[ "$VWAP" != "0" ]] && log "✅ Bootstrap complete — VWAP active" || log "⚠️ VWAP not bootstrapped"