// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; /** * @title Simple Scenario Analysis for LiquidityManagerV2 * @notice Lightweight analysis script for researching profitable trading scenarios * @dev Separated from unit tests to focus on research and scenario discovery * Uses the new modular LiquidityManagerV2 architecture for analysis * Run with: forge script analysis/SimpleAnalysis.s.sol --ffi */ import "../test/LiquidityManager.t.sol"; import "../test/mocks/MockOptimizer.sol"; import "../test/helpers/CSVManager.sol"; contract SimpleAnalysis is LiquidityManagerTest, CSVManager { uint256 public scenariosAnalyzed; uint256 public profitableScenarios; // Market condition scenarios for sentiment analysis struct SentimentScenario { uint256 capitalInefficiency; uint256 anchorShare; uint24 anchorWidth; uint256 discoveryDepth; string description; } struct ScenarioResults { uint256 totalScenarios; uint256 profitableScenarios; uint256 totalProfit; uint256 maxProfit; uint256 avgProfit; } /// @notice Entry point for forge script execution function run() public { console.log("Starting LiquidityManagerV2 Market Condition Analysis..."); console.log("This will analyze trading scenarios across different sentiment conditions."); // Run parameter validation analysis first runParameterValidationAnalysis(); // Then run sentiment fuzzing analysis runSentimentFuzzingAnalysis(); console.log("Market condition analysis complete."); } /// @notice Simple parameter validation without complex trading function runParameterValidationAnalysis() public { console.log("\\n=== PARAMETER VALIDATION ANALYSIS ==="); // Test 3 key sentiment scenarios SentimentScenario memory bullMarket = SentimentScenario({ capitalInefficiency: 2 * 10 ** 17, // 20% - aggressive anchorShare: 8 * 10 ** 17, // 80% - large anchor anchorWidth: 30, // narrow width discoveryDepth: 9 * 10 ** 17, // 90% - deep discovery description: "Bull Market (High Risk)" }); SentimentScenario memory neutralMarket = SentimentScenario({ capitalInefficiency: 5 * 10 ** 17, // 50% - balanced anchorShare: 5 * 10 ** 17, // 50% - balanced anchor anchorWidth: 50, // standard width discoveryDepth: 5 * 10 ** 17, // 50% - balanced discovery description: "Neutral Market (Balanced)" }); SentimentScenario memory bearMarket = SentimentScenario({ capitalInefficiency: 8 * 10 ** 17, // 80% - conservative anchorShare: 2 * 10 ** 17, // 20% - small anchor anchorWidth: 80, // wide width discoveryDepth: 2 * 10 ** 17, // 20% - shallow discovery description: "Bear Market (Low Risk)" }); // Test parameter configuration and basic recenter testParameterConfiguration(bullMarket); testParameterConfiguration(neutralMarket); testParameterConfiguration(bearMarket); } /// @notice Test parameter configuration and basic functionality function testParameterConfiguration(SentimentScenario memory scenario) internal { console.log("\\nTesting:", scenario.description); console.log("Capital Inefficiency:", scenario.capitalInefficiency * 100 / 1e18, "%"); console.log("Anchor Share:", scenario.anchorShare * 100 / 1e18, "%"); console.log("Anchor Width:", scenario.anchorWidth); console.log("Discovery Depth:", scenario.discoveryDepth * 100 / 1e18, "%"); // Configure MockOptimizer with sentiment parameters MockOptimizer mockOptimizer = MockOptimizer(address(optimizer)); mockOptimizer.setLiquidityParams( scenario.capitalInefficiency, scenario.anchorShare, scenario.anchorWidth, scenario.discoveryDepth ); // Verify parameters were set correctly (uint256 capIneff, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) = mockOptimizer.getLiquidityParams(); bool parametersCorrect = ( capIneff == scenario.capitalInefficiency && anchorShare == scenario.anchorShare && anchorWidth == scenario.anchorWidth && discoveryDepth == scenario.discoveryDepth ); console.log("Parameters configured correctly:", parametersCorrect); // Test a simple recenter to see if positions are created with new parameters try lm.recenter() returns (bool isUp) { console.log("Recenter successful, price moved:", isUp ? "UP" : "DOWN"); // Check position allocation using Stage enum (uint128 floorLiq,,) = lm.positions(LiquidityManager.Stage.FLOOR); (uint128 anchorLiq,,) = lm.positions(LiquidityManager.Stage.ANCHOR); (uint128 discoveryLiq,,) = lm.positions(LiquidityManager.Stage.DISCOVERY); console.log("Position liquidity created:"); console.log("Floor:", floorLiq > 0 ? "YES" : "NO"); console.log("Anchor:", anchorLiq > 0 ? "YES" : "NO"); console.log("Discovery:", discoveryLiq > 0 ? "YES" : "NO"); } catch Error(string memory reason) { console.log("Recenter failed:", reason); } catch { console.log("Recenter failed with unknown error"); } console.log("---"); } /// @notice Run fuzzing analysis with different sentiment configurations function runSentimentFuzzingAnalysis() public { console.log("\\n=== SENTIMENT FUZZING ANALYSIS ==="); console.log("Testing for profitable trading opportunities under different market conditions..."); // Test scenarios with small trade amounts to avoid slippage limits uint8[] memory amounts = new uint8[](6); amounts[0] = 10; amounts[1] = 15; amounts[2] = 20; amounts[3] = 25; amounts[4] = 12; amounts[5] = 18; // Test the three key scenarios with fuzzing console.log("\\n--- FUZZING BULL MARKET (Expected: Profitable) ---"); SentimentScenario memory bullMarket = SentimentScenario({ capitalInefficiency: 2 * 10 ** 17, // 20% - aggressive anchorShare: 8 * 10 ** 17, // 80% - large anchor anchorWidth: 30, // narrow width discoveryDepth: 9 * 10 ** 17, // 90% - deep discovery description: "Bull Market Fuzzing" }); runSentimentFuzzing(bullMarket, amounts); console.log("\\n--- FUZZING NEUTRAL MARKET (Expected: Some Profitable) ---"); SentimentScenario memory neutralMarket = SentimentScenario({ capitalInefficiency: 5 * 10 ** 17, // 50% - balanced anchorShare: 5 * 10 ** 17, // 50% - balanced anchor anchorWidth: 50, // standard width discoveryDepth: 5 * 10 ** 17, // 50% - balanced discovery description: "Neutral Market Fuzzing" }); runSentimentFuzzing(neutralMarket, amounts); console.log("\\n--- FUZZING BEAR MARKET (Expected: Minimal Profitable) ---"); SentimentScenario memory bearMarket = SentimentScenario({ capitalInefficiency: 8 * 10 ** 17, // 80% - conservative anchorShare: 2 * 10 ** 17, // 20% - small anchor anchorWidth: 80, // wide width discoveryDepth: 2 * 10 ** 17, // 20% - shallow discovery description: "Bear Market Fuzzing" }); runSentimentFuzzing(bearMarket, amounts); } /// @notice Run fuzzing for a specific sentiment scenario function runSentimentFuzzing(SentimentScenario memory scenario, uint8[] memory amounts) internal { console.log("Testing:", scenario.description); console.log("Capital Inefficiency:", scenario.capitalInefficiency * 100 / 1e18, "%"); // Configure sentiment parameters MockOptimizer mockOptimizer = MockOptimizer(address(optimizer)); mockOptimizer.setLiquidityParams( scenario.capitalInefficiency, scenario.anchorShare, scenario.anchorWidth, scenario.discoveryDepth ); uint256 totalTests = 0; uint256 profitableTests = 0; uint256 totalProfit = 0; uint256 maxProfit = 0; // Test different trading patterns for (uint8 numActions = 3; numActions <= 7; numActions += 2) { for (uint8 frequency = 2; frequency <= 4; frequency++) { totalTests++; uint256 profit = runFuzzingSequence(numActions, frequency, amounts); if (profit > 0) { profitableTests++; totalProfit += profit; if (profit > maxProfit) { maxProfit = profit; } console.log("PROFITABLE - Actions:", numActions); console.log("Frequency:", frequency); console.log("Profit:", profit); } } } // Calculate percentage uint256 profitablePercentage = totalTests > 0 ? (profitableTests * 100) / totalTests : 0; console.log("Results:"); console.log("Total tests:", totalTests); console.log("Profitable:", profitableTests); console.log("Percentage:", profitablePercentage, "%"); console.log("Max profit:", maxProfit); console.log("Total profit:", totalProfit); // Alert on high profitability (potential vulnerability) if (profitablePercentage > 30) { console.log("[ALERT] High profitability detected! Potential vulnerability in", scenario.description); } else if (profitablePercentage > 10) { console.log("[WARNING] Moderate profitability detected in", scenario.description); } else { console.log("[SAFE] Low profitability - good protection in", scenario.description); } console.log("---"); } /// @notice Run a fuzzing sequence with small trades to avoid slippage limits function runFuzzingSequence(uint8 numActions, uint8 frequency, uint8[] memory amounts) internal returns (uint256 profit) { // Reset account with modest balance to avoid large swaps vm.deal(account, 100 ether); vm.prank(account); weth.deposit{value: 20 ether}(); uint256 balanceBefore = weth.balanceOf(account); // Execute smaller trading sequence uint8 f = 0; for (uint i = 0; i < numActions && i < amounts.length; i++) { // Scale down amounts to avoid slippage protection uint256 amount = (uint256(amounts[i]) * 0.1 ether) / 10; // Much smaller amounts uint256 harbergBal = harberg.balanceOf(account); // Execute trade based on current balances (skip error handling for now) if (harbergBal == 0) { uint256 wethBal = weth.balanceOf(account); if (wethBal > 1 ether) { amount = amount % (wethBal / 10); // Use only 10% of balance amount = amount == 0 ? wethBal / 100 : amount; // Minimum 1% if (amount > 0.01 ether && amount < wethBal) { buy(amount); } } } else if (weth.balanceOf(account) < 0.1 ether) { // Sell some HARB to get WETH uint256 sellAmount = amount % (harbergBal / 10); if (sellAmount > 0) { sell(sellAmount); } } else { // Random choice if (amount % 2 == 0) { uint256 wethBal = weth.balanceOf(account); amount = amount % (wethBal / 20); // Even smaller portion if (amount > 0.005 ether && amount < wethBal) { buy(amount); } } else { uint256 sellAmount = amount % (harbergBal / 20); if (sellAmount > 0) { sell(sellAmount); } } } // Periodic recentering if (f >= frequency) { recenter(false); f = 0; } else { f++; } } // Final cleanup uint256 finalHarbBal = harberg.balanceOf(account); if (finalHarbBal > 0) { sell(finalHarbBal); } recenter(true); uint256 balanceAfter = weth.balanceOf(account); // Calculate profit if (balanceAfter > balanceBefore) { profit = balanceAfter - balanceBefore; } else { profit = 0; } return profit; } /// @notice Simplified analysis with fewer scenarios to avoid setup retries function runSimplifiedMarketAnalysis() public { console.log("\\n=== SIMPLIFIED MARKET CONDITION ANALYSIS ==="); // Test trading sequences uint8[] memory amounts = new uint8[](5); amounts[0] = 100; amounts[1] = 75; amounts[2] = 90; amounts[3] = 60; amounts[4] = 80; // Test 3 key scenarios only console.log("\\n--- KEY MARKET SCENARIOS ---"); SentimentScenario memory bullMarket = SentimentScenario({ capitalInefficiency: 2 * 10 ** 17, // 20% - aggressive anchorShare: 8 * 10 ** 17, // 80% - large anchor anchorWidth: 30, // narrow width discoveryDepth: 9 * 10 ** 17, // 90% - deep discovery description: "Bull Market (High Risk)" }); SentimentScenario memory neutralMarket = SentimentScenario({ capitalInefficiency: 5 * 10 ** 17, // 50% - balanced anchorShare: 5 * 10 ** 17, // 50% - balanced anchor anchorWidth: 50, // standard width discoveryDepth: 5 * 10 ** 17, // 50% - balanced discovery description: "Neutral Market (Balanced)" }); SentimentScenario memory bearMarket = SentimentScenario({ capitalInefficiency: 8 * 10 ** 17, // 80% - conservative anchorShare: 2 * 10 ** 17, // 20% - small anchor anchorWidth: 80, // wide width discoveryDepth: 2 * 10 ** 17, // 20% - shallow discovery description: "Bear Market (Low Risk)" }); // Test each scenario with reduced iterations testSimplifiedScenario(bullMarket, amounts); testSimplifiedScenario(neutralMarket, amounts); testSimplifiedScenario(bearMarket, amounts); } /// @notice Test a simplified scenario with minimal iterations function testSimplifiedScenario(SentimentScenario memory scenario, uint8[] memory amounts) internal { console.log("Testing:", scenario.description); console.log("Capital Inefficiency:", scenario.capitalInefficiency * 100 / 1e18, "%"); console.log("Anchor Share:", scenario.anchorShare * 100 / 1e18, "%"); // Configure sentiment once MockOptimizer mockOptimizer = MockOptimizer(address(optimizer)); mockOptimizer.setLiquidityParams( scenario.capitalInefficiency, scenario.anchorShare, scenario.anchorWidth, scenario.discoveryDepth ); uint256 totalProfit = 0; uint256 profitableCount = 0; // Test only 2 scenarios to minimize setup calls for (uint8 i = 0; i < 2; i++) { uint8 numActions = 5 + (i * 3); // 5, 8 uint8 frequency = 3 + i; // 3, 4 uint256 profit = runSingleTest(numActions, frequency, amounts); if (profit > 0) { profitableCount++; totalProfit += profit; console.log("Profitable scenario found - Actions:", numActions, "Profit:", profit); } } console.log("Results: 2 tests,", profitableCount, "profitable, total profit:", totalProfit); if (profitableCount > 0) { console.log("[ALERT] Profitable scenarios detected!"); } console.log("---"); } /// @notice Run a single test without setup changes function runSingleTest(uint8 numActions, uint8 frequency, uint8[] memory amounts) internal returns (uint256 profit) { // Reset account balance vm.deal(account, 300 ether); vm.prank(account); weth.deposit{value: 50 ether}(); uint256 balanceBefore = weth.balanceOf(account); // Execute trading sequence _executeRandomTradingSequenceWrapper(numActions, frequency, amounts); uint256 balanceAfter = weth.balanceOf(account); // Calculate profit if (balanceAfter > balanceBefore) { profit = balanceAfter - balanceBefore; } else { profit = 0; } return profit; } /// @notice Analyze profitability across different market conditions function runMarketConditionMatrix() public { console.log("\\n=== MARKET CONDITION MATRIX ANALYSIS ==="); // Test trading sequences uint8[] memory amounts = new uint8[](10); amounts[0] = 100; amounts[1] = 50; amounts[2] = 75; amounts[3] = 120; amounts[4] = 30; amounts[5] = 90; amounts[6] = 45; amounts[7] = 110; amounts[8] = 60; amounts[9] = 80; // Bull Market Scenarios (Low Sentiment = High Risk) console.log("\\n--- BULL MARKET CONDITIONS ---"); SentimentScenario memory extremeBull = SentimentScenario({ capitalInefficiency: 1 * 10 ** 17, // 10% - very aggressive anchorShare: 9 * 10 ** 17, // 90% - maximum anchor anchorWidth: 20, // narrow width discoveryDepth: 95 * 10 ** 16, // 95% - maximum discovery description: "Extreme Bull (Maximum Risk)" }); SentimentScenario memory moderateBull = SentimentScenario({ capitalInefficiency: 25 * 10 ** 16, // 25% - aggressive anchorShare: 75 * 10 ** 16, // 75% - large anchor anchorWidth: 30, // moderately narrow discoveryDepth: 8 * 10 ** 17, // 80% - deep discovery description: "Moderate Bull (High Risk)" }); // Neutral Market Scenarios (Medium Sentiment = Balanced Risk) console.log("\\n--- NEUTRAL MARKET CONDITIONS ---"); SentimentScenario memory neutralBalanced = SentimentScenario({ capitalInefficiency: 5 * 10 ** 17, // 50% - balanced anchorShare: 5 * 10 ** 17, // 50% - balanced anchor anchorWidth: 50, // standard width discoveryDepth: 5 * 10 ** 17, // 50% - balanced discovery description: "Neutral Market (Balanced Risk)" }); SentimentScenario memory neutralConservative = SentimentScenario({ capitalInefficiency: 6 * 10 ** 17, // 60% - slightly conservative anchorShare: 4 * 10 ** 17, // 40% - smaller anchor anchorWidth: 60, // wider width discoveryDepth: 4 * 10 ** 17, // 40% - moderate discovery description: "Neutral Conservative (Medium Risk)" }); // Bear Market Scenarios (High Sentiment = Low Risk) console.log("\\n--- BEAR MARKET CONDITIONS ---"); SentimentScenario memory moderateBear = SentimentScenario({ capitalInefficiency: 8 * 10 ** 17, // 80% - conservative anchorShare: 2 * 10 ** 17, // 20% - small anchor anchorWidth: 80, // wide width discoveryDepth: 2 * 10 ** 17, // 20% - shallow discovery description: "Moderate Bear (Low Risk)" }); SentimentScenario memory extremeBear = SentimentScenario({ capitalInefficiency: 95 * 10 ** 16, // 95% - maximum conservative anchorShare: 5 * 10 ** 16, // 5% - minimal anchor anchorWidth: 100, // maximum width discoveryDepth: 5 * 10 ** 16, // 5% - minimal discovery description: "Extreme Bear (Minimum Risk)" }); // Run analysis for each scenario testSentimentScenario(extremeBull, amounts); testSentimentScenario(moderateBull, amounts); testSentimentScenario(neutralBalanced, amounts); testSentimentScenario(neutralConservative, amounts); testSentimentScenario(moderateBear, amounts); testSentimentScenario(extremeBear, amounts); } /// @notice Test a specific sentiment scenario function testSentimentScenario(SentimentScenario memory scenario, uint8[] memory amounts) internal { console.log("Testing:", scenario.description); console.log("Capital Inefficiency:", scenario.capitalInefficiency * 100 / 1e18, "%"); console.log("Anchor Share:", scenario.anchorShare * 100 / 1e18, "%"); console.log("Anchor Width:", scenario.anchorWidth); console.log("Discovery Depth:", scenario.discoveryDepth * 100 / 1e18, "%"); ScenarioResults memory results = ScenarioResults({ totalScenarios: 0, profitableScenarios: 0, totalProfit: 0, maxProfit: 0, avgProfit: 0 }); // Test fewer scenarios to avoid setup issues for (uint8 numActions = 5; numActions <= 10; numActions += 5) { for (uint8 frequency = 3; frequency <= 5; frequency += 2) { results.totalScenarios++; uint256 profit = runSentimentAnalysis(scenario, numActions, frequency, amounts); if (profit > 0) { results.profitableScenarios++; results.totalProfit += profit; if (profit > results.maxProfit) { results.maxProfit = profit; } } } } // Calculate average profit if (results.profitableScenarios > 0) { results.avgProfit = results.totalProfit / results.profitableScenarios; } // Log results console.log("Results - Total:", results.totalScenarios); console.log("Profitable:", results.profitableScenarios); console.log("Max Profit:", results.maxProfit); console.log("Avg Profit:", results.avgProfit); // Warning for high profitability if (results.profitableScenarios > results.totalScenarios / 2) { console.log("[ALERT] High profitability detected - potential vulnerability!"); } console.log("---"); } /// @notice Run analysis with specific sentiment parameters function runSentimentAnalysis( SentimentScenario memory scenario, uint8 numActions, uint8 frequency, uint8[] memory amounts ) internal returns (uint256 profit) { // Configure MockOptimizer with sentiment parameters MockOptimizer mockOptimizer = MockOptimizer(address(optimizer)); mockOptimizer.setLiquidityParams( scenario.capitalInefficiency, scenario.anchorShare, scenario.anchorWidth, scenario.discoveryDepth ); // Reset account balance for consistent testing vm.deal(account, 300 ether); vm.prank(account); weth.deposit{value: 50 ether}(); uint256 balanceBefore = weth.balanceOf(account); // Execute trading sequence _executeRandomTradingSequenceWrapper(numActions, frequency, amounts); uint256 balanceAfter = weth.balanceOf(account); // Calculate profit if (balanceAfter > balanceBefore) { profit = balanceAfter - balanceBefore; } else { profit = 0; } return profit; } /// @notice Analyzes a trading scenario for profitability /// @dev Records CSV data if profitable - THIS IS NOT A UNIT TEST function runAnalysis(uint8 numActions, uint8 frequency, uint8[] memory amounts) public { // Bound inputs vm.assume(numActions > 3 && numActions <= 50); vm.assume(frequency > 0 && frequency < 20); vm.assume(amounts.length >= numActions); // Setup _setupCustom(false, 50 ether); uint256 balanceBefore = weth.balanceOf(account); // Execute trading sequence (need to convert memory to calldata) _executeRandomTradingSequenceWrapper(numActions, frequency, amounts); uint256 balanceAfter = weth.balanceOf(account); scenariosAnalyzed++; // Check profitability if (balanceAfter > balanceBefore) { profitableScenarios++; uint256 profit = balanceAfter - balanceBefore; console.log("[ALERT] Profitable scenario found!"); console.log("Profit:", vm.toString(profit)); console.log("Actions:", numActions); console.log("Frequency:", frequency); // Write CSV for analysis to analysis folder writeCSVToFile("./analysis/profitable_scenario.csv"); } console.log("Scenario", scenariosAnalyzed, balanceAfter > balanceBefore ? "PROFIT" : "SAFE"); } /// @notice Wrapper to handle memory to calldata conversion function _executeRandomTradingSequenceWrapper(uint8 numActions, uint8 frequency, uint8[] memory amounts) internal { // Create a simple trading sequence without the complex calldata dependency uint8 f = 0; for (uint i = 0; i < numActions && i < amounts.length; i++) { uint256 amount = (uint256(amounts[i]) * 1 ether) + 1 ether; uint256 harbergBal = harberg.balanceOf(account); // Execute trade based on current balances if (harbergBal == 0) { amount = amount % (weth.balanceOf(account) / 2); amount = amount == 0 ? weth.balanceOf(account) / 10 : amount; if (amount > 0) buy(amount); } else if (weth.balanceOf(account) == 0) { sell(amount % harbergBal); } else { if (amount % 2 == 0) { amount = amount % (weth.balanceOf(account) / 2); amount = amount == 0 ? weth.balanceOf(account) / 10 : amount; if (amount > 0) buy(amount); } else { sell(amount % harbergBal); } } // Periodic recentering if (f >= frequency) { recenter(false); f = 0; } else { f++; } } // Final cleanup uint256 finalHarbBal = harberg.balanceOf(account); if (finalHarbBal > 0) { sell(finalHarbBal); } recenter(true); } /// @notice Get analysis statistics function getStats() public view returns (uint256 total, uint256 profitable) { return (scenariosAnalyzed, profitableScenarios); } }