harb/onchain/src/OptimizerV2.sol

132 lines
5.6 KiB
Solidity
Raw Normal View History

// 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;
}
}