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>
191 lines
7.8 KiB
Solidity
191 lines
7.8 KiB
Solidity
// SPDX-License-Identifier: GPL-3.0-or-later
|
||
pragma solidity ^0.8.19;
|
||
|
||
import { Kraiken } from "./Kraiken.sol";
|
||
import { Stake } from "./Stake.sol";
|
||
|
||
import { Initializable } from "@openzeppelin/proxy/utils/Initializable.sol";
|
||
import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
||
|
||
/**
|
||
* @title OptimizerV3
|
||
* @notice Direct 2D (staking%, avgTax) → binary bear/bull liquidity optimizer.
|
||
* @dev Replaces the three-zone score-based model with a direct mapping:
|
||
*
|
||
* staked ≤ 91% → BEAR always (no euphoria signal)
|
||
* staked > 91% → BULL if deltaS³ × effIdx / 20 < 50, else BEAR
|
||
*
|
||
* where deltaS = 100 - stakedPct, effIdx = min(29, taxIdx + (taxIdx >= 14 ? 1 : 0))
|
||
*
|
||
* Bear: AS=30%, AW=100, CI=0, DD=0.3e18
|
||
* Bull: AS=100%, AW=20, CI=0, DD=1e18
|
||
*
|
||
* The binary step avoids the AW 30-90 kill zone where intermediate params are exploitable.
|
||
* CI = 0 always (proven to have zero effect on fee revenue).
|
||
*
|
||
* Bull requires >91% staked with low enough tax. Any decline → instant snap to bear.
|
||
*/
|
||
contract OptimizerV3 is Initializable, UUPSUpgradeable {
|
||
Kraiken private kraiken;
|
||
Stake private stake;
|
||
|
||
/// @dev Reserved storage gap for future upgrades (50 slots total: 2 used + 48 reserved)
|
||
uint256[48] private __gap;
|
||
|
||
/// @dev Reverts if the caller is not the admin.
|
||
error UnauthorizedAccount(address account);
|
||
|
||
/// @notice Initializes the proxy with Kraiken and Stake contract references.
|
||
/// @param _kraiken The Kraiken token contract address
|
||
/// @param _stake The Stake contract address
|
||
function initialize(address _kraiken, address _stake) public initializer {
|
||
_changeAdmin(msg.sender);
|
||
kraiken = Kraiken(_kraiken);
|
||
stake = Stake(_stake);
|
||
}
|
||
|
||
modifier onlyAdmin() {
|
||
_checkAdmin();
|
||
_;
|
||
}
|
||
|
||
function _checkAdmin() internal view virtual {
|
||
if (_getAdmin() != msg.sender) {
|
||
revert UnauthorizedAccount(msg.sender);
|
||
}
|
||
}
|
||
|
||
function _authorizeUpgrade(address newImplementation) internal override onlyAdmin { }
|
||
|
||
/**
|
||
* @notice Maps a normalized average tax rate (0-1e18) to an effective tax rate index.
|
||
* @dev The Stake contract normalizes: averageTaxRate = rawRate * 1e18 / MAX_TAX_RATE.
|
||
* We compare against pre-computed normalized midpoints between adjacent TAX_RATES
|
||
* to find the closest index, avoiding double-truncation from denormalization.
|
||
*
|
||
* The effective index has a +1 shift at position ≥14 to account for the
|
||
* non-uniform spacing in the TAX_RATES array (130% → 180% is a 38% jump),
|
||
* capped at 29.
|
||
*/
|
||
function _taxRateToEffectiveIndex(uint256 averageTaxRate) internal pure returns (uint256) {
|
||
// Pre-computed normalized midpoints between adjacent TAX_RATES:
|
||
// midpoint_norm = ((TAX_RATES[i] + TAX_RATES[i+1]) / 2) * 1e18 / 9700
|
||
// Using these directly avoids integer truncation from denormalization.
|
||
uint256 idx;
|
||
if (averageTaxRate <= 206_185_567_010_309) idx = 0; // midpoint(1,3)
|
||
|
||
else if (averageTaxRate <= 412_371_134_020_618) idx = 1; // midpoint(3,5)
|
||
|
||
else if (averageTaxRate <= 618_556_701_030_927) idx = 2; // midpoint(5,8)
|
||
|
||
else if (averageTaxRate <= 1_030_927_835_051_546) idx = 3; // midpoint(8,12)
|
||
|
||
else if (averageTaxRate <= 1_546_391_752_577_319) idx = 4; // midpoint(12,18)
|
||
|
||
else if (averageTaxRate <= 2_164_948_453_608_247) idx = 5; // midpoint(18,24)
|
||
|
||
else if (averageTaxRate <= 2_783_505_154_639_175) idx = 6; // midpoint(24,30)
|
||
|
||
else if (averageTaxRate <= 3_608_247_422_680_412) idx = 7; // midpoint(30,40)
|
||
|
||
else if (averageTaxRate <= 4_639_175_257_731_958) idx = 8; // midpoint(40,50)
|
||
|
||
else if (averageTaxRate <= 5_670_103_092_783_505) idx = 9; // midpoint(50,60)
|
||
|
||
else if (averageTaxRate <= 7_216_494_845_360_824) idx = 10; // midpoint(60,80)
|
||
|
||
else if (averageTaxRate <= 9_278_350_515_463_917) idx = 11; // midpoint(80,100)
|
||
|
||
else if (averageTaxRate <= 11_855_670_103_092_783) idx = 12; // midpoint(100,130)
|
||
|
||
else if (averageTaxRate <= 15_979_381_443_298_969) idx = 13; // midpoint(130,180)
|
||
|
||
else if (averageTaxRate <= 22_164_948_453_608_247) idx = 14; // midpoint(180,250)
|
||
|
||
else if (averageTaxRate <= 29_381_443_298_969_072) idx = 15; // midpoint(250,320)
|
||
|
||
else if (averageTaxRate <= 38_144_329_896_907_216) idx = 16; // midpoint(320,420)
|
||
|
||
else if (averageTaxRate <= 49_484_536_082_474_226) idx = 17; // midpoint(420,540)
|
||
|
||
else if (averageTaxRate <= 63_917_525_773_195_876) idx = 18; // midpoint(540,700)
|
||
|
||
else if (averageTaxRate <= 83_505_154_639_175_257) idx = 19; // midpoint(700,920)
|
||
|
||
else if (averageTaxRate <= 109_278_350_515_463_917) idx = 20; // midpoint(920,1200)
|
||
|
||
else if (averageTaxRate <= 144_329_896_907_216_494) idx = 21; // midpoint(1200,1600)
|
||
|
||
else if (averageTaxRate <= 185_567_010_309_278_350) idx = 22; // midpoint(1600,2000)
|
||
|
||
else if (averageTaxRate <= 237_113_402_061_855_670) idx = 23; // midpoint(2000,2600)
|
||
|
||
else if (averageTaxRate <= 309_278_350_515_463_917) idx = 24; // midpoint(2600,3400)
|
||
|
||
else if (averageTaxRate <= 402_061_855_670_103_092) idx = 25; // midpoint(3400,4400)
|
||
|
||
else if (averageTaxRate <= 520_618_556_701_030_927) idx = 26; // midpoint(4400,5700)
|
||
|
||
else if (averageTaxRate <= 680_412_371_134_020_618) idx = 27; // midpoint(5700,7500)
|
||
|
||
else if (averageTaxRate <= 886_597_938_144_329_896) idx = 28; // midpoint(7500,9700)
|
||
|
||
else idx = 29;
|
||
|
||
// Apply effective index shift: +1 at idx >= 14, capped at 29
|
||
if (idx >= 14) {
|
||
idx = idx + 1;
|
||
if (idx > 29) idx = 29;
|
||
}
|
||
|
||
return idx;
|
||
}
|
||
|
||
/**
|
||
* @notice Determines if the market is in bull configuration.
|
||
* @param percentageStaked Percentage of authorized stake in use (0 to 1e18).
|
||
* @param averageTaxRate Normalized average tax rate from Stake contract (0 to 1e18).
|
||
* @return bull True if bull config, false if bear.
|
||
*
|
||
* @dev Direct 2D mapping — no intermediate score:
|
||
* staked ≤ 91% → always bear (no euphoria signal)
|
||
* staked > 91% → bull if deltaS³ × effIdx / 20 < 50
|
||
* where deltaS = 100 - stakedPct (integer percentage)
|
||
*/
|
||
function isBullMarket(uint256 percentageStaked, uint256 averageTaxRate) public pure returns (bool bull) {
|
||
require(percentageStaked <= 1e18, "Invalid percentage staked");
|
||
|
||
uint256 stakedPct = percentageStaked * 100 / 1e18; // 0-100
|
||
if (stakedPct <= 91) return false;
|
||
|
||
uint256 deltaS = 100 - stakedPct; // 0-8
|
||
uint256 effIdx = _taxRateToEffectiveIndex(averageTaxRate);
|
||
uint256 penalty = deltaS * deltaS * deltaS * effIdx / 20;
|
||
return penalty < 50;
|
||
}
|
||
|
||
/**
|
||
* @notice Returns liquidity parameters driven by the direct 2D staking→config mapping.
|
||
*
|
||
* @return capitalInefficiency Always 0 — proven to have zero effect on fee revenue.
|
||
* @return anchorShare Bear=30% (0.3e18), Bull=100% (1e18).
|
||
* @return anchorWidth Bear=100, Bull=20.
|
||
* @return discoveryDepth Bear=0.3e18, Bull=1e18.
|
||
*/
|
||
function getLiquidityParams() external view returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) {
|
||
uint256 percentageStaked = stake.getPercentageStaked();
|
||
uint256 averageTaxRate = stake.getAverageTaxRate();
|
||
|
||
capitalInefficiency = 0;
|
||
|
||
if (isBullMarket(percentageStaked, averageTaxRate)) {
|
||
anchorShare = 1e18; // 100%
|
||
anchorWidth = 20;
|
||
discoveryDepth = 1e18;
|
||
} else {
|
||
anchorShare = 3e17; // 30%
|
||
anchorWidth = 100;
|
||
discoveryDepth = 3e17; // 0.3e18
|
||
}
|
||
}
|
||
}
|