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

357 lines
12 KiB
Solidity
Raw Permalink 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
/**
* @title YTPriceFeed
* @notice YT合约读取价格变量
* @dev UUPS可升级合约
*/
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();
2026-01-12 14:33:16 +08:00
error StalePrice();
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% 最大价格变动
2026-01-12 14:33:16 +08:00
uint256 public priceStalenesThreshold; // 价格过期阈值(秒)
2025-12-24 16:41:26 +08:00
/// @notice USDC价格Feed
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();
_;
}
/**
* @notice
*/
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% 最大价格变动
2026-01-12 14:33:16 +08:00
priceStalenesThreshold = 3600; // 默认1小时
2025-12-18 13:07:35 +08:00
}
2025-12-24 16:41:26 +08:00
2026-01-19 11:56:15 +08:00
/**
* @notice gov可调用
* @param newImplementation
*/
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
2025-12-24 16:41:26 +08:00
/**
* @notice USDC地址
* @param _usdcAddress USDC地址
*/
function setUSDCAddress(address _usdcAddress) external onlyGov {
if (_usdcAddress == address(0)) revert InvalidAddress();
usdcAddress = _usdcAddress;
}
/**
* @notice USDC价格Feed
* @param _usdcPriceFeed USDC价格Feed地址
*/
function setUSDCPriceFeed(address _usdcPriceFeed) external onlyGov {
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
}
2025-12-18 13:07:35 +08:00
/**
* @notice keeper权限
* @param _keeper keeper地址
* @param _isActive
*/
function setKeeper(address _keeper, bool _isActive) external onlyGov {
isKeeper[_keeper] = _isActive;
emit KeeperSet(_keeper, _isActive);
}
/**
* @notice
* @param _maxPriceChangeBps
*/
function setMaxPriceChangeBps(uint256 _maxPriceChangeBps) external onlyGov {
if (_maxPriceChangeBps > 2000) revert MaxChangeTooHigh(); // 最大20%
maxPriceChangeBps = _maxPriceChangeBps;
}
2026-01-12 14:33:16 +08:00
/**
* @notice
* @param _threshold 3600 = 186400 = 24
*/
function setPriceStalenessThreshold(uint256 _threshold) external onlyGov {
require(_threshold > 0 && _threshold <= 7 days, "Invalid threshold");
priceStalenesThreshold = _threshold;
}
2025-12-18 13:07:35 +08:00
/**
* @notice
* @param _token
* @param _spreadBasisPoints 10 = 0.1%, 100 = 1%
*/
function setSpreadBasisPoints(address _token, uint256 _spreadBasisPoints) external onlyGov {
if (_spreadBasisPoints > MAX_SPREAD_BASIS_POINTS) revert SpreadTooHigh();
spreadBasisPoints[_token] = _spreadBasisPoints;
emit SpreadUpdate(_token, _spreadBasisPoints);
}
/**
* @notice
* @param _tokens
* @param _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
/**
* @notice keeper调用
* @param _token
* @return
*/
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
/**
* @notice
* @param _token
* @param _price
*/
function forceUpdatePrice(address _token, uint256 _price) external onlyGov {
uint256 oldPrice = lastPrice[_token];
lastPrice[_token] = _price;
emit PriceUpdate(_token, oldPrice, _price, block.timestamp);
}
/**
* @notice YT代币价格
* @param _token
* @param _maximise true=, false=
* @return 30
*
* 使
* - AUM计算_maximise=trueAUMLP
* - AUM计算_maximise=falseAUM
* - buyUSDY时_maximise=false
* - sellUSDY时_maximise=true
* - swap时tokenIn_maximise=false
* - swap时tokenOut_maximise=true
*/
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);
}
/**
* @notice YT代币的ytPrice变量
*/
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
* @notice USDC价格Chainlink
* @return uint256格式的USDC价格1e30
2025-12-18 13:07:35 +08:00
*/
2025-12-24 16:41:26 +08:00
function _getUSDCPrice() internal view returns (uint256) {
(
2026-01-12 14:33:16 +08:00
uint80 roundId,
2025-12-24 16:41:26 +08:00
int256 price,
/* uint256 startedAt */,
2026-01-12 14:33:16 +08:00
uint256 updatedAt,
uint80 answeredInRound
2025-12-24 16:41:26 +08:00
) = usdcPriceFeed.latestRoundData();
2026-01-12 14:33:16 +08:00
// 价格有效性检查
2025-12-24 16:41:26 +08:00
if (price <= 0) revert InvalidChainlinkPrice();
2026-01-12 14:33:16 +08:00
// 新鲜度检查:确保价格数据不过期
if (updatedAt == 0) revert StalePrice();
if (answeredInRound < roundId) revert StalePrice();
if (block.timestamp - updatedAt > priceStalenesThreshold) revert StalePrice();
2025-12-24 16:41:26 +08:00
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
/**
* @notice
* @param _token
* @param _basePrice
* @param _maximise true=false=
* @return
*/
function _applySpread(
address _token,
uint256 _basePrice,
bool _maximise
) private view returns (uint256) {
uint256 spread = spreadBasisPoints[_token];
// 如果没有设置价差,直接返回基础价格
if (spread == 0) {
return _basePrice;
}
if (_maximise) {
// 上浮价格basePrice * (1 + spread%)
return _basePrice * (BASIS_POINTS_DIVISOR + spread) / BASIS_POINTS_DIVISOR;
} else {
// 下压价格basePrice * (1 - spread%)
return _basePrice * (BASIS_POINTS_DIVISOR - spread) / BASIS_POINTS_DIVISOR;
}
}
/**
* @notice
*/
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();
}
/**
* @notice
*/
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);
}
}
/**
* @notice
*/
function getMaxPrice(address _token) external view returns (uint256) {
2025-12-24 16:41:26 +08:00
if (_token == usdcAddress) {
// USDC通常不需要价差直接返回原价格
return _getUSDCPrice();
2025-12-18 13:07:35 +08:00
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, true);
}
/**
* @notice
*/
function getMinPrice(address _token) external view returns (uint256) {
2025-12-24 16:41:26 +08:00
if (_token == usdcAddress) {
// USDC通常不需要价差直接返回原价格
return _getUSDCPrice();
2025-12-18 13:07:35 +08:00
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, false);
}
/**
* @dev
* 50slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}