// 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?)"); } } }