diff --git a/onchain/analysis/ImprovedFuzzingAnalysis.s.sol b/onchain/analysis/ImprovedFuzzingAnalysis.s.sol index 4369154..a4b9102 100644 --- a/onchain/analysis/ImprovedFuzzingAnalysis.s.sol +++ b/onchain/analysis/ImprovedFuzzingAnalysis.s.sol @@ -34,8 +34,7 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { // Reusable swap executor to avoid repeated deployments SwapExecutor swapExecutor; - address account = makeAddr("trader"); - address whale = makeAddr("whale"); + address trader = makeAddr("trader"); address feeDestination = makeAddr("fees"); // Analysis metrics @@ -47,15 +46,9 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { uint256 public totalSnatchesAttempted; uint256 public totalSnatchesSucceeded; - // OPTIMIZATION: Circular buffer for position tracking (max 50 positions) - uint256 constant MAX_TRACKED_POSITIONS = 50; - uint256[MAX_TRACKED_POSITIONS] public trackedPositions; - uint256 public positionWriteIndex; - uint256 public totalPositionsCreated; - mapping(uint256 => address) public positionOwners; - - // Counter to limit CSV recording and prevent memory issues - uint256 private csvRecordCount; + // Staking tracking + mapping(address => uint256[]) public activePositions; + uint256[] public allPositionIds; // Track all positions for snatching // Configuration uint256 public fuzzingRuns; @@ -84,10 +77,9 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { // Get optimizer address optimizerAddress = _getOptimizerByClass(optimizerClass); - // Track profitable scenarios (limit string concatenation to prevent memory issues) + // Track profitable scenarios string memory profitableCSV = "Scenario,Seed,Initial Balance,Final Balance,Profit,Profit %,Discovery Reached\n"; uint256 profitableCount; - uint256 maxProfitableRecords = 20; // Limit to prevent memory issues for (uint256 seed = 0; seed < fuzzingRuns; seed++) { if (seed % 10 == 0 && seed > 0) { @@ -98,26 +90,29 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { (factory, pool, weth, harberg, stake, lm,, token0isWeth) = testEnv.setupEnvironmentWithExistingFactory(factory, seed % 2 == 0, feeDestination, optimizerAddress); - // Fund LiquidityManager with MORE ETH for deeper liquidity - vm.deal(address(lm), 200 ether); // Increased from 50 + // Fund LiquidityManager with ETH proportional to trades + uint256 lmFunding = 100 ether + (tradesPerRun * 2 ether); // Base 100 + 2 ETH per trade + vm.deal(address(lm), lmFunding); - // 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 + // Fund trader with capital proportional to number of trades + // Combine what was previously split between trader and whale + uint256 traderFund = 150 ether + (tradesPerRun * 8 ether); // 150 ETH base + 8 ETH per trade - vm.deal(account, traderFund * 2); - vm.deal(whale, whaleFund * 2); + // Add some randomness but keep it proportional + uint256 traderRandom = uint256(keccak256(abi.encodePacked(seed, "trader"))) % (tradesPerRun * 3 ether); + traderFund += traderRandom; - vm.prank(account); + + // Deal 2x to have extra for gas + vm.deal(trader, traderFund * 2); + + vm.prank(trader); 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); + uint256 initialBalance = weth.balanceOf(trader); // Initial recenter vm.prank(feeDestination); @@ -125,13 +120,9 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { // Initialize position tracking for each seed if (trackPositions) { - // Reset tracking for new scenario - _resetPositionTracking(); - clearCSV(); // Clear any previous data - csvRecordCount = 0; // Reset counter + // Initialize CSV header for each seed (after clearCSV from previous run) initializePositionsCSV(); _recordPositionData("Initial"); - csvRecordCount = 1; // Count the initial record } // Run improved trading scenario @@ -152,19 +143,16 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { console.log(string.concat(" Profit: ", vm.toString(profit / 1e15), " finney (", vm.toString(profitPct), "%)")); console.log(string.concat(" Discovery reached: ", reachedDiscovery ? "YES" : "NO")); - // Only record limited profitable scenarios to prevent memory issues - if (profitableCount < maxProfitableRecords) { - profitableCSV = string.concat( - profitableCSV, - optimizerClass, ",", - vm.toString(seed), ",", - vm.toString(initialBalance), ",", - vm.toString(finalBalance), ",", - vm.toString(profit), ",", - vm.toString(profitPct), ",", - reachedDiscovery ? "true" : "false", "\n" - ); - } + 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++; } @@ -187,6 +175,7 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { console.log(string.concat("Discovery rate: ", vm.toString((discoveryReachedCount * 100) / scenariosAnalyzed), "%")); console.log(string.concat("Profit rate: ", vm.toString((profitableScenarios * 100) / scenariosAnalyzed), "%")); + if (enableStaking) { console.log("\n=== STAKING METRICS ==="); console.log(string.concat("Stakes attempted: ", vm.toString(totalStakesAttempted))); @@ -215,8 +204,7 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { (, int24 discoveryLower, int24 discoveryUpper) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY); // Initial buy to generate some KRAIKEN tokens for trading/staking - _executeBuy(account, weth.balanceOf(account) / 4); // Buy 25% of ETH worth - _executeBuy(whale, weth.balanceOf(whale) / 4); // Whale buys 25% of ETH worth + _executeBuy(trader, weth.balanceOf(trader) / 4); // Buy 25% of ETH worth // Always use random trading strategy for consistent behavior _executeRandomLargeTrades(rand); @@ -233,25 +221,31 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { } } + // Check final balances before cleanup + uint256 traderKraiken = harberg.balanceOf(trader); + uint256 stakedAmount = harberg.balanceOf(address(stake)); + uint256 outstandingSupply = harberg.outstandingSupply(); + uint256 minStake = harberg.minStake(); + // Final cleanup: sell all KRAIKEN - uint256 finalKraiken = harberg.balanceOf(account); - if (finalKraiken > 0) { - _executeSell(account, finalKraiken); + if (traderKraiken > 0) { + _executeSell(trader, traderKraiken); } - finalBalance = weth.balanceOf(account); + finalBalance = weth.balanceOf(trader); } function _executeRandomLargeTrades(uint256 rand) internal { console.log(" Strategy: Random Large Trades"); - console.log(" Buy bias:", buyBias, "%"); - console.log(" Trades:", tradesPerRun); + console.log(string.concat(" Buy bias: ", vm.toString(buyBias), "%")); + console.log(string.concat(" Trades per run: ", vm.toString(tradesPerRun))); if (enableStaking) { - console.log(" Staking bias:", stakingBias, "%"); + console.log(string.concat(" Staking bias: ", vm.toString(stakingBias), "%")); + console.log(" Staking every 3rd trade"); } + uint256 stakingAttempts = 0; for (uint256 i = 0; i < tradesPerRun; i++) { rand = uint256(keccak256(abi.encodePacked(rand, i))); - uint256 actor = rand % 2; // 0 = trader, 1 = whale // Use buy bias to determine action uint256 actionRoll = rand % 100; @@ -265,21 +259,20 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { action = 2; // Recenter (10% chance) } - address actorAddr = actor == 0 ? account : whale; - if (action == 0) { // Large buy (30-80% of balance, or more with high buy bias) uint256 buyPct = buyBias > 70 ? 50 + (rand % 40) : 30 + (rand % 51); - uint256 buyAmount = weth.balanceOf(actorAddr) * buyPct / 100; - if (buyAmount > 0) { - _executeBuy(actorAddr, buyAmount); + uint256 wethBalance = weth.balanceOf(trader); + uint256 buyAmount = wethBalance * buyPct / 100; + if (buyAmount > 0 && wethBalance > 0) { + _executeBuy(trader, buyAmount); } } else if (action == 1) { // Large sell (reduced amounts with high buy bias) uint256 sellPct = buyBias > 70 ? 10 + (rand % 30) : 30 + (rand % 71); - uint256 sellAmount = harberg.balanceOf(actorAddr) * sellPct / 100; + uint256 sellAmount = harberg.balanceOf(trader) * sellPct / 100; if (sellAmount > 0) { - _executeSell(actorAddr, sellAmount); + _executeSell(trader, sellAmount); } } else { // Recenter @@ -290,6 +283,7 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { // Every 3rd trade, attempt staking/unstaking if enabled if (enableStaking && i % 3 == 2) { + stakingAttempts++; uint256 stakingRoll = uint256(keccak256(abi.encodePacked(rand, "staking", i))) % 100; if (stakingRoll < stakingBias) { // Try to stake @@ -300,11 +294,18 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { } } - // Record position data for visualization (always record all trades) - if (trackPositions) { - _recordPositionDataSafe(string.concat("T", vm.toString(i))); + // Only record every 5th trade to avoid memory issues with large trade counts + if (trackPositions && i % 5 == 0) { + _recordPositionData("Trade"); } } + + uint256 expectedStakeActions = tradesPerRun / 3; + uint256 expectedStakes = (expectedStakeActions * stakingBias) / 100; + console.log(string.concat(" Total staking actions: ", vm.toString(stakingAttempts), " (every 3rd trade)")); + console.log(string.concat(" Expected stake calls: ~", vm.toString(expectedStakes), " (", vm.toString(stakingBias), "% of actions)")); + console.log(string.concat(" Expected exit calls: ~", vm.toString(expectedStakeActions - expectedStakes), " (", vm.toString(100 - stakingBias), "% of actions)")); + console.log(" Note: Actual stakes limited by available KRAIKEN balance"); } function _executeBuy(address buyer, uint256 amount) internal virtual { @@ -345,26 +346,16 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { optimizerClass = vm.envOr("OPTIMIZER_CLASS", string("BullMarketOptimizer")); } - // Safe wrapper that prevents gas and memory issues - function _recordPositionDataSafe(string memory label) internal { - // Only record if we have enough gas remaining - if (gasleft() < 1_000_000) { - return; // Skip recording if low on gas - } - - // Skip if CSV is getting extremely large (increased limit for all trades) - if (bytes(csv).length > 100000) { - return; // Skip to avoid memory issues - } - - csvRecordCount++; - _recordPositionData(label); + function _recordPositionData(string memory label) internal { + // Split into separate function calls to avoid stack too deep + _recordPositionDataInternal(label); } - function _recordPositionData(string memory label) internal { - // Absolutely prevent recording if buffer is too large - if (bytes(csv).length > 100000) { - return; // Stop recording to prevent memory issues + function _recordPositionDataInternal(string memory label) private { + // Disable position tracking if it causes memory issues + if (bytes(csv).length > 50000) { + // CSV is getting too large, skip recording + return; } (,int24 currentTick,,,,,) = pool.slot0(); @@ -374,205 +365,243 @@ contract ImprovedFuzzingAnalysis is Test, CSVManager { (uint128 anchorLiq, int24 anchorLower, int24 anchorUpper) = lm.positions(ThreePositionStrategy.Stage.ANCHOR); (uint128 discoveryLiq, int24 discoveryLower, int24 discoveryUpper) = lm.positions(ThreePositionStrategy.Stage.DISCOVERY); - // Skip staking data for trades to save memory (only get for key events) - uint256 pctStaked = 0; - uint256 avgTax = 0; - if (enableStaking && bytes(label).length > 1 && bytes(label)[0] != 0x54) { // Not a "T" trade label - try stake.getPercentageStaked{gas: 30000}() returns (uint256 pct) { - pctStaked = pct; - } catch {} - try stake.getAverageTaxRate{gas: 30000}() returns (uint256 tax) { - avgTax = tax; - } catch {} - } - - // Build CSV row with minimal concatenations - string memory row = string.concat( - label, ",", - vm.toString(currentTick), ",", - vm.toString(floorLower), "," - ); - - row = string.concat( - row, - vm.toString(floorUpper), ",", - vm.toString(floorLiq), "," - ); - - row = string.concat( - row, - vm.toString(anchorLower), ",", - vm.toString(anchorUpper), "," - ); - - row = string.concat( - row, - vm.toString(anchorLiq), ",", - vm.toString(discoveryLower), "," - ); - - row = string.concat( - row, - vm.toString(discoveryUpper), ",", - vm.toString(discoveryLiq), "," - ); - - row = string.concat( - row, - token0isWeth ? "1" : "0", ",", - vm.toString(pctStaked), ",", - vm.toString(avgTax) - ); + // Use simpler row building to avoid memory issues + string memory row = label; + row = string.concat(row, ",", vm.toString(currentTick)); + row = string.concat(row, ",", vm.toString(floorLower)); + row = string.concat(row, ",", vm.toString(floorUpper)); + row = string.concat(row, ",", vm.toString(floorLiq)); + row = string.concat(row, ",", vm.toString(anchorLower)); + row = string.concat(row, ",", vm.toString(anchorUpper)); + row = string.concat(row, ",", vm.toString(anchorLiq)); + row = string.concat(row, ",", vm.toString(discoveryLower)); + row = string.concat(row, ",", vm.toString(discoveryUpper)); + row = string.concat(row, ",", vm.toString(discoveryLiq)); + row = string.concat(row, ",", token0isWeth ? "true" : "false"); + row = string.concat(row, ",", vm.toString(stake.getPercentageStaked())); + row = string.concat(row, ",", vm.toString(stake.getAverageTaxRate())); appendCSVRow(row); } + function _executeStakingAction(uint256 rand) internal { + uint256 action = rand % 100; + + // 70% chance to stake, 5% chance to exit (to fill pool faster) + if (action < 70) { + _executeStake(rand); + } else if (action < 75) { + _executeExitPosition(rand); + } + } + + function _executeStakeWithAmount(address staker, uint256 amount, uint32 taxRate) internal { + // Direct stake with specific amount and tax rate + _doStake(staker, amount, taxRate); + } function _executeStake(uint256 rand) internal { - address staker = rand % 2 == 0 ? account : whale; + address staker = trader; uint256 harbBalance = harberg.balanceOf(staker); + uint256 minStakeAmount = harberg.minStake(); - if (harbBalance > harberg.minStake()) { - uint256 amount = harbBalance * (30 + (rand % 50)) / 100; - if (amount < harberg.minStake()) { - amount = harberg.minStake(); + + if (harbBalance > minStakeAmount) { + // Stake between 5% and 20% of balance for more granular positions + uint256 amount = harbBalance * (5 + (rand % 16)) / 100; + if (amount < minStakeAmount) { + amount = minStakeAmount; } - uint32 taxRate = uint32(rand % 30); + // Initial staking: use lower tax rates (0-15) to enable snatching later + uint32 taxRate = uint32(rand % 16); // 0-15 instead of 0-29 vm.prank(staker); harberg.approve(address(stake), amount); - - totalStakesAttempted++; - vm.prank(staker); - try stake.snatch{gas: 10_000_000}(amount, staker, taxRate, new uint256[](0)) returns (uint256 positionId) { - totalStakesSucceeded++; - _addTrackedPosition(positionId, staker); - console.log(" STAKED:", amount / 1e18, "KRAIKEN"); - if (trackPositions) { - _recordPositionDataSafe("Stake"); - } - } catch { - // If regular stake fails, try snatching with higher tax rate - if (totalPositionsCreated > 0) { - _tryOptimizedSnatch(staker, amount); - } - } + _doStake(staker, amount, taxRate); } + // Silently skip if insufficient balance - this is expected behavior } - function _tryOptimizedSnatch(address staker, uint256 amount) internal { - uint32 maxTaxRate = 29; - - // Find up to 3 snatchable positions quickly - uint256[] memory toSnatch = _findSnatchableQuick(maxTaxRate, amount); - - if (toSnatch.length > 0) { - totalSnatchesAttempted++; - vm.prank(staker); - try stake.snatch{gas: 15_000_000}(amount, staker, maxTaxRate, toSnatch) returns (uint256 positionId) { - totalSnatchesSucceeded++; - _addTrackedPosition(positionId, staker); - console.log(" SNATCHED", toSnatch.length, "positions"); + function _doStake(address staker, uint256 amount, uint32 taxRate) internal { + vm.startPrank(staker); + + // Check current pool capacity before attempting stake + uint256 currentPercentStaked = stake.getPercentageStaked(); + + // Log pool status before attempting (currentPercentStaked is in 1e18, where 1e18 = 100%) + if (currentPercentStaked > 95e16) { // > 95% + console.log(string.concat(" [POOL NEAR FULL] ", vm.toString(currentPercentStaked / 1e16), "% of authorized")); + } + + // First try to stake without snatching + totalStakesAttempted++; + + + try stake.snatch(amount, staker, taxRate, new uint256[](0)) returns (uint256 positionId) { + totalStakesSucceeded++; + activePositions[staker].push(positionId); + allPositionIds.push(positionId); if (trackPositions) { - _recordPositionDataSafe("Snatch"); + _recordPositionData("Stake"); } - } catch {} - } + } catch Error(string memory reason) { + // Caught string error - try snatching + + // Use high tax rate (28) for snatching - can snatch anything with rate 0-27 + uint32 snatchTaxRate = 28; + + // Find positions to snatch (those with lower tax rates) + uint256[] memory positionsToSnatch = _findSnatchablePositions(snatchTaxRate, amount); + + if (positionsToSnatch.length > 0) { + totalSnatchesAttempted++; + try stake.snatch(amount, staker, snatchTaxRate, positionsToSnatch) returns (uint256 positionId) { + totalSnatchesSucceeded++; + activePositions[staker].push(positionId); + allPositionIds.push(positionId); + + // Remove snatched positions from tracking + _removeSnatchedPositions(positionsToSnatch); + + if (trackPositions) { + _recordPositionData("Snatch"); + } + } catch {} + } + } catch { + // Catch-all for non-string errors (likely ExceededAvailableStake) + console.log(string.concat(" Stake failed at ", vm.toString(currentPercentStaked / 1e16), "% - trying snatch")); + + // Now try snatching with high tax rate + uint32 snatchTaxRate = 28; + uint256[] memory positionsToSnatch = _findSnatchablePositions(snatchTaxRate, amount); + + if (positionsToSnatch.length > 0) { + totalSnatchesAttempted++; + try stake.snatch(amount, staker, snatchTaxRate, positionsToSnatch) returns (uint256 positionId) { + totalSnatchesSucceeded++; + activePositions[staker].push(positionId); + allPositionIds.push(positionId); + _removeSnatchedPositions(positionsToSnatch); + + if (trackPositions) { + _recordPositionData("Snatch"); + } + } catch {} + } + } + vm.stopPrank(); } function _executeExitPosition(uint256 rand) internal { - address staker = rand % 2 == 0 ? account : whale; + address staker = trader; - // Find a position owned by this staker - uint256 positionToExit = _findOwnedPosition(staker); - - if (positionToExit > 0) { + if (activePositions[staker].length > 0) { + uint256 index = rand % activePositions[staker].length; + uint256 positionId = activePositions[staker][index]; + vm.prank(staker); - try stake.exitPosition{gas: 5_000_000}(positionToExit) { - _removeTrackedPosition(positionToExit); + try stake.exitPosition(positionId) { + // Remove from array + activePositions[staker][index] = activePositions[staker][activePositions[staker].length - 1]; + activePositions[staker].pop(); + + // Also remove from allPositionIds + for (uint256 j = 0; j < allPositionIds.length; j++) { + if (allPositionIds[j] == positionId) { + allPositionIds[j] = 0; // Mark as removed + break; + } + } + if (trackPositions) { - _recordPositionDataSafe("Unstake"); + _recordPositionData("ExitStake"); } } catch { - // Position might be already exited/snatched - _removeTrackedPosition(positionToExit); + // Exit failed (position might be liquidated), remove from tracking + activePositions[staker][index] = activePositions[staker][activePositions[staker].length - 1]; + activePositions[staker].pop(); + + // Also remove from allPositionIds + for (uint256 j = 0; j < allPositionIds.length; j++) { + if (allPositionIds[j] == positionId) { + allPositionIds[j] = 0; // Mark as removed + break; + } + } } } } - function _findSnatchableQuick(uint32 maxTaxRate, uint256 amountNeeded) internal view returns (uint256[] memory) { - uint256[] memory result = new uint256[](3); // Max 3 positions + function _findSnatchablePositions(uint32 snatchTaxRate, uint256 amountNeeded) internal view returns (uint256[] memory) { + // Find positions with tax rates lower than snatchTaxRate + uint256[] memory snatchable = new uint256[](10); // Max 10 positions to snatch uint256 count = 0; uint256 totalShares = 0; + uint256 skippedHighTax = 0; + uint256 skippedEmpty = 0; - // Check only recent positions (last 20 in circular buffer) - uint256 checkCount = totalPositionsCreated < 20 ? totalPositionsCreated : 20; - uint256 startIdx = positionWriteIndex > checkCount ? positionWriteIndex - checkCount : 0; - - for (uint256 i = 0; i < checkCount && count < 3; i++) { - uint256 idx = (startIdx + i) % MAX_TRACKED_POSITIONS; - uint256 positionId = trackedPositions[idx]; + for (uint256 i = 0; i < allPositionIds.length && count < 10; i++) { + uint256 positionId = allPositionIds[i]; if (positionId == 0) continue; - (uint256 shares,, , , uint32 taxRate) = stake.positions(positionId); + // Get position info + (uint256 shares, address owner,, , uint32 taxRate) = stake.positions(positionId); - if (shares > 0 && taxRate < maxTaxRate) { - result[count] = positionId; - totalShares += shares; - count++; - - if (stake.sharesToAssets(totalShares) >= amountNeeded) { + // Skip if position doesn't exist or tax rate is too high + if (shares == 0) { + skippedEmpty++; + continue; + } + if (taxRate >= snatchTaxRate) { + skippedHighTax++; + continue; + } + + snatchable[count] = positionId; + totalShares += shares; + count++; + + // Check if we have enough shares to cover the amount needed + uint256 assetsFromShares = stake.sharesToAssets(totalShares); + if (assetsFromShares >= amountNeeded) { + break; + } + } + + + // Resize array to actual count + uint256[] memory result = new uint256[](count); + for (uint256 i = 0; i < count; i++) { + result[i] = snatchable[i]; + } + + return result; + } + + function _removeSnatchedPositions(uint256[] memory snatchedIds) internal { + // Remove snatched positions from allPositionIds + for (uint256 i = 0; i < snatchedIds.length; i++) { + uint256 snatchedId = snatchedIds[i]; + + // Find and remove from allPositionIds + for (uint256 j = 0; j < allPositionIds.length; j++) { + if (allPositionIds[j] == snatchedId) { + allPositionIds[j] = 0; // Mark as removed + break; + } + } + + // Remove from trader's activePositions + uint256[] storage traderPositions = activePositions[trader]; + for (uint256 m = 0; m < traderPositions.length; m++) { + if (traderPositions[m] == snatchedId) { + traderPositions[m] = traderPositions[traderPositions.length - 1]; + traderPositions.pop(); break; } } } - - // Resize result - uint256[] memory finalResult = new uint256[](count); - for (uint256 i = 0; i < count; i++) { - finalResult[i] = result[i]; - } - - return finalResult; - } - - // Helper functions for circular buffer position tracking - function _addTrackedPosition(uint256 positionId, address owner) internal { - trackedPositions[positionWriteIndex] = positionId; - positionOwners[positionId] = owner; - positionWriteIndex = (positionWriteIndex + 1) % MAX_TRACKED_POSITIONS; - totalPositionsCreated++; - } - - function _removeTrackedPosition(uint256 positionId) internal { - delete positionOwners[positionId]; - // Don't remove from circular buffer, just mark owner as deleted - } - - function _resetPositionTracking() internal { - // Clear circular buffer - for (uint256 i = 0; i < MAX_TRACKED_POSITIONS; i++) { - trackedPositions[i] = 0; - } - positionWriteIndex = 0; - totalPositionsCreated = 0; - } - - function _findOwnedPosition(address owner) internal view returns (uint256) { - // Check last 10 positions for ownership - uint256 checkCount = totalPositionsCreated < 10 ? totalPositionsCreated : 10; - uint256 startIdx = positionWriteIndex > checkCount ? positionWriteIndex - checkCount : 0; - - for (uint256 i = 0; i < checkCount; i++) { - uint256 idx = (startIdx + i) % MAX_TRACKED_POSITIONS; - uint256 positionId = trackedPositions[idx]; - - if (positionId > 0 && positionOwners[positionId] == owner) { - return positionId; - } - } - - return 0; } } \ No newline at end of file diff --git a/onchain/analysis/RecordedFuzzingAnalysis.s.sol b/onchain/analysis/RecordedFuzzingAnalysis.s.sol index 08b076f..93add0e 100644 --- a/onchain/analysis/RecordedFuzzingAnalysis.s.sol +++ b/onchain/analysis/RecordedFuzzingAnalysis.s.sol @@ -92,46 +92,6 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis { _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(); @@ -152,8 +112,8 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis { (uint160 sqrtPriceX96, int24 currentTick,,,,,) = pool.slot0(); recorder.recordPreState( - weth.balanceOf(account), - harberg.balanceOf(account), + weth.balanceOf(trader), + harberg.balanceOf(trader), currentTick, uint256(sqrtPriceX96), 0, // VWAP placeholder @@ -230,14 +190,14 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis { script = string.concat( script, " _executeBuy(", - action.actor == account ? "trader" : "whale", + action.actor == trader ? "trader" : "system", ", ", vm.toString(action.amount), ");\n" ); } else if (keccak256(bytes(action.actionType)) == keccak256("SELL")) { script = string.concat( script, " _executeSell(", - action.actor == account ? "trader" : "whale", + action.actor == trader ? "trader" : "system", ", ", vm.toString(action.amount), ");\n" ); } else if (keccak256(bytes(action.actionType)) == keccak256("RECENTER")) { @@ -288,7 +248,7 @@ contract RecordedFuzzingAnalysis is ImprovedFuzzingAnalysis { summary, " ", vm.toString(i + 1), ". ", action.actionType, " - ", - action.actor == account ? "Trader" : action.actor == whale ? "Whale" : "System", + action.actor == trader ? "Trader" : "System", action.amount > 0 ? string.concat(" - Amount: ", vm.toString(action.amount / 1e18), " ETH") : "", " - Tick: ", vm.toString(action.tickBefore), " -> ", vm.toString(action.tickAfter), "\n" diff --git a/onchain/analysis/run-improved-fuzzing.sh b/onchain/analysis/run-improved-fuzzing.sh new file mode 100755 index 0000000..6570803 --- /dev/null +++ b/onchain/analysis/run-improved-fuzzing.sh @@ -0,0 +1,162 @@ +#!/bin/bash + +# Usage: ./run-improved-fuzzing.sh [optimizer] [runs=N] [staking=on|off] [buybias=N] [trades=N] [stakingbias=N] +# Examples: +# ./run-improved-fuzzing.sh BullMarketOptimizer runs=50 +# ./run-improved-fuzzing.sh WhaleOptimizer runs=20 staking=off +# ./run-improved-fuzzing.sh BullMarketOptimizer runs=200 staking=on buybias=100 trades=30 stakingbias=95 + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color +BOLD='\033[1m' + +# Configuration +OPTIMIZER=${1:-BullMarketOptimizer} +RUNS=${2:-runs=20} +STAKING=${3:-staking=on} +BUYBIAS=${4:-buybias=50} +TRADES=${5:-trades=15} +STAKINGBIAS=${6:-stakingbias=80} + +# Parse runs parameter +if [[ $RUNS == runs=* ]]; then + RUNS_VALUE=${RUNS#runs=} +else + RUNS_VALUE=$RUNS +fi + +# Parse staking parameter +STAKING_ENABLED="true" +if [[ $STAKING == staking=* ]]; then + STAKING_VALUE=${STAKING#staking=} + if [[ $STAKING_VALUE == "off" ]] || [[ $STAKING_VALUE == "false" ]] || [[ $STAKING_VALUE == "0" ]]; then + STAKING_ENABLED="false" + fi +fi + +# Parse buy bias parameter +BUYBIAS_VALUE="50" +if [[ $BUYBIAS == buybias=* ]]; then + BUYBIAS_VALUE=${BUYBIAS#buybias=} +fi + +# Parse trades parameter +TRADES_VALUE="15" +if [[ $TRADES == trades=* ]]; then + TRADES_VALUE=${TRADES#trades=} +fi + +# Parse staking bias parameter +STAKINGBIAS_VALUE="80" +if [[ $STAKINGBIAS == stakingbias=* ]]; then + STAKINGBIAS_VALUE=${STAKINGBIAS#stakingbias=} +fi + +TIMESTAMP=$(date +%Y%m%d_%H%M%S) +OUTPUT_DIR="fuzzing_results_improved_${OPTIMIZER}_${TIMESTAMP}" + +echo -e "${GREEN}=== Improved Fuzzing Analysis ===${NC}" +echo "Optimizer: $OPTIMIZER" +echo "Total runs: $RUNS_VALUE" +echo "Trades per run: $TRADES_VALUE" +echo "Staking: $([ "$STAKING_ENABLED" = "true" ] && echo "enabled" || echo "disabled")" +if [ "$STAKING_ENABLED" = "true" ]; then + echo "Staking bias: $STAKINGBIAS_VALUE%" +fi +echo "Buy bias: $BUYBIAS_VALUE%" +echo "Output directory: $OUTPUT_DIR" +echo "" + +# Validate optimizer +case $OPTIMIZER in + BullMarketOptimizer|BearMarketOptimizer|NeutralMarketOptimizer|WhaleOptimizer|ExtremeOptimizer|MaliciousOptimizer) + echo "Optimizer validation passed" + ;; + *) + echo -e "${RED}Error: Invalid optimizer class${NC}" + echo "Valid options: BullMarketOptimizer, BearMarketOptimizer, NeutralMarketOptimizer, WhaleOptimizer, ExtremeOptimizer, MaliciousOptimizer" + exit 1 + ;; +esac + +# Create output directory +mkdir -p $OUTPUT_DIR + +# Run the improved fuzzing with buy bias +echo -e "${YELLOW}Starting improved fuzzing analysis with buy bias...${NC}" +echo "" + +FUZZING_RUNS=$RUNS_VALUE \ +OPTIMIZER_CLASS=$OPTIMIZER \ +ENABLE_STAKING=$STAKING_ENABLED \ +BUY_BIAS=$BUYBIAS_VALUE \ +TRADES_PER_RUN=$TRADES_VALUE \ +STAKING_BIAS=$STAKINGBIAS_VALUE \ +TRACK_POSITIONS=true \ +forge script analysis/ImprovedFuzzingAnalysis.s.sol:ImprovedFuzzingAnalysis --gas-limit 100000000 -vv 2>&1 | tee $OUTPUT_DIR/fuzzing.log + +# Extract key metrics +echo "" +echo -e "${GREEN}=== ANALYSIS COMPLETE ===${NC}" + +# Show summary from log +tail -20 $OUTPUT_DIR/fuzzing.log | grep -E "Total scenarios|Profitable|Discovery|Stakes|Snatches" || true + +# Check for position CSVs +POSITION_CSV_COUNT=$(ls -1 improved_positions_*.csv 2>/dev/null | wc -l) + +if [ $POSITION_CSV_COUNT -gt 0 ]; then + # Move position CSVs to output directory + mv improved_positions_*.csv $OUTPUT_DIR/ + echo "" + echo -e "${GREEN}Position CSVs saved to: $OUTPUT_DIR/${NC}" + + # Calculate max staking percentage + echo "" + echo -e "${YELLOW}=== Maximum Staking Level ===${NC}" + for f in $OUTPUT_DIR/improved_positions_*.csv; do + tail -1 "$f" | cut -d',' -f13 + done | sort -n | tail -1 | awk '{ + pct = $1/1e16 + printf "%.2f%% of authorized stake (%.2f%% of KRAIKEN supply)\n", pct, pct*0.2 + }' +fi + +# Check for snatching activity +echo "" +echo -e "${YELLOW}=== Snatching Activity ===${NC}" +SNATCH_COUNT=$(grep -c "SNATCHED" $OUTPUT_DIR/fuzzing.log 2>/dev/null || true) +if [ -z "$SNATCH_COUNT" ]; then + SNATCH_COUNT="0" +fi +if [ "$SNATCH_COUNT" -gt "0" ]; then + echo -e "${GREEN}Snatching observed! Found $SNATCH_COUNT snatching events${NC}" + grep "SNATCHED" $OUTPUT_DIR/fuzzing.log | head -5 +else + echo "No snatching observed in this run" +fi + +# Check for profitable scenarios +PROFITABLE_COUNT=$(grep -c "PROFITABLE!" $OUTPUT_DIR/fuzzing.log 2>/dev/null || true) +if [ -z "$PROFITABLE_COUNT" ]; then + PROFITABLE_COUNT="0" +fi +if [ "$PROFITABLE_COUNT" -gt "0" ]; then + echo "" + echo -e "${GREEN}=== PROFITABLE SCENARIOS FOUND ===${NC}" + echo "Found $PROFITABLE_COUNT profitable scenarios" + grep "PROFITABLE!" $OUTPUT_DIR/fuzzing.log | head -5 +fi + +echo "" +echo -e "${GREEN}Full results saved to: $OUTPUT_DIR/${NC}" +echo "" +echo "To view detailed logs:" +echo " cat $OUTPUT_DIR/fuzzing.log" +echo "" +echo "To visualize position movements (if CSVs generated):" +echo " ./analysis/view-scenarios.sh $OUTPUT_DIR" \ No newline at end of file diff --git a/onchain/lib/pt-v5-twab-controller b/onchain/lib/pt-v5-twab-controller index 2992696..258ab89 160000 --- a/onchain/lib/pt-v5-twab-controller +++ b/onchain/lib/pt-v5-twab-controller @@ -1 +1 @@ -Subproject commit 29926961b2ecfa89e0f61a6d874c71b6f8e29112 +Subproject commit 258ab89ee5da09a494d5721c18d27133ad844b63 diff --git a/onchain/lib/solmate b/onchain/lib/solmate index 89365b8..c892309 160000 --- a/onchain/lib/solmate +++ b/onchain/lib/solmate @@ -1 +1 @@ -Subproject commit 89365b880c4f3c786bdd453d4b8e8fe410344a69 +Subproject commit c892309933b25c03d32b1b0d674df7ae292ba925