recorded fuzzing

This commit is contained in:
johba 2025-08-18 22:09:03 +02:00
parent 85cc8191be
commit 7eef96f366
25 changed files with 56 additions and 6468 deletions

View file

@ -30,6 +30,9 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager {
LiquidityManager lm;
bool token0isWeth;
// Reusable swap executor to avoid repeated deployments
SwapExecutor swapExecutor;
address account = makeAddr("trader");
address whale = makeAddr("whale");
address feeDestination = makeAddr("fees");
@ -87,15 +90,23 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager {
vm.prank(whale);
weth.deposit{value: whaleFund}();
// Create SwapExecutor once per scenario to avoid repeated deployments
swapExecutor = new SwapExecutor(pool, weth, harberg, token0isWeth);
uint256 initialBalance = weth.balanceOf(account);
// Initial recenter
vm.prank(feeDestination);
lm.recenter();
// Initialize position tracking
// Initialize position tracking (skip CSV init if already done)
if (trackPositions) {
initializePositionsCSV();
if (seed == 0) {
// Only initialize CSV for first seed if not already initialized
if (bytes(csv).length == 0) {
initializePositionsCSV();
}
}
_recordPositionData("Initial");
}
@ -402,21 +413,19 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager {
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);
weth.transfer(address(swapExecutor), amount);
try executor.executeBuy(amount, buyer) {} catch {}
try swapExecutor.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);
harberg.transfer(address(swapExecutor), amount);
try executor.executeSell(amount, seller) {} catch {}
try swapExecutor.executeSell(amount, seller) {} catch {}
}
function _getOptimizerByClass(string memory class) internal returns (address) {

View file

@ -13,6 +13,9 @@ 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 {
@ -32,42 +35,29 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
// 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) {
// Initialize recorder for this scenario
if (enableRecording) {
recorder = new ScenarioRecorder();
// Always track positions when recording to generate CSV for visualizer
trackPositions = true;
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
}
// Clear previous scenario
delete currentActions;
isRecordingProfitable = false;
// Record initial state
_recordState("INITIAL", address(0), 0);
// Run the scenario with recording
// Run the base scenario
(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);
// Export summary if profitable
uint256 initialBalance = 50 ether;
if (finalBalance > initialBalance && reachedDiscovery) {
_exportSummary(seed, initialBalance, finalBalance, reachedDiscovery);
}
return (finalBalance, reachedDiscovery);
@ -79,22 +69,13 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
(, 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);
}
// Only record action summary (not full state)
_recordAction("BUY", buyer, amount, tickBefore, tickAfter);
}
function _executeSell(address seller, uint256 amount) internal override {
@ -102,22 +83,13 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
(, 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);
}
// Only record action summary (not full state)
_recordAction("SELL", seller, amount, tickBefore, tickAfter);
}
// Override each strategy to add recenter recording
@ -163,25 +135,19 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
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);
}
// Only record action summary
_recordAction("RECENTER", feeDestination, 0, tickBefore, tickAfter);
}
function _recordState(string memory label, address actor, uint256 amount) internal {
if (!enableRecording) return;
// Only record states if we're exporting a profitable scenario
if (!isRecordingProfitable || !enableRecording) return;
(uint160 sqrtPriceX96, int24 currentTick,,,,,) = pool.slot0();
@ -193,17 +159,6 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
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(
@ -225,7 +180,7 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
}));
}
function _exportScenario(
function _exportSummary(
uint256 seed,
uint256 initialBalance,
uint256 finalBalance,
@ -235,23 +190,6 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
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,
@ -263,6 +201,15 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis {
);
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) {

View file

@ -59,10 +59,10 @@ echo -e "${YELLOW}Starting recorded fuzzing analysis...${NC}"
FUZZING_RUNS=$RUNS_VALUE \
OPTIMIZER_CLASS=$OPTIMIZER \
RUN_ID=$RUN_ID \
forge script analysis/RecordedFuzzingAnalysis.s.sol:RecordedFuzzingAnalysis --disable-block-gas-limit -vv 2>&1 | tee $OUTPUT_DIR/fuzzing.log
forge script analysis/RecordedFuzzingAnalysis.s.sol:RecordedFuzzingAnalysis --gas-limit 1000000000 -vv 2>&1 | tee $OUTPUT_DIR/fuzzing.log
# Check for generated scenario files
SCENARIO_COUNT=$(ls -1 recorded_scenario_*.json 2>/dev/null | wc -l)
# Check for generated scenario files (check for summaries, they're always generated)
SCENARIO_COUNT=$(ls -1 scenario_summary_seed*.txt 2>/dev/null | wc -l)
if [ $SCENARIO_COUNT -gt 0 ]; then
echo ""
@ -176,6 +176,7 @@ if [ $POSITION_CSV_COUNT -gt 0 ] && [ $SCENARIO_COUNT -gt 0 ]; then
fi
# Use absolute path for the symlink
ln -s "$(realpath $FIRST_CSV)" "$TEMP_LINK"
echo "Created symlink: $TEMP_LINK -> $FIRST_CSV"
# Check if server is already running on common ports
SERVER_RUNNING=false