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
|
|
@ -56,11 +56,14 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
/// @notice Storage for the three positions
|
||||
mapping(Stage => TokenPosition) public positions;
|
||||
|
||||
/// @notice Deprecated — was floor high-water mark. Kept for storage layout compatibility.
|
||||
int24 public __deprecated_floorHighWaterMark;
|
||||
|
||||
/// @notice Events for tracking ETH abundance/scarcity scenarios
|
||||
event EthScarcity(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
|
||||
event EthAbundance(int24 currentTick, uint256 ethBalance, uint256 outstandingSupply, uint256 vwap, int24 vwapTick);
|
||||
|
||||
/// @notice Abstract functions that must be implemented by inheriting contracts
|
||||
|
||||
function _getKraikenToken() internal view virtual returns (address);
|
||||
function _getWethToken() internal view virtual returns (address);
|
||||
function _isToken0Weth() internal view virtual returns (bool);
|
||||
|
|
@ -188,43 +191,10 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
outstandingSupply -= pulledKraiken;
|
||||
outstandingSupply -= (outstandingSupply >= discoveryAmount) ? discoveryAmount : outstandingSupply;
|
||||
|
||||
// Use VWAP for floor position (historical price memory for dormant whale protection)
|
||||
uint256 vwapX96 = getAdjustedVWAP(params.capitalInefficiency);
|
||||
uint256 ethBalance = _getEthBalance();
|
||||
int24 vwapTick;
|
||||
|
||||
if (vwapX96 > 0) {
|
||||
// vwapX96 is price² in X96 format, need to convert to regular price
|
||||
// price = sqrt(price²) = sqrt(vwapX96) * 2^48 / 2^96 = sqrt(vwapX96) / 2^48
|
||||
uint256 sqrtVwapX96 = Math.sqrt(vwapX96) << 48; // sqrt(price²) in X96 format
|
||||
uint256 requiredEthForBuyback = outstandingSupply.mulDiv(sqrtVwapX96, (1 << 96));
|
||||
|
||||
if (floorEthBalance < requiredEthForBuyback) {
|
||||
// ETH scarcity: not enough ETH to buy back at VWAP price
|
||||
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * params.capitalInefficiency / 10 ** 18);
|
||||
vwapTick = _tickAtPrice(token0isWeth, balancedCapital, floorEthBalance);
|
||||
emit EthScarcity(currentTick, ethBalance, outstandingSupply, vwapX96, vwapTick);
|
||||
} else {
|
||||
// ETH abundance: sufficient ETH reserves
|
||||
// vwapX96 is price² in X96 format, need to convert to regular price in X64 format
|
||||
// price = sqrt(price²), then convert from X96 to X64 by >> 32
|
||||
uint256 sqrtVwapX96Abundance = Math.sqrt(vwapX96) << 48; // sqrt(price²) in X96 format
|
||||
vwapTick = _tickAtPriceRatio(int128(int256(sqrtVwapX96Abundance >> 32)));
|
||||
vwapTick = token0isWeth ? -vwapTick : vwapTick;
|
||||
emit EthAbundance(currentTick, ethBalance, outstandingSupply, vwapX96, vwapTick);
|
||||
}
|
||||
} else {
|
||||
// No VWAP data available, use current tick
|
||||
vwapTick = currentTick;
|
||||
}
|
||||
|
||||
// Ensure floor doesn't overlap with anchor position
|
||||
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
|
||||
if (token0isWeth) {
|
||||
vwapTick = (vwapTick < currentTick + anchorSpacing) ? currentTick + anchorSpacing : vwapTick;
|
||||
} else {
|
||||
vwapTick = (vwapTick > currentTick - anchorSpacing) ? currentTick - anchorSpacing : vwapTick;
|
||||
}
|
||||
// Floor placement: max of (scarcity, VWAP mirror, clamp) toward KRK-cheap side.
|
||||
// VWAP mirror uses distance from VWAP as floor distance — during selling, price moves
|
||||
// away from VWAP so floor retreats automatically. No sell-pressure detection needed.
|
||||
int24 vwapTick = _computeFloorTick(currentTick, floorEthBalance, outstandingSupply, token0isWeth, params);
|
||||
|
||||
// Normalize and create floor position
|
||||
vwapTick = _clampToTickSpacing(vwapTick, TICK_SPACING);
|
||||
|
|
@ -246,4 +216,54 @@ abstract contract ThreePositionStrategy is UniswapMath, VWAPTracker {
|
|||
|
||||
_mintPosition(Stage.FLOOR, token0isWeth ? vwapTick : floorTick, token0isWeth ? floorTick : vwapTick, liquidity);
|
||||
}
|
||||
|
||||
/// @notice Computes floor tick from three signals: scarcity, VWAP mirror, and anti-overlap clamp.
|
||||
/// @dev Takes the one furthest into KRK-cheap territory (highest tick when token0isWeth, lowest when not).
|
||||
function _computeFloorTick(
|
||||
int24 currentTick,
|
||||
uint256 floorEthBalance,
|
||||
uint256 outstandingSupply,
|
||||
bool token0isWeth,
|
||||
PositionParams memory params
|
||||
)
|
||||
internal
|
||||
view
|
||||
returns (int24 floorTarget)
|
||||
{
|
||||
// 1. Scarcity tick: at what price can our ETH buy back the adjusted supply?
|
||||
uint256 balancedCapital = (7 * outstandingSupply / 10) + (outstandingSupply * params.capitalInefficiency / 10 ** 18);
|
||||
int24 scarcityTick = currentTick;
|
||||
if (outstandingSupply > 0 && floorEthBalance > 0) {
|
||||
scarcityTick = _tickAtPrice(token0isWeth, balancedCapital, floorEthBalance);
|
||||
}
|
||||
|
||||
// 2. Mirror tick: VWAP distance mirrored to KRK-cheap side
|
||||
// Uses adjusted VWAP (CI controls distance → CI is the risk lever).
|
||||
int24 mirrorTick = currentTick;
|
||||
{
|
||||
uint256 vwapX96 = getAdjustedVWAP(params.capitalInefficiency);
|
||||
if (vwapX96 > 0) {
|
||||
int24 rawVwapTick = _tickAtPriceRatio(int128(int256(vwapX96 >> 32)));
|
||||
rawVwapTick = token0isWeth ? -rawVwapTick : rawVwapTick;
|
||||
int24 vwapDistance = currentTick - rawVwapTick;
|
||||
if (vwapDistance < 0) vwapDistance = -vwapDistance;
|
||||
mirrorTick = token0isWeth ? currentTick + vwapDistance : currentTick - vwapDistance;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Clamp tick: minimum distance (anti-overlap with anchor)
|
||||
int24 anchorSpacing = TICK_SPACING + (34 * int24(params.anchorWidth) * TICK_SPACING / 100);
|
||||
int24 clampTick = token0isWeth ? currentTick + anchorSpacing : currentTick - anchorSpacing;
|
||||
|
||||
// Take the one furthest into KRK-cheap territory
|
||||
if (token0isWeth) {
|
||||
floorTarget = scarcityTick;
|
||||
if (mirrorTick > floorTarget) floorTarget = mirrorTick;
|
||||
if (clampTick > floorTarget) floorTarget = clampTick;
|
||||
} else {
|
||||
floorTarget = scarcityTick;
|
||||
if (mirrorTick < floorTarget) floorTarget = mirrorTick;
|
||||
if (clampTick < floorTarget) floorTarget = clampTick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue