harb/onchain/analysis/ImprovedFuzzingAnalysis.s.sol
2025-08-18 22:09:03 +02:00

471 lines
No EOL
19 KiB
Solidity

// 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;
// Reusable swap executor to avoid repeated deployments
SwapExecutor swapExecutor;
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}();
// 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 (skip CSV init if already done)
if (trackPositions) {
if (seed == 0) {
// Only initialize CSV for first seed if not already initialized
if (bytes(csv).length == 0) {
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;
vm.prank(buyer);
weth.transfer(address(swapExecutor), amount);
try swapExecutor.executeBuy(amount, buyer) {} catch {}
}
function _executeSell(address seller, uint256 amount) internal virtual {
if (amount == 0 || harberg.balanceOf(seller) < amount) return;
vm.prank(seller);
harberg.transfer(address(swapExecutor), amount);
try swapExecutor.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);
}
}