LRTOracle

Créé Le diff n'expire jamais
68 suppressions
215 lignes
180 ajouts
317 lignes
// SPDX-License-Identifier: BUSL-1.1
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.27;
pragma solidity 0.8.27;


import { UtilLib } from "./utils/UtilLib.sol";
import { UtilLib } from "./utils/UtilLib.sol";
import { LRTConstants } from "./utils/LRTConstants.sol";
import { LRTConstants } from "./utils/LRTConstants.sol";
import { LRTConfigRoleChecker, ILRTConfig } from "./utils/LRTConfigRoleChecker.sol";
import { LRTConfigRoleChecker, ILRTConfig } from "./utils/LRTConfigRoleChecker.sol";
import { WadMath } from "./utils/WadMath.sol";


import { IRSETH } from "./interfaces/IRSETH.sol";
import { IRSETH } from "./interfaces/IRSETH.sol";
import { IPriceFetcher } from "./interfaces/IPriceFetcher.sol";
import { IPriceFetcher } from "./interfaces/IPriceFetcher.sol";
import { ILRTOracle } from "./interfaces/ILRTOracle.sol";
import { ILRTOracle } from "./interfaces/ILRTOracle.sol";
import { ILRTDepositPool } from "./interfaces/ILRTDepositPool.sol";
import { ILRTDepositPool } from "./interfaces/ILRTDepositPool.sol";
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";


interface IPausable {
interface IPausable {
function pause() external;
function pause() external;
function paused() external view returns (bool);
}
}


/// @title LRTOracle Contract
/// @title LRTOracle Contract
/// @notice oracle contract that calculates the exchange rate of assets
/// @notice oracle contract that calculates the exchange rate of assets
contract LRTOracle is ILRTOracle, LRTConfigRoleChecker, Initializable {
contract LRTOracle is ILRTOracle, LRTConfigRoleChecker, Initializable {
using WadMath for uint256;

mapping(address asset => address priceOracle) public override assetPriceOracle;
mapping(address asset => address priceOracle) public override assetPriceOracle;


uint256 public override rsETHPrice;
uint256 public override rsETHPrice;
uint256 public pricePercentageLimit;
uint256 public pricePercentageLimit;
uint256 public highestRsethPrice;
uint256 public highestRsethPrice;


// legacy variables
// Daily fee minting limit variables
uint256 public legacy_cooldownPeriodInTimestamp;
uint256 public currentPeriodMintedFeeAmount;
uint256 public legacy_lastUpdatedCoolDownTimestamp;
uint256 public feePeriodStartTime;
uint256 public maxFeeMintAmountPerDay;

/// @notice New variable added for pausable functionality
bool public paused;


modifier onlySupportedOracle(address asset) {
modifier onlySupportedOracle(address asset) {
if (assetPriceOracle[asset] == address(0)) {
if (assetPriceOracle[asset] == address(0)) {
revert AssetOracleNotSupported();
revert AssetOracleNotSupported();
}
}
_;
_;
}
}


modifier whenNotPaused() {
if (paused) revert ContractPaused();
_;
}

modifier whenPaused() {
if (!paused) revert ContractNotPaused();
_;
}

/// @custom:oz-upgrades-unsafe-allow constructor
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
constructor() {
_disableInitializers();
_disableInitializers();
}
}


/// @dev Initializes the contract
/// @dev Initializes the contract
/// @param lrtConfigAddr LRT config address
/// @param lrtConfigAddr LRT config address
function initialize(address lrtConfigAddr) external initializer {
function initialize(address lrtConfigAddr) external initializer {
UtilLib.checkNonZeroAddress(lrtConfigAddr);
UtilLib.checkNonZeroAddress(lrtConfigAddr);
lrtConfig = ILRTConfig(lrtConfigAddr);
lrtConfig = ILRTConfig(lrtConfigAddr);
emit UpdatedLRTConfig(lrtConfigAddr);
emit UpdatedLRTConfig(lrtConfigAddr);
}
}


/*//////////////////////////////////////////////////////////////
/*//////////////////////////////////////////////////////////////
write functions
write functions
//////////////////////////////////////////////////////////////*/
//////////////////////////////////////////////////////////////*/


/// @notice updates RSETH/ETH exchange rate
/// @notice updates RSETH/ETH exchange rate
/// @dev calculates based on stakedAsset value received from eigen layer
/// @dev calculates rsETH price based on stakedAsset value received from EigenLayer
function updateRSETHPrice() public {
function updateRSETHPrice() public whenNotPaused {
_updateRsETHPrice();
}

/// @dev update rsETH price as an manager account
/// @dev main benefit is to be able to update the price in case of the price going above the threshold
/// @dev only LRT manager is allowed to call this function
function updateRSETHPriceAsManager() external onlyLRTManager {
_updateRsETHPrice();
}

/// @dev add/update the price oracle of any asset
/// @dev only onlyLRTAdmin is allowed
/// @param asset asset address for which oracle price needs to be added/updated
function updatePriceOracleForValidated(address asset, address priceOracle) external onlyLRTAdmin {
// Sanity check: oracle price must have precision between 1e16 and 1e19
uint256 price = IPriceFetcher(priceOracle).getAssetPrice(asset);
if (price > 1e19 || price < 1e16) {
revert InvalidPriceOracle();
}
updatePriceOracleFor(asset, priceOracle);
}

/// @dev add/update the price oracle of any asset
/// @dev only onlyLRTAdmin is allowed
/// @param asset asset address for which oracle price needs to be added/updated
function updatePriceOracleFor(address asset, address priceOracle) public onlyLRTAdmin {
if (lrtConfig.isSupportedAsset(asset)) {
UtilLib.checkNonZeroAddress(priceOracle);
}
Text moved from lines 168-180
assetPriceOracle[asset] = priceOracle;
emit AssetPriceOracleUpdate(asset, priceOracle);
}

/// @dev set the price percentage limit. Only onlyLRTAdmin is allowed
/// @dev PricePercentageLimit for 1% is 1e16
/// @dev Price Percentage Limit for 100% is 1e18
/// @param _pricePercentageLimit price percentage limit
function setPricePercentageLimit(uint256 _pricePercentageLimit) external onlyLRTAdmin {
pricePercentageLimit = _pricePercentageLimit;
emit PricePercentageLimitUpdate(_pricePercentageLimit);
}

/// @dev set the maximum fee minting amount per day. Only onlyLRTManager is allowed
/// @param _maxFeeMintAmountPerDay maximum amount of fee that can be minted per day
function setMaxFeeMintAmountPerDay(uint256 _maxFeeMintAmountPerDay) external onlyLRTManager {
maxFeeMintAmountPerDay = _maxFeeMintAmountPerDay;
emit MaxFeeMintAmountPerDayUpdated(_maxFeeMintAmountPerDay);
}

/// @dev Pauses the pausable methods in the contract
function pause() external onlyLRTManager whenNotPaused {
_pause();
}

/// @dev Unpauses the pausable methods in the contract
function unpause() external onlyLRTAdmin whenPaused {
paused = false;
emit Unpaused(msg.sender);
}

/*//////////////////////////////////////////////////////////////
view functions
//////////////////////////////////////////////////////////////*/

/// @notice Provides Asset/ETH exchange rate
/// @dev reads from priceFetcher interface which may fetch price from any supported oracle
/// @param asset the asset for which exchange rate is required
/// @return assetPrice exchange rate of asset
function getAssetPrice(address asset) public view onlySupportedOracle(asset) returns (uint256) {
return IPriceFetcher(assetPriceOracle[asset]).getAssetPrice(asset);
}

/*//////////////////////////////////////////////////////////////
internal functions
//////////////////////////////////////////////////////////////*/

/// @dev Checks and updates daily fee minting limits
/// @param feeAmount The amount of fee to be minted
function _checkAndUpdateDailyFeeMintLimit(uint256 feeAmount) internal {
// Reset or initialize the period if it's unset or a day has passed
if (feePeriodStartTime == 0 || block.timestamp >= feePeriodStartTime + 1 days) {
currentPeriodMintedFeeAmount = 0;
feePeriodStartTime = block.timestamp;
}

// Check if minting would exceed the daily limit
if (currentPeriodMintedFeeAmount + feeAmount > maxFeeMintAmountPerDay) {
revert DailyFeeMintLimitExceeded(currentPeriodMintedFeeAmount + feeAmount, maxFeeMintAmountPerDay);
}

currentPeriodMintedFeeAmount += feeAmount;
}

/// @dev Internal function to update rsETH price
// solhint-disable-next-line code-complexity
function _updateRsETHPrice() internal {
address rsETHTokenAddress = lrtConfig.rsETH();
address rsETHTokenAddress = lrtConfig.rsETH();
uint256 rsethSupply = IRSETH(rsETHTokenAddress).totalSupply();
uint256 rsethSupply = IRSETH(rsETHTokenAddress).totalSupply();


if (rsethSupply == 0) {
if (rsethSupply == 0) {
rsETHPrice = 1 ether;
rsETHPrice = 1 ether;
highestRsethPrice = 1 ether;
highestRsethPrice = 1 ether;
return;
return;
}
}


if (highestRsethPrice == 0) {
if (highestRsethPrice == 0) {
highestRsethPrice = rsETHPrice;
highestRsethPrice = rsETHPrice;
}
}


uint256 previousPrice = rsETHPrice;
uint256 previousPrice = rsETHPrice;


// get total ETH in the protocol
// get total ETH in the protocol (normalized to 1e18)
uint256 totalETHInProtocol = _getTotalEthInProtocol();
uint256 totalETHInProtocol = _getTotalEthInProtocol();


// calculate previousTVL using rsethSupply multiplied by rsETHPrice
// calculate previousTVL using rsethSupply multiplied by rsETHPrice
uint256 previousTVL = rsethSupply * rsETHPrice;
uint256 previousTVL = rsethSupply.mulWad(rsETHPrice);


// only take fee if TVL increased
IPausable lrtDepositPool = IPausable(lrtConfig.getContract(LRTConstants.LRT_DEPOSIT_POOL));
IPausable withdrawalManager = IPausable(lrtConfig.getContract(LRTConstants.LRT_WITHDRAW_MANAGER));

// determine if the protocol is active (not paused)
bool protocolPaused = lrtDepositPool.paused() || withdrawalManager.paused() || paused;

// only take fee if TVL increased and protocol is not paused
uint256 protocolFeeInETH = 0;
uint256 protocolFeeInETH = 0;
if (totalETHInProtocol > previousTVL) {
if (!protocolPaused && totalETHInProtocol > previousTVL) {
uint256 rewardAmount = totalETHInProtocol - previousTVL;
uint256 rewardAmount = totalETHInProtocol - previousTVL;
protocolFeeInETH = (rewardAmount * lrtConfig.protocolFeeInBPS()) / 10_000;
protocolFeeInETH = (rewardAmount * lrtConfig.protocolFeeInBPS()) / 10_000;
}
}


// compute new rsETH price based on total ETH minus fee
// compute new rsETH price based on total ETH minus fee
uint256 newRsETHPrice = (totalETHInProtocol - protocolFeeInETH) / rsethSupply;
uint256 newRsETHPrice = (totalETHInProtocol - protocolFeeInETH).divWad(rsethSupply);


if (newRsETHPrice > highestRsethPrice) {
if (newRsETHPrice > highestRsethPrice) {
// check if the price is above the threshold
// check if the price is above the threshold
uint256 priceDifference = newRsETHPrice - highestRsethPrice;
uint256 priceDifference = newRsETHPrice - highestRsethPrice;
// pricePercentageLimit is in 1e18 precision (100% = 1e18, 1% = 1e16)
bool isPriceIncreaseOffLimit =
bool isPriceIncreaseOffLimit =
pricePercentageLimit > 0 && priceDifference > (pricePercentageLimit * highestRsethPrice) / 1e18;
pricePercentageLimit > 0 && priceDifference > pricePercentageLimit.mulWad(highestRsethPrice);


// check if the price difference is above the threshold
// check if the price difference is above the threshold
if (isPriceIncreaseOffLimit) {
if (isPriceIncreaseOffLimit) {
// if sender has a manager role, this doesn't revert.
// if sender has a manager role, this doesn't revert.