harb/onchain/analysis/KRAIKEN_RESEARCH_REPORT.md

415 lines
19 KiB
Markdown
Raw Permalink Normal View History

# KRAIKEN Floor & Optimizer Research Report
**Period:** 2026-02-04 to 2026-02-12
**Branch:** `fix/floor-ratchet` (primary), `feat/optimizer-v2-validation`
**Authors:** Johann (design, direction) + Clawy (implementation, analysis)
---
## Table of Contents
1. [Executive Summary](#1-executive-summary)
2. [Critical Bugs Found & Fixed](#2-critical-bugs-found--fixed)
3. [Floor Drain Vulnerability](#3-floor-drain-vulnerability)
4. [VWAP Mirror Defense](#4-vwap-mirror-defense)
5. [Parameter Space Safety Map](#5-parameter-space-safety-map)
6. [Fee Revenue Analysis](#6-fee-revenue-analysis)
7. [LP Competition Analysis](#7-lp-competition-analysis)
8. [Optimizer Evolution (V1 → V2 → V3)](#8-optimizer-evolution-v1--v2--v3)
9. [Staking Dynamics & Triangle Cycle](#9-staking-dynamics--triangle-cycle)
10. [Remaining Work](#10-remaining-work)
11. [Key Commits](#11-key-commits)
12. [Appendix: Fuzzing Infrastructure](#appendix-fuzzing-infrastructure)
---
## 1. Executive Summary
### What we discovered
The KRAIKEN LiquidityManager had two critical math bugs and a fundamental floor placement vulnerability that allowed attackers to extract 100+ ETH per attack cycle. We fixed the bugs, designed a VWAP mirror defense, mapped the entire parameter safety space, and designed a new optimizer (V3) that uses Harberger staking signals to switch between safe and aggressive configurations.
### Key outcomes
- **Two critical math bugs fixed** (sqrt inflation + missing supply accounting)
- **Floor drain vulnerability understood and mitigated** via VWAP mirror + parameter-space defense
- **Complete 2D safety frontier mapped**: only extreme configs (AS≤35%/AW=100 or AS≥50%/AW=20) are safe without HWM
- **Fee revenue fully characterized**: CI has zero effect; only AS and AW matter
- **Realistic LP competition modeled**: LM retains 55-65% of WETH fees with 200 ETH competing LP
- **OptimizerV3 designed**: three-zone piecewise function from Harberger staking data → binary step between safe bear/bull configs
- **Bear config optimized**: AS=30% AW=100 earns 48% more WETH fees than AS=10% while remaining safe
### Design philosophy (Johann)
- Transaction fees are the product — locked ETH has no value
- No magic numbers — CI should be the risk lever
- Fix root causes, not symptoms
- Parameter space provides safety; no position ratchets needed
---
## 2. Critical Bugs Found & Fixed
### Bug 1: sqrt inflation in `_setFloorPosition` (ThreePositionStrategy.sol)
**Impact:** Made EthScarcity **permanently true**, forcing floor to always use scarcity path regardless of actual ETH balance.
**Root cause:** The scarcity calculation used `FullMath.mulDiv(sqrtVwapX96, sqrtVwapX96, FixedPoint96.Q96)` which squares `sqrtVwapX96`, inflating `requiredEthForBuyback` by ~445×.
**Fix:** Use `vwapX96` directly (already the squared value): `requiredEthForBuyback = FullMath.mulDiv(outstandingSupply, vwapX96, FixedPoint96.Q96)`
### Bug 2: `outstandingSupply()` missing pool balance
**Impact:** `outstandingSupply()` only subtracted LM's own balance from total supply, but not KRK held by `feeDestination` and `stakingPool`. This inflated the supply figure, making scarcity trigger even more easily.
**Fix:** Subtract `feeDestination` and `stakingPool` KRK balances from outstanding supply.
**Combined commit:** `0e2104b`. 130 tests pass after fix.
**Effect:** After fixing both bugs, CI works correctly as a solvency lever:
- CI=0% → safest (floor furthest from current)
- CI=100% → riskiest (floor closest)
- Previously, CI had no effect because scarcity was permanently true.
---
## 3. Floor Drain Vulnerability
### Attack pattern
1. Attacker buys ~950 ETH of KRK (building position)
2. Executes ~2000 trades with 90% sells (buybias=10)
3. Each sell triggers a recenter that rebuilds the floor position
4. Floor walks progressively closer to current price via ~140 recenters
5. Final dump sweeps all floor + anchor positions → net profit +130-170 ETH
### Root cause: `floor = f(current_tick)`
The floor position's absolute tick is always computed from the current tick:
```
floorTick = currentTick + distance(VWAP, CI, ...)
```
During sell-heavy trading, `currentTick` drops through recenters. Even if the VWAP distance grows, the absolute floor position still walks down because `current` drops faster than `distance` grows.
### What we tried (in order)
| Approach | Result | Why it failed/worked |
|----------|--------|---------------------|
| Directional VWAP | Reduced profits 20× | Prevents VWAP dilution but doesn't prevent floor walking |
| VWAP bootstrap | No improvement | 8/10 still profitable |
| Anti-overlap clamp removal | Regression | Clamp is a correctness constraint (floor must hold WETH) |
| Floor ratchet on `isUp` | Works but rejected | "Bandage, not elegant" — Johann |
| VWAP mirror | Partial | Floor walks less but still walks |
| Mirror + wide floor | No improvement | Width irrelevant when attacker sweeps everything |
| Mirror + peak VWAP distance | CI=0% safe, CI>0% leaks | Distance is relative, not absolute |
| Mirror + floor high-water mark | **135/135 safe** | Absolute position ratchet |
| **Parameter space defense** | **Works without HWM** | Safe configs prevent floor walking by geometry |
### Why HWM was removed
Johann's argument: HWM creates a one-way valve. If the optimizer changes CI during operations, HWM prevents the floor from moving back. This defeats the purpose of having CI as a risk lever. Safety should come from the parameter space, not from a position ratchet.
---
## 4. VWAP Mirror Defense
### Current floor formula (no HWM, no Scarcity/Abundance branching)
```
floorTick = max(scarcityTick, mirrorTick, clampTick) toward KRK-cheap side
```
**Three signals:**
- **scarcityTick**: from `vwapX96` and ETH/supply ratio. Correct when ETH is scarce.
- **mirrorTick**: `currentTick + |adjustedVwapTick - currentTick|` on KRK-cheap side. Reflects VWAP distance symmetrically. Uses `getAdjustedVWAP(CI)`.
- **clampTick**: minimum distance from anchor edge. `anchorSpacing = 200 + (34 × 20 × AW / 100)` ticks.
**Key properties:**
- CI controls mirror distance through `getAdjustedVWAP(CI)` — no magic numbers
- Selling naturally grows the mirror distance (current moves away from VWAP)
- AW=100 → clamp = 7000 ticks minimum distance (provides safety floor)
- Unified formula — no EthScarcity/EthAbundance branching
### VWAP recording (directional)
- `LiquidityManager._scrapePositions(bool recordVWAP)` — only records on ETH inflow
- `shouldRecordVWAP` flag: compares `lastRecenterTick` to current tick to detect direction
- Records anchor midpoint price weighted by WETH fees
- Bootstrap: always records when `cumulativeVolume == 0`
- Commit `c97714c` (directional) + `ba018c4` (bootstrap)
---
## 5. Parameter Space Safety Map
### 2D Safe Frontier (AS × AW)
Tested 29 (AS, AW) combinations adversarially (buybias=10, 2000 trades, CI=0%). Full results in `analysis/2D_FRONTIER_LOG.md` and `analysis/2d-frontier-results.csv`.
```
AW=100: AS ≤ 35% safe ✅ (clamp = 7000 ticks)
AW= 90: AS ≤ 30% safe ✅ (clamp = 6320 ticks)
AW= 80: AS ≤ 30% safe ✅ (clamp = 5640 ticks)
AW= 60: ALL AS broken ❌ (clamp = 4280 ticks — too close)
AW= 40: ALL AS broken ❌ (clamp = 2920 ticks)
AW= 30: AS ≥ 80% safe ✅ (floor not under pressure with thin anchor)
AW= 20: AS ≥ 50% safe ✅ (clamp = 1560 ticks but anchor is thin)
```
**The kill zone is AW 40-80 at most AS levels.** Only extreme configurations survive adversarial attack.
### Why only extremes work
| Config | Why safe |
|--------|----------|
| AS low + AW high (bear) | Wide clamp (7000 ticks) prevents floor from reaching current. Thin anchor means less ETH available for attacker to trade against. |
| AS high + AW low (bull) | Thin, concentrated anchor near current price. Floor far away with minimal ETH. Attacker can't drain deep anchor efficiently. |
| AS mid + AW mid | Moderate anchor depth AND moderate clamp distance. Attacker can trade against enough ETH through the anchor while floor is close enough to drain. |
### Other parameter effects
- **CI (capitalInefficiency)**: Zero effect on fee revenue. Pure risk lever for floor placement. CI=0% safest.
- **DD (discoveryDepth)**: Zero effect on floor safety. Pure fee lever for discovery position liquidity. Tested 20/20 safe across DD 0 to 1e18 at AS=10% AW=100.
- **Staking level**: r≈0 correlation with attack profitability.
---
## 6. Fee Revenue Analysis
### Core finding: CI has ZERO effect on fees
Tested across CI=0%, 25%, 50%, 75%, 100% with identical results. CI only affects floor placement, which doesn't generate fees.
### AS × AW fee matrix (without background LP)
| Config | WETH fees | KRK fees | Notes |
|--------|-----------|----------|-------|
| AS=10% AW=100 | 48 W | 1.3M K | Best WETH per ETH |
| AS=30% AW=100 | 72 W | 1.7M K | **Optimal bear** |
| AS=35% AW=100 | 72 W | 1.7M K | Edge of safe zone |
| AS=100% AW=20 | 19 W | 9.2M K | Best KRK fees |
**Two opposing forces:**
- WETH fees ↑ with wider AW (more trades complete through range)
- KRK fees ↑ with higher AS + narrower AW (concentrated liquidity captures more per-tick)
### Volatility effect
High volatility → 5-8× more fees (more trading activity). Fee revenue is primarily driven by volume, not parameters.
---
## 7. LP Competition Analysis
### Background LP model
`BackgroundLP.sol`: 5 stacked Gaussian positions at ±10/20/40/80/160 tick spacings centered on current tick. 200 ETH total (40 ETH/layer). Buys KRK from pool realistically. Rebalances every 10th recenter. Commit `e008b42` + `381b1cc`.
### Fee retention with competition
| Config | WETH (no BG LP) | WETH (200 ETH BG LP) | Retained |
|--------|-----------------|---------------------|----------|
| AS=30% AW=100 | 72 W | 43 W | 60% |
| AS=35% AW=100 | 72 W | 38 W | 52% |
| AS=10% AW=100 | 48 W | 28 W | 58% |
| AS=50% AW=150 | 75 W | 37 W | 49% |
| AS=50% AW=200 | 68 W | 31 W | 41% |
| AS=100% AW=20 | 19 W | 13 W | 66% |
**Wider AW = more fee leakage.** The LM's liquidity is spread thinner → concentrated competitor captures disproportionate share.
### Real LP distribution data (Uniswap V3 mainnet, Feb 2026)
Scanned 4 small-cap pools on mainnet:
| Pool | #Ticks | ±10sp | ±50sp | Avg dist | Character |
|------|--------|-------|-------|----------|-----------|
| AZTEC/WETH 1% | 9 | 50% | 100% | 15 sp | Very concentrated |
| wTAO/USDC 1% | 72 | 54% | 85% | 21 sp | Moderate, long tail |
| wTAO/WETH 0.3% | 109 | 5% | 50% | 80 sp | Very spread |
| LQTY/WETH 0.3% | 41 | 0% | 24% | 234 sp | Extremely spread |
**Key insight:** 1% fee pools (same as KRAIKEN) have more concentrated LPs (avg ~18 spacings). Our Gaussian model at avg ~62 spacings is less concentrated than typical real competition for 1% fee pools. This means our fee estimates are **slightly optimistic** — real competing LPs would capture somewhat more fees.
**Caveat:** KRAIKEN is unique. The LM owns the entire initial liquidity and rebalances every recenter. Competing LPs face a protocol-owned position that actively tracks price, which is very different from normal pools.
---
## 8. Optimizer Evolution (V1 → V2 → V3)
### V1 (Original)
- Sentiment-based: bear → high AS, bull → low AS
- CI, DD driven by sentiment
- **Inverted**: high AS in bear = deep anchor = more ETH for attacker
### V2 (`src/OptimizerV2.sol`, commit `c13d4ca`)
- CI = 0 always (no fee impact, pure safety)
- AS: 100% (bull) → 10% (bear) — **corrected inversion**
- AW: 20 (bull) → 100 (bear)
- DD: proportional to sentiment
- UUPS upgradeable
- 131 tests pass
### V3 (`src/OptimizerV3.sol`, commit `44b8510` + `94446e7`)
Uses on-chain Harberger staking data to determine market phase:
**Inputs:** `percentageStaked` (0-100%), `averageTaxRate` (maps to index 0-29)
**Tax rate array:** `[1, 3, 5, 8, 12, 18, 24, 30, 40, 50, 60, 80, 100, 130, 180, 250, 320, 420, 540, 700, 920, 1200, 1600, 2000, 2600, 3400, 4400, 5700, 7500, 9700]`
**Three zones → score 0-200:**
| Zone | Condition | Formula | Meaning |
|------|-----------|---------|---------|
| A | staked ≤ 39% | `100 - stakedPct² × effIdx / 3600` | Early filling, score ~100 |
| B | 40-91% staked | `max(0, 120 - 8 × effIdx)` | Main operating range |
| C | >91% staked | `max(0, 200 - deltaS³ × effIdx / 20)` | Euphoria zone, cubic snap |
Where `effIdx = min(29, taxIdx + (taxIdx >= 14 ? 1 : 0))`, `deltaS = 100 - stakedPct`
**Score → parameters (step function):**
- Score ≤ 140 → **BEAR**: AS=30%, AW=100, CI=0
- Score ≥ 160 → **BULL**: AS=100%, AW=20, CI=0
- 140-160: narrow linear ramp
**Parameter space distribution:** 94.3% bear, 0.5% transition, 5.2% bull.
**Why step function:** Linear gradient through the score range would pass through vulnerable middle AW values (40-80). Step function snaps between proven-safe extremes.
### Pending: Direct 2D mapping (remove score intermediate)
Johann directed removing the score variable. Score collapses useful 2D (staking%, avgTax) information into lossy 1D.
**Direct rule:** `staked ≤ 91% → BEAR always`. `staked > 91% → BULL if deltaS³ × effIdx / 20 < 50, else BEAR`.
At specific thresholds:
- 97%+ staked: any tax → bull
- 95% staked: tax ≤ 30% (idx ≤ 7) → bull
- 92% staked: tax ≤ 3% (idx ≤ 1) → bull
- <92%: always bear
---
## 9. Staking Dynamics & Triangle Cycle
### Harberger tax mechanics
- Tax rate = **self-assessed defense price** (not conviction)
- Snatching a position requires strictly higher tax rate
- MAX_STAKE = 20% of KRK supply
- TAX_FLOOR_DURATION = 3 days
- High tax erodes position exponentially — cost of defense
### Triangle cycle model
The staking system naturally traces a triangle in (staking%, avgTax) space:
```
100% staked
/ \
/ BULL \
fill up / (euphoria) \ collapse
(bottom) / \ (hypotenuse)
/ BEAR \
/________________________\
0% staked 0% staked
low tax high tax
```
**Phase 1 — Bottom edge (fill up):** Staking grows 0→100%, tax starts low. Optimizer stays bear. Transitions to bull at ~95% staked.
**Phase 2 — Right edge (snatching wars):** 100% staked, snatching wars push average tax rate up. `deltaS = 0` → score = 200 (always bull). Euphoria overwhelms tax cost.
**Phase 3 — Hypotenuse (collapse):** Nervous exits. High tax + declining staking → cubic term in Zone C snaps score to 0 (bear) within 4-6% staking drop. Fast transition because `deltaS³` is cubic.
**Game theory validates this trajectory** — no agent simulation needed (Johann's direction). Incentive structures force participants through this cycle.
---
## 10. Remaining Work
### Immediate
- [ ] **Refactor OptimizerV3**: Remove score intermediate. Direct 2D (staking%, avgTax) → binary config.
- [ ] **Fix run-v3-step-test.sh**: Parameter passing bug causes false positives (script bug, not V3 bug).
- [ ] **Complete bear AS sweep**: Test AS=40-100% at AW=100 to confirm frontier. Fix PnL parsing.
- [ ] **Re-validate after V3 refactor**: Run adversarial suite against refactored V3.
### Deferred
- [ ] Update PR: `https://codeberg.org/johba/harb/compare/master...fix/floor-ratchet`
- [ ] Bull→bear transition testing: What happens when optimizer switches mid-cycle?
- [ ] Test V3 with real staking scenarios end-to-end
- [ ] Consider if bear AS can ramp with staking confidence (AS=30% base → higher as staking grows)
### Won't do
- ~~Agent-based staking simulation~~ — game theory argument sufficient
- ~~HWM / position ratchet~~ — removed by design decision
- ~~New fuzzing scripts~~ — extend existing infrastructure only
---
## 11. Key Commits
| Commit | Description | Branch |
|--------|-------------|--------|
| `0e2104b` | sqrt + outstandingSupply bugfix | fix/floor-ratchet |
| `c97714c` | Directional VWAP recording | fix/floor-ratchet |
| `ba018c4` | VWAP bootstrap fix | fix/floor-ratchet |
| `59b30a6` | Initial VWAP mirror | fix/floor-ratchet |
| `49d15f0` | Revert to Version A (CI controls mirror) | fix/floor-ratchet |
| `bf92977` | Floor HWM (later removed) | fix/floor-ratchet |
| `c7ea6d0` | Cleanup (diagnostics, stale CSVs) | fix/floor-ratchet |
| `e008b42` | Gaussian background LP | feat/optimizer-v2-validation |
| `381b1cc` | Realistic BG LP funding | feat/optimizer-v2-validation |
| `c13d4ca` | OptimizerV2 + HWM removal | feat/optimizer-v2-validation |
| `7b83dd4` | BG LP rebalance fix (every 10th) | feat/optimizer-v2-validation |
| `44b8510` | OptimizerV3: three-zone piecewise | feat/optimizer-v2-validation |
| `94446e7` | OptimizerV3: step function | feat/optimizer-v2-validation |
---
## Appendix: Fuzzing Infrastructure
### Scripts
| Script | Purpose |
|--------|---------|
| `analysis/run-fuzzing.sh` | Single-optimizer fuzzing, CSV per run |
| `analysis/run-adversarial.sh` | Attack specific configs with varied strategies |
| `analysis/run-v3-adversarial.sh` | Attack V3 with staking scenarios |
| `analysis/run-v3-step-test.sh` | Test step function across parameter space (HAS BUG) |
| `analysis/run-deep-search.sh` | Deep search across 4D parameter space |
| `analysis/run-bglp-fee-test.sh` | Fee revenue with background LP competition |
| `analysis/scan-final.py` | On-chain LP distribution scanner |
| `analysis/clean-csvs.sh` | Clean generated CSV files |
### Solidity
| Contract | Purpose |
|----------|---------|
| `StreamlinedFuzzing.s.sol` | Main fuzzing script. ConfigurableOptimizer, staking, BG LP, uncapped swaps. |
| `ParameterSweepFuzzing.s.sol` | Multi-combo sweep in single execution |
| `BullBearSweep.s.sol` | Deterministic bull→bear scenario |
| `helpers/FuzzingBase.sol` | Shared infrastructure |
| `helpers/BackgroundLP.sol` | Gaussian competing LP (5 layers, rebalances on recenters) |
| `helpers/ConfigurableOptimizer.sol` | Test optimizer with env-var-driven params |
### Environment variables
| Var | Default | Description |
|-----|---------|-------------|
| `CI_VALUE` | 0 | Capital inefficiency (0-1e18) |
| `AS_VALUE` | 1e17 | Anchor share (0-1e18) |
| `AW_VALUE` | 20 | Anchor width (0-200+) |
| `DD_VALUE` | 5e17 | Discovery depth (0-1e18) |
| `BUY_BIAS` | 50 | % of trades that are buys (0-100) |
| `TRADES_PER_RUN` | 15 | Trades per run |
| `FUZZING_RUNS` | 1 | Runs per invocation |
| `BATCH_SEED` | 0 | Random seed |
| `OPTIMIZER_CLASS` | BullMarketOptimizer | Which optimizer to use |
| `UNCAPPED_SWAPS` | false | Use uncapped swap amounts |
| `BG_LP_ETH_PER_LAYER` | 0 | ETH per BG LP layer (0 = disabled) |
| `STAKING_LEVEL` | 0 | Staking % (0-100) |
| `STAKING_TAX_RATE` | 3 | Tax rate index (0-29) |
### Constraints
- **1 run per forge invocation**: EVM MemoryOOG after ~2 runs of 2000 trades. Loop in shell.
- **VPS: 8GB RAM, no swap**: Cargo tests OOM. Use `CARGO_BUILD_JOBS=1`.
- **Disk**: ~50% used. Run `clean-csvs.sh` periodically.
- **Forge PATH**: `~/.foundry/bin/forge` (not in default PATH).
### Data files
| File | Description |
|------|-------------|
| `analysis/2D_FRONTIER_LOG.md` | 29-combo (AS, AW) safety frontier |
| `analysis/2d-frontier-results.csv` | Machine-readable frontier data |
| `analysis/V3_FUZZING_LOG.md` | V3 adversarial test results |
| `analysis/V3_STEP_LOG.md` | Step function test results |
| `analysis/FUZZING_LOG.md` | General fuzzing log |
| `analysis/AS_SWEEP_LOG.md` | AS sweep results |
| `analysis/PARAMETER_SEARCH_RESULTS.md` | 4D parameter search |