harb/onchain/analysis/KRAIKEN_RESEARCH_REPORT.md
openhands b7260b2eaf chore: analysis tooling, research artifacts, and code quality
- Analysis: parameter sweep scripts, adversarial testing, 2D frontier maps
- Research: KRAIKEN_RESEARCH_REPORT, SECURITY_REVIEW, STORAGE_LAYOUT
- FuzzingBase: consolidated fuzzing helper, BackgroundLP simulation
- Sweep results: CSV data for full 4D sweep (1050 combos), bull-bear,
  AS sweep, VWAP fix validation
- Code quality: .gitignore for fuzz CSVs, gas snapshot, updated docs
- Remove dead analysis helpers (CSVHelper, CSVManager, ScenarioRecorder)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:22:03 +00:00

414 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 0100%, 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`
- [ ] Bullbear 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 bullbear 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 |