harb/onchain/analysis/RecordedFuzzingAnalysis.s.sol
2025-08-19 13:47:04 +02:00

305 lines
No EOL
11 KiB
Solidity

// 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
bool private isRecordingProfitable; // Only record if scenario is profitable
// Constructor removed - set trackPositions in run() instead
// 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"));
// Force position tracking to be enabled for CSV generation
vm.setEnv("TRACK_POSITIONS", "true");
console.log("=== RECORDED Fuzzing Analysis ===");
console.log(string.concat("Run ID: ", runId));
console.log("Recording enabled for profitable scenario replay");
super.run();
}
// Override to add recording for profitable scenarios
function _runImprovedScenario(uint256 seed) internal override returns (uint256 finalBalance, bool reachedDiscovery) {
// Clear previous scenario
delete currentActions;
isRecordingProfitable = false;
// Run the base scenario
(finalBalance, reachedDiscovery) = super._runImprovedScenario(seed);
// Export summary if profitable
uint256 initialBalance = 50 ether;
if (finalBalance > initialBalance && reachedDiscovery) {
_exportSummary(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();
// Execute trade
super._executeBuy(buyer, amount);
(, int24 tickAfter,,,,,) = pool.slot0();
// Only record action summary (not full state)
_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();
// Execute trade
super._executeSell(seller, amount);
(, int24 tickAfter,,,,,) = pool.slot0();
// Only record action summary (not full state)
_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();
vm.warp(block.timestamp + 1 hours);
vm.prank(feeDestination);
try lm.recenter{gas: 50_000_000}() {} catch {}
(, int24 tickAfter,,,,,) = pool.slot0();
// Only record action summary
_recordAction("RECENTER", feeDestination, 0, tickBefore, tickAfter);
}
function _recordState(string memory label, address actor, uint256 amount) internal {
// Only record states if we're exporting a profitable scenario
if (!isRecordingProfitable || !enableRecording) return;
(uint160 sqrtPriceX96, int24 currentTick,,,,,) = pool.slot0();
recorder.recordPreState(
weth.balanceOf(account),
harberg.balanceOf(account),
currentTick,
uint256(sqrtPriceX96),
0, // VWAP placeholder
harberg.outstandingSupply()
);
}
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 _exportSummary(
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)));
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));
if (currentActions.length > 0) {
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));
}
}
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;
}
}