2025-02-01 21:49:15 +01:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
pragma solidity ^0.8.19;
|
|
|
|
|
|
2025-07-11 13:47:42 +02:00
|
|
|
import {Kraiken} from "./Kraiken.sol";
|
2025-02-01 21:49:15 +01:00
|
|
|
import {Stake} from "./Stake.sol";
|
|
|
|
|
import {UUPSUpgradeable} from "@openzeppelin/proxy/utils/UUPSUpgradeable.sol";
|
|
|
|
|
import {Initializable} from "@openzeppelin/proxy/utils/Initializable.sol";
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @title Optimizer
|
2025-08-18 00:16:09 +02:00
|
|
|
* @notice This contract (formerly Sentimenter) calculates a "sentiment" value and liquidity parameters
|
2025-07-11 13:47:42 +02:00
|
|
|
* based on the tax rate and the percentage of Kraiken staked.
|
2025-02-01 21:49:15 +01:00
|
|
|
* @dev It is upgradeable using UUPS. Only the admin (set during initialization) can upgrade.
|
2025-08-18 00:16:09 +02:00
|
|
|
*
|
|
|
|
|
* 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
|
2025-02-01 21:49:15 +01:00
|
|
|
*/
|
|
|
|
|
contract Optimizer is Initializable, UUPSUpgradeable {
|
2025-07-11 13:47:42 +02:00
|
|
|
Kraiken private harberg;
|
2025-02-01 21:49:15 +01:00
|
|
|
Stake private stake;
|
|
|
|
|
|
|
|
|
|
/// @dev Reverts if the caller is not the admin.
|
|
|
|
|
error UnauthorizedAccount(address account);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @notice Initialize the Optimizer.
|
2025-07-11 13:47:42 +02:00
|
|
|
* @param _harberg The address of the Kraiken token.
|
2025-02-01 21:49:15 +01:00
|
|
|
* @param _stake The address of the Stake contract.
|
|
|
|
|
*/
|
2025-07-08 10:33:10 +02:00
|
|
|
function initialize(address _harberg, address _stake) public initializer {
|
2025-02-01 21:49:15 +01:00
|
|
|
// Set the admin for upgradeability (using ERC1967Upgrade _changeAdmin)
|
|
|
|
|
_changeAdmin(msg.sender);
|
2025-07-11 13:47:42 +02:00
|
|
|
harberg = Kraiken(_harberg);
|
2025-02-01 21:49:15 +01:00
|
|
|
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.
|
|
|
|
|
*/
|
2025-07-08 10:33:10 +02:00
|
|
|
function calculateSentiment(uint256 averageTaxRate, uint256 percentageStaked)
|
|
|
|
|
public
|
|
|
|
|
pure
|
|
|
|
|
returns (uint256 sentimentValue)
|
|
|
|
|
{
|
2025-02-01 21:49:15 +01:00
|
|
|
// 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.
|
2025-08-18 00:16:09 +02:00
|
|
|
* @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)
|
2025-02-01 21:49:15 +01:00
|
|
|
* @return discoveryDepth Set equal to the sentiment.
|
|
|
|
|
*/
|
|
|
|
|
function getLiquidityParams()
|
|
|
|
|
external
|
|
|
|
|
view
|
2025-07-08 10:33:10 +02:00
|
|
|
returns (uint256 capitalInefficiency, uint256 anchorShare, uint24 anchorWidth, uint256 discoveryDepth)
|
2025-02-01 21:49:15 +01:00
|
|
|
{
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|