diff --git a/onchain/GEMINI.md b/onchain/GEMINI.md new file mode 100644 index 0000000..b3b71cc --- /dev/null +++ b/onchain/GEMINI.md @@ -0,0 +1,139 @@ +# Project Overview + +This project is a Solidity-based decentralized finance (DeFi) application built using the Foundry framework. The core of the project revolves around the `Kraiken` token, a sophisticated liquidity management strategy, and a unique staking mechanism. + +## Core Contracts + +* **Kraiken.sol:** An ERC20 token with a self-assessed tax staking system. The supply is managed by a `LiquidityManager`, and a `stakingPool` receives a proportional share of all mints and burns. There's a 20% cap on the total supply that can be staked. + +* **LiquidityManager.sol:** Implements a three-position liquidity strategy: + * **ANCHOR:** A narrow, near-price position. + * **DISCOVERY:** A wider position that borders the anchor. + * **FLOOR:** A deep liquidity position at a VWAP-adjusted price. + This strategy uses asymmetric slippage to prevent arbitrage. + +* **VWAPTracker.sol:** Stores historical price data (as price squared in X96 format) to provide a time-weighted average price. It includes a `getAdjustedVWAP()` function that accounts for capital inefficiency. + +* **Optimizer.sol:** A dynamic and upgradeable contract that reads staking sentiment (percentage of tokens staked, average tax rate) and returns four parameters to adjust the liquidity positions. + +* **Stake.sol:** A self-assessed tax system where users can stake their `Kraiken` tokens. It features a continuous auction mechanism for staking positions. + +## Architecture + +The system is designed around a dominant liquidity manager strategy. The `LiquidityManager` controls the token supply and manages the three-tiered liquidity positions. The `Optimizer` dynamically adjusts the parameters of these positions based on market conditions and staking sentiment. The `Stake` contract provides a competitive environment for users to earn rewards, with a "snatching" mechanism that adds a game-theoretic layer to the staking strategy. + +# Building and Running + +## Dependencies + +* [Foundry](https://getfoundry.sh/) + +## Installation + +1. Clone the repository: + ```bash + git clone + ``` +2. Initialize and update submodules: + ```bash + git submodule init + git submodule update + ``` +3. Install uni-v3-lib dependencies: + ```bash + cd lib/uni-v3-lib + yarn + ``` + +## Build + +```bash +forge build +``` + +## Testing + +```bash +forge test +``` + +For more verbose output: + +```bash +forge test -vvv +``` + +To run tests matching a specific contract: + +```bash +forge test --mc TestContractName +``` + +## Deployment + +The project includes scripts for deploying to Base Sepolia. + +```bash +source .env +forge script script/BaseSepoliaDeploy.sol:BaseSepoliaDeploy --slow --broadcast --verify --rpc-url ${BASE_SEPOLIA_RPC_URL} +``` + +# Fuzzing Analysis + +The project includes a sophisticated fuzzing framework to test the resilience of the trading strategy under various market conditions. + +## Running Fuzzing Tests + +```bash +# Basic test with default parameters +./analysis/run-fuzzing.sh BullMarketOptimizer runs=20 + +# Advanced test with custom parameters +./analysis/run-fuzzing.sh BullMarketOptimizer runs=50 staking=on buybias=85 trades=60 stakingbias=95 +``` + +### Fuzzing Parameters + +* `runs=N`: Number of fuzzing scenarios (default: 20) +* `staking=on|off`: Enable/disable staking (default: on) +* `buybias=N`: 0-100% bias towards buying vs selling (default: 50) +* `trades=N`: Number of trades per scenario (default: 15) +* `stakingbias=N`: 0-100% bias towards staking vs unstaking (default: 80) + +## Advanced Recording & Replay + +The framework can record and replay scenarios that trigger invariant violations. + +* **Record scenarios:** + ```bash + ./analysis/run-recorded-fuzzing.sh BullMarketOptimizer runs=50 + ``` +* **Replay a scenario:** + ```bash + ./analysis/replay-scenario.sh + ``` + +## Optimizer Strategies + +The fuzzing framework can be run with different optimizer strategies: + +* `BullMarketOptimizer`: Aggressive risk-taking. +* `BearMarketOptimizer`: Conservative positioning. +* `NeutralMarketOptimizer`: Balanced approach. +* `WhaleOptimizer`: Large capital movements. +* `ExtremeOptimizer`: Cycles through parameter extremes. +* `MaliciousOptimizer`: Intentionally adversarial parameters. + +# Development Conventions + +* **Code Style:** The code follows standard Solidity style conventions. +* **Testing:** The project uses Foundry for testing. The `test` directory contains the test files. +* **Access Control:** The contracts use a combination of `onlyLiquidityManager` modifiers and owner checks to enforce access control. +* **Important:** Do not modify implementation files like `LiquidityProvider` or `ThreePositionStrategy`. + +## Key Files + +* `test/helpers/UniswapTestBase.sol`: Test helper for Uniswap pool setup. +* `test/helpers/KraikenTestBase.sol`: Common test utilities. +* `lib/uni-v3-lib/`: Uniswap V3 math library. +* `UNISWAP_V3_MATH.md`: Reference for the Uniswap V3 math. \ No newline at end of file diff --git a/onchain/analysis/StreamlinedFuzzing.s.sol b/onchain/analysis/StreamlinedFuzzing.s.sol index 109ad41..84b933b 100644 --- a/onchain/analysis/StreamlinedFuzzing.s.sol +++ b/onchain/analysis/StreamlinedFuzzing.s.sol @@ -111,8 +111,9 @@ contract StreamlinedFuzzing is Script { // Execute trades for (uint256 i = 0; i < tradesPerRun; i++) { - // Check for recenter opportunity every 5 trades - if (i > 0 && i % 5 == 0) { + // Check for recenter opportunity on average every 3 trades + uint256 recenterRand = uint256(keccak256(abi.encodePacked(runIndex, i, "recenter"))) % 3; + if (recenterRand == 0) { _tryRecenter(); } @@ -233,16 +234,13 @@ contract StreamlinedFuzzing is Script { } function _tryRecenter() internal { - uint256 blocksSinceRecenter = block.number - lastRecenterBlock; - if (blocksSinceRecenter > 100) { - vm.warp(block.timestamp + 1 hours); - vm.roll(block.number + 1); // Advance block - vm.prank(fees); - try lm.recenter{gas: 50_000_000}() { - lastRecenterBlock = block.number; - _recordState("RECENTER", 0); - } catch {} - } + vm.warp(block.timestamp + 1 hours); + vm.roll(block.number + 1); // Advance block + vm.prank(fees); + try lm.recenter{gas: 50_000_000}() { + lastRecenterBlock = block.number; + _recordState("RECENTER", 0); + } catch {} } function _getTradeAmount(uint256 runIndex, uint256 tradeIndex, bool isBuy) internal pure returns (uint256) { diff --git a/onchain/analysis/run-visualizer.html b/onchain/analysis/run-visualizer.html index 62eeac4..b9eefe9 100644 --- a/onchain/analysis/run-visualizer.html +++ b/onchain/analysis/run-visualizer.html @@ -505,11 +505,18 @@ let floorEth, anchorEth, discoveryEth; let floorKraiken, anchorKraiken, discoveryKraiken; + // Note: In KRAIKEN protocol, positions are named opposite to their price location: + // - "Floor" position is actually at high ticks (above current price) + // - "Discovery" position is at low ticks (below current price) + // This is counterintuitive but matches the contract implementation + if (token0isWeth) { + // Floor position (high ticks, above current) holds token0 (ETH) when above price floorEth = floorAmounts.amount0 / 1e18; floorKraiken = floorAmounts.amount1 / 1e18; anchorEth = anchorAmounts.amount0 / 1e18; anchorKraiken = anchorAmounts.amount1 / 1e18; + // Discovery position (low ticks, below current) holds token1 (KRAIKEN) when below price discoveryEth = discoveryAmounts.amount0 / 1e18; discoveryKraiken = discoveryAmounts.amount1 / 1e18; } else {