feat: Add staking functionality to fuzzing analysis with snatching

- Integrate staking and exitPosition actions into fuzzing scenarios
- Add staking metrics (percentageStaked, avgTaxRate) to CSV output
- Implement snatching mechanism when stake pool reaches capacity
- Add configurable staking parameters (enable/disable, buy bias, staking bias)
- Support configurable number of trades per run
- Simplify to single trader account with proportional ETH funding
- Configure tax rates: 0-15 for initial stakes, 28 for snatching
- Clean up debug logging and verbose output

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
johba 2025-08-19 18:24:09 +02:00
parent 74e09bb38b
commit 8b537302d8
5 changed files with 439 additions and 288 deletions

View file

@ -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;
}
}

View file

@ -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"

View file

@ -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"