From c5f0323df7740dd9a539eaf83373f68c8fe5dfca Mon Sep 17 00:00:00 2001 From: giteadmin Date: Thu, 17 Jul 2025 21:35:18 +0200 Subject: [PATCH] fixed tests --- onchain/LIQUIDITY_MANAGER_REFACTORING.md | 200 --------------------- onchain/TEST_REFACTORING_SUMMARY.md | 218 ----------------------- onchain/VWAP_TEST_GAPS.md | 120 ------------- onchain/test/LiquidityManager.t.sol | 114 +++--------- onchain/test/helpers/CSVHelper.sol | 2 +- onchain/test/helpers/UniswapTestBase.sol | 128 ++++++++++++- 6 files changed, 151 insertions(+), 631 deletions(-) delete mode 100644 onchain/LIQUIDITY_MANAGER_REFACTORING.md delete mode 100644 onchain/TEST_REFACTORING_SUMMARY.md delete mode 100644 onchain/VWAP_TEST_GAPS.md diff --git a/onchain/LIQUIDITY_MANAGER_REFACTORING.md b/onchain/LIQUIDITY_MANAGER_REFACTORING.md deleted file mode 100644 index f59a832..0000000 --- a/onchain/LIQUIDITY_MANAGER_REFACTORING.md +++ /dev/null @@ -1,200 +0,0 @@ -# LiquidityManager Refactoring Analysis - -## Executive Summary - -The original `LiquidityManager.sol` (439 lines) has been successfully refactored into a modular architecture with 4 separate contracts totaling ~600 lines. This improves maintainability, testability, and separation of concerns while preserving all original functionality. - -## Identified Issues in Original Code - -### 1. **Single Responsibility Principle Violations** -- **Mathematical utilities** mixed with business logic -- **Oracle validation** embedded in main contract -- **Position strategy** tightly coupled with implementation details -- **Fee collection** intertwined with position management - -### 2. **Testing and Maintenance Challenges** -- **116-line `_set()` function** difficult to test individual position logic -- **Mathematical functions** cannot be tested in isolation -- **Price validation** logic cannot be unit tested separately -- **No clear boundaries** between different responsibilities - -### 3. **Code Reusability Issues** -- **Price/tick utilities** could benefit other contracts -- **Oracle logic** useful for other price-sensitive contracts -- **Position strategy** could be adapted for different tokens - -## Refactoring Solution - -### **Modular Architecture Overview** - -``` -LiquidityManagerV2 -├── inherits from ThreePositionStrategy (anti-arbitrage logic) -│ ├── inherits from UniswapMath (mathematical utilities) -│ └── inherits from VWAPTracker (dormant whale protection) -└── inherits from PriceOracle (TWAP validation) -``` - -### **1. UniswapMath.sol (Mathematical Utilities)** -```solidity -abstract contract UniswapMath { - function _tickAtPrice(bool t0isWeth, uint256 tokenAmount, uint256 ethAmount) internal pure returns (int24); - function _tickAtPriceRatio(int128 priceRatioX64) internal pure returns (int24); - function _priceAtTick(int24 tick) internal pure returns (uint256); - function _clampToTickSpacing(int24 tick, int24 spacing) internal pure returns (int24); -} -``` - -**Benefits:** -- ✅ **Pure functions** easily unit testable -- ✅ **Reusable** across multiple contracts -- ✅ **Gas efficient** (no state variables) -- ✅ **Clear responsibility** (mathematical operations only) - -### **2. PriceOracle.sol (TWAP Validation)** -```solidity -abstract contract PriceOracle { - function _isPriceStable(int24 currentTick) internal view returns (bool); - function _validatePriceMovement(int24 currentTick, int24 centerTick, int24 tickSpacing, bool token0isWeth) - internal pure returns (bool isUp, bool isEnough); -} -``` - -**Benefits:** -- ✅ **Isolated oracle logic** for independent testing -- ✅ **Configurable parameters** (intervals, deviations) -- ✅ **Reusable** for other price-sensitive contracts -- ✅ **Clear error handling** for oracle failures - -### **3. ThreePositionStrategy.sol (Anti-Arbitrage Strategy)** -```solidity -abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker { - function _setPositions(int24 currentTick, PositionParams memory params) internal; - function _setAnchorPosition(...) internal returns (uint256 pulledHarb); - function _setDiscoveryPosition(...) internal returns (uint256 discoveryAmount); - function _setFloorPosition(...) internal; -} -``` - -**Benefits:** -- ✅ **Separated position logic** for individual testing -- ✅ **Clear dependencies** (ANCHOR → DISCOVERY → FLOOR) -- ✅ **Economic rationale** documented in each function -- ✅ **VWAP exclusivity** clearly implemented (only floor position) - -### **4. LiquidityManagerV2.sol (Main Contract)** -```solidity -contract LiquidityManagerV2 is ThreePositionStrategy, PriceOracle { - // Focused on: - // - Uniswap V3 integration (callbacks, minting) - // - Access control and fee management - // - Orchestration of inherited functionality -} -``` - -## Key Improvements - -### **1. Separation of Concerns** -| Responsibility | Original Location | New Location | -|----------------|------------------|--------------| -| Mathematical utilities | Mixed throughout | `UniswapMath.sol` | -| Oracle validation | `_isPriceStable()` | `PriceOracle.sol` | -| Position strategy | 116-line `_set()` | `ThreePositionStrategy.sol` | -| Uniswap integration | Main contract | `LiquidityManagerV2.sol` | - -### **2. Enhanced Testability** -- ✅ **Unit test** mathematical functions in isolation -- ✅ **Mock** oracle responses for price validation testing -- ✅ **Test** individual position strategies (anchor, discovery, floor) -- ✅ **Validate** position dependencies separately - -### **3. Improved Maintainability** -- ✅ **Clear boundaries** between different concerns -- ✅ **Smaller functions** easier to understand and modify -- ✅ **Documented dependencies** between position types -- ✅ **Reusable components** for future development - -### **4. Preserved Functionality** -- ✅ **Identical behavior** to original contract -- ✅ **Same gas efficiency** (abstract contracts have no overhead) -- ✅ **All events and errors** preserved -- ✅ **Complete API compatibility** - -## Testing Strategy for Refactored Version - -### **1. Unit Tests by Component** -```solidity -// UniswapMathTest.sol - Test mathematical utilities -function testTickAtPrice() { /* Test price → tick conversion */ } -function testPriceAtTick() { /* Test tick → price conversion */ } -function testClampToTickSpacing() { /* Test tick normalization */ } - -// PriceOracleTest.sol - Test oracle validation -function testPriceStabilityValidation() { /* Mock oracle responses */ } -function testPriceMovementValidation() { /* Test movement detection */ } - -// ThreePositionStrategyTest.sol - Test position logic -function testAnchorPositionSetting() { /* Test shallow liquidity */ } -function testDiscoveryPositionDependency() { /* Test pulledHarb dependency */ } -function testFloorPositionVWAPUsage() { /* Test VWAP exclusivity */ } -``` - -### **2. Integration Tests** -```solidity -// LiquidityManagerV2Test.sol - Test full integration -function testAntiArbitrageStrategyValidation() { /* Reuse existing test */ } -function testRecenteringOrchestration() { /* Test component coordination */ } -``` - -## Migration Path - -### **Option 1: Gradual Migration** -1. Deploy new modular contracts alongside existing ones -2. Test extensively with same parameters -3. Gradually migrate functionality -4. Deprecate original contract - -### **Option 2: Direct Replacement** -1. Comprehensive testing of refactored version -2. Deploy as upgrade/replacement -3. Maintain identical interface for existing integrations - -## Performance Analysis - -### **Gas Usage Comparison** -- ✅ **No overhead** from abstract contracts (compiled away) -- ✅ **Same function calls** (inheritance is compile-time) -- ✅ **Potential savings** from better optimization opportunities -- ✅ **Identical storage layout** (same state variables) - -### **Code Size Comparison** -| Metric | Original | Refactored | Change | -|--------|----------|------------|--------| -| Main contract lines | 439 | 267 | -39% | -| Total lines | 439 | ~600 | +37% | -| Functions per file | 20+ | 5-8 | Better organization | -| Testable units | 1 | 4 | +400% | - -## Recommendations - -### **Immediate Actions** -1. ✅ **Deploy and test** refactored version -2. ✅ **Create comprehensive test suite** for each component -3. ✅ **Validate gas usage** against original contract -4. ✅ **Document migration strategy** - -### **Future Enhancements** -1. **Library conversion**: Convert `UniswapMath` to library for even better reusability -2. **Interface extraction**: Create interfaces for position strategies -3. **Plugin architecture**: Allow swappable position strategies -4. **Enhanced monitoring**: Add more granular events per component - -## Conclusion - -The refactored `LiquidityManagerV2` maintains 100% functional compatibility while providing: -- **Better separation of concerns** for maintainability -- **Enhanced testability** for quality assurance -- **Improved reusability** for future development -- **Clearer documentation** of anti-arbitrage strategy - -This modular architecture makes the sophisticated three-position anti-arbitrage strategy more understandable and maintainable while preserving the proven economic protection mechanisms. \ No newline at end of file diff --git a/onchain/TEST_REFACTORING_SUMMARY.md b/onchain/TEST_REFACTORING_SUMMARY.md deleted file mode 100644 index a91e6ee..0000000 --- a/onchain/TEST_REFACTORING_SUMMARY.md +++ /dev/null @@ -1,218 +0,0 @@ -# Test Refactoring Summary - -## Overview - -Successfully refactored the LiquidityManager test suite to support the new modular architecture while maintaining all existing functionality and adding comprehensive unit tests for individual components. - -## Test Files Created - -### 1. **Unit Tests for Modular Components** - -#### `/test/libraries/UniswapMath.t.sol` ✅ -- **15 comprehensive tests** for mathematical utilities -- **Tick/price conversion functions** tested in isolation -- **Boundary conditions** and **edge cases** covered -- **Fuzz testing** for robustness validation -- **Round-trip conversions** verified - -**Key Tests:** -- `testTickAtPriceBasic()` - Basic price to tick conversion -- `testPriceAtTickSymmetry()` - Validates mathematical reciprocals -- `testClampToTickSpacing()` - Tick alignment and boundary checking -- `testFuzzTickAtPrice()` - Comprehensive input validation - -#### `/test/abstracts/PriceOracle.t.sol` ✅ -- **15+ tests** for TWAP oracle validation -- **Mock Uniswap pool** for isolated testing -- **Price stability scenarios** with various deviations -- **Price movement validation** for different token orderings -- **Oracle failure fallback** behavior testing - -**Key Tests:** -- `testPriceStableWithinDeviation()` - Oracle validation logic -- `testPriceMovementWethToken0Up()` - Token ordering edge cases -- `testPriceStabilityOracleFailureFallback()` - Error handling - -#### `/test/abstracts/ThreePositionStrategy.t.sol` ✅ -- **20+ tests** for position strategy logic -- **Position dependencies** (ANCHOR → DISCOVERY → FLOOR) validated -- **VWAP exclusivity** for floor position confirmed -- **Asymmetric slippage profile** architecture tested -- **Mock implementation** for isolated component testing - -**Key Tests:** -- `testAnchorPositionSymmetricAroundCurrentTick()` - Shallow liquidity validation -- `testDiscoveryPositionDependsOnAnchor()` - Dependency verification -- `testFloorPositionUsesVWAP()` - VWAP exclusivity validation -- `testSetPositionsAsymmetricProfile()` - Anti-arbitrage architecture - -### 2. **Integration Tests** - -#### `/test/ModularComponentsTest.t.sol` ✅ -- **Compilation verification** for all modular components -- **Basic functionality testing** of integrated architecture -- **Proof that refactoring maintains compatibility** - -### 3. **Analysis Scripts Updated** - -#### `/analysis/SimpleAnalysis.s.sol` ✅ -- **Updated to reference** modular architecture -- **Documentation updated** to reflect new components -- **Analysis capabilities preserved** for security research - -#### `/analysis/README.md` ✅ -- **Comprehensive documentation** of analysis suite capabilities -- **Updated references** to LiquidityManagerV2 architecture -- **Enhanced coverage** of modular component interactions - -## Test Results Validation - -### ✅ **All Core Tests Pass** -```bash -forge test --match-test testModularArchitectureCompiles -forge test --match-test testUniswapMathCompilation -forge test --match-test testTickAtPriceBasic -forge test --match-test testAntiArbitrageStrategyValidation -``` - -### ✅ **Modular Architecture Verified** -- **Mathematical utilities** work independently -- **Price oracle logic** functions in isolation -- **Position strategy** maintains economic dependencies -- **Anti-arbitrage protection** preserved in original tests - -### ✅ **Compatibility Maintained** -- **Existing anti-arbitrage test** still passes (80% slippage protection) -- **Original functionality** completely preserved -- **Test architecture** supports both original and modular versions - -## Benefits Achieved - -### **1. Enhanced Test Coverage** -| Component | Original | Refactored | Improvement | -|-----------|----------|------------|-------------| -| Mathematical utilities | Embedded | 15 unit tests | Isolated testing | -| Oracle logic | Integrated | 15+ unit tests | Mock-based testing | -| Position strategy | Monolithic | 20+ unit tests | Component validation | -| **Total test granularity** | **Low** | **High** | **+300% coverage** | - -### **2. Improved Debugging** -- **Unit test failures** pinpoint exact component issues -- **Mock implementations** allow isolated problem diagnosis -- **Mathematical errors** can be caught without full integration -- **Oracle issues** testable without blockchain interaction - -### **3. Development Velocity** -- **Fast unit tests** for individual components (milliseconds) -- **Slower integration tests** only when needed -- **Component changes** can be validated independently -- **Regression testing** more targeted and efficient - -### **4. Documentation Through Tests** -- **Component boundaries** clearly defined through test structure -- **Dependencies** explicitly tested and documented -- **Economic logic** validated at component level -- **Anti-arbitrage strategy** broken down into testable parts - -## Test Architecture Design - -### **Inheritance Hierarchy** -``` -LiquidityManagerTest (original) -├── Contains full integration tests -├── Anti-arbitrage validation test ✅ -└── Supports both original and modular contracts - -ModularComponentsTest -├── Quick validation of architecture -└── Compilation and basic functionality tests - -Component-Specific Tests -├── UniswapMathTest (mathematical utilities) -├── PriceOracleTest (TWAP validation) -└── ThreePositionStrategyTest (position logic) -``` - -### **Test Separation Strategy** -- **Unit tests** for individual components (fast, isolated) -- **Integration tests** for full system behavior (comprehensive) -- **Compatibility tests** ensuring refactoring doesn't break functionality -- **Performance tests** validating no gas overhead from modular design - -## Migration Path for Development - -### **Current State** -- ✅ **Original LiquidityManager** fully tested and functional -- ✅ **Modular LiquidityManagerV2** compiles and basic functions work -- ✅ **Unit tests** provide comprehensive component coverage -- ✅ **Analysis scripts** updated for new architecture - -### **Next Steps for Full Integration** -1. **Complete LiquidityManagerV2 integration tests** - - Create comprehensive test suite that exercises full contract - - Validate gas usage equivalent to original - - Confirm identical behavior across all scenarios - -2. **Production deployment preparation** - - Extensive testing on testnets - - Security audit of modular architecture - - Performance benchmarking vs original contract - -3. **Gradual migration strategy** - - Deploy modular version alongside original - - Comparative testing in production conditions - - Gradual migration of functionality - -## Key Insights from Refactoring - -### **1. Component Boundaries Well-Defined** -The refactoring revealed clear separation of concerns: -- **Mathematical utilities** are pure functions (no state) -- **Oracle logic** depends only on external data -- **Position strategy** has clear input/output boundaries -- **Main contract** orchestrates components effectively - -### **2. Anti-Arbitrage Strategy More Understandable** -Breaking down the complex `_set()` function into component parts: -- **ANCHOR position** creates shallow liquidity (high slippage) -- **DISCOVERY position** depends on anchor minting -- **FLOOR position** uses VWAP for historical memory -- **Dependencies** follow economic logic precisely - -### **3. Testing Strategy More Effective** -- **Component tests** catch issues early in development -- **Integration tests** validate system behavior -- **Mock implementations** enable isolated debugging -- **Fuzz testing** more targeted and effective - -## Validation of Refactoring Success - -### ✅ **Functional Equivalence** -- Original anti-arbitrage test still passes -- Mathematical calculations identical -- Position creation logic preserved -- Event emission maintained - -### ✅ **Architectural Improvement** -- Clear component boundaries -- Enhanced testability -- Better code organization -- Maintained performance - -### ✅ **Development Experience** -- Faster test execution for components -- Clearer error messages and debugging -- Better documentation through test structure -- Enhanced maintainability - -## Conclusion - -The test refactoring successfully demonstrates that the modular LiquidityManagerV2 architecture: - -1. **✅ Maintains 100% functional compatibility** with the original design -2. **✅ Provides significantly enhanced testability** through component isolation -3. **✅ Improves code organization** without sacrificing performance -4. **✅ Enables better debugging and maintenance** through clear boundaries -5. **✅ Preserves the proven anti-arbitrage protection** mechanism - -The comprehensive test suite provides confidence that the modular architecture can be safely deployed as a drop-in replacement for the original LiquidityManager while providing substantial benefits for ongoing development and maintenance. \ No newline at end of file diff --git a/onchain/VWAP_TEST_GAPS.md b/onchain/VWAP_TEST_GAPS.md deleted file mode 100644 index fd5d245..0000000 --- a/onchain/VWAP_TEST_GAPS.md +++ /dev/null @@ -1,120 +0,0 @@ -# VWAP Test Gaps Analysis - Post Dormant Whale Fix - -## Executive Summary -With the dormant whale protection implementation, comprehensive VWAPTracker test suite, and anti-arbitrage strategy validation, the most critical security gaps have been addressed. Remaining gaps are lower-priority LiquidityManager integration tests. - -## Context: Anti-Arbitrage Strategy -**Trade-Recenter-Reverse Attack**: Trader profits by exploiting predictable rebalancing through: large trade → trigger recenter() → reverse trade at new configuration. - -**Protection**: Asymmetric slippage profile where ANCHOR (shallow liquidity, high slippage) forces attackers through expensive slippage twice, while FLOOR/DISCOVERY (deep liquidity) provide edge protection. - -**Key Architectural Principle**: Only FLOOR uses VWAP (historical memory), while ANCHOR/DISCOVERY use current tick (immediate response). - ---- - -## VWAPTracker Tests - COMPREHENSIVE ✅ - -### Core VWAP Logic ✅ -- ✅ **Basic VWAP calculation** (`testSinglePriceRecording`, `testMultiplePriceRecording`) -- ✅ **Volume recording from fees** (`testSinglePriceRecording` - validates `volume = fee * 100`) -- ✅ **Multi-trade accumulation** (`testConcreteMultipleRecordings` - mathematical precision) -- ✅ **70% discount + capital inefficiency** (`testAdjustedVWAPCalculation`) -- ✅ **Zero volume edge cases** (`testZeroVolumeHandling`, `testInitialState`) - -### Security & Edge Cases ✅ -- ✅ **Dormant whale protection** (`testDormantWhaleProtection` - core security) -- ✅ **Overflow handling** (`testOverflowHandling`, `testOverflowCompressionRatio`) -- ✅ **Compression preserves history** (prevents whale manipulation) -- ✅ **Extreme values** (`testLargeButSafeValues`, `testMinimalValues`) -- ✅ **Fuzz testing** (`testFuzzVWAPCalculation` - random inputs) - -### Capital Inefficiency ✅ -- ✅ **Zero capital inefficiency** (`testAdjustedVWAPWithZeroCapitalInefficiency`) -- ✅ **Max capital inefficiency** (`testAdjustedVWAPWithMaxCapitalInefficiency`) -- ✅ **Variable impact testing** (`testAdjustedVWAPCalculation`) - -**Result**: VWAPTracker has comprehensive test coverage. No gaps remain. - ---- - -## LiquidityManager Integration Tests - -### Priority: HIGH - -#### 1. Anti-Arbitrage Strategy Validation ✅ -**Status**: COMPLETED -**Goal**: Prove asymmetric slippage profile protects against trade-recenter-reverse attacks -**Details**: Verified ANCHOR (shallow) vs FLOOR/DISCOVERY (deep) liquidity creates expensive round-trip slippage -**Test Location**: `test/LiquidityManager.t.sol::testAntiArbitrageStrategyValidation()` -**Results**: 80% slippage loss, 17% anchor ratio, validates protection mechanism -**Priority Justification**: Core protection mechanism against sophisticated arbitrage - -#### 2. Position Dependency Order Test -**Status**: MISSING -**Goal**: Verify _set() function order: ANCHOR → DISCOVERY → FLOOR with correct dependencies -**Details**: Test that discovery amount depends on anchor's pulledHarb, floor depends on final outstanding supply after all minting -**Test Location**: Should be in `test/LiquidityManager.t.sol` -**Priority Justification**: Validates economic logic behind position ordering - -#### 3. Floor Position VWAP Exclusivity Test -**Status**: MISSING -**Goal**: Prove only floor position uses VWAP, anchor/discovery use current tick -**Details**: Critical architectural verification - ensure VWAP only affects floor positioning for historical memory -**Test Location**: Should be in `test/LiquidityManager.t.sol` -**Priority Justification**: Validates separation of historical (floor) vs immediate (anchor/discovery) price responses - -#### 4. EthScarcity vs EthAbundance Scenarios -**Status**: MISSING -**Goal**: Test EthScarcity vs EthAbundance event emission and VWAP application logic -**Details**: Verify different VWAP usage when ETH reserves < vs > required buyback amount -**Test Location**: Should be in `test/LiquidityManager.t.sol` - -### Priority: MEDIUM - -#### 5. Floor Position Discount Verification -**Status**: PARTIALLY COVERED -**Goal**: Verify floor position pricing uses adjusted VWAP (70% + capital inefficiency) -**Details**: Current `testVWAPIntegrationValidation` touches this but needs dedicated precision testing. -**Gap**: Need exact discount formula verification in isolation. - -### Priority: LOW - -#### 6. Cross-Position Independence -**Status**: MISSING -**Goal**: Verify anchor/discovery positions unaffected by VWAP changes -**Details**: Ensure VWAP compression/changes don't affect non-floor positions. -**Note**: Low priority as architecture makes this unlikely to break. - -## Completed ✅ - -### ✅ Anti-Arbitrage Strategy Validation -**Status**: Completed -**Goal**: Prove asymmetric slippage profile protects against trade-recenter-reverse attacks -**File**: `test/LiquidityManager.t.sol::testAntiArbitrageStrategyValidation()` -**Results**: 80% round-trip slippage loss, 17% anchor liquidity ratio, validates protection mechanism -**What it tests**: Trade-recenter-reverse attack simulation, asymmetric liquidity profile validation, economic attack deterrent - -### ✅ VWAP Integration Validation -**Status**: Completed -**Goal**: Validate VWAP system stability and cross-component behavior -**File**: `test/LiquidityManager.t.sol::testVWAPIntegrationValidation()` -**What it tests**: System stability, reasonable value ranges, floor positioning differences, cumulative data accumulation - ---- - -## Notes for Implementation - -- Current test file: `/home/ubuntu/dev/harb/onchain/test/LiquidityManager.t.sol` -- Next recommended task: **Position Dependency Order Test** (highest remaining priority) -- Key insight discovered: Floor positioned at tick -176700 vs current -113852 shows 62k tick difference, confirming VWAP-based positioning -- Token ordering: HARB is token0 in test setup (`DEFAULT_TOKEN0_IS_WETH = false`) -- **_set() Function Analysis**: Position order ANCHOR → DISCOVERY → FLOOR reflects economic dependencies for anti-arbitrage protection - -## Test Architecture Pattern - -Each test should: -1. Use `_setupCustom()` for custom scenarios -2. Include proper assertions (not just console.log) -3. Test specific functionality, not general integration -4. Consider token ordering implications (HARB/WETH vs WETH/HARB) -5. Verify economic logic makes sense \ No newline at end of file diff --git a/onchain/test/LiquidityManager.t.sol b/onchain/test/LiquidityManager.t.sol index b1603bf..061f84e 100644 --- a/onchain/test/LiquidityManager.t.sol +++ b/onchain/test/LiquidityManager.t.sol @@ -31,16 +31,23 @@ import "../test/mocks/MockOptimizer.sol"; uint24 constant FEE = uint24(10_000); // 1% fee int24 constant TICK_SPACING = 200; int24 constant ANCHOR_SPACING = 5 * TICK_SPACING; -int24 constant EXTREME_PRICE_MARGIN = 12000; // Safety margin from tick boundaries - // Time constants uint256 constant ORACLE_UPDATE_INTERVAL = 5 hours; - -// Trading constants -uint256 constant NORMALIZATION_SELL_PERCENTAGE = 100; // 1% -uint256 constant NORMALIZATION_BUY_AMOUNT = 0.01 ether; uint256 constant BALANCE_DIVISOR = 2; uint256 constant MIN_TRADE_AMOUNT = 1 ether; +uint256 constant FALLBACK_TRADE_DIVISOR = 10; + +// Test bounds constants +uint8 constant MIN_FUZZ_ACTIONS = 5; +uint8 constant MAX_FUZZ_ACTIONS = 50; +uint8 constant MIN_FUZZ_FREQUENCY = 1; +uint8 constant MAX_FUZZ_FREQUENCY = 20; + +// Test setup constants +uint256 constant INITIAL_LM_ETH_BALANCE = 10 ether; +uint256 constant OVERFLOW_TEST_BALANCE = 201 ether; +uint256 constant FUZZ_TEST_BALANCE = 20 ether; +uint256 constant VWAP_TEST_BALANCE = 100 ether; // Error handling constants bytes32 constant AMPLITUDE_ERROR = keccak256("amplitude not reached."); @@ -154,7 +161,7 @@ contract LiquidityManagerTest is UniswapTestBase { harberg.setStakingPool(address(stake)); vm.prank(feeDestination); harberg.setLiquidityManager(address(lm)); - vm.deal(address(lm), 10 ether); + vm.deal(address(lm), INITIAL_LM_ETH_BALANCE); } /// @notice Intelligent recenter function that handles extreme price conditions @@ -173,15 +180,8 @@ contract LiquidityManagerTest is UniswapTestBase { /// @notice Handles extreme price conditions with normalizing swaps function _handleExtremePrice() internal { - (, int24 currentTick,,,,,) = pool.slot0(); - - if (_isExtremelyExpensive(currentTick)) { - console.log("Detected extremely expensive HARB, performing normalizing swap..."); - _performNormalizingSwap(currentTick, true); - } else if (_isExtremelyCheap(currentTick)) { - console.log("Detected extremely cheap HARB, performing normalizing swap..."); - _performNormalizingSwap(currentTick, false); - } + // Use the unified extreme price handling from UniswapTestBase + handleExtremePrice(); } /// @notice Attempts the recenter operation with proper error handling @@ -194,19 +194,6 @@ contract LiquidityManagerTest is UniswapTestBase { } } - /// @notice Checks if HARB price is extremely expensive - /// @param currentTick The current price tick - /// @return True if HARB is extremely expensive - function _isExtremelyExpensive(int24 currentTick) internal pure returns (bool) { - return currentTick >= TickMath.MAX_TICK - EXTREME_PRICE_MARGIN; - } - - /// @notice Checks if HARB price is extremely cheap - /// @param currentTick The current price tick - /// @return True if HARB is extremely cheap - function _isExtremelyCheap(int24 currentTick) internal pure returns (bool) { - return currentTick <= TickMath.MIN_TICK + EXTREME_PRICE_MARGIN; - } /// @notice Validates recenter operation results /// @param isUp Whether the recenter moved positions up or down @@ -248,55 +235,6 @@ contract LiquidityManagerTest is UniswapTestBase { } } - /// @notice Performs a normalizing swap to bring extreme prices back to manageable levels - /// @param currentTick The current tick position - /// @param isExpensive True if HARB is extremely expensive, false if extremely cheap - function _performNormalizingSwap(int24 currentTick, bool isExpensive) internal { - console.log("Current tick before normalization:", vm.toString(currentTick)); - - if (isExpensive) { - // HARB is extremely expensive - we need to bring the price DOWN - // This means we need to SELL HARB for ETH (not buy HARB with ETH) - - // Get HARB balance from account (who has been buying) to use for normalization - uint256 accountHarbBalance = harberg.balanceOf(account); - if (accountHarbBalance > 0) { - uint256 harbToSell = accountHarbBalance / NORMALIZATION_SELL_PERCENTAGE; // Sell 1% of account's HARB balance - if (harbToSell == 0) harbToSell = 1; // Minimum 1 wei - - vm.prank(account); - harberg.transfer(address(this), harbToSell); - - console.log("Performing normalizing swap: selling", vm.toString(harbToSell), "HARB to bring price down"); - - // Approve for swap - harberg.approve(address(pool), harbToSell); - - // Swap should work - if it doesn't, there's a fundamental problem - performSwap(harbToSell, false); // false = selling HARB for ETH - } else { - console.log("No HARB balance available for normalization"); - } - } else { - // HARB is extremely cheap - we need to bring the price UP - // This means we need to BUY HARB with ETH (not sell HARB) - uint256 ethToBuy = NORMALIZATION_BUY_AMOUNT; // Small amount for price normalization - - // Ensure we have enough ETH - if (weth.balanceOf(address(this)) < ethToBuy) { - vm.deal(address(this), ethToBuy); - weth.deposit{value: ethToBuy}(); - } - - console.log("Performing normalizing swap: buying HARB with", vm.toString(ethToBuy), "ETH to bring price up"); - performSwap(ethToBuy, true); // true = buying HARB with ETH - } - - // Check the new price - (, int24 newTick,,,,,) = pool.slot0(); - console.log("New tick after normalization:", vm.toString(newTick)); - console.log("Price change:", vm.toString(newTick - currentTick), "ticks"); - } /// @notice Retrieves liquidity position information for a specific stage /// @param s The liquidity stage (FLOOR, ANCHOR, DISCOVERY) @@ -411,7 +349,7 @@ contract LiquidityManagerTest is UniswapTestBase { /// @notice Tests overflow handling in cumulative calculations /// @dev Simulates extreme values that could cause arithmetic overflow function testHandleCumulativeOverflow() public { - _setupCustom(false, 201 ether); + _setupCustom(false, OVERFLOW_TEST_BALANCE); vm.store(address(lm), bytes32(uint256(0)), bytes32(uint256(type(uint256).max - 10))); @@ -507,7 +445,7 @@ contract LiquidityManagerTest is UniswapTestBase { // Test client-side detection and normalization console.log("\n=== PHASE 2: Test client-side normalization ==="); - if (postBuyTick >= TickMath.MAX_TICK - EXTREME_PRICE_MARGIN) { + if (postBuyTick >= TickMath.MAX_TICK - 15000) { console.log("[SUCCESS] Successfully pushed to extreme expensive range"); console.log("[SUCCESS] Client-side detection should trigger normalization swap"); } else { @@ -740,9 +678,9 @@ contract LiquidityManagerTest is UniswapTestBase { // Diagnose the scenario type console.log("\n=== SCENARIO DIAGNOSIS ==="); - if (postBuyTick >= TickMath.MAX_TICK - EXTREME_PRICE_MARGIN) { + if (postBuyTick >= TickMath.MAX_TICK - 15000) { console.log("[DIAGNOSIS] EXTREME EXPENSIVE HARB - should trigger normalization"); - } else if (postBuyTick <= TickMath.MIN_TICK + EXTREME_PRICE_MARGIN) { + } else if (postBuyTick <= TickMath.MIN_TICK + 15000) { console.log("[DIAGNOSIS] EXTREME CHEAP HARB - potential protocol death"); } else { console.log("[DIAGNOSIS] NORMAL RANGE - may still have arithmetic issues"); @@ -796,7 +734,7 @@ contract LiquidityManagerTest is UniswapTestBase { function _calculateBuyAmount(uint256 baseAmount) internal view returns (uint256) { uint256 wethBalance = weth.balanceOf(account); uint256 amount = baseAmount % (wethBalance / BALANCE_DIVISOR); - return amount == 0 ? wethBalance / 10 : amount; + return amount == 0 ? wethBalance / FALLBACK_TRADE_DIVISOR : amount; } // ======================================== @@ -810,11 +748,11 @@ contract LiquidityManagerTest is UniswapTestBase { /// @param frequency How often to trigger recentering operations /// @param amounts Array of trade amounts to use (bounded automatically) function testFuzzRobustness(uint8 numActions, uint8 frequency, uint8[] calldata amounts) public { - vm.assume(numActions > 5 && numActions < 50); // Reasonable bounds for unit testing - vm.assume(frequency > 0 && frequency < 20); + vm.assume(numActions > MIN_FUZZ_ACTIONS && numActions < MAX_FUZZ_ACTIONS); // Reasonable bounds for unit testing + vm.assume(frequency > MIN_FUZZ_FREQUENCY && frequency < MAX_FUZZ_FREQUENCY); vm.assume(amounts.length >= numActions); - _setupCustom(numActions % 2 == 0 ? true : false, 20 ether); + _setupCustom(numActions % 2 == 0 ? true : false, FUZZ_TEST_BALANCE); uint256 traderBalanceBefore = weth.balanceOf(account); @@ -879,7 +817,7 @@ contract LiquidityManagerTest is UniswapTestBase { /// @dev Validates VWAP accumulation, floor positioning, and system stability across trading sequences function testVWAPIntegrationValidation() public { // Setup with known initial conditions - _setupCustom(false, 100 ether); + _setupCustom(false, VWAP_TEST_BALANCE); // Record initial state - should be zero volume assertEq(lm.cumulativeVolumeWeightedPriceX96(), 0, "Initial VWAP should be zero"); @@ -1006,7 +944,7 @@ contract LiquidityManagerTest is UniswapTestBase { /// @notice Tests the asymmetric slippage profile that protects against trade-recenter-reverse attacks /// @dev Validates that ANCHOR (shallow) vs FLOOR/DISCOVERY (deep) liquidity creates expensive round-trip slippage function testAntiArbitrageStrategyValidation() public { - _setupCustom(false, 100 ether); // HARB is token0, large balance for meaningful slippage testing + _setupCustom(false, VWAP_TEST_BALANCE); // HARB is token0, large balance for meaningful slippage testing // Phase 1: Record initial state and execute first large trade (, int24 initialTick,,,,,) = pool.slot0(); diff --git a/onchain/test/helpers/CSVHelper.sol b/onchain/test/helpers/CSVHelper.sol index 55583b0..85071a9 100644 --- a/onchain/test/helpers/CSVHelper.sol +++ b/onchain/test/helpers/CSVHelper.sol @@ -12,7 +12,7 @@ library CSVHelper { */ function createPositionsHeader() internal pure returns (string memory) { return - "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb"; + "precedingAction, currentTick, floorTickLower, floorTickUpper, floorEth, floorHarb, anchorTickLower, anchorTickUpper, anchorEth, anchorHarb, discoveryTickLower, discoveryTickUpper, discoveryEth, discoveryHarb, token0isWeth"; } function createTimeSeriesHeader() internal pure returns (string memory) { diff --git a/onchain/test/helpers/UniswapTestBase.sol b/onchain/test/helpers/UniswapTestBase.sol index 071046a..5356a0c 100644 --- a/onchain/test/helpers/UniswapTestBase.sol +++ b/onchain/test/helpers/UniswapTestBase.sol @@ -43,27 +43,73 @@ abstract contract UniswapTestBase is Test { if (zeroForOne) { // Swapping token0 for token1 - price goes down // sqrtPriceLimitX96 must be less than current price but greater than MIN_SQRT_RATIO - limit = TickMath.MIN_SQRT_RATIO + 1; + uint160 minAllowedLimit = TickMath.MIN_SQRT_RATIO + 1; + if (currentSqrtPrice <= minAllowedLimit + PRICE_LIMIT_BUFFER) { + // If we're very close to the min, use the absolute minimum + limit = minAllowedLimit; + } else { + // Use a limit that's reasonably below current price to avoid SPL + // Set limit to be halfway between MIN_SQRT_RATIO and current price + limit = minAllowedLimit + (currentSqrtPrice - minAllowedLimit) / 2; + } } else { // Swapping token1 for token0 - price goes up // sqrtPriceLimitX96 must be greater than current price but less than MAX_SQRT_RATIO - // At extreme high prices, we need to be more conservative with the limit uint160 maxAllowedLimit = TickMath.MAX_SQRT_RATIO - 1; - if (currentSqrtPrice >= maxAllowedLimit - 1000) { + if (currentSqrtPrice >= maxAllowedLimit - PRICE_LIMIT_BUFFER) { // If we're very close to the max, use a more conservative limit limit = currentSqrtPrice + (maxAllowedLimit - currentSqrtPrice) / 2; } else { - limit = maxAllowedLimit; + // Use a limit that's reasonably above current price to avoid SPL + // Set limit to be halfway between current price and MAX_SQRT_RATIO + limit = currentSqrtPrice + (maxAllowedLimit - currentSqrtPrice) / 2; } } pool.swap(account, zeroForOne, int256(amount), limit, abi.encode(account, int256(amount), isBuy)); } + /** + * @notice Performs a swap with aggressive price limits for extreme price normalization + * @param amount The amount to swap + * @param isBuy True if buying HARB, false if selling HARB + */ + function performSwapWithAggressiveLimits(uint256 amount, bool isBuy) internal { + uint160 limit; + // Determine the swap direction + bool zeroForOne = isBuy ? token0isWeth : !token0isWeth; + + if (isBuy) { + vm.prank(account); + weth.transfer(address(this), amount); + } else { + vm.prank(account); + harberg.approve(address(this), amount); + } + + // Set aggressive price limits that allow price to move to liquidity ranges + if (zeroForOne) { + // Swapping token0 for token1 - price goes down + // Use very aggressive limit close to MIN_SQRT_RATIO + limit = TickMath.MIN_SQRT_RATIO + 1; + } else { + // Swapping token1 for token0 - price goes up + // Use very aggressive limit close to MAX_SQRT_RATIO + limit = TickMath.MAX_SQRT_RATIO - 1; + } + + pool.swap(account, zeroForOne, int256(amount), limit, abi.encode(account, int256(amount), isBuy)); + } + /** * @dev The Uniswap V3 swap callback. */ function uniswapV3SwapCallback(int256 amount0Delta, int256 amount1Delta, bytes calldata _data) external { + // Handle the case where no swap occurred (both deltas are 0) + if (amount0Delta == 0 && amount1Delta == 0) { + return; + } + require(amount0Delta > 0 || amount1Delta > 0); (address seller,, bool isBuy) = abi.decode(_data, (address, uint256, bool)); @@ -99,4 +145,78 @@ abstract contract UniswapTestBase is Test { weth.transfer(msg.sender, amount1Owed); } } + + // ======================================== + // EXTREME PRICE HANDLING + // ======================================== + + // Safety margin to prevent tick boundary violations (conservative approach) + int24 constant TICK_BOUNDARY_SAFETY_MARGIN = 15000; + + // Price normalization constants + uint256 constant NORMALIZATION_HARB_PERCENTAGE = 100; // 1% of HARB balance + uint256 constant NORMALIZATION_ETH_AMOUNT = 0.01 ether; // Fixed ETH amount for normalization + uint256 constant MAX_NORMALIZATION_ATTEMPTS = 3; // Prevent infinite loops + uint256 constant PRICE_LIMIT_BUFFER = 1000; // Buffer from sqrt price limits + + /** + * @notice Handles extreme price conditions by executing normalizing trades + * @dev This function should be called before any recenter operation to ensure + * the price is within safe boundaries for liquidity position creation + */ + function handleExtremePrice() internal { + uint256 attempts = 0; + + while (attempts < MAX_NORMALIZATION_ATTEMPTS) { + (, int24 currentTick,,,,,) = pool.slot0(); + + if (currentTick >= TickMath.MAX_TICK - TICK_BOUNDARY_SAFETY_MARGIN) { + _executeNormalizingTrade(true); // Move price down + attempts++; + } else if (currentTick <= TickMath.MIN_TICK + TICK_BOUNDARY_SAFETY_MARGIN) { + _executeNormalizingTrade(false); // Move price up + attempts++; + } else { + // Price is now safe, exit loop + break; + } + } + } + + + /** + * @notice Executes a small trade to move price away from tick boundaries + * @param moveDown True to move price down (sell HARB), false to move price up (buy HARB) + */ + function _executeNormalizingTrade(bool moveDown) internal { + if (moveDown) { + // Need to move price DOWN (reduce HARB price) + // This means: sell HARB for ETH (increase HARB supply in pool) + uint256 harbBalance = harberg.balanceOf(account); + if (harbBalance > 0) { + // Use 1% of account's HARB balance (conservative approach like original) + uint256 harbToSell = harbBalance / NORMALIZATION_HARB_PERCENTAGE; + if (harbToSell == 0) harbToSell = 1; + + vm.prank(account); + harberg.transfer(address(this), harbToSell); + harberg.approve(address(pool), harbToSell); + + // Sell HARB for ETH with aggressive price limits for normalization + performSwapWithAggressiveLimits(harbToSell, false); + } + } else { + // Need to move price UP (increase HARB price) + // This means: buy HARB with ETH (reduce HARB supply in pool) + uint256 ethBalance = weth.balanceOf(account); + if (ethBalance > 0) { + // Use small amount for normalization (like original) + uint256 ethToBuy = NORMALIZATION_ETH_AMOUNT; + if (ethToBuy > ethBalance) ethToBuy = ethBalance; + + // Buy HARB with ETH with aggressive price limits for normalization + performSwapWithAggressiveLimits(ethToBuy, true); + } + } + } }