// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import "./ImprovedFuzzingAnalysis.s.sol"; import "./helpers/ScenarioRecorder.sol"; /** * @title RecordedFuzzingAnalysis * @notice Enhanced fuzzing that records profitable scenarios for exact replay * @dev Captures all actions, states, and parameters when invariants fail */ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis { ScenarioRecorder public recorder; bool public enableRecording = true; string public runId; // Unique identifier for this run // Store actions for current scenario struct ActionRecord { uint256 step; string actionType; address actor; uint256 amount; uint256 timestamp; uint256 blockNumber; int24 tickBefore; int24 tickAfter; } ActionRecord[] currentActions; function run() public override { // Get run ID from environment or generate default runId = vm.envOr("RUN_ID", string("LOCAL")); console.log("=== RECORDED Fuzzing Analysis ==="); console.log(string.concat("Run ID: ", runId)); console.log("Recording enabled for profitable scenario replay"); super.run(); } function _runImprovedScenario(uint256 seed) internal override returns (uint256 finalBalance, bool reachedDiscovery) { // Initialize recorder for this scenario if (enableRecording) { recorder = new ScenarioRecorder(); recorder.initializeScenario( seed, optimizerClass, 200 ether, // LM initial ETH weth.balanceOf(account) + weth.balanceOf(whale), // Total trader ETH token0isWeth, 10000 // Pool fee ); delete currentActions; // Clear previous scenario } // Record initial state _recordState("INITIAL", address(0), 0); // Run the scenario with recording (finalBalance, reachedDiscovery) = super._runImprovedScenario(seed); // Record final state _recordState("FINAL", address(0), 0); // If profitable, export the recording uint256 initialBalance = 50 ether; // Approximate initial if (finalBalance > initialBalance && enableRecording) { _exportScenario(seed, initialBalance, finalBalance, reachedDiscovery); } return (finalBalance, reachedDiscovery); } // Override trade execution to record actions function _executeBuy(address buyer, uint256 amount) internal override { if (amount == 0 || weth.balanceOf(buyer) < amount) return; (, int24 tickBefore,,,,,) = pool.slot0(); // Record pre-state if (enableRecording) { _recordState("PRE_BUY", buyer, amount); recorder.recordBuy(buyer, amount, 0, 0); } // Execute trade super._executeBuy(buyer, amount); (, int24 tickAfter,,,,,) = pool.slot0(); // Record post-state and action if (enableRecording) { _recordState("POST_BUY", buyer, amount); _recordAction("BUY", buyer, amount, tickBefore, tickAfter); } } function _executeSell(address seller, uint256 amount) internal override { if (amount == 0 || harberg.balanceOf(seller) < amount) return; (, int24 tickBefore,,,,,) = pool.slot0(); // Record pre-state if (enableRecording) { _recordState("PRE_SELL", seller, amount); recorder.recordSell(seller, amount, 0, 0); } // Execute trade super._executeSell(seller, amount); (, int24 tickAfter,,,,,) = pool.slot0(); // Record post-state and action if (enableRecording) { _recordState("POST_SELL", seller, amount); _recordAction("SELL", seller, amount, tickBefore, tickAfter); } } // Override each strategy to add recenter recording function _executeDiscoveryPush(uint256 rand) internal override { console.log(" Strategy: Discovery Push [RECORDING]"); // Both accounts buy large amounts first uint256 traderBuy = weth.balanceOf(account) * 7 / 10; uint256 whaleBuy = weth.balanceOf(whale) * 8 / 10; _executeBuy(account, traderBuy); _executeBuy(whale, whaleBuy); // Record position snapshot if (trackPositions) { _recordPositionData("MassiveBuy"); } // Whale dumps uint256 whaleKraiken = harberg.balanceOf(whale); _executeSell(whale, whaleKraiken); if (trackPositions) { _recordPositionData("WhaleDump"); } // Trader actions uint256 traderKraiken = harberg.balanceOf(account); if (traderKraiken > 0) { _executeSell(account, traderKraiken / 2); // Recenter _executeRecenter(); // Buy back uint256 remainingWeth = weth.balanceOf(account); if (remainingWeth > 0) { _executeBuy(account, remainingWeth / 2); } } } function _executeRecenter() internal { (, int24 tickBefore,,,,,) = pool.slot0(); if (enableRecording) { _recordState("PRE_RECENTER", feeDestination, 0); recorder.recordRecenter(feeDestination, tx.gasprice); } vm.warp(block.timestamp + 1 hours); vm.prank(feeDestination); try lm.recenter() {} catch {} (, int24 tickAfter,,,,,) = pool.slot0(); if (enableRecording) { _recordState("POST_RECENTER", feeDestination, 0); _recordAction("RECENTER", feeDestination, 0, tickBefore, tickAfter); } } function _recordState(string memory label, address actor, uint256 amount) internal { if (!enableRecording) return; (uint160 sqrtPriceX96, int24 currentTick,,,,,) = pool.slot0(); recorder.recordPreState( weth.balanceOf(account), harberg.balanceOf(account), currentTick, uint256(sqrtPriceX96), 0, // VWAP placeholder harberg.outstandingSupply() ); // Also record optimizer params if during recenter if (keccak256(bytes(label)) == keccak256("POST_RECENTER")) { // Get optimizer params (simplified - would need actual values) recorder.recordOptimizerParams( 5e17, // capitalInefficiency placeholder 5e17, // anchorShare placeholder 50, // anchorWidth placeholder 5e17 // discoveryDepth placeholder ); } } function _recordAction( string memory actionType, address actor, uint256 amount, int24 tickBefore, int24 tickAfter ) internal { currentActions.push(ActionRecord({ step: currentActions.length, actionType: actionType, actor: actor, amount: amount, timestamp: block.timestamp, blockNumber: block.number, tickBefore: tickBefore, tickAfter: tickAfter })); } function _exportScenario( uint256 seed, uint256 initialBalance, uint256 finalBalance, bool reachedDiscovery ) internal { console.log("\n[RECORDING] Exporting profitable scenario..."); console.log(string.concat(" Run ID: ", runId)); console.log(string.concat(" Seed: ", vm.toString(seed))); // Export as JSON string memory json = recorder.exportToJson(); string memory jsonFilename = string.concat( "recorded_scenario_seed", vm.toString(seed), ".json" ); vm.writeFile(jsonFilename, json); console.log(string.concat(" JSON exported to: ", jsonFilename)); // Also export simplified replay script string memory replayScript = _generateReplayScript(seed); string memory scriptFilename = string.concat( "replay_script_seed", vm.toString(seed), ".sol" ); vm.writeFile(scriptFilename, replayScript); console.log(string.concat(" Replay script exported to: ", scriptFilename)); // Export action summary with Run ID string memory summary = _generateActionSummary( seed, initialBalance, finalBalance, reachedDiscovery ); string memory summaryFilename = string.concat( "scenario_summary_seed", vm.toString(seed), ".txt" ); vm.writeFile(summaryFilename, summary); console.log(string.concat(" Summary exported to: ", summaryFilename)); } function _generateReplayScript(uint256 seed) internal view returns (string memory) { string memory script = string.concat( "// Replay script for profitable scenario\n", "// Seed: ", vm.toString(seed), "\n", "// Optimizer: ", optimizerClass, "\n", "// Discovery reached: YES\n\n", "function replayScenario() public {\n" ); for (uint256 i = 0; i < currentActions.length; i++) { ActionRecord memory action = currentActions[i]; script = string.concat(script, " // Step ", vm.toString(i), "\n"); if (keccak256(bytes(action.actionType)) == keccak256("BUY")) { script = string.concat( script, " _executeBuy(", action.actor == account ? "trader" : "whale", ", ", vm.toString(action.amount), ");\n" ); } else if (keccak256(bytes(action.actionType)) == keccak256("SELL")) { script = string.concat( script, " _executeSell(", action.actor == account ? "trader" : "whale", ", ", vm.toString(action.amount), ");\n" ); } else if (keccak256(bytes(action.actionType)) == keccak256("RECENTER")) { script = string.concat( script, " vm.warp(", vm.toString(action.timestamp), ");\n", " vm.prank(feeDestination);\n", " lm.recenter();\n" ); } script = string.concat( script, " // Tick moved from ", vm.toString(action.tickBefore), " to ", vm.toString(action.tickAfter), "\n\n" ); } script = string.concat(script, "}\n"); return script; } function _generateActionSummary( uint256 seed, uint256 initialBalance, uint256 finalBalance, bool reachedDiscovery ) internal view returns (string memory) { uint256 profit = finalBalance - initialBalance; uint256 profitPct = (profit * 100) / initialBalance; string memory summary = string.concat( "=== PROFITABLE SCENARIO SUMMARY ===\n", "Run ID: ", runId, "\n", "Seed: ", vm.toString(seed), "\n", "Optimizer: ", optimizerClass, "\n", "Discovery Reached: ", reachedDiscovery ? "YES" : "NO", "\n\n", "Financial Results:\n", " Initial Balance: ", vm.toString(initialBalance / 1e18), " ETH\n", " Final Balance: ", vm.toString(finalBalance / 1e18), " ETH\n", " Profit: ", vm.toString(profit / 1e18), " ETH (", vm.toString(profitPct), "%)\n\n", "Action Sequence:\n" ); for (uint256 i = 0; i < currentActions.length; i++) { ActionRecord memory action = currentActions[i]; summary = string.concat( summary, " ", vm.toString(i + 1), ". ", action.actionType, " - ", action.actor == account ? "Trader" : action.actor == whale ? "Whale" : "System", action.amount > 0 ? string.concat(" - Amount: ", vm.toString(action.amount / 1e18), " ETH") : "", " - Tick: ", vm.toString(action.tickBefore), " -> ", vm.toString(action.tickAfter), "\n" ); } summary = string.concat( summary, "\nThis scenario can be replayed using the generated replay script.\n" ); return summary; } }