// SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.19; import {Kraiken} from "./Kraiken.sol"; import {Stake} from "./Stake.sol"; import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol"; import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol"; /** * @title Optimizer * @notice This contract (formerly Sentimenter) calculates a "sentiment" value and liquidity parameters * based on the tax rate and the percentage of Kraiken staked. * @dev It is upgradeable using UUPS. Only the admin (set during initialization) can upgrade. * * Key features: * - Analyzes staking sentiment (% staked, average tax rate) * - Returns four key parameters for liquidity management: * 1. capitalInefficiency (0 to 1e18): Capital buffer level * 2. anchorShare (0 to 1e18): % of non-floor ETH in anchor * 3. anchorWidth (0 to 100): Anchor position width % * 4. discoveryDepth (0 to 1e18): Discovery liquidity density (2x-10x) * - Upgradeable for future algorithm improvements */ contract Optimizer is Initializable, UUPSUpgradeable { Kraiken private harberg; Stake private stake; /// @dev Reverts if the caller is not the admin. error UnauthorizedAccount(address account); /** * @notice Initialize the Optimizer. * @param _harberg The address of the Kraiken token. * @param _stake The address of the Stake contract. */ function initialize(address _harberg, address _stake) public initializer { // Set the admin for upgradeability (using ERC1967Upgrade _changeAdmin) _changeAdmin(msg.sender); harberg = Kraiken(_harberg); 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 the sentiment based on the average tax rate and the percentage staked. * @param averageTaxRate The average tax rate (as returned by the Stake contract). * @param percentageStaked The percentage (in 1e18 precision) of the authorized stake that is currently staked. * @return sentimentValue A value in the range 0 to 1e18 where 1e18 represents the worst sentiment. */ function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked) public pure returns (uint256 sentimentValue) { // deltaS is the “slack” available below full staking uint256 deltaS = 1e18 - percentageStaked; if (percentageStaked > 92e16) { // If more than 92% of the authorized stake is in use, the sentiment drops rapidly. // Penalty is computed as: (deltaS^3 * averageTaxRate) / (20 * 1e48) uint256 penalty = (deltaS * deltaS * deltaS * averageTaxRate) / (20 * 1e48); sentimentValue = penalty / 2; } else { // For lower staked percentages, sentiment decreases roughly linearly. uint256 baseSentiment = 1e18 - ((percentageStaked * 1e18) / (92e16)); // Apply a penalty based on the average tax rate. if (averageTaxRate <= 1e16) { sentimentValue = baseSentiment; } else if (averageTaxRate <= 5e16) { uint256 ratePenalty = ((averageTaxRate - 1e16) * baseSentiment) / (4e16); sentimentValue = baseSentiment > ratePenalty ? baseSentiment - ratePenalty : 0; } else { // For very high tax rates, sentiment is maximally poor. sentimentValue = 1e18; } } return sentimentValue; } /** * @notice Returns the current sentiment. * @return sentiment A number (with 1e18 precision) representing the staker sentiment. */ function getSentiment() external view returns (uint256 sentiment) { uint256 percentageStaked = stake.getPercentageStaked(); uint256 averageTaxRate = stake.getAverageTaxRate(); sentiment = calculateSentiment(averageTaxRate, percentageStaked); } /** * @notice Returns liquidity parameters for the liquidity manager. * @return capitalInefficiency Calculated as (1e18 - sentiment). Capital buffer level (0-1e18) * @return anchorShare Set equal to the sentiment. % of non-floor ETH in anchor (0-1e18) * @return anchorWidth Here set to a constant 100. Anchor position width % (1-100) * @return discoveryDepth Set equal to the sentiment. */ 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); capitalInefficiency = 1e18 - sentiment; anchorShare = sentiment; // Here we simply set anchorWidth to 100; adjust this formula if needed. anchorWidth = 100; discoveryDepth = sentiment; } }