Files
assetxContracts/contracts/ytLp/core/YTPriceFeed.sol

230 lines
7.7 KiB
Solidity
Raw Normal View History

2025-12-18 13:07:35 +08:00
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
2025-12-24 16:41:26 +08:00
import "../../interfaces/IYTAssetVault.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
2025-12-18 13:07:35 +08:00
contract YTPriceFeed is Initializable, UUPSUpgradeable {
2025-12-23 14:05:41 +08:00
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
2025-12-18 13:07:35 +08:00
error Forbidden();
error MaxChangeTooHigh();
error PriceChangeTooLarge();
error SpreadTooHigh();
error InvalidAddress();
2025-12-24 16:41:26 +08:00
error InvalidChainlinkPrice();
2025-12-18 13:07:35 +08:00
address public gov;
uint256 public constant PRICE_PRECISION = 10 ** 30;
uint256 public constant BASIS_POINTS_DIVISOR = 10000;
uint256 public constant MAX_SPREAD_BASIS_POINTS = 200; // 最大2%价差
2025-12-24 16:41:26 +08:00
address public usdcAddress;
2025-12-18 13:07:35 +08:00
uint256 public maxPriceChangeBps; // 5% 最大价格变动
2025-12-24 16:41:26 +08:00
AggregatorV3Interface internal usdcPriceFeed;
2025-12-18 13:07:35 +08:00
mapping(address => uint256) public spreadBasisPoints;
mapping(address => uint256) public lastPrice;
mapping(address => bool) public isKeeper;
event PriceUpdate(address indexed token, uint256 oldPrice, uint256 newPrice, uint256 timestamp);
event SpreadUpdate(address indexed token, uint256 spreadBps);
event KeeperSet(address indexed keeper, bool isActive);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
_;
}
modifier onlyKeeper() {
if (!isKeeper[msg.sender] && msg.sender != gov) revert Forbidden();
_;
}
2025-12-24 16:41:26 +08:00
function initialize(address _usdcAddress, address _usdcPriceFeed) external initializer {
2025-12-18 13:07:35 +08:00
__UUPSUpgradeable_init();
2025-12-24 16:41:26 +08:00
if (_usdcAddress == address(0)) revert InvalidAddress();
usdcAddress = _usdcAddress;
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
2025-12-18 13:07:35 +08:00
gov = msg.sender;
maxPriceChangeBps = 500; // 5% 最大价格变动
}
2025-12-24 16:41:26 +08:00
function setUSDCAddress(address _usdcAddress) external onlyGov {
if (_usdcAddress == address(0)) revert InvalidAddress();
usdcAddress = _usdcAddress;
}
function setUSDCPriceFeed(address _usdcPriceFeed) external onlyGov {
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
}
2025-12-18 13:07:35 +08:00
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
function setKeeper(address _keeper, bool _isActive) external onlyGov {
isKeeper[_keeper] = _isActive;
emit KeeperSet(_keeper, _isActive);
}
function setMaxPriceChangeBps(uint256 _maxPriceChangeBps) external onlyGov {
if (_maxPriceChangeBps > 2000) revert MaxChangeTooHigh(); // 最大20%
maxPriceChangeBps = _maxPriceChangeBps;
}
function setSpreadBasisPoints(address _token, uint256 _spreadBasisPoints) external onlyGov {
if (_spreadBasisPoints > MAX_SPREAD_BASIS_POINTS) revert SpreadTooHigh();
spreadBasisPoints[_token] = _spreadBasisPoints;
emit SpreadUpdate(_token, _spreadBasisPoints);
}
function setSpreadBasisPointsForMultiple(
address[] calldata _tokens,
uint256[] calldata _spreadBasisPoints
) external onlyGov {
require(_tokens.length == _spreadBasisPoints.length, "length mismatch");
for (uint256 i = 0; i < _tokens.length; i++) {
if (_spreadBasisPoints[i] > MAX_SPREAD_BASIS_POINTS) revert SpreadTooHigh();
spreadBasisPoints[_tokens[i]] = _spreadBasisPoints[i];
emit SpreadUpdate(_tokens[i], _spreadBasisPoints[i]);
}
}
2025-12-24 16:41:26 +08:00
function updatePrice(address _token) external onlyKeeper returns (uint256) {
if (_token == usdcAddress) {
return _getUSDCPrice();
}
uint256 oldPrice = lastPrice[_token];
uint256 newPrice = _getRawPrice(_token);
_validatePriceChange(_token, newPrice);
lastPrice[_token] = newPrice;
emit PriceUpdate(_token, oldPrice, newPrice, block.timestamp);
return newPrice;
}
2025-12-18 13:07:35 +08:00
function forceUpdatePrice(address _token, uint256 _price) external onlyGov {
uint256 oldPrice = lastPrice[_token];
lastPrice[_token] = _price;
emit PriceUpdate(_token, oldPrice, _price, block.timestamp);
}
2025-12-25 13:29:35 +08:00
2025-12-18 13:07:35 +08:00
function getPrice(address _token, bool _maximise) external view returns (uint256) {
2025-12-24 16:41:26 +08:00
if (_token == usdcAddress) {
return _getUSDCPrice();
2025-12-18 13:07:35 +08:00
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, _maximise);
}
function _getRawPrice(address _token) private view returns (uint256) {
2025-12-24 16:41:26 +08:00
return IYTAssetVault(_token).ytPrice();
2025-12-18 13:07:35 +08:00
}
2025-12-24 16:41:26 +08:00
function _getUSDCPrice() internal view returns (uint256) {
(
/* uint80 roundId */,
int256 price,
/* uint256 startedAt */,
/* uint256 updatedAt */,
/* uint80 answeredInRound */
) = usdcPriceFeed.latestRoundData();
if (price <= 0) revert InvalidChainlinkPrice();
return uint256(price) * 1e22; // 1e22 = 10^(30-8)
2025-12-18 13:07:35 +08:00
}
2025-12-24 16:41:26 +08:00
2025-12-18 13:07:35 +08:00
function _applySpread(
address _token,
uint256 _basePrice,
bool _maximise
) private view returns (uint256) {
uint256 spread = spreadBasisPoints[_token];
if (spread == 0) {
return _basePrice;
}
if (_maximise) {
return _basePrice * (BASIS_POINTS_DIVISOR + spread) / BASIS_POINTS_DIVISOR;
} else {
return _basePrice * (BASIS_POINTS_DIVISOR - spread) / BASIS_POINTS_DIVISOR;
}
}
function _validatePriceChange(address _token, uint256 _newPrice) private view {
uint256 oldPrice = lastPrice[_token];
if (oldPrice == 0) {
return;
}
uint256 priceDiff = _newPrice > oldPrice ? _newPrice - oldPrice : oldPrice - _newPrice;
uint256 maxDiff = oldPrice * maxPriceChangeBps / BASIS_POINTS_DIVISOR;
if (priceDiff > maxDiff) revert PriceChangeTooLarge();
}
function getPriceInfo(address _token) external view returns (
uint256 currentPrice,
uint256 cachedPrice,
uint256 maxPrice,
uint256 minPrice,
uint256 spread
) {
2025-12-24 16:41:26 +08:00
if (_token == usdcAddress) {
uint256 usdcPrice = _getUSDCPrice();
currentPrice = usdcPrice;
cachedPrice = usdcPrice;
maxPrice = usdcPrice;
minPrice = usdcPrice;
2025-12-18 13:07:35 +08:00
spread = 0;
} else {
currentPrice = _getRawPrice(_token);
cachedPrice = lastPrice[_token];
spread = spreadBasisPoints[_token];
maxPrice = _applySpread(_token, currentPrice, true);
minPrice = _applySpread(_token, currentPrice, false);
}
}
2025-12-25 13:29:35 +08:00
2025-12-18 13:07:35 +08:00
function getMaxPrice(address _token) external view returns (uint256) {
2025-12-24 16:41:26 +08:00
if (_token == usdcAddress) {
return _getUSDCPrice();
2025-12-18 13:07:35 +08:00
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, true);
}
function getMinPrice(address _token) external view returns (uint256) {
2025-12-24 16:41:26 +08:00
if (_token == usdcAddress) {
return _getUSDCPrice();
2025-12-18 13:07:35 +08:00
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, false);
}
uint256[50] private __gap;
2025-12-25 13:29:35 +08:00
}