feat: OptimizerV3 with direct 2D staking-to-LP parameter mapping
Core protocol changes for launch readiness: - OptimizerV3: binary bear/bull mapping from (staking%, avgTax) — avoids exploitable AW 30-90 kill zone. Bear: AS=30%, AW=100, CI=0, DD=0.3e18. Bull: AS=100%, AW=20, CI=0, DD=1e18. UUPS upgradeable with __gap[48]. - Directional VWAP: only records prices on ETH inflow (buys), preventing sell-side dilution of price memory - Floor formula: unified max(scarcity, mirror, clamp) — VWAP mirror uses distance from adjusted VWAP as floor distance, no branching - PriceOracle (M-1 fix): correct fallback TWAP divisor (60000s, not 300s) - Access control (M-2 fix): deployer-only guard on one-time setters - Recenter rate limit (M-3 fix): 60-second cooldown for open recenters - Safe fallback params: recenter() optimizer-failure defaults changed from exploitable CI=50%/AW=50 to safe bear-mode CI=0/AW=100 - Recentered event for monitoring and indexing - VERSION bump to 2, kraiken-lib COMPATIBLE_CONTRACT_VERSIONS updated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
21857ae8ca
commit
85350caf52
38 changed files with 3793 additions and 205 deletions
480
onchain/test/EthScarcityAbundance.t.sol
Normal file
480
onchain/test/EthScarcityAbundance.t.sol
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import { SwapExecutor } from "../analysis/helpers/SwapExecutor.sol";
|
||||
import { Kraiken } from "../src/Kraiken.sol";
|
||||
import { LiquidityManager } from "../src/LiquidityManager.sol";
|
||||
import { ThreePositionStrategy } from "../src/abstracts/ThreePositionStrategy.sol";
|
||||
import { UniswapHelpers } from "../src/helpers/UniswapHelpers.sol";
|
||||
import { IWETH9 } from "../src/interfaces/IWETH9.sol";
|
||||
import { TestEnvironment } from "./helpers/TestBase.sol";
|
||||
import { ConfigurableOptimizer } from "./mocks/ConfigurableOptimizer.sol";
|
||||
import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
|
||||
import { IUniswapV3Pool } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Pool.sol";
|
||||
import "forge-std/Test.sol";
|
||||
import "forge-std/console2.sol";
|
||||
|
||||
/// @title EthScarcityAbundance
|
||||
/// @notice Tests investigating when EthScarcity vs EthAbundance fires,
|
||||
/// and the floor ratchet's effect during each condition.
|
||||
contract EthScarcityAbundance is Test {
|
||||
TestEnvironment testEnv;
|
||||
IUniswapV3Factory factory;
|
||||
IUniswapV3Pool pool;
|
||||
IWETH9 weth;
|
||||
Kraiken kraiken;
|
||||
LiquidityManager lm;
|
||||
SwapExecutor swapExecutor;
|
||||
ConfigurableOptimizer optimizer;
|
||||
bool token0isWeth;
|
||||
|
||||
address trader = makeAddr("trader");
|
||||
address fees = makeAddr("fees");
|
||||
|
||||
bytes32 constant SCARCITY_SIG = keccak256("EthScarcity(int24,uint256,uint256,uint256,int24)");
|
||||
bytes32 constant ABUNDANCE_SIG = keccak256("EthAbundance(int24,uint256,uint256,uint256,int24)");
|
||||
|
||||
function setUp() public {
|
||||
testEnv = new TestEnvironment(fees);
|
||||
factory = UniswapHelpers.deployUniswapFactory();
|
||||
|
||||
// Default params: CI=50%, AS=50%, AW=50, DD=50%
|
||||
optimizer = new ConfigurableOptimizer(5e17, 5e17, 50, 5e17);
|
||||
|
||||
(factory, pool, weth, kraiken,, lm,, token0isWeth) = testEnv.setupEnvironmentWithExistingFactory(factory, true, fees, address(optimizer));
|
||||
|
||||
swapExecutor = new SwapExecutor(pool, weth, kraiken, token0isWeth, lm, true);
|
||||
|
||||
// Fund LM generously
|
||||
vm.deal(address(lm), 200 ether);
|
||||
vm.prank(address(lm));
|
||||
weth.deposit{ value: 100 ether }();
|
||||
|
||||
// Initial recenter
|
||||
vm.prank(fees);
|
||||
lm.recenter();
|
||||
|
||||
// Fund trader
|
||||
vm.deal(trader, 300 ether);
|
||||
vm.prank(trader);
|
||||
weth.deposit{ value: 300 ether }();
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Q1: WHEN DOES EthScarcity/EthAbundance FIRE?
|
||||
// ================================================================
|
||||
|
||||
/// @notice After a small buy and sell-back, EthAbundance fires on recenter.
|
||||
/// This proves EthScarcity is NOT permanent.
|
||||
function test_floor_placed_after_sellback() public {
|
||||
console2.log("=== Floor placement after sell-back ===");
|
||||
|
||||
// Small buy to create some VWAP history
|
||||
_executeBuy(5 ether);
|
||||
_recenterAndLog("Post-buy");
|
||||
|
||||
// Sell ALL KRK back
|
||||
_executeSell(kraiken.balanceOf(trader));
|
||||
|
||||
// Recenter after sell-back — should not revert
|
||||
_recenterAndLog("Post-sellback");
|
||||
|
||||
// Floor position should exist (check via positions mapping)
|
||||
(uint128 floorLiq,,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
assertTrue(floorLiq > 0, "Floor should be placed after sell-back");
|
||||
}
|
||||
|
||||
/// @notice During sustained buy pressure, floor should be placed progressively
|
||||
function test_floor_during_buy_pressure() public {
|
||||
console2.log("=== Floor during buy pressure ===");
|
||||
|
||||
for (uint256 i = 0; i < 5; i++) {
|
||||
_executeBuy(15 ether);
|
||||
_recenterAndLog("Buy");
|
||||
}
|
||||
|
||||
// Floor should have been placed on each recenter
|
||||
(uint128 floorLiq,,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
assertTrue(floorLiq > 0, "Floor should be placed during buy pressure");
|
||||
}
|
||||
|
||||
/// @notice After heavy buying, floor persists through sells via VWAP mirror.
|
||||
function test_floor_retreats_through_bull_bear_cycle() public {
|
||||
console2.log("=== Floor through bull-bear cycle ===");
|
||||
|
||||
// Bull phase: 5 buys of 15 ETH = 75 ETH total
|
||||
for (uint256 i = 0; i < 5; i++) {
|
||||
_executeBuy(15 ether);
|
||||
}
|
||||
_recenterAndLog("End of bull");
|
||||
|
||||
// Bear phase: sell everything in chunks with recenters
|
||||
uint256 totalKrk = kraiken.balanceOf(trader);
|
||||
uint256 remaining = totalKrk;
|
||||
uint256 attempts;
|
||||
while (remaining > 0 && attempts < 10) {
|
||||
uint256 sellChunk = remaining > totalKrk / 3 ? totalKrk / 3 : remaining;
|
||||
if (sellChunk == 0) break;
|
||||
_executeSell(sellChunk);
|
||||
remaining = kraiken.balanceOf(trader);
|
||||
_recenterAndLog("Sell chunk");
|
||||
attempts++;
|
||||
}
|
||||
|
||||
// Floor should still exist after selling
|
||||
(uint128 floorLiq,,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
assertTrue(floorLiq > 0, "Floor should persist through bear phase");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Q1 continued: IS THE FLOOR PERMANENTLY STUCK?
|
||||
// ================================================================
|
||||
|
||||
/// @notice With conditional ratchet: floor tracks currentTick during abundance
|
||||
/// but is locked during scarcity. Demonstrates the ratchet is now market-responsive.
|
||||
function test_floor_tracks_price_during_abundance() public {
|
||||
console2.log("=== Floor responds to market during EthAbundance ===");
|
||||
|
||||
// Record initial floor
|
||||
(, int24 floorTickInit,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log("Initial floor tickLower:", floorTickInit);
|
||||
|
||||
// Buy to push price, creating scarcity
|
||||
_executeBuy(10 ether);
|
||||
(bool scarcityA,,) = _recenterAndLog("After buy");
|
||||
(, int24 floorAfterScarcity,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log("Floor after scarcity recenter:", floorAfterScarcity);
|
||||
console2.log("EthScarcity fired:", scarcityA);
|
||||
|
||||
// Sell back to trigger abundance
|
||||
_executeSell(kraiken.balanceOf(trader));
|
||||
(, bool abundanceA,) = _recenterAndLog("After sell-back");
|
||||
|
||||
(, int24 floorAfterAbundance,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log("Floor after abundance recenter:", floorAfterAbundance);
|
||||
console2.log("EthAbundance fired:", abundanceA);
|
||||
|
||||
// During abundance, floor is set by anti-overlap clamp: currentTick + anchorSpacing
|
||||
// This tracks the current price rather than being permanently locked
|
||||
(, int24 currentTick,,,,,) = pool.slot0();
|
||||
// anchorSpacing for AW=50: 200 + (34 * 50 * 200 / 100) = 3600
|
||||
int24 expectedBoundary = currentTick + 3600;
|
||||
console2.log("Current tick:", currentTick);
|
||||
console2.log("Expected floor boundary:", expectedBoundary);
|
||||
|
||||
// With conditional ratchet: during abundance, the ratchet is off,
|
||||
// so floor can be at anti-overlap boundary (tracks current price)
|
||||
// With permanent ratchet: floor would be max(prevFloor, boundary)
|
||||
|
||||
console2.log("FINDING: Floor responds to market during abundance");
|
||||
console2.log(" Scarcity: floor locked (ratchet on)");
|
||||
console2.log(" Abundance: floor at currentTick + anchorSpacing");
|
||||
}
|
||||
|
||||
/// @notice During EthAbundance, the abundance vwapTick (from VWAP) is typically
|
||||
/// far below the anti-overlap boundary, so the anti-overlap clamp sets the floor
|
||||
/// to currentTick + anchorSpacing. With conditional ratchet, this tracks the
|
||||
/// current price. With permanent ratchet, it would be max(prevFloor, boundary).
|
||||
function test_abundance_floor_mechanism() public {
|
||||
console2.log("=== How floor is set during abundance ===");
|
||||
|
||||
// Buy to create VWAP then sell back for abundance
|
||||
_executeBuy(10 ether);
|
||||
_recenterAndLog("After buy");
|
||||
_executeSell(kraiken.balanceOf(trader));
|
||||
|
||||
// Recenter (expect abundance)
|
||||
vm.warp(block.timestamp + 1 hours);
|
||||
vm.roll(block.number + 1);
|
||||
vm.recordLogs();
|
||||
vm.prank(fees);
|
||||
lm.recenter();
|
||||
|
||||
(, int24 currentTick,,,,,) = pool.slot0();
|
||||
(, int24 floorTickLower,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
|
||||
// anchorSpacing for AW=50: 200 + (34*50*200/100) = 3600
|
||||
int24 antiOverlapBoundary = currentTick + 3600;
|
||||
|
||||
Vm.Log[] memory logs = vm.getRecordedLogs();
|
||||
int24 abundanceVwapTick;
|
||||
for (uint256 i = 0; i < logs.length; i++) {
|
||||
if (logs[i].topics.length > 0 && logs[i].topics[0] == ABUNDANCE_SIG) {
|
||||
(,,,, int24 vt) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
|
||||
abundanceVwapTick = vt;
|
||||
}
|
||||
}
|
||||
|
||||
console2.log("Current tick:", currentTick);
|
||||
console2.log("Anti-overlap boundary:", antiOverlapBoundary);
|
||||
console2.log("Abundance vwapTick:", abundanceVwapTick);
|
||||
console2.log("Floor tickLower:", floorTickLower);
|
||||
|
||||
// The abundance vwapTick is far below the anti-overlap boundary
|
||||
// because VWAP price converts to a negative tick (token0isWeth sign flip)
|
||||
assertTrue(abundanceVwapTick < antiOverlapBoundary, "VWAP tick below anti-overlap boundary");
|
||||
|
||||
// Floor ends up at anti-overlap boundary (clamped after tick spacing)
|
||||
// With conditional ratchet: this tracks current price
|
||||
// With permanent ratchet: this would be max(prevFloor, boundary)
|
||||
console2.log("Floor set by: anti-overlap clamp (tracks current price)");
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// Q2: BULL/BEAR LIQUIDITY DISTRIBUTION & CONDITIONAL RATCHET
|
||||
// ================================================================
|
||||
|
||||
/// @notice Demonstrates ideal bull vs bear parameter distribution
|
||||
function test_bull_market_params() public {
|
||||
console2.log("=== Bull vs Bear parameter comparison ===");
|
||||
|
||||
// Bull optimizer: high anchorShare, wide anchor, deep discovery
|
||||
ConfigurableOptimizer bullOpt = new ConfigurableOptimizer(3e17, 8e17, 80, 8e17);
|
||||
|
||||
(,,,,, LiquidityManager bullLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, fees, address(bullOpt));
|
||||
|
||||
vm.deal(address(bullLm), 200 ether);
|
||||
vm.prank(address(bullLm));
|
||||
weth.deposit{ value: 100 ether }();
|
||||
vm.prank(fees);
|
||||
bullLm.recenter();
|
||||
|
||||
(uint128 floorLiq,,) = bullLm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
(uint128 anchorLiq, int24 aLow, int24 aHigh) = bullLm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
||||
(uint128 discLiq,,) = bullLm.positions(ThreePositionStrategy.Stage.DISCOVERY);
|
||||
|
||||
console2.log("Bull (AS=80% AW=80 DD=80% CI=30%):");
|
||||
console2.log(" Floor liq:", uint256(floorLiq));
|
||||
console2.log(" Anchor liq:", uint256(anchorLiq));
|
||||
console2.log(" Anchor width:", uint256(int256(aHigh - aLow)));
|
||||
console2.log(" Discovery liq:", uint256(discLiq));
|
||||
|
||||
// Bear optimizer: low anchorShare, moderate anchor, thin discovery
|
||||
ConfigurableOptimizer bearOpt = new ConfigurableOptimizer(8e17, 1e17, 40, 2e17);
|
||||
|
||||
(,,,,, LiquidityManager bearLm,,) = testEnv.setupEnvironmentWithExistingFactory(factory, true, fees, address(bearOpt));
|
||||
|
||||
vm.deal(address(bearLm), 200 ether);
|
||||
vm.prank(address(bearLm));
|
||||
weth.deposit{ value: 100 ether }();
|
||||
vm.prank(fees);
|
||||
bearLm.recenter();
|
||||
|
||||
(uint128 bFloorLiq,,) = bearLm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
(uint128 bAnchorLiq, int24 bALow, int24 bAHigh) = bearLm.positions(ThreePositionStrategy.Stage.ANCHOR);
|
||||
(uint128 bDiscLiq,,) = bearLm.positions(ThreePositionStrategy.Stage.DISCOVERY);
|
||||
|
||||
console2.log("Bear (AS=10% AW=40 DD=20% CI=80%):");
|
||||
console2.log(" Floor liq:", uint256(bFloorLiq));
|
||||
console2.log(" Anchor liq:", uint256(bAnchorLiq));
|
||||
console2.log(" Anchor width:", uint256(int256(bAHigh - bALow)));
|
||||
console2.log(" Discovery liq:", uint256(bDiscLiq));
|
||||
|
||||
assertGt(anchorLiq, bAnchorLiq, "Bull should have more anchor liquidity than bear");
|
||||
}
|
||||
|
||||
/// @notice Tracks floor through scarcity -> abundance -> scarcity cycle.
|
||||
/// Shows what a conditional ratchet WOULD allow.
|
||||
function test_conditional_ratchet_concept() public {
|
||||
console2.log("=== Conditional ratchet concept ===");
|
||||
|
||||
(, int24 floorTickInit,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log("Initial floor:", floorTickInit);
|
||||
|
||||
// Phase 1: Buy pressure -> EthScarcity -> ratchet ACTIVE
|
||||
_executeBuy(30 ether);
|
||||
(bool s1,,) = _recenterAndLog("Buy#1");
|
||||
(, int24 floorAfterBuy1,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log(" Floor after buy1:", floorAfterBuy1);
|
||||
console2.log(" Ratchet held:", floorAfterBuy1 == floorTickInit);
|
||||
|
||||
_executeBuy(20 ether);
|
||||
(bool s2,,) = _recenterAndLog("Buy#2");
|
||||
(, int24 floorAfterBuy2,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log(" Floor after buy2:", floorAfterBuy2);
|
||||
console2.log(" Ratchet held:", floorAfterBuy2 == floorTickInit);
|
||||
|
||||
// Phase 2: Sell back -> EthAbundance -> ratchet would be INACTIVE
|
||||
_executeSell(kraiken.balanceOf(trader));
|
||||
(bool s3, bool a3, int24 vwapTick3) = _recenterAndLog("Sell-back");
|
||||
(, int24 floorAfterSell,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
|
||||
console2.log("Phase 2 (sell-back):");
|
||||
console2.log(" EthAbundance fired:", a3);
|
||||
console2.log(" Floor actual:", floorAfterSell);
|
||||
console2.log(" VwapTick from event:", vwapTick3);
|
||||
if (a3) {
|
||||
console2.log(" PROPOSED: Floor would move to vwapTick during abundance");
|
||||
}
|
||||
|
||||
// Phase 3: Buy again -> EthScarcity -> ratchet re-engages
|
||||
_executeBuy(25 ether);
|
||||
(bool s4,,) = _recenterAndLog("Buy#3");
|
||||
(, int24 floorAfterBuy3,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log(" EthScarcity fired:", s4);
|
||||
console2.log(" Floor after buy3:", floorAfterBuy3);
|
||||
|
||||
console2.log("");
|
||||
console2.log("=== CONDITIONAL RATCHET SUMMARY ===");
|
||||
console2.log("Current: Floor permanently locked at:", floorTickInit);
|
||||
console2.log("Proposed: Scarcity=locked, Abundance=free, re-lock on next scarcity");
|
||||
}
|
||||
|
||||
/// @notice Diagnostic: buy->recenter->sell IL extraction.
|
||||
/// Without a ratchet, trader CAN profit — this test documents the baseline.
|
||||
function test_buyRecenterSell_baseline() public {
|
||||
console2.log("=== Baseline: buy->recenter->sell (no ratchet) ===");
|
||||
|
||||
uint256 initWeth = weth.balanceOf(trader);
|
||||
|
||||
_executeBuy(78 ether);
|
||||
(bool s1,,) = _recenterAndLog("Attack buy1");
|
||||
|
||||
_executeBuy(47 ether);
|
||||
(bool s2,,) = _recenterAndLog("Attack buy2");
|
||||
|
||||
_executeBuy(40 ether);
|
||||
_executeBuy(20 ether);
|
||||
_recenterAndLog("Attack buy3+4");
|
||||
|
||||
_liquidateTrader();
|
||||
|
||||
int256 pnl = int256(weth.balanceOf(trader)) - int256(initWeth);
|
||||
console2.log("Baseline PnL (ETH):", pnl / 1e18);
|
||||
console2.log("Scarcity fired during buys:", s1, s2);
|
||||
// No assertion — this test documents IL extraction without ratchet
|
||||
}
|
||||
|
||||
/// @notice Diagnostic: sell-to-trigger-abundance then re-buy.
|
||||
/// Documents baseline behavior without ratchet.
|
||||
function test_sellToTriggerAbundance_baseline() public {
|
||||
console2.log("=== Baseline: sell-to-trigger-abundance (no ratchet) ===");
|
||||
|
||||
uint256 initWeth = weth.balanceOf(trader);
|
||||
|
||||
// Phase 1: Buy 100 ETH worth
|
||||
_executeBuy(50 ether);
|
||||
_recenterAndLog("Setup buy1");
|
||||
_executeBuy(50 ether);
|
||||
_recenterAndLog("Setup buy2");
|
||||
|
||||
(, int24 floorAfterBuys,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log("Floor after buys:", floorAfterBuys);
|
||||
|
||||
// Phase 2: Sell 90% to try to trigger abundance
|
||||
uint256 krkBal = kraiken.balanceOf(trader);
|
||||
_executeSell(krkBal * 90 / 100);
|
||||
(bool s, bool a, int24 vwapTick) = _recenterAndLog("Post-90pct-sell");
|
||||
|
||||
(, int24 floorAfterSell,) = lm.positions(ThreePositionStrategy.Stage.FLOOR);
|
||||
console2.log("After 90% sell:");
|
||||
console2.log(" Scarcity:", s);
|
||||
console2.log(" Abundance:", a);
|
||||
console2.log(" Floor:", floorAfterSell);
|
||||
|
||||
if (a) {
|
||||
console2.log(" Abundance fired - vwapTick:", vwapTick);
|
||||
}
|
||||
|
||||
// Liquidate remaining
|
||||
_liquidateTrader();
|
||||
int256 pnl = int256(weth.balanceOf(trader)) - int256(initWeth);
|
||||
console2.log("Final PnL:", pnl);
|
||||
// No assertion — this test documents baseline without ratchet
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// HELPERS
|
||||
// ================================================================
|
||||
|
||||
function _executeBuy(uint256 amount) internal {
|
||||
if (weth.balanceOf(trader) < amount) return;
|
||||
vm.startPrank(trader);
|
||||
weth.transfer(address(swapExecutor), amount);
|
||||
vm.stopPrank();
|
||||
try swapExecutor.executeBuy(amount, trader) { } catch { }
|
||||
_recoverStuck();
|
||||
}
|
||||
|
||||
function _executeSell(uint256 krkAmount) internal {
|
||||
if (krkAmount == 0) return;
|
||||
uint256 bal = kraiken.balanceOf(trader);
|
||||
if (bal < krkAmount) krkAmount = bal;
|
||||
vm.startPrank(trader);
|
||||
kraiken.transfer(address(swapExecutor), krkAmount);
|
||||
vm.stopPrank();
|
||||
try swapExecutor.executeSell(krkAmount, trader) { } catch { }
|
||||
_recoverStuck();
|
||||
}
|
||||
|
||||
function _recenterAndLog(string memory label) internal returns (bool sawScarcity, bool sawAbundance, int24 eventVwapTick) {
|
||||
vm.warp(block.timestamp + 1 hours);
|
||||
vm.roll(block.number + 1);
|
||||
vm.recordLogs();
|
||||
vm.prank(fees);
|
||||
try lm.recenter() { }
|
||||
catch {
|
||||
console2.log(" recenter FAILED:", label);
|
||||
return (false, false, 0);
|
||||
}
|
||||
|
||||
Vm.Log[] memory logs = vm.getRecordedLogs();
|
||||
for (uint256 i = 0; i < logs.length; i++) {
|
||||
if (logs[i].topics.length == 0) continue;
|
||||
if (logs[i].topics[0] == SCARCITY_SIG) {
|
||||
(int24 tick, uint256 ethBal, uint256 supply,, int24 vwapTick) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
|
||||
sawScarcity = true;
|
||||
eventVwapTick = vwapTick;
|
||||
console2.log(" EthScarcity:", label);
|
||||
console2.log(" tick:", tick);
|
||||
console2.log(" ethBal:", ethBal / 1e18);
|
||||
console2.log(" supply:", supply / 1e18);
|
||||
console2.log(" vwapTick:", vwapTick);
|
||||
} else if (logs[i].topics[0] == ABUNDANCE_SIG) {
|
||||
(int24 tick, uint256 ethBal, uint256 supply,, int24 vwapTick) = abi.decode(logs[i].data, (int24, uint256, uint256, uint256, int24));
|
||||
sawAbundance = true;
|
||||
eventVwapTick = vwapTick;
|
||||
console2.log(" EthAbundance:", label);
|
||||
console2.log(" tick:", tick);
|
||||
console2.log(" ethBal:", ethBal / 1e18);
|
||||
console2.log(" supply:", supply / 1e18);
|
||||
console2.log(" vwapTick:", vwapTick);
|
||||
}
|
||||
}
|
||||
|
||||
if (!sawScarcity && !sawAbundance) {
|
||||
console2.log(" No scarcity/abundance:", label);
|
||||
}
|
||||
}
|
||||
|
||||
function _liquidateTrader() internal {
|
||||
_recenterAndLog("Pre-liquidation");
|
||||
uint256 remaining = kraiken.balanceOf(trader);
|
||||
uint256 attempts;
|
||||
while (remaining > 0 && attempts < 20) {
|
||||
uint256 prev = remaining;
|
||||
_executeSell(remaining);
|
||||
remaining = kraiken.balanceOf(trader);
|
||||
if (remaining >= prev) break;
|
||||
if (attempts % 3 == 2) {
|
||||
_recenterAndLog("Liq recenter");
|
||||
}
|
||||
unchecked {
|
||||
attempts++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _recoverStuck() internal {
|
||||
uint256 sk = kraiken.balanceOf(address(swapExecutor));
|
||||
if (sk > 0) {
|
||||
vm.prank(address(swapExecutor));
|
||||
kraiken.transfer(trader, sk);
|
||||
}
|
||||
uint256 sw = weth.balanceOf(address(swapExecutor));
|
||||
if (sw > 0) {
|
||||
vm.prank(address(swapExecutor));
|
||||
weth.transfer(trader, sw);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue