192 lines
7.2 KiB
Solidity
192 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?)");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|