// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import { Kraiken } from "./Kraiken.sol"; import { Stake } from "./Stake.sol"; import { Math } from "@openzeppelin/utils/math/Math.sol"; import { Initializable } from "@openzeppelin/proxy/utils/Initializable.sol"; import { UUPSUpgradeable } from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol"; /** * @title OptimizerV2 * @notice Sentiment-driven liquidity parameter optimizer based on empirical fuzzing results. * @dev Replaces the original Optimizer with a mapping informed by adversarial analysis: * * Key findings from parameter sweep + Gaussian competition model: * * 1. capitalInefficiency has ZERO effect on fee revenue. It only affects floor placement. * Always set to 0 for maximum safety (adjusted VWAP = 0.7× → furthest floor). * * 2. anchorShare and anchorWidth are the ONLY fee levers: * - Bull: AS=100%, AW=20 → deep narrow anchor → maximizes KRK fees (which appreciate) * - Bear: AS=10%, AW=100 → thin wide anchor → maximizes WETH fees + safe floor distance * * 3. The two regimes naturally align safety with fee optimization: * - Bearish config is also the safest against drain attacks (AW=100 → 7000 tick clamp) * - Bullish config maximizes revenue when floor safety is least needed * * Staking sentiment drives the interpolation: * - High staking % + low tax rate → bullish (sentiment=0) → aggressive fee capture * - Low staking % + high tax rate → bearish (sentiment=1e18) → defensive positioning */ contract OptimizerV2 is Initializable, UUPSUpgradeable { Kraiken private kraiken; Stake private stake; /// @dev Reverts if the caller is not the admin. error UnauthorizedAccount(address account); 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 Calculates sentiment from staking metrics. * @dev Reuses the V1 sentiment formula for continuity. * sentiment = 0 → bullish, sentiment = 1e18 → bearish. */ function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked) public pure returns (uint256 sentimentValue) { require(percentageStaked <= 1e18, "Invalid percentage staked"); uint256 deltaS = 1e18 - percentageStaked; if (percentageStaked > 92e16) { uint256 penalty = (deltaS * deltaS * deltaS * averageTaxRate) / (20 * 1e48); sentimentValue = penalty / 2; } else { uint256 scaledStake = (percentageStaked * 1e18) / (92e16); uint256 baseSentiment = scaledStake >= 1e18 ? 0 : 1e18 - scaledStake; if (averageTaxRate <= 1e16) { sentimentValue = baseSentiment; } else if (averageTaxRate <= 5e16) { uint256 ratePenalty = ((averageTaxRate - 1e16) * baseSentiment) / (4e16); sentimentValue = baseSentiment > ratePenalty ? baseSentiment - ratePenalty : 0; } else { sentimentValue = 1e18; } } } function getSentiment() external view returns (uint256 sentiment) { uint256 percentageStaked = stake.getPercentageStaked(); uint256 averageTaxRate = stake.getAverageTaxRate(); sentiment = calculateSentiment(averageTaxRate, percentageStaked); } /** * @notice Returns liquidity parameters driven by staking sentiment. * * @return capitalInefficiency Always 0 — maximizes floor safety with no fee cost. * @return anchorShare sqrt-scaled: bull(0)=100% → bear(1e18)=10%. * @return anchorWidth sqrt-scaled: bull(0)=20 → bear(1e18)=100. * @return discoveryDepth Interpolated with sentiment (unchanged from V1). * * @dev Uses square-root response curve for ASYMMETRIC transitions: * - Slow ramp to bull: requires sustained high staking to reach aggressive params * - Fast snap to bear: small drops in staking cause large safety jumps * Makes staking manipulation expensive: attacker must maintain >90% staking. */ function getLiquidityParams() external view returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth) { uint256 percentageStaked = stake.getPercentageStaked(); uint256 averageTaxRate = stake.getAverageTaxRate(); uint256 sentiment = calculateSentiment(averageTaxRate, percentageStaked); if (sentiment > 1e18) sentiment = 1e18; // CI = 0 always. No fee impact, maximum floor safety. capitalInefficiency = 0; // sqrt(sentiment) for aggressive bear transition: // sentiment=2.2% (staking=90%) → sqrtS=14.8% → already shifting defensive // sentiment=13% (staking=80%) → sqrtS=36% → well into defensive // sentiment=100% (staking=0%) → sqrtS=100% → full bear uint256 sqrtS = Math.sqrt(sentiment * 1e18); // sqrtS is now in range [0, 1e18]. Scale to match sentiment range. // AS: 100% (bull) → 10% (bear), sqrt-scaled anchorShare = 1e18 - (sqrtS * 90 / 100); // AW: 20 (bull) → 100 (bear), sqrt-scaled anchorWidth = uint24(20 + (sqrtS * 80 / 1e18)); // DD: keep sentiment-driven (V1 behavior) discoveryDepth = sentiment; } }