// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import "forge-std/Test.sol"; import {TestEnvironment} from "../test/helpers/TestBase.sol"; import {IUniswapV3Pool} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol"; import {IUniswapV3Factory} from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; import {IWETH9} from "../src/interfaces/IWETH9.sol"; import {Kraiken} from "../src/Kraiken.sol"; import {Stake} from "../src/Stake.sol"; import {LiquidityManager} from "../src/LiquidityManager.sol"; import {ThreePositionStrategy} from "../src/abstracts/ThreePositionStrategy.sol"; import "../test/mocks/BullMarketOptimizer.sol"; import "../test/mocks/WhaleOptimizer.sol"; import "./helpers/CSVManager.sol"; import "./helpers/SwapExecutor.sol"; /** * @title ImprovedFuzzingAnalysis * @notice Enhanced fuzzing with larger trades designed to reach discovery position * @dev Uses more aggressive trading patterns to explore the full liquidity range */ contract ImprovedFuzzingAnalysis is Test, CSVManager { TestEnvironment testEnv; IUniswapV3Factory factory; IUniswapV3Pool pool; IWETH9 weth; Kraiken harberg; Stake stake; LiquidityManager lm; bool token0isWeth; address account = makeAddr("trader"); address whale = makeAddr("whale"); address feeDestination = makeAddr("fees"); // Analysis metrics uint256 public scenariosAnalyzed; uint256 public profitableScenarios; uint256 public discoveryReachedCount; // Configuration uint256 public fuzzingRuns; bool public trackPositions; string public optimizerClass; function run() public virtual { _loadConfiguration(); console.log("=== IMPROVED Fuzzing Analysis ==="); console.log("Designed to reach discovery position with larger trades"); console.log(string.concat("Optimizer: ", optimizerClass)); console.log(string.concat("Fuzzing runs: ", vm.toString(fuzzingRuns))); console.log(""); testEnv = new TestEnvironment(feeDestination); // Get optimizer address optimizerAddress = _getOptimizerByClass(optimizerClass); // Track profitable scenarios string memory profitableCSV = "Scenario,Seed,Initial Balance,Final Balance,Profit,Profit %,Discovery Reached\n"; uint256 profitableCount; for (uint256 seed = 0; seed < fuzzingRuns; seed++) { if (seed % 10 == 0 && seed > 0) { console.log(string.concat("Progress: ", vm.toString(seed), "/", vm.toString(fuzzingRuns))); } // Create fresh environment (factory, pool, weth, harberg, stake, lm,, token0isWeth) = testEnv.setupEnvironmentWithOptimizer(seed % 2 == 0, feeDestination, optimizerAddress); // Fund LiquidityManager with MORE ETH for deeper liquidity vm.deal(address(lm), 200 ether); // Increased from 50 // Fund accounts with MORE capital uint256 traderFund = 50 ether + (uint256(keccak256(abi.encodePacked(seed, "trader"))) % 150 ether); // 50-200 ETH uint256 whaleFund = 200 ether + (uint256(keccak256(abi.encodePacked(seed, "whale"))) % 300 ether); // 200-500 ETH vm.deal(account, traderFund * 2); vm.deal(whale, whaleFund * 2); vm.prank(account); weth.deposit{value: traderFund}(); vm.prank(whale); weth.deposit{value: whaleFund}(); uint256 initialBalance = weth.balanceOf(account); // Initial recenter vm.prank(feeDestination); lm.recenter(); // Initialize position tracking if (trackPositions) { initializePositionsCSV(); _recordPositionData("Initial"); } // Run improved trading scenario (uint256 finalBalance, bool reachedDiscovery) = _runImprovedScenario(seed); scenariosAnalyzed++; if (reachedDiscovery) { discoveryReachedCount++; } // Check profitability if (finalBalance > initialBalance) { uint256 profit = finalBalance - initialBalance; uint256 profitPct = (profit * 100) / initialBalance; profitableScenarios++; console.log(string.concat("PROFITABLE! Seed: ", vm.toString(seed))); console.log(string.concat(" Profit: ", vm.toString(profit / 1e15), " finney (", vm.toString(profitPct), "%)")); console.log(string.concat(" Discovery reached: ", reachedDiscovery ? "YES" : "NO")); profitableCSV = string.concat( profitableCSV, optimizerClass, ",", vm.toString(seed), ",", vm.toString(initialBalance), ",", vm.toString(finalBalance), ",", vm.toString(profit), ",", vm.toString(profitPct), ",", reachedDiscovery ? "true" : "false", "\n" ); profitableCount++; } // Write position CSV if tracking if (trackPositions) { _recordPositionData("Final"); string memory positionFilename = string.concat( "improved_positions_", optimizerClass, "_", vm.toString(seed), ".csv" ); writeCSVToFile(positionFilename); } } // Summary console.log("\n=== ANALYSIS COMPLETE ==="); console.log(string.concat("Total scenarios: ", vm.toString(scenariosAnalyzed))); console.log(string.concat("Profitable scenarios: ", vm.toString(profitableScenarios))); console.log(string.concat("Discovery reached: ", vm.toString(discoveryReachedCount), " times")); console.log(string.concat("Discovery rate: ", vm.toString((discoveryReachedCount * 100) / scenariosAnalyzed), "%")); console.log(string.concat("Profit rate: ", vm.toString((profitableScenarios * 100) / scenariosAnalyzed), "%")); if (profitableCount > 0) { string memory filename = string.concat("improved_profitable_", vm.toString(block.timestamp), ".csv"); vm.writeFile(filename, profitableCSV); console.log(string.concat("\nResults written to: ", filename)); } } function _runImprovedScenario(uint256 seed) internal virtual returns (uint256 finalBalance, bool reachedDiscovery) { uint256 rand = uint256(keccak256(abi.encodePacked(seed, block.timestamp))); // Get initial discovery position (, int24 discoveryLower, int24 discoveryUpper) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY); // Strategy selection based on seed uint256 strategy = seed % 5; if (strategy == 0) { // STRATEGY 1: Massive coordinated sell to push into discovery _executeDiscoveryPush(rand); } else if (strategy == 1) { // STRATEGY 2: Whale manipulation _executeWhaleManipulation(rand); } else if (strategy == 2) { // STRATEGY 3: Volatile swings _executeVolatileSwings(rand); } else if (strategy == 3) { // STRATEGY 4: Sustained pressure _executeSustainedPressure(rand); } else { // STRATEGY 5: Random large trades _executeRandomLargeTrades(rand); } // Check if we reached discovery (, int24 currentTick,,,,,) = pool.slot0(); reachedDiscovery = (currentTick >= discoveryLower && currentTick < discoveryUpper); if (reachedDiscovery) { console.log(" [DISCOVERY REACHED] at tick", vm.toString(currentTick)); if (trackPositions) { _recordPositionData("Discovery_Reached"); } } // Final cleanup: sell all KRAIKEN uint256 finalKraiken = harberg.balanceOf(account); if (finalKraiken > 0) { _executeSell(account, finalKraiken); } finalBalance = weth.balanceOf(account); } function _executeDiscoveryPush(uint256 rand) internal virtual { console.log(" Strategy: Discovery Push"); // Both accounts buy large amounts first uint256 traderBuy = weth.balanceOf(account) * 7 / 10; // 70% of balance uint256 whaleBuy = weth.balanceOf(whale) * 8 / 10; // 80% of balance _executeBuy(account, traderBuy); _executeBuy(whale, whaleBuy); if (trackPositions) { _recordPositionData("MassiveBuy"); } // Now coordinated massive sell to push price down uint256 whaleKraiken = harberg.balanceOf(whale); _executeSell(whale, whaleKraiken); // Whale dumps all if (trackPositions) { _recordPositionData("WhaleDump"); } // Trader tries to profit from the movement uint256 traderKraiken = harberg.balanceOf(account); if (traderKraiken > 0) { // Sell half during crash _executeSell(account, traderKraiken / 2); // Recenter during low price vm.warp(block.timestamp + 1 hours); vm.prank(feeDestination); try lm.recenter() {} catch {} // Buy back at low price uint256 remainingWeth = weth.balanceOf(account); if (remainingWeth > 0) { _executeBuy(account, remainingWeth / 2); } } } function _executeWhaleManipulation(uint256 rand) internal { console.log(" Strategy: Whale Manipulation"); // Whale does large trades to move price significantly for (uint256 i = 0; i < 5; i++) { uint256 action = (rand >> i) % 3; if (action == 0) { // Large buy uint256 buyAmount = weth.balanceOf(whale) / 2; if (buyAmount > 0) { _executeBuy(whale, buyAmount); } } else if (action == 1) { // Large sell uint256 sellAmount = harberg.balanceOf(whale) / 2; if (sellAmount > 0) { _executeSell(whale, sellAmount); } } else { // Trigger recenter vm.warp(block.timestamp + 30 minutes); vm.prank(feeDestination); try lm.recenter() {} catch {} } // Trader tries to follow/counter if (harberg.balanceOf(account) > 0) { _executeSell(account, harberg.balanceOf(account) / 4); } else if (weth.balanceOf(account) > 0) { _executeBuy(account, weth.balanceOf(account) / 4); } if (trackPositions && i % 2 == 0) { _recordPositionData(string.concat("Whale_", vm.toString(i))); } } } function _executeVolatileSwings(uint256 rand) internal { console.log(" Strategy: Volatile Swings"); // Create large price swings for (uint256 i = 0; i < 8; i++) { if (i % 2 == 0) { // Swing down - coordinated sells uint256 traderSell = harberg.balanceOf(account); uint256 whaleSell = harberg.balanceOf(whale); if (traderSell > 0) _executeSell(account, traderSell); if (whaleSell > 0) _executeSell(whale, whaleSell); // If we pushed price low enough, recenter (, int24 tick,,,,,) = pool.slot0(); (, int24 discoveryLower,) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY); if (tick < discoveryLower + 1000) { vm.warp(block.timestamp + 1 hours); vm.prank(feeDestination); try lm.recenter() {} catch {} } } else { // Swing up - coordinated buys uint256 traderBuy = weth.balanceOf(account) * 6 / 10; uint256 whaleBuy = weth.balanceOf(whale) * 7 / 10; if (traderBuy > 0) _executeBuy(account, traderBuy); if (whaleBuy > 0) _executeBuy(whale, whaleBuy); } if (trackPositions) { _recordPositionData(string.concat("Swing_", vm.toString(i))); } } } function _executeSustainedPressure(uint256 rand) internal { console.log(" Strategy: Sustained Sell Pressure"); // First accumulate KRAIKEN _executeBuy(account, weth.balanceOf(account) * 9 / 10); // 90% buy _executeBuy(whale, weth.balanceOf(whale) * 9 / 10); // 90% buy if (trackPositions) { _recordPositionData("Accumulation"); } // Now sustained selling pressure uint256 totalKraiken = harberg.balanceOf(account) + harberg.balanceOf(whale); uint256 sellsPerAccount = 10; uint256 amountPerSell = totalKraiken / (sellsPerAccount * 2); for (uint256 i = 0; i < sellsPerAccount; i++) { // Alternate sells between accounts if (harberg.balanceOf(account) >= amountPerSell) { _executeSell(account, amountPerSell); } if (harberg.balanceOf(whale) >= amountPerSell) { _executeSell(whale, amountPerSell); } // Check if we're approaching discovery (, int24 currentTick,,,,,) = pool.slot0(); (, int24 discoveryUpper,) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY); if (currentTick < discoveryUpper + 500) { console.log(" Approaching discovery, tick:", vm.toString(currentTick)); // Recenter while near discovery vm.warp(block.timestamp + 30 minutes); vm.prank(feeDestination); try lm.recenter() {} catch {} if (trackPositions) { _recordPositionData("Near_Discovery"); } } } } function _executeRandomLargeTrades(uint256 rand) internal { console.log(" Strategy: Random Large Trades"); for (uint256 i = 0; i < 15; i++) { rand = uint256(keccak256(abi.encodePacked(rand, i))); uint256 actor = rand % 2; // 0 = trader, 1 = whale uint256 action = (rand >> 8) % 3; // buy, sell, recenter address actorAddr = actor == 0 ? account : whale; if (action == 0) { // Large buy (30-80% of balance) uint256 buyPct = 30 + (rand % 51); uint256 buyAmount = weth.balanceOf(actorAddr) * buyPct / 100; if (buyAmount > 0) { _executeBuy(actorAddr, buyAmount); } } else if (action == 1) { // Large sell (30-100% of KRAIKEN) uint256 sellPct = 30 + (rand % 71); uint256 sellAmount = harberg.balanceOf(actorAddr) * sellPct / 100; if (sellAmount > 0) { _executeSell(actorAddr, sellAmount); } } else { // Recenter vm.warp(block.timestamp + (rand % 2 hours)); vm.prank(feeDestination); try lm.recenter() {} catch {} } if (trackPositions && i % 3 == 0) { _recordPositionData(string.concat("Random_", vm.toString(i))); } } } function _executeBuy(address buyer, uint256 amount) internal virtual { if (amount == 0 || weth.balanceOf(buyer) < amount) return; SwapExecutor executor = new SwapExecutor(pool, weth, harberg, token0isWeth); vm.prank(buyer); weth.transfer(address(executor), amount); try executor.executeBuy(amount, buyer) {} catch {} } function _executeSell(address seller, uint256 amount) internal virtual { if (amount == 0 || harberg.balanceOf(seller) < amount) return; SwapExecutor executor = new SwapExecutor(pool, weth, harberg, token0isWeth); vm.prank(seller); harberg.transfer(address(executor), amount); try executor.executeSell(amount, seller) {} catch {} } function _getOptimizerByClass(string memory class) internal returns (address) { if (keccak256(bytes(class)) == keccak256("BullMarketOptimizer")) { return address(new BullMarketOptimizer()); } else if (keccak256(bytes(class)) == keccak256("WhaleOptimizer")) { return address(new WhaleOptimizer()); } else { return address(new BullMarketOptimizer()); } } function _loadConfiguration() internal { fuzzingRuns = vm.envOr("FUZZING_RUNS", uint256(20)); trackPositions = vm.envOr("TRACK_POSITIONS", false); optimizerClass = vm.envOr("OPTIMIZER_CLASS", string("BullMarketOptimizer")); } function _recordPositionData(string memory label) internal { (,int24 currentTick,,,,,) = pool.slot0(); (uint128 floorLiq, int24 floorLower, int24 floorUpper) = lm.positions(ThreePositionStrategy.Stage.FLOOR); (uint128 anchorLiq, int24 anchorLower, int24 anchorUpper) = lm.positions(ThreePositionStrategy.Stage.ANCHOR); (uint128 discoveryLiq, int24 discoveryLower, int24 discoveryUpper) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY); string memory row = string.concat( label, ",", vm.toString(currentTick), ",", vm.toString(floorLower), ",", vm.toString(floorUpper), ",", vm.toString(floorLiq), ",", vm.toString(anchorLower), ",", vm.toString(anchorUpper), ",", vm.toString(anchorLiq), ",", vm.toString(discoveryLower), ",", vm.toString(discoveryUpper), ",", vm.toString(discoveryLiq), ",", token0isWeth ? "true" : "false" ); appendCSVRow(row); } }