fix: bootstrap-light.sh lacks Push3 candidate injection (#999)

Add CANDIDATE env var support to bootstrap-light.sh. When set to a
.push3 file path, the script:
1. Invokes push3-transpiler to regenerate OptimizerV3Push3.sol
2. Extracts the function body into OptimizerV3Push3Lib.sol
3. Deploys contracts normally via DeployLocal.sol
4. Deploys OptimizerV3 and upgrades the UUPS proxy via upgradeTo()

Also updates formulas/run-red-team.toml to reflect the implementation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
johba 2026-03-22 13:19:48 +00:00
parent 29b8f7d426
commit 349bd2c2c6
2 changed files with 134 additions and 17 deletions

View file

@ -55,9 +55,9 @@ description = "Timeout in seconds for the adversarial agent run (maps to CLAUDE_
# → promote-attacks (if floor broken) → deliver → teardown. # → promote-attacks (if floor broken) → deliver → teardown.
# #
# CANDIDATE_NAME and OPTIMIZER_PROFILE label the evidence record and attack # CANDIDATE_NAME and OPTIMIZER_PROFILE label the evidence record and attack
# filenames; they do not select which optimizer is deployed — bootstrap-light # filenames. To deploy a specific Push3 candidate, set the CANDIDATE env var
# always deploys via DeployLocal.sol. Per-candidate Push3 injection is planned # (path to a .push3 file) — bootstrap-light.sh will transpile, recompile, and
# but not yet wired (see notes.candidate_injection). # upgrade the Optimizer proxy to OptimizerV3 (see notes.candidate_injection).
[execution] [execution]
script = "scripts/harb-evaluator/red-team.sh" script = "scripts/harb-evaluator/red-team.sh"
@ -81,9 +81,9 @@ scripts/harb-evaluator/bootstrap-light.sh:
liquidity into positions establishing a realistic baseline. liquidity into positions establishing a realistic baseline.
- Verifies Anvil responds and all contract addresses are present in - Verifies Anvil responds and all contract addresses are present in
onchain/deployments-local.json before proceeding. onchain/deployments-local.json before proceeding.
Note: the deployed optimizer is always the default from DeployLocal.sol. When the CANDIDATE env var is set (path to a .push3 file), bootstrap-light.sh
Per-candidate Push3 transpilation is not yet implemented here; see transpiles the candidate and upgrades the Optimizer proxy to OptimizerV3.
notes.candidate_injection. See notes.candidate_injection for details.
""" """
[[steps]] [[steps]]
@ -242,13 +242,14 @@ rediscoveries are silently dropped and the step exits 0.
""" """
candidate_injection = """ candidate_injection = """
Push3 candidate injection is not yet implemented: bootstrap-light.sh always Push3 candidate injection is supported via the CANDIDATE env var in
deploys the default optimizer via DeployLocal.sol and does not read the bootstrap-light.sh. When CANDIDATE points to a .push3 file the script:
CANDIDATE env var. The candidate_name and optimizer_profile inputs are used 1. Invokes push3-transpiler to regenerate OptimizerV3Push3.sol.
only for labelling (evidence records, attack filenames, PR titles); they do not 2. Extracts the function body into OptimizerV3Push3Lib.sol (shared library).
affect which optimizer is deployed. 3. Deploys contracts normally via DeployLocal.sol (Optimizer v1 behind UUPS proxy).
Wiring CANDIDATE push3-transpiler forge compile bootstrap-light is 4. Deploys a fresh OptimizerV3 implementation and upgrades the proxy via upgradeTo().
tracked as a follow-up issue. The candidate_name and optimizer_profile inputs remain metadata-only (evidence
records, attack filenames, PR titles).
""" """
run_attack_suite_gap = """ run_attack_suite_gap = """

View file

@ -1,13 +1,21 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Lightweight bootstrap for red-team / evaluator use. # Lightweight bootstrap for red-team / evaluator use.
# Starts only Anvil + deploys contracts. No ponder, no webapp, no txnbot. # 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 set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)" REPO_ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
ONCHAIN_DIR="$REPO_ROOT/onchain" ONCHAIN_DIR="$REPO_ROOT/onchain"
RPC_URL="http://localhost:8545" RPC_URL="${RPC_URL:-http://localhost:8545}"
CAST="$HOME/.foundry/bin/cast" CAST="$HOME/.foundry/bin/cast"
FORGE="$HOME/.foundry/bin/forge" FORGE="$HOME/.foundry/bin/forge"
CANDIDATE="${CANDIDATE:-}"
log() { echo "[bootstrap-light] $*"; } log() { echo "[bootstrap-light] $*"; }
die() { log "ERROR: $*" >&2; exit 1; } die() { log "ERROR: $*" >&2; exit 1; }
@ -30,7 +38,85 @@ 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 "$DEPLOYER" "0x" 2>/dev/null || true
$CAST rpc --rpc-url "$RPC_URL" anvil_setCode "0xf6a3eef9088A255c32b6aD2025f83E57291D9011" "0x" 2>/dev/null || true $CAST rpc --rpc-url "$RPC_URL" anvil_setCode "0xf6a3eef9088A255c32b6aD2025f83E57291D9011" "0x" 2>/dev/null || true
# 3. Deploy contracts — capture output for addresses # 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..." log "Deploying contracts..."
cd "$ONCHAIN_DIR" cd "$ONCHAIN_DIR"
# Fix ownership of forge artifacts (docker creates root-owned files) # Fix ownership of forge artifacts (docker creates root-owned files)
@ -39,7 +125,7 @@ rm -f deployments-local.json # force fresh
DEPLOY_OUT=$($FORGE script script/DeployLocal.sol --rpc-url "$RPC_URL" --broadcast 2>&1) DEPLOY_OUT=$($FORGE script script/DeployLocal.sol --rpc-url "$RPC_URL" --broadcast 2>&1)
echo "$DEPLOY_OUT" | grep -E "^\[|deployed|complete|Summary" || true echo "$DEPLOY_OUT" | grep -E "^\[|deployed|complete|Summary" || true
# 4. Extract addresses from output and write deployments-local.json # 5. Extract addresses from output and write deployments-local.json
KRK=$(echo "$DEPLOY_OUT" | grep -oP 'Kraiken deployed: \K0x[a-fA-F0-9]+') KRK=$(echo "$DEPLOY_OUT" | grep -oP 'Kraiken deployed: \K0x[a-fA-F0-9]+')
[[ -n "$KRK" ]] || die "Could not extract Kraiken address from deploy output" [[ -n "$KRK" ]] || die "Could not extract Kraiken address from deploy output"
STAKE=$(echo "$DEPLOY_OUT" | grep -oP 'Stake deployed: \K0x[a-fA-F0-9]+') STAKE=$(echo "$DEPLOY_OUT" | grep -oP 'Stake deployed: \K0x[a-fA-F0-9]+')
@ -74,7 +160,37 @@ cat > "$ONCHAIN_DIR/deployments-local.json" << EOF
} }
EOF EOF
# 5. Verify # 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") VWAP=$($CAST call --rpc-url "$RPC_URL" "$LM" "cumulativeVolume()(uint256)" 2>/dev/null || echo "0")
log "LiquidityManager: $LM" log "LiquidityManager: $LM"
log "cumulativeVolume: $VWAP" log "cumulativeVolume: $VWAP"