harb/onchain/analysis/ScarcityDiagnostic.s.sol
openhands b7260b2eaf chore: analysis tooling, research artifacts, and code quality
- Analysis: parameter sweep scripts, adversarial testing, 2D frontier maps
- Research: KRAIKEN_RESEARCH_REPORT, SECURITY_REVIEW, STORAGE_LAYOUT
- FuzzingBase: consolidated fuzzing helper, BackgroundLP simulation
- Sweep results: CSV data for full 4D sweep (1050 combos), bull-bear,
  AS sweep, VWAP fix validation
- Code quality: .gitignore for fuzz CSVs, gas snapshot, updated docs
- Remove dead analysis helpers (CSVHelper, CSVManager, ScenarioRecorder)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 18:22:03 +00:00

191 lines
7.2 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import { Kraiken } from "../src/Kraiken.sol";
import { LiquidityManager } from "../src/LiquidityManager.sol";
import { ThreePositionStrategy } from "../src/abstracts/ThreePositionStrategy.sol";
import { IWETH9 } from "../src/interfaces/IWETH9.sol";
import { ConfigurableOptimizer } from "../test/mocks/ConfigurableOptimizer.sol";
import { FuzzingBase } from "./helpers/FuzzingBase.sol";
import "forge-std/Vm.sol";
import "forge-std/console2.sol";
/// @title ScarcityDiagnostic
/// @notice Diagnose when EthScarcity vs EthAbundance fires and why CI has zero effect
/// @dev Tests various scenarios to find conditions where EthAbundance triggers
contract ScarcityDiagnostic is FuzzingBase {
event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
function run() public {
_initInfrastructure();
console2.log("=== Scarcity/Abundance Diagnostic ===");
console2.log("");
_testScenario1_InitialRecenter();
_testScenario2_BullPhase();
_testScenario3_PostSellRecenter();
_testScenario4_HighFundingRatio();
}
/// Scenario 1: Fresh deployment — no trading. Should be abundance.
function _testScenario1_InitialRecenter() internal {
console2.log("--- Scenario 1: Fresh deployment, initial recenter ---");
ConfigurableOptimizer opt = new ConfigurableOptimizer(5e17, 2e17, 80, 5e17);
_setupEnvironment(address(opt), true, true);
// Check state before recenter
uint256 totalSupply = kraiken.totalSupply();
uint256 lmBalance = kraiken.balanceOf(address(lm));
uint256 outstanding = kraiken.outstandingSupply();
uint256 ethBal = address(lm).balance + weth.balanceOf(address(lm));
console2.log(" totalSupply:", totalSupply);
console2.log(" lmBalance:", lmBalance);
console2.log(" outstandingSupply:", outstanding);
console2.log(" lm ETH balance:", ethBal);
// Now do a recenter and look for events
console2.log(" Doing recenter...");
vm.recordLogs();
_tryRecenter();
_analyzeEvents();
// Post-recenter state
outstanding = kraiken.outstandingSupply();
console2.log(" outstandingSupply after:", outstanding);
console2.log("");
}
/// Scenario 2: After 5 buys (bull phase). Should be scarcity.
function _testScenario2_BullPhase() internal {
console2.log("--- Scenario 2: After 5 buys x 15 ETH (bull phase) ---");
ConfigurableOptimizer opt = new ConfigurableOptimizer(5e17, 2e17, 80, 5e17);
_setupEnvironment(address(opt), false, true);
// Fund trader
vm.deal(trader, 100 ether);
vm.prank(trader);
weth.deposit{ value: 100 ether }();
// Do 5 buys with recenters
for (uint256 i = 0; i < 5; i++) {
_executeBuy(15 ether);
if (i % 2 == 1) _tryRecenter();
}
uint256 outstanding = kraiken.outstandingSupply();
uint256 ethBal = address(lm).balance + weth.balanceOf(address(lm));
uint256 traderKrk = kraiken.balanceOf(trader);
console2.log(" outstandingSupply:", outstanding);
console2.log(" lm ETH balance:", ethBal);
console2.log(" trader KRK:", traderKrk);
console2.log(" Doing recenter...");
vm.recordLogs();
_tryRecenter();
_analyzeEvents();
console2.log("");
}
/// Scenario 3: After bull + complete sell. Should be closer to abundance.
function _testScenario3_PostSellRecenter() internal {
console2.log("--- Scenario 3: Bull then sell all, recenter ---");
ConfigurableOptimizer opt = new ConfigurableOptimizer(5e17, 2e17, 80, 5e17);
_setupEnvironment(address(opt), true, true);
// Fund trader
vm.deal(trader, 100 ether);
vm.prank(trader);
weth.deposit{ value: 100 ether }();
// Buy phase
for (uint256 i = 0; i < 5; i++) {
_executeBuy(15 ether);
if (i % 2 == 1) _tryRecenter();
}
_tryRecenter();
// Now sell ALL KRK
_liquidateTraderHoldings();
uint256 outstanding = kraiken.outstandingSupply();
uint256 ethBal = address(lm).balance + weth.balanceOf(address(lm));
uint256 traderKrk = kraiken.balanceOf(trader);
console2.log(" outstandingSupply (after sell):", outstanding);
console2.log(" lm ETH balance:", ethBal);
console2.log(" trader KRK remaining:", traderKrk);
console2.log(" Doing recenter after sell...");
vm.recordLogs();
_tryRecenter();
_analyzeEvents();
console2.log("");
}
/// Scenario 4: Very high LM funding relative to trading (should be abundance)
function _testScenario4_HighFundingRatio() internal {
console2.log("--- Scenario 4: High LM funding (2000 ETH), tiny buy ---");
ConfigurableOptimizer opt = new ConfigurableOptimizer(5e17, 2e17, 80, 5e17);
_setupEnvironment(address(opt), false, true, 2000 ether);
// Fund trader with small amount
vm.deal(trader, 5 ether);
vm.prank(trader);
weth.deposit{ value: 5 ether }();
// Tiny buy
_executeBuy(1 ether);
uint256 outstanding = kraiken.outstandingSupply();
uint256 ethBal = address(lm).balance + weth.balanceOf(address(lm));
console2.log(" outstandingSupply:", outstanding);
console2.log(" lm ETH balance:", ethBal);
console2.log(" Doing recenter...");
vm.recordLogs();
_tryRecenter();
_analyzeEvents();
console2.log("");
}
function _analyzeEvents() internal {
Vm.Log[] memory logs = vm.getRecordedLogs();
bytes32 scarcitySig = keccak256("EthScarcity(int24,uint256,uint256,uint256,int24)");
bytes32 abundanceSig = keccak256("EthAbundance(int24,uint256,uint256,uint256,int24)");
bool foundScarcity = false;
bool foundAbundance = false;
for (uint256 i = 0; i < logs.length; i++) {
if (logs[i].topics.length > 0 && logs[i].topics[0] == scarcitySig) {
foundScarcity = true;
(int24 tick, uint256 ethBal, uint256 supply, uint256 vwap, int24 vwapTick) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
console2.log(" >> EthScarcity fired");
console2.log(" ethBalance:", ethBal);
console2.log(" outstandingSupply:", supply);
}
if (logs[i].topics.length > 0 && logs[i].topics[0] == abundanceSig) {
foundAbundance = true;
(int24 tick, uint256 ethBal, uint256 supply, uint256 vwap, int24 vwapTick) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
console2.log(" >> EthAbundance fired");
console2.log(" ethBalance:", ethBal);
console2.log(" outstandingSupply:", supply);
}
}
if (!foundScarcity && !foundAbundance) {
console2.log(" >> No scarcity/abundance event (no VWAP data?)");
}
}
}