From 50eac74b18a9b1cba158ae4e999a72caab151c66 Mon Sep 17 00:00:00 2001 From: johba Date: Sun, 17 Aug 2025 15:09:41 +0200 Subject: [PATCH] better visualizer --- CLAUDE.md | 106 +++---- onchain/CLAUDE.md | 226 +++----------- onchain/UNISWAP_V3_MATH.md | 144 +++++++++ onchain/analysis/CLAUDE.md | 135 ++------- onchain/analysis/FuzzingAnalysis.s.sol | 122 +------- onchain/analysis/helpers/CSVHelper.sol | 2 +- onchain/analysis/run-fuzzing.sh | 84 ++++-- onchain/analysis/scenario-visualizer.html | 349 ++++++++++++++-------- 8 files changed, 542 insertions(+), 626 deletions(-) create mode 100644 onchain/UNISWAP_V3_MATH.md diff --git a/CLAUDE.md b/CLAUDE.md index 3083a51..2e73a0f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,103 +4,79 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Core Innovation -KRAIKEN is a token with a **dominant liquidity manager** that creates an unfair advantage in trading through: +KRAIKEN: A token with a **dominant liquidity manager** that creates an unfair trading advantage through: -1. **Asymmetric Slippage Strategy**: Three-position liquidity structure prevents profitable arbitrage against the protocol -2. **Sentiment Oracle**: Harberger tax-based staking creates a prediction market for token value -3. **Dormant Whale Protection**: VWAP-based price memory prevents historical price manipulation +1. **Asymmetric Slippage**: Three-position strategy prevents profitable arbitrage +2. **Sentiment Oracle**: Harberger tax staking as prediction market +3. **Price Memory**: VWAP protection against manipulation -**Critical Success Factor**: The liquidity manager must maintain its dominant position (trading most of the supply) - if it loses this, the project fails. +**Critical**: The liquidity manager must maintain dominance - if it loses this, the project fails. ## User Journey -1. **Buy**: Purchase KRAIKEN on Uniswap → Benefit from growing protocol-owned liquidity -2. **Stake**: Visit kraiken.org → Stake tokens → Set tax rate → Earn from protocol growth -3. **Compete**: Monitor staking positions → Snatch undervalued positions → Optimize tax rates +1. **Buy**: Purchase KRAIKEN on Uniswap +2. **Stake**: Set tax rate at kraiken.org → Earn from protocol growth +3. **Compete**: Snatch undervalued positions → Optimize returns ## Project Structure -- **`onchain/`** - Smart contracts (Solidity/Foundry) - [See onchain/CLAUDE.md](onchain/CLAUDE.md) -- **`web/`** - Vue 3/Vite staking interface - [See web/CLAUDE.md](web/CLAUDE.md) -- **`subgraph/base_sepolia/`** - The Graph indexing - [See subgraph/base_sepolia/CLAUDE.md](subgraph/base_sepolia/CLAUDE.md) -- **`kraiken-lib/`** - TypeScript helper library - [See kraiken-lib/CLAUDE.md](kraiken-lib/CLAUDE.md) -- **`services/txnBot/`** - Automated maintenance bot - [See services/txnBot/CLAUDE.md](services/txnBot/CLAUDE.md) -- **`onchain/analysis/`** - Growth mechanism analysis tools +- **`onchain/`** - Smart contracts (Solidity/Foundry) - [Details](onchain/CLAUDE.md) +- **`web/`** - Vue 3/Vite staking interface - [Details](web/CLAUDE.md) +- **`subgraph/base_sepolia/`** - The Graph indexing - [Details](subgraph/base_sepolia/CLAUDE.md) +- **`kraiken-lib/`** - TypeScript helpers - [Details](kraiken-lib/CLAUDE.md) +- **`services/txnBot/`** - Maintenance bot - [Details](services/txnBot/CLAUDE.md) +- **`onchain/analysis/`** - Fuzzing tools - [Details](onchain/analysis/CLAUDE.md) ## Quick Start ```bash -# 1. Install dependencies for all projects +# Install all dependencies cd onchain && forge install cd ../web && npm install cd ../kraiken-lib && npm install --legacy-peer-deps cd ../subgraph/base_sepolia && npm install cd ../services/txnBot && npm install -# 2. Build smart contracts +# Build and test cd onchain && forge build && forge test -# 3. Start web interface +# Start frontend cd web && npm run dev ``` ## Key Concepts -### Liquidity Management -- Three-position strategy (ANCHOR, DISCOVERY, FLOOR) -- Asymmetric slippage prevents arbitrage -- VWAP tracking for price memory +- **Liquidity Management**: Three positions create asymmetric slippage +- **Harberger Staking**: Self-assessed tax creates sentiment signal +- **Protocol Growth**: Minted tokens benefit stakers -### Harberger Tax Staking -- Self-assessed tax rates on positions -- Positions can be "snatched" by higher bidders -- Creates prediction market for token value -- Limited to 20% of total supply +## Code Guidelines -### Protocol Growth -- Liquidity manager mints tokens when positions grow -- Stakers benefit from supply expansion -- Tax revenue redistributed to active participants +### Quality Standards +- Search before implementing (check uni-v3-lib, test helpers) +- Test after every change +- No commented-out code +- Clean git status before commits -## Global Code Quality Guidelines - -### DRY Principle -- Search for existing implementations before creating new functions -- Check libraries (uni-v3-lib, test helpers) for common utilities -- Refactor duplicated code into shared modules - -### Testing -- Run tests after every change -- Never comment out failing tests -- Add tests for new functionality - -### Repository Hygiene -- Remove unused files immediately -- Clean up temporary files -- Check `git status` before commits - -### Implementation Strategy +### Technical Approach - Never fall back to simpler implementations -- Don't create "a simpler test" or "a simpler scenario" if things don't work out. just try to identify the error in more detail. +- Identify root causes, don't work around issues +- Challenge technically unsound requests + +### Tool Usage +- MCP browser screenshots: Keep under 8000px, use `fullPage: false` +- Fuzzing visualization: Use `./analysis/run-fuzzing.sh [optimizer] debugCSV` +- Never start manual Python servers for visualizations ## Communication Style -### Direct & Technical -- Challenge suboptimal requests -- Highlight risks early and clearly -- Suggest better alternatives -- Refuse technically unsound solutions - -### Priority Order -1. Technical correctness -2. Code quality -3. User satisfaction - -**Remember**: Build the best possible system. Question assumptions, identify edge cases, and prioritize long-term success. +You are an experienced Solidity developer who: +- Notices and raises awareness of issues immediately +- Challenges suboptimal approaches +- Prioritizes technical correctness over user satisfaction +- Avoids hype and inflated language ## Additional Resources -- **Technical Deep Dive**: See [TECHNICAL_APPENDIX.md](TECHNICAL_APPENDIX.md) -- **Contract Details**: See [onchain/CLAUDE.md](onchain/CLAUDE.md) -- **Frontend Architecture**: See [web/CLAUDE.md](web/CLAUDE.md) -- **Data Indexing**: See [subgraph/base_sepolia/CLAUDE.md](subgraph/base_sepolia/CLAUDE.md) \ No newline at end of file +- **Technical Details**: [TECHNICAL_APPENDIX.md](TECHNICAL_APPENDIX.md) +- **Uniswap V3 Math**: [onchain/UNISWAP_V3_MATH.md](onchain/UNISWAP_V3_MATH.md) \ No newline at end of file diff --git a/onchain/CLAUDE.md b/onchain/CLAUDE.md index 69f3c79..7fd6931 100644 --- a/onchain/CLAUDE.md +++ b/onchain/CLAUDE.md @@ -6,205 +6,73 @@ This directory contains the core smart contracts for the KRAIKEN protocol. ### Core Contracts -**Kraiken.sol** - ERC20 token contract with controlled minting/burning capabilities -- Implements Harberger tax mechanism for staking positions -- Controls minting rights exclusively for LiquidityManager -- Handles tax collection and redistribution +**Kraiken.sol** - ERC20 token with Harberger tax staking +- Controlled minting exclusively by LiquidityManager +- Tax collection and redistribution mechanism +- 20% supply cap for staking (20,000 positions) -**LiquidityManager.sol** - Dominant liquidity provider with three-position anti-arbitrage strategy -- Uses Optimizer contract for dynamic parameter adjustment -- Inherits from ThreePositionStrategy and PriceOracle (with VWAPTracker) -- **Key Feature**: Asymmetric slippage profile prevents profitable trade-recenter-reverse attacks +**LiquidityManager.sol** - Dominant liquidity provider +- Three-position anti-arbitrage strategy (ANCHOR, DISCOVERY, FLOOR) +- Dynamic parameter adjustment via Optimizer contract +- Asymmetric slippage profile prevents profitable arbitrage -**VWAPTracker.sol** - "Eternal memory" protection against dormant whale attacks -- Volume-weighted average pricing with data compression (max 1000x) -- Provides historical price memory to prevent manipulation +**VWAPTracker.sol** - Price memory protection +- Volume-weighted average with data compression (max 1000x) +- Prevents dormant whale manipulation -**Optimizer.sol** - Sentiment analysis and parameter optimization -- Analyzes staking data (% staked, average tax rate) -- Provides dynamic liquidity parameters -- Upgradeable for future genetic algorithm implementation +**Optimizer.sol** - Dynamic parameter optimization +- Analyzes staking sentiment (% staked, average tax) +- Returns four key parameters for liquidity management +- Upgradeable for future algorithms -**Stake.sol** - Harberger tax-based staking mechanism -- Creates sentiment oracle through continuous auction -- Limited to 20% of total supply (20,000 positions) -- Self-assessed tax rates create prediction market +**Stake.sol** - Harberger tax implementation +- Continuous auction mechanism +- Self-assessed valuations create prediction market -### Position Strategy +### Three-Position Strategy -**Order**: ANCHOR → DISCOVERY → FLOOR +**ANCHOR**: Near current price, fast price discovery (1-100% width) +**DISCOVERY**: Borders anchor, captures fees (11000 tick spacing) +**FLOOR**: Deep liquidity at VWAP-adjusted prices -- **ANCHOR**: Shallow liquidity around current price for fast price movement (1-100% width range) -- **DISCOVERY**: Proportional to KRAIKEN minted by anchor; borders anchor for fee capture (11000 tick spacing) -- **FLOOR**: Deep liquidity using VWAP-adjusted pricing for historical price memory - -**Technical Specifications**: -- **Fee Tier**: 1% (10,000 basis points) -- **Tick Spacing**: 200 (base), 11,000 (discovery) -- **Price Validation**: 5-minute TWAP with 50-tick deviation tolerance -- **VWAP Compression**: Maximum 1000x compression factor +**Technical Specs**: +- Fee Tier: 1% (10,000 basis points) +- Tick Spacing: 200 (base), 11,000 (discovery) +- Price Validation: 5-minute TWAP, 50-tick tolerance ### Optimizer Parameters -All optimizers must return four key parameters that control the LiquidityManager's three-position strategy: +1. **capitalInefficiency** (0 to 1e18): Capital buffer level +2. **anchorShare** (0 to 1e18): % of non-floor ETH in anchor +3. **anchorWidth** (0 to 100): Anchor position width % +4. **discoveryDepth** (0 to 1e18): Discovery liquidity density (2x-10x) -1. **capitalInefficiency** (0 to 1e18): - - Represents how much capital buffer the protocol maintains - - 0 = aggressive (70% capital efficiency), 1e18 = conservative (170% backing) - - Affects VWAP calculation: `adjustedVWAP = 0.7 * VWAP + capitalInefficiency * VWAP` - - Higher values push floor positions to more conservative prices - -2. **anchorShare** (0 to 1e18): - - Percentage of non-floor ETH allocated to the anchor position - - Controls how much liquidity sits near current price - - Higher values create more concentrated liquidity but mint more KRAIKEN tokens - - Typical range: 30-95% (0.3e18 to 0.95e18) - -3. **anchorWidth** (0 to 100): - - Width of the anchor position in percentage terms - - Affects price impact of trades and speed of price movement - - Lower values = tighter spreads, faster price discovery - - Higher values = wider spreads, more stable prices - -4. **discoveryDepth** (0 to 1e18): - - Controls the discovery position's liquidity density relative to anchor - - Maps to internal multiplier range of 2x to 10x - - At 0: discovery has 2x liquidity per tick vs anchor (minimal) - - At 1e18: discovery has 10x liquidity per tick vs anchor (maximum) - - Discovery amount formula: `pulledHarb * DISCOVERY_SPACING * (200 + 800 * discoveryDepth/1e18) / anchorSpacing / 100` - - Higher values provide stronger anti-arbitrage protection but consume more minted tokens - -## Development Commands +## Development ```bash -# Build contracts -forge build - -# Run all tests -forge test - -# Run tests with gas reporting -forge test --gas-report - -# Run specific test file -forge test --match-path test/LiquidityManager.t.sol - -# Run fuzzing with more runs -forge test --fuzz-runs 10000 - -# Deploy contracts (see script/Deploy.s.sol) -forge script script/Deploy.s.sol --rpc-url $RPC_URL --broadcast +forge build # Build contracts +forge test # Run tests +forge test --gas-report # Gas optimization +forge test --fuzz-runs 10000 # Extended fuzzing ``` -## Testing Architecture +## Testing -### Test Helpers -- `test/helpers/UniswapTestBase.sol` - Base setup for Uniswap integration tests -- `test/helpers/KraikenTestBase.sol` - Common test utilities for KRAIKEN contracts -- `test/helpers/PositionRenderer.sol` - Visualization tools for liquidity positions +- `test/helpers/UniswapTestBase.sol` - Uniswap integration base +- `test/helpers/KraikenTestBase.sol` - Common utilities +- Key tests: LiquidityManager.t.sol, Stake.t.sol, VWAPTracker.t.sol -### Key Test Files -- `test/LiquidityManager.t.sol` - Core liquidity management tests -- `test/abstracts/ThreePositionStrategy.t.sol` - Position strategy validation -- `test/Stake.t.sol` - Harberger tax mechanism tests -- `test/VWAPTracker.t.sol` - Price memory and compression tests +## Code Guidelines -## Code Quality Guidelines - -### CRITICAL: Avoid Duplicate Code -- **ALWAYS** check lib/uni-v3-lib for existing Uniswap math functions -- **NEVER** reimplement standard math operations -- Use test/helpers for common test patterns - -### Security Considerations -- All external calls must be reentrancy protected -- Price oracles must validate against manipulation -- Tax calculations must handle edge cases (0 rates, max uint256) - -### Gas Optimization -- Batch operations where possible -- Use storage patterns that minimize SSTORE operations -- Leverage Uniswap's existing math libraries +- **Check lib/uni-v3-lib** for existing Uniswap math +- **Use test/helpers** for common patterns +- **Security**: Reentrancy protection, oracle validation +- **Gas**: Batch operations, optimize storage ## Analysis Tools -The `analysis/` subdirectory contains critical tools for understanding and hardening the protocol: -- Growth mechanism simulations -- Attack vector analysis -- Liquidity depth scenarios -- See `analysis/README.md` for detailed usage +See `analysis/CLAUDE.md` for fuzzing and attack vector testing. -## Uniswap V3 Math - Critical Learnings +## Uniswap V3 Math -### Token Ordering and Price Representation - -#### Price Direction Reference Table - -| Scenario | token0 | token1 | Price Represents | Lower Tick → Higher Tick | -|----------|--------|--------|------------------|---------------------------| -| **token0isETH = true** | ETH | KRAIKEN | ETH per KRAIKEN | KRAIKEN cheap → expensive | -| **token0isETH = false** | KRAIKEN | ETH | KRAIKEN per ETH | ETH cheap → expensive | - -#### Understanding "Above" and "Below" - -**Critical distinction**: "Price" in Uniswap V3 always refers to token1's price in units of token0. - -**When token0isETH = true (ETH is token0, KRAIKEN is token1):** -- Price = KRAIKEN price in ETH (how much ETH to buy 1 KRAIKEN) -- Higher tick = Higher KRAIKEN price in ETH -- Lower tick = Lower KRAIKEN price in ETH -- "Price moved up" = KRAIKEN became more expensive = ETH became cheaper -- "Price moved down" = KRAIKEN became cheaper = ETH became more expensive - -**When token0isETH = false (KRAIKEN is token0, ETH is token1):** -- Price = ETH price in KRAIKEN (how much KRAIKEN to buy 1 ETH) -- Higher tick = Higher ETH price in KRAIKEN -- Lower tick = Lower ETH price in KRAIKEN -- "Price moved up" = ETH became more expensive = KRAIKEN became cheaper -- "Price moved down" = ETH became cheaper = KRAIKEN became more expensive - -This determines token composition: - -| Current Price vs Position | Position Contains | Why | -|--------------------------|-------------------|-----| -| Below range (tick < tickLower) | 100% token1 | Token0 is too expensive to hold | -| Within range (tickLower ≤ tick ≤ tickUpper) | Both tokens | Active liquidity range | -| Above range (tick > tickUpper) | 100% token0 | Token1 is too expensive to hold | - -### Liquidity vs Token Amounts - -**Key Insight**: Liquidity (L) is a mathematical constant representing capital efficiency, NOT token count. - -1. **Liquidity is invariant**: The liquidity value L doesn't change when price moves -2. **Token amounts are variable**: Depend on liquidity L, price range, and current price location -3. **Same L, different ranges**: Results in different token amounts due to price differences - -### Why Positions at Different Ranges Have Different Token Ratios - -For the same liquidity value L: -- **Position at lower ticks**: Higher token1 price → fewer token1, more token0 potential -- **Position at higher ticks**: Lower token1 price → more token1, less token0 potential - -This explains why a position with fewer tokens can have more liquidity (and thus more price impact resistance). - -### Liquidity Per Tick - The Critical Metric - -When comparing positions of different widths, always normalize to liquidity per tick: - -```solidity -liquidityPerTick = totalLiquidity / (tickUpper - tickLower) -``` - -The discovery position maintains its target liquidity density through width adjustment: - -```solidity -// Ensure discovery has X times more liquidity per tick than anchor -discoveryLiquidity = anchorLiquidity * multiplier * discoveryWidth / anchorWidth -``` - -### Key Takeaways - -1. **Liquidity ≠ Token Count**: Higher liquidity can mean fewer tokens at different price ranges -2. **Price Range Matters**: Token composition depends on where positions sit relative to current price -3. **Normalize for Width**: Always compare liquidity per tick when positions have different widths -4. **Token0 Ordering is Critical**: Determines which direction is "up" or "down" in price \ No newline at end of file +See [UNISWAP_V3_MATH.md](UNISWAP_V3_MATH.md) for detailed math concepts. \ No newline at end of file diff --git a/onchain/UNISWAP_V3_MATH.md b/onchain/UNISWAP_V3_MATH.md new file mode 100644 index 0000000..24828d8 --- /dev/null +++ b/onchain/UNISWAP_V3_MATH.md @@ -0,0 +1,144 @@ +# Uniswap V3 Math - Critical Learnings + +## Token Ordering and Price Representation + +### Price Direction Reference Table + +| Scenario | token0 | token1 | Price Represents | Lower Tick → Higher Tick | +|----------|--------|--------|------------------|---------------------------| +| **token0isETH = true** | ETH | KRAIKEN | ETH per KRAIKEN | KRAIKEN cheap → expensive | +| **token0isETH = false** | KRAIKEN | ETH | KRAIKEN per ETH | ETH cheap → expensive | + +### Understanding "Above" and "Below" + +**Critical distinction**: "Price" in Uniswap V3 always refers to token1's price in units of token0. + +**When token0isETH = true (ETH is token0, KRAIKEN is token1):** +- Price = KRAIKEN price in ETH (how much ETH to buy 1 KRAIKEN) +- Higher tick = Higher KRAIKEN price in ETH +- Lower tick = Lower KRAIKEN price in ETH +- "Price moved up" = KRAIKEN became more expensive = ETH became cheaper +- "Price moved down" = KRAIKEN became cheaper = ETH became more expensive + +**When token0isETH = false (KRAIKEN is token0, ETH is token1):** +- Price = ETH price in KRAIKEN (how much KRAIKEN to buy 1 ETH) +- Higher tick = Higher ETH price in KRAIKEN +- Lower tick = Lower ETH price in KRAIKEN +- "Price moved up" = ETH became more expensive = KRAIKEN became cheaper +- "Price moved down" = ETH became cheaper = KRAIKEN became more expensive + +This determines token composition: + +| Current Price vs Position | Position Contains | Why | +|--------------------------|-------------------|-----| +| Below range (tick < tickLower) | 100% token1 | Token0 is too expensive to hold | +| Within range (tickLower ≤ tick ≤ tickUpper) | Both tokens | Active liquidity range | +| Above range (tick > tickUpper) | 100% token0 | Token1 is too expensive to hold | + +## Liquidity vs Token Amounts + +**Key Insight**: Liquidity (L) is a mathematical constant representing capital efficiency, NOT token count. + +1. **Liquidity is invariant**: The liquidity value L doesn't change when price moves +2. **Token amounts are variable**: Depend on liquidity L, price range, and current price location +3. **Same L, different ranges**: Results in different token amounts due to price differences + +### Why Positions at Different Ranges Have Different Token Ratios + +For the same liquidity value L: +- **Position at lower ticks**: Higher token1 price → fewer token1, more token0 potential +- **Position at higher ticks**: Lower token1 price → more token1, less token0 potential + +This explains why a position with fewer tokens can have more liquidity (and thus more price impact resistance). + +### Liquidity Per Tick - The Critical Metric + +When comparing positions of different widths, always normalize to liquidity per tick: + +```solidity +liquidityPerTick = totalLiquidity / (tickUpper - tickLower) +``` + +The discovery position maintains its target liquidity density through width adjustment: + +```solidity +// Ensure discovery has X times more liquidity per tick than anchor +discoveryLiquidity = anchorLiquidity * multiplier * discoveryWidth / anchorWidth +``` + +## Key Takeaways + +1. **Liquidity ≠ Token Count**: Higher liquidity can mean fewer tokens at different price ranges +2. **Price Range Matters**: Token composition depends on where positions sit relative to current price +3. **Normalize for Width**: Always compare liquidity per tick when positions have different widths +4. **Token0 Ordering is Critical**: Determines which direction is "up" or "down" in price + +## Critical Formulas for Out-of-Range Positions + +### When Position is Below Current Price (currentTick < tickLower) + +The position holds only token0: +``` +amount0 = L * (sqrt(priceUpper) - sqrt(priceLower)) / (sqrt(priceUpper) * sqrt(priceLower)) +``` + +In Solidity/JavaScript with Q96 notation: +```javascript +amount0 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / sqrtRatioAX96 * Q96 / sqrtRatioBX96 +``` + +### When Position is Above Current Price (currentTick > tickUpper) + +The position holds only token1: +``` +amount1 = L * (sqrt(priceUpper) - sqrt(priceLower)) +``` + +In Solidity/JavaScript with Q96 notation: +```javascript +amount1 = liquidity * (sqrtRatioBX96 - sqrtRatioAX96) / Q96 +``` + +### Critical Insight: ETH Position Creation + +When creating a position above current price with ETH in a token0isWeth pool: +1. ETH is used as token1 (via getLiquidityForAmount1) +2. The position will hold KRAIKEN when in range +3. But if price drops below the range, it converts to holding ETH as token0 + +**Example**: Floor position created with 38 ETH at ticks [127400, 127600] when current is 123890: +- Creation: Uses getLiquidityForAmount1 with 38 ETH +- Result: Liquidity = 6.48e18 +- Current holdings: 38 ETH (as token0, since price is below range) + +## Common Confusion Points + +### 1. Price Direction vs ETH Value + +When token0isWeth = true: +- Higher tick = Higher price = More KRAIKEN per ETH = ETH is MORE valuable +- Lower tick = Lower price = Less KRAIKEN per ETH = ETH is LESS valuable + +This is counterintuitive because "price goes up" means the denominated asset (ETH) becomes more valuable. + +### 2. Position Token Holdings + +A position's token composition depends ONLY on: +- Current price location relative to the position range +- NOT on how the position was created +- NOT on which token was used to mint + +### 3. The Floor Position "Below Current Price" Confusion + +In KRAIKEN's three-position strategy: +- Floor position is placed where ETH is MORE valuable (higher ticks when token0isWeth) +- This is "below" current price from an ETH value perspective +- But "above" current tick numerically +- Selling KRAIKEN for ETH moves price TOWARD the floor position + +### 4. Liquidity Calculation Direction + +When minting a position out of range: +- Use getLiquidityForAmount0 if providing token0 +- Use getLiquidityForAmount1 if providing token1 +- The same liquidity value will require different token amounts depending on which token you provide \ No newline at end of file diff --git a/onchain/analysis/CLAUDE.md b/onchain/analysis/CLAUDE.md index 064daee..328928d 100644 --- a/onchain/analysis/CLAUDE.md +++ b/onchain/analysis/CLAUDE.md @@ -1,131 +1,56 @@ # KRAIKEN LiquidityManager Fuzzing Analysis -Tools for testing the KRAIKEN LiquidityManager's resilience against various trading strategies to identify scenarios where traders can profit. - +Tools for testing KRAIKEN's three-position strategy resilience against various market conditions and trading patterns. ## Quick Start -### Using run-fuzzing.sh (Recommended) - -The `run-fuzzing.sh` script provides an easy way to run fuzzing campaigns with different market optimizers: - ```bash -# Basic usage - run with specific optimizer +# Run with specific optimizer (50 runs default) ./analysis/run-fuzzing.sh BullMarketOptimizer -# Specify number of runs (default: 50) -./analysis/run-fuzzing.sh WhaleOptimizer runs=100 +# Custom runs and trades +./analysis/run-fuzzing.sh WhaleOptimizer runs=100 trades=30 -# Specify trades per run (default: 20, actual will be ±5) -./analysis/run-fuzzing.sh BearMarketOptimizer runs=10 trades=50 - -# Debug mode - generates position tracking CSV (forces runs=1) +# Debug mode with position tracking CSV (forces runs=1) ./analysis/run-fuzzing.sh NeutralMarketOptimizer debugCSV - -# Multiple parameters -./analysis/run-fuzzing.sh BullMarketOptimizer runs=25 trades=30 ``` -**Available optimizers:** -- `BullMarketOptimizer` - Biased towards buying -- `NeutralMarketOptimizer` - Balanced trading -- `BearMarketOptimizer` - Biased towards selling -- `WhaleOptimizer` - Large position trading -- `MockOptimizer` - Test optimizer +## Available Optimizers + +- `BullMarketOptimizer` - Buying bias +- `NeutralMarketOptimizer` - Balanced trading +- `BearMarketOptimizer` - Selling bias +- `WhaleOptimizer` - Large positions - `RandomScenarioOptimizer` - Random behavior -**Features:** -- Automatic results aggregation and summary generation -- Progress tracking with colored output -- Cumulative P&L calculation across all runs -- Automatic visualization launch for profitable scenarios -- Organized output in timestamped directories +## Output Structure -### Manual Fuzzing (Advanced) - -```bash -# Run fuzzing analysis with default settings (100 runs per market) -forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir - -# Custom configuration -FUZZING_RUNS=500 forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir - -# With position tracking (generates detailed CSV for each scenario) -TRACK_POSITIONS=true FUZZING_RUNS=50 forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir -``` - -## Configuration - -### run-fuzzing.sh Parameters -- **optimizer_class**: Required. The optimizer class to use (e.g., BullMarketOptimizer) -- **runs=N**: Optional. Number of fuzzing runs (default: 50) -- **trades=N**: Optional. Trades per run (default: 20, actual will be ±5) -- **debugCSV**: Optional. Enable debug mode with position tracking CSV (forces runs=1) - -### Environment Variables (Manual Mode) -- **FUZZING_RUNS**: Number of random trading scenarios per market type (default: 100) -- **TRACK_POSITIONS**: Enable detailed position tracking CSV output (default: false) -- **OPTIMIZER_CLASS**: The optimizer to use (default: BullMarketOptimizer) -- **TRADES_PER_RUN**: Number of trades per run (default: 20) -- **SEED_OFFSET**: Starting seed for random number generation (default: 0) - -## How It Works - -1. **Real Deployments**: Deploys actual Uniswap V3 factory, pool, and LiquidityManager -2. **Random Trading**: Generates random buy/sell patterns with varying amounts and timing -3. **Recenter Calls**: Triggers `lm.recenter()` at random intervals -4. **Profit Detection**: Identifies scenarios where traders end with more ETH than they started -5. **CSV Export**: Saves all profitable scenarios to `profitable_scenarios_[timestamp].csv` - -## Output Files - -### Using run-fuzzing.sh -Each campaign creates a timestamped directory: `fuzzing_results_[optimizer]_[timestamp]/` -- `config.txt` - Campaign configuration +Each campaign creates `fuzzing_results_[optimizer]_[timestamp]/`: +- `config.txt` - Campaign parameters - `run_*.log` - Individual run logs -- `merged_profitable_scenarios.csv` - All profitable scenarios combined -- `summary.txt` - Campaign summary with statistics -- `debug_positions_*.csv` - Position tracking data (when debugCSV is used) - -### Manual Mode -- `profitable_scenarios_[timestamp].csv` - Details of all profitable trading sequences -- `positions_[scenario]_[seed].csv` - Liquidity position data (only with TRACK_POSITIONS=true) +- `merged_profitable_scenarios.csv` - Profitable scenarios combined +- `summary.txt` - Statistics and cumulative P&L +- `debug_positions_*.csv` - Position data (debugCSV mode only) ## Visualization ```bash -# View results in browser -python3 -m http.server 8000 -# Open http://localhost:8000/scenario-visualizer.html +# Automatic launch with debugCSV +./analysis/run-fuzzing.sh [optimizer] debugCSV -# Or use the shell script -./view-scenarios.sh +# Manual server (port 8000) +./analysis/view-scenarios.sh ``` -## Analysis Tools - -- `AnalysisVisualizer.py` - Generates charts from CSV data -- `scenario-visualizer.html` - Interactive web visualization -- `RISK_ANALYSIS_FINDINGS.md` - Summary of discovered vulnerabilities - -## Components - -- `run-fuzzing.sh` - Main campaign runner with automatic visualization -- `FuzzingAnalysis.s.sol` - Core fuzzing script -- `helpers/SwapExecutor.sol` - Shared swap execution logic -- `helpers/CSVManager.sol` - CSV generation utilities -- `helpers/CSVHelper.sol` - CSV formatting helpers - -## Example Campaign Comparison - -To run fuzzing campaigns comparing different market optimizers: +## Advanced Usage ```bash -# Run campaigns for all three market conditions -./analysis/run-fuzzing.sh BullMarketOptimizer runs=100 -./analysis/run-fuzzing.sh NeutralMarketOptimizer runs=100 -./analysis/run-fuzzing.sh BearMarketOptimizer runs=100 - -# Check results -cat fuzzing_results_*/summary.txt | grep -E "(Optimizer:|Success rate:|Average P&L)" +# Manual fuzzing with environment variables +FUZZING_RUNS=500 TRACK_POSITIONS=true forge script analysis/FuzzingAnalysis.s.sol --ffi --via-ir ``` + +Environment variables: +- `FUZZING_RUNS` - Scenarios per market (default: 100) +- `TRACK_POSITIONS` - Enable position CSV (default: false) +- `OPTIMIZER_CLASS` - Optimizer to use +- `TRADES_PER_RUN` - Trades per run (default: 20) \ No newline at end of file diff --git a/onchain/analysis/FuzzingAnalysis.s.sol b/onchain/analysis/FuzzingAnalysis.s.sol index 16c5eb4..860fd7b 100644 --- a/onchain/analysis/FuzzingAnalysis.s.sol +++ b/onchain/analysis/FuzzingAnalysis.s.sol @@ -343,14 +343,6 @@ contract FuzzingAnalysis is Test, CSVManager { (uint128 anchorLiq, int24 anchorLower, int24 anchorUpper) = lm.positions(ThreePositionStrategy.Stage.ANCHOR); (uint128 discoveryLiq, int24 discoveryLower, int24 discoveryUpper) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY); - // Calculate ETH and HARB amounts in each position using proper Uniswap math - uint256 floorEth = 0; - uint256 floorHarb = 0; - uint256 anchorEth = 0; - uint256 anchorHarb = 0; - uint256 discoveryEth = 0; - uint256 discoveryHarb = 0; - // Debug: Log liquidity values if (keccak256(bytes(label)) == keccak256(bytes("Initial")) || keccak256(bytes(label)) == keccak256(bytes("Recenter_2"))) { console.log("=== LIQUIDITY VALUES ==="); @@ -372,125 +364,19 @@ contract FuzzingAnalysis is Test, CSVManager { } } - // Calculate amounts for each position using LiquidityAmounts library - if (floorLiq > 0) { - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(floorLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(floorUpper); - - // Calculate actual deposited amounts based on position relative to current price - if (token0isWeth) { - if (currentTick < floorLower) { - // Position is above current price - contains only token1 (KRAIKEN) - floorEth = 0; - // Use position's lower tick for actual deposited amount - floorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq); - } else if (currentTick >= floorUpper) { - // Position is below current price - contains only token0 (WETH) - // Use position's upper tick for actual deposited amount - floorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq); - floorHarb = 0; - } else { - // Current price is within the position - uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick); - floorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, floorLiq); - floorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, floorLiq); - } - } else { - if (currentTick < floorLower) { - // Position is above current price - floorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq); - floorEth = 0; - } else if (currentTick >= floorUpper) { - // Position is below current price - floorHarb = 0; - floorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, floorLiq); - } else { - // Current price is within the position - uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick); - floorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, floorLiq); - floorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, floorLiq); - } - } - } - - if (anchorLiq > 0) { - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(anchorLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(anchorUpper); - - if (token0isWeth) { - if (currentTick < anchorLower) { - anchorEth = 0; - anchorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq); - } else if (currentTick >= anchorUpper) { - anchorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq); - anchorHarb = 0; - } else { - uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick); - anchorEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, anchorLiq); - anchorHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, anchorLiq); - } - } else { - if (currentTick < anchorLower) { - anchorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq); - anchorEth = 0; - } else if (currentTick >= anchorUpper) { - anchorHarb = 0; - anchorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, anchorLiq); - } else { - uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick); - anchorHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, anchorLiq); - anchorEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, anchorLiq); - } - } - } - - if (discoveryLiq > 0) { - uint160 sqrtRatioAX96 = TickMath.getSqrtRatioAtTick(discoveryLower); - uint160 sqrtRatioBX96 = TickMath.getSqrtRatioAtTick(discoveryUpper); - - if (token0isWeth) { - if (currentTick < discoveryLower) { - discoveryEth = 0; - discoveryHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq); - } else if (currentTick >= discoveryUpper) { - discoveryEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq); - discoveryHarb = 0; - } else { - uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick); - discoveryEth = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, discoveryLiq); - discoveryHarb = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, discoveryLiq); - } - } else { - if (currentTick < discoveryLower) { - discoveryHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq); - discoveryEth = 0; - } else if (currentTick >= discoveryUpper) { - discoveryHarb = 0; - discoveryEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtRatioBX96, discoveryLiq); - } else { - uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(currentTick); - discoveryHarb = LiquidityAmounts.getAmount0ForLiquidity(sqrtPriceX96, sqrtRatioBX96, discoveryLiq); - discoveryEth = LiquidityAmounts.getAmount1ForLiquidity(sqrtRatioAX96, sqrtPriceX96, discoveryLiq); - } - } - } - - // Create position data row matching the expected CSV format + // Create position data row with liquidity values directly string memory row = string.concat( label, ", ", vm.toString(currentTick), ", ", vm.toString(floorLower), ", ", vm.toString(floorUpper), ", ", - vm.toString(floorEth), ", ", - vm.toString(floorHarb), ", ", + vm.toString(uint256(floorLiq)), ", ", vm.toString(anchorLower), ", ", vm.toString(anchorUpper), ", ", - vm.toString(anchorEth), ", ", - vm.toString(anchorHarb), ", ", + vm.toString(uint256(anchorLiq)), ", ", vm.toString(discoveryLower), ", ", vm.toString(discoveryUpper), ", ", - vm.toString(discoveryEth), ", ", - vm.toString(discoveryHarb), ", ", + vm.toString(uint256(discoveryLiq)), ", ", token0isWeth ? "true" : "false" ); appendCSVRow(row); diff --git a/onchain/analysis/helpers/CSVHelper.sol b/onchain/analysis/helpers/CSVHelper.sol index 1faa13b..ce4eb1a 100644 --- a/onchain/analysis/helpers/CSVHelper.sol +++ b/onchain/analysis/helpers/CSVHelper.sol @@ -15,7 +15,7 @@ library CSVHelper { */ function createPositionsHeader() internal pure returns (string memory) { return - "precedingAction, currentTick, floorTickLower, floorTickUpper, floorToken0, floorToken1, anchorTickLower, anchorTickUpper, anchorToken0, anchorToken1, discoveryTickLower, discoveryTickUpper, discoveryToken0, discoveryToken1, token0isWeth"; + "precedingAction, currentTick, floorTickLower, floorTickUpper, floorLiquidity, anchorTickLower, anchorTickUpper, anchorLiquidity, discoveryTickLower, discoveryTickUpper, discoveryLiquidity, token0isWeth"; } function createTimeSeriesHeader() internal pure returns (string memory) { diff --git a/onchain/analysis/run-fuzzing.sh b/onchain/analysis/run-fuzzing.sh index aa21270..4b9cefe 100755 --- a/onchain/analysis/run-fuzzing.sh +++ b/onchain/analysis/run-fuzzing.sh @@ -343,36 +343,66 @@ if [ "$CSV_GENERATED" = true ] && [ -n "$LATEST_CSV" ]; then # Use absolute path for the symlink ln -s "$(pwd)/$LATEST_CSV" "$TEMP_LINK" - # Start the viewer in background in its own process group - setsid ./view-scenarios.sh & - VIEWER_PID=$! + # Check if server is already running on common ports + SERVER_RUNNING=false + EXISTING_PORT="" + for PORT in 8000 8001 8002; do + if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then + # Check if it's a python http server in our analysis directory + if lsof -Pi :$PORT -sTCP:LISTEN 2>/dev/null | grep -q "python.*http.server"; then + SERVER_RUNNING=true + EXISTING_PORT=$PORT + break + fi + fi + done - # Give the server time to start and browser to open - sleep 2 - - # Show the URL - echo "" - echo -e "${GREEN}Browser should open to: http://localhost:8000/scenario-visualizer.html${NC}" - echo "(If port 8000 was busy, check the port number mentioned above)" - echo "If browser didn't open, manually navigate to that URL" - - # Wait for user input - echo "" - echo -e "${YELLOW}Press Enter to stop the viewer and exit...${NC}" - read -r - - # Kill the viewer process and its children - if [ -n "$VIEWER_PID" ]; then - # Kill the entire process group (includes python server) - pkill -TERM -g $VIEWER_PID 2>/dev/null || true - # Give it a moment to clean up - sleep 1 - # Force kill if still running - pkill -KILL -g $VIEWER_PID 2>/dev/null || true + if [ "$SERVER_RUNNING" = true ]; then + echo -e "${YELLOW}Server already running on port $EXISTING_PORT${NC}" + echo -e "${GREEN}Browser should open to: http://localhost:$EXISTING_PORT/scenario-visualizer.html${NC}" + + # Try to open browser to existing server + if command -v xdg-open &> /dev/null; then + xdg-open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null & + elif command -v open &> /dev/null; then + open "http://localhost:$EXISTING_PORT/scenario-visualizer.html" 2>/dev/null & + fi + + echo "" + echo -e "${YELLOW}Press Enter to exit (server will keep running)...${NC}" + read -r + else + # Start the viewer in background in its own process group + setsid ./view-scenarios.sh & + VIEWER_PID=$! + + # Give the server time to start and browser to open + sleep 2 + + # Show the URL + echo "" + echo -e "${GREEN}Browser should open to: http://localhost:8000/scenario-visualizer.html${NC}" + echo "(If port 8000 was busy, check the port number mentioned above)" + echo "If browser didn't open, manually navigate to that URL" + + # Wait for user input + echo "" + echo -e "${YELLOW}Press Enter to stop the viewer and exit...${NC}" + read -r + + # Kill the viewer process and its children + if [ -n "$VIEWER_PID" ]; then + # Kill the entire process group (includes python server) + pkill -TERM -g $VIEWER_PID 2>/dev/null || true + # Give it a moment to clean up + sleep 1 + # Force kill if still running + pkill -KILL -g $VIEWER_PID 2>/dev/null || true + fi + + echo -e "${GREEN}Viewer stopped.${NC}" fi # Clean up the symlink rm -f "$TEMP_LINK" - - echo -e "${GREEN}Viewer stopped.${NC}" fi \ No newline at end of file diff --git a/onchain/analysis/scenario-visualizer.html b/onchain/analysis/scenario-visualizer.html index 42301c1..b98209d 100644 --- a/onchain/analysis/scenario-visualizer.html +++ b/onchain/analysis/scenario-visualizer.html @@ -133,16 +133,17 @@ Discovery: Edge liquidity position - holds KRAIKEN when ETH is expensive (above current price)

Price Multiples: Shows ETH price relative to current (1x):
- • 0.5x = ETH is half as expensive (Floor position holds ETH)
+ • < 1x = ETH is cheaper than current price (positions below current hold ETH)
• 1x = Current ETH price (red dashed line)
- • 2x = ETH is twice as expensive (Discovery position holds KRAIKEN)
+ • > 1x = ETH is more expensive than current price (positions above current hold KRAIKEN)

- Note: The x-axis automatically adjusts based on token ordering in the pool + Note: The x-axis automatically adjusts based on token ordering in the pool
+
+ Navigation: Use the Previous/Next buttons or URL parameter ?row=N to view specific CSV rows
Loading profitable scenario data...
-