# formulas/run-red-team.toml # # Adversarial red-team — spin up isolated stack, run adversarial agent against # the active optimizer, commit evidence, export newly discovered attack vectors. # # Type: act. Produces evidence (floor held / broken) AND git artifacts # (new attack vectors via PR to onchain/script/backtesting/attacks/). # # Depends on: #973 (evidence/red-team/ directory structure) # #974 (promote-attacks.sh for attack vector export) [formula] id = "run-red-team" name = "Adversarial Red-Team" description = "Spin up isolated stack, run adversarial agent against the active optimizer, commit evidence, export new attack vectors." type = "act" # "sense" → read-only, produces metrics only # "act" → produces git artifacts (cf. run-evolution, run-red-team) depends_on = [973, 974] # ── Inputs ───────────────────────────────────────────────────────────────────── [inputs.candidate_name] type = "string" required = false default = "unknown" description = "Human-readable label used in evidence records and attack filenames (passed as CANDIDATE_NAME)." [inputs.optimizer_profile] type = "string" required = false default = "push3-default" description = "Named optimizer profile / variant (e.g. push3-default, evo_run004_champion) passed as OPTIMIZER_PROFILE." [inputs.attack_dir] type = "string" required = false default = "onchain/script/backtesting/attacks" description = """ Directory containing existing .jsonl attack patterns for the structured attack suite. Forwarded to red-team.sh as ATTACK_DIR. """ [inputs.claude_timeout] type = "integer" required = false default = 7200 description = "Timeout in seconds for the adversarial agent run (maps to CLAUDE_TIMEOUT env var)." # ── Execution ────────────────────────────────────────────────────────────────── # # red-team.sh owns the full lifecycle: # bootstrap-light → fund LM → snapshot → adversarial agent → collect # → promote-attacks (if floor broken) → deliver → teardown. # # CANDIDATE_NAME and OPTIMIZER_PROFILE label the evidence record and attack # filenames. To deploy a specific Push3 candidate, set the CANDIDATE env var # (path to a .push3 file) — bootstrap-light.sh will transpile, recompile, and # upgrade the Optimizer proxy to OptimizerV3 (see notes.candidate_injection). [execution] script = "scripts/harb-evaluator/red-team.sh" invocation = "CANDIDATE_NAME={candidate_name} OPTIMIZER_PROFILE={optimizer_profile} CLAUDE_TIMEOUT={claude_timeout} ATTACK_DIR={attack_dir} bash scripts/harb-evaluator/red-team.sh" # Exit codes propagated by red-team.sh: # 0 floor held (LM total ETH did not decrease) # 1 floor broken (adversary extracted ETH from LiquidityManager) # 2 infra error (Anvil unreachable, bootstrap failed, missing dependency, etc.) # ── Steps ────────────────────────────────────────────────────────────────────── [[steps]] id = "stack-up" description = """ Bootstrap an isolated Anvil fork with contracts deployed. scripts/harb-evaluator/bootstrap-light.sh: - Starts a fresh Anvil instance (or reuses one if already running). - Deploys KRK, LM, Stake, and OptimizerProxy via DeployLocal.sol. - Funds LM with 1 000 ETH (as WETH) and calls recenter() to deploy liquidity into positions — establishing a realistic baseline. - Verifies Anvil responds and all contract addresses are present in onchain/deployments-local.json before proceeding. When the CANDIDATE env var is set (path to a .push3 file), bootstrap-light.sh transpiles the candidate and upgrades the Optimizer proxy to OptimizerV3. See notes.candidate_injection for details. """ [[steps]] id = "run-attack-suite" description = """ Run every existing .jsonl attack file in {attack_dir} through onchain/script/backtesting/AttackRunner.s.sol. For each file: - Record LM total ETH before and after via forge script LmTotalEth.s.sol. - Revert to the baseline Anvil snapshot between files so attacks are independent. - Emit one result entry: strategy name, abstract op pattern, floor held / broken, delta in basis points. This phase exhausts the known attack catalogue before the adversarial agent is given a turn, seeding its memory with which strategies are already understood. """ attack_source = "{attack_dir}/*.jsonl" forge_script = "onchain/script/backtesting/AttackRunner.s.sol" snapshot_mode = "revert-between-attacks" [[steps]] id = "run-adversarial-agent" description = """ Spawn the Claude adversarial agent (red-team-program.md prompt) with full write access to cast / forge / python3 / jq. Goal: make ethPerToken() decrease — i.e. extract ETH from LiquidityManager. The agent: 1. Iterates freely: snapshot → craft novel attack → execute → measure → revert → repeat. 2. Appends each attempted strategy to tmp/red-team-report.txt and tmp/red-team-stream.jsonl. 3. On any confirmed ETH decrease: exports the winning op sequence to tmp/red-team-attacks.jsonl and continues searching. Runs until CLAUDE_TIMEOUT expires or the agent signals completion. """ timeout_env = "CLAUDE_TIMEOUT" memory_file = "tmp/red-team-memory.jsonl" # cross-run pattern learning report_file = "tmp/red-team-report.txt" stream_file = "tmp/red-team-stream.jsonl" [[steps]] id = "collect" description = """ After the agent run, red-team.sh: 1. Reads LM total ETH after (forge script LmTotalEth.s.sol). 2. Extracts strategy findings from tmp/red-team-stream.jsonl and appends them to tmp/red-team-memory.jsonl for cross-run learning. 3. Exports the agent's cast send commands from the stream log to tmp/red-team-attacks.jsonl via export-attacks.py. 4. Replays the exported sequence through AttackRunner.s.sol, writing full state snapshots to tmp/red-team-snapshots.jsonl (used for optimizer training; non-fatal if replay produces no output). 5. Computes floor_held / floor_broken and writes evidence/red-team/{date}.json conforming to the schema in evidence/README.md ## Schema: red-team/. """ output = "evidence/red-team/{date}.json" schema = "evidence/README.md" # see ## Schema: red-team/YYYY-MM-DD.json side_output_file = "tmp/red-team-snapshots.jsonl" # AttackRunner state snapshots for optimizer training [[steps]] id = "export-vectors" description = """ Only runs when the floor is broken (BROKE=true in red-team.sh). If tmp/red-team-attacks.jsonl is non-empty, call promote-attacks.sh to open a Codeberg PR with the newly discovered attack vectors. promote-attacks.sh: - Deduplicates by op-type fingerprint against existing files in onchain/script/backtesting/attacks/. - Auto-classifies the attack type (staking, il-crystallization, floor-ratchet, fee-drain, lp-manipulation, floor-attack, …). - Creates a git branch, commits the new .jsonl, and opens a Codeberg PR targeting main, including the ETH extraction amount in the PR title and body. - Exits 0 when no novel patterns remain after deduplication (non-fatal). Skipped gracefully if CODEBERG_TOKEN and ~/.netrc are both absent. Not called when the floor holds — novel-but-non-exploiting patterns are not promoted. """ script = "scripts/harb-evaluator/promote-attacks.sh" args = "--attacks tmp/red-team-attacks.jsonl --candidate {candidate_name} --profile {optimizer_profile} --eth-extracted --eth-before " # --eth-extracted and --eth-before are computed at runtime by red-team.sh (lm_eth_before − lm_eth_after) # and passed directly to promote-attacks.sh — they are not formula inputs. [[steps]] id = "stack-down" description = """ Tear down the Anvil instance started in stack-up. red-team.sh registers cleanup() as a shell trap (EXIT / INT / TERM): - Reverts to the baseline Anvil snapshot. - Kills the Claude sub-process if still running. Always runs — even on infra error — so port 8545 is not left occupied. """ [[steps]] id = "deliver" description = """ Commit evidence/red-team/{date}.json to main and post a summary comment to the originating issue. Comment includes: - Verdict (floor_held / floor_broken). - ETH extracted (formatted in ETH) and delta in basis points. - Total attacks tried (agent-discovered count + structured suite count). - Link to committed evidence file. - If novel vectors were promoted: link to the attack-vector PR. On floor_broken: also include the highest-yield attack strategy name and its abstract op pattern. """ # ── Products ─────────────────────────────────────────────────────────────────── [products.evidence_file] path = "evidence/red-team/{date}.json" delivery = "commit to main" schema = "evidence/README.md" # see ## Schema: red-team/YYYY-MM-DD.json [products.attack_vectors] path = "onchain/script/backtesting/attacks/{attack_type}-{candidate_name}.jsonl" # {attack_type} is not a formula input — it is computed at runtime by # promote-attacks.sh's classifier (staking, il-crystallization, floor-ratchet, …). delivery = "PR to main" script = "scripts/harb-evaluator/promote-attacks.sh" note = "Only created when the floor is broken AND novel (deduplicated) attack vectors are discovered." [products.issue_comment] delivery = "post to originating issue" content = "verdict (floor_held/floor_broken), ETH extracted, attacks tried, link to evidence file; if vectors found: link to attack-vector PR" on_failure = "include highest-yield attack name and op pattern; full agent transcript available in tmp/red-team-stream.jsonl" # ── Resources ────────────────────────────────────────────────────────────────── [resources] profile = "heavy" compute = "local — Anvil fork + revm, no Docker required" rpc = "Anvil (bootstrap-light, default port 8545)" agent = "Claude (claude CLI, CLAUDE_TIMEOUT seconds)" concurrency = "exclusive — shares Anvil port 8545 with run-holdout and other heavy formulas" # ── Notes ────────────────────────────────────────────────────────────────────── [notes] floor_metric = """ The primary safety metric is LM total ETH: free ETH + free WETH + ETH locked across all three Uniswap V3 positions (floor, anchor, discovery). Measured via forge script LmTotalEth.s.sol using exact Uniswap V3 integer math (LiquidityAmounts + TickMath). A decrease in total ETH = floor broken. """ attack_dedup = """ promote-attacks.sh fingerprints each candidate attack by its abstract op sequence (e.g. wrap → buy → stake → recenter_multi → sell) and compares against all existing files in onchain/script/backtesting/attacks/. Only genuinely novel sequences are included in the PR — duplicate rediscoveries are silently dropped and the step exits 0. """ candidate_injection = """ Push3 candidate injection is supported via the CANDIDATE env var in bootstrap-light.sh. When CANDIDATE points to a .push3 file the script: 1. Invokes push3-transpiler to regenerate OptimizerV3Push3.sol. 2. Extracts the function body into OptimizerV3Push3Lib.sol (shared library). 3. Deploys contracts normally via DeployLocal.sol (Optimizer v1 behind UUPS proxy). 4. Deploys a fresh OptimizerV3 implementation and upgrades the proxy via upgradeTo(). The candidate_name and optimizer_profile inputs remain metadata-only (evidence records, attack filenames, PR titles). """ run_attack_suite_gap = """ The run-attack-suite step is implemented in red-team.sh (step 5a). It loops through every *.jsonl file in the attack directory, replays each through AttackRunner.s.sol, records LM total ETH before/after with snapshot revert between files, and injects results into the agent prompt. """