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:
openhands 2026-02-13 18:21:18 +00:00
parent 21857ae8ca
commit 85350caf52
38 changed files with 3793 additions and 205 deletions

View file

@ -264,42 +264,47 @@ contract ThreePositionStrategyTest is TestConstants {
assertNotEq(centerTick, CURRENT_TICK, "Floor should not be positioned at current tick when VWAP available");
}
function testFloorPositionEthScarcity() public {
function testFloorPositionScarcityDominates() public {
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
// Set up scenario where ETH is insufficient for VWAP price
// Set up scenario where ETH is insufficient scarcity tick dominates
uint256 vwapX96 = 79_228_162_514_264_337_593_543_950_336 * 10; // High VWAP price
strategy.setVWAP(vwapX96, 1000 ether);
uint256 smallEthBalance = 1 ether; // Insufficient ETH
uint256 smallEthBalance = 1 ether; // Very low ETH scarcity tick far away
uint256 pulledHarb = 1000 ether;
uint256 discoveryAmount = 500 ether;
// Should emit EthScarcity event (check event type, not exact values)
vm.expectEmit(true, false, false, false);
emit ThreePositionStrategy.EthScarcity(CURRENT_TICK, 0, 0, 0, 0);
// Should not revert floor placed using max(scarcity, mirror, clamp)
strategy.setFloorPosition(CURRENT_TICK, smallEthBalance, pulledHarb, discoveryAmount, params);
// Floor should be minted (position exists)
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
assertTrue(pos.liquidity > 0, "Floor should have liquidity");
}
function testFloorPositionEthAbundance() public {
function testFloorPositionMirrorDominates() public {
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
// Set up scenario where ETH is sufficient for VWAP price
// Set up scenario where VWAP is far from current mirror tick dominates
uint256 baseVwap = 79_228_162_514_264_337_593_543_950_336; // 1.0 in X96 format
uint256 vwapX96 = baseVwap / 100_000; // Very low VWAP price to ensure abundance
uint256 vwapX96 = baseVwap / 100_000; // Very low VWAP price far from current
strategy.setVWAP(vwapX96, 1000 ether);
uint256 largeEthBalance = 100_000 ether; // Very large ETH balance
uint256 largeEthBalance = 100_000 ether; // Lots of ETH
uint256 pulledHarb = 1000 ether;
uint256 discoveryAmount = 500 ether;
// Should emit EthAbundance event (check event type, not exact values)
// The exact VWAP and vwapTick values are calculated, so we just check the event type
vm.expectEmit(true, false, false, false);
emit ThreePositionStrategy.EthAbundance(CURRENT_TICK, 0, 0, 0, 0);
strategy.setFloorPosition(CURRENT_TICK, largeEthBalance, pulledHarb, discoveryAmount, params);
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
assertTrue(pos.liquidity > 0, "Floor should have liquidity");
// Floor should be further than just anchorSpacing (mirror should push it)
int24 anchorSpacing = 200 + (34 * 50 * 200 / 100); // 3600
int24 floorCenter = (pos.tickLower + pos.tickUpper) / 2;
// Mirror should push floor significantly beyond clamp minimum
assertTrue(floorCenter > CURRENT_TICK + anchorSpacing + 200, "Mirror should push floor beyond clamp minimum");
}
function testFloorPositionNoVWAP() public {
@ -316,16 +321,34 @@ contract ThreePositionStrategyTest is TestConstants {
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
// Without VWAP, should default to current tick but adjusted for anchor spacing
// Without VWAP, mirror = current tick, so floor uses max(scarcity, clamp)
// With these balances, scarcity tick should dominate (low ETH relative to supply)
int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
// Expected spacing: TICK_SPACING + (34 * anchorWidth * TICK_SPACING / 100) = 200 + (34 * 50 * 200 / 100) = 3600
int24 expectedSpacing = 200 + (34 * 50 * 200 / 100);
assertApproxEqAbs(
uint256(int256(centerTick)),
uint256(int256(CURRENT_TICK + expectedSpacing)),
200,
"Floor should be positioned away from current tick to avoid anchor overlap"
);
// Floor should be above current tick (on KRK-cheap side)
assertTrue(centerTick > CURRENT_TICK, "Floor should be on KRK-cheap side of current tick");
assertTrue(pos.liquidity > 0, "Floor should have liquidity");
}
function testFloorPositionNoVWAPClampOrScarcity() public {
ThreePositionStrategy.PositionParams memory params = getDefaultParams();
// No VWAP data, large ETH balance, small supply
strategy.setVWAP(0, 0);
uint256 floorEthBalance = 100_000 ether; // Very large
uint256 pulledHarb = 100 ether; // Small supply
uint256 discoveryAmount = 50 ether;
strategy.setFloorPosition(CURRENT_TICK, floorEthBalance, pulledHarb, discoveryAmount, params);
MockThreePositionStrategy.MintedPosition memory pos = strategy.getMintedPosition(0);
// With no VWAP: mirror = current. Floor uses max(scarcity, clamp).
// The scarcity formula with small supply and large ETH may still push floor
// significantly beyond the clamp minimum. Just verify floor is on correct side.
int24 centerTick = (pos.tickLower + pos.tickUpper) / 2;
int24 minSpacing = 200 + (34 * 50 * 200 / 100); // 3600
assertTrue(centerTick >= CURRENT_TICK + minSpacing, "Floor should be positioned away from current tick to avoid anchor overlap");
}
function testFloorPositionOutstandingSupplyCalculation() public {