Files
assetxContracts/contracts/ytLp/core/YTPriceFeed.sol
2025-12-23 14:05:41 +08:00

320 lines
10 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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";
import "../../interfaces/IYTToken.sol";
/**
* @title YTPriceFeed
* @notice 价格读取器直接从YT合约读取价格变量带保护机制和价差
* @dev UUPS可升级合约
*/
contract YTPriceFeed is Initializable, UUPSUpgradeable {
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
error Forbidden();
error MaxChangeTooHigh();
error PriceChangeTooLarge();
error SpreadTooHigh();
error InvalidAddress();
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%价差
// WUSD固定价格
address public wusdAddress;
// WUSD价格来源
address public wusdPriceSource;
// 价格保护参数
uint256 public maxPriceChangeBps; // 5% 最大价格变动
// 价差配置(每个代币可以有不同的价差)
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 初始化合约
*/
function initialize(address _wusdAddress) external initializer {
__UUPSUpgradeable_init();
if (_wusdAddress == address(0)) revert InvalidAddress();
wusdAddress = _wusdAddress;
gov = msg.sender;
maxPriceChangeBps = 500; // 5% 最大价格变动
}
/**
* @notice 授权升级仅gov可调用
* @param newImplementation 新实现合约地址
*/
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
/**
* @notice 设置WUSD价格来源YTAssetVault地址
* @param _wusdPriceSource YTAssetVault合约地址
*/
function setWusdPriceSource(address _wusdPriceSource) external onlyGov {
wusdPriceSource = _wusdPriceSource;
}
/**
* @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;
}
/**
* @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]);
}
}
/**
* @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=true高估AUM用户获得较少LP
* - 移除流动性时AUM计算_maximise=false低估AUM用户获得较少代币
* - buyUSDY时用户卖代币_maximise=false低估用户代币价值
* - sellUSDY时用户买代币_maximise=true高估需支付的代币价值
* - swap时tokenIn_maximise=false低估输入
* - swap时tokenOut_maximise=true高估输出
*/
function getPrice(address _token, bool _maximise) external view returns (uint256) {
if (_token == wusdAddress) {
return _getWUSDPrice();
}
uint256 basePrice = _getRawPrice(_token);
// 价格波动检查
_validatePriceChange(_token, basePrice);
// 应用价差
return _applySpread(_token, basePrice, _maximise);
}
/**
* @notice 更新价格并返回由keeper调用
* @param _token 代币地址
* @return 新价格
*/
function updatePrice(address _token) external onlyKeeper returns (uint256) {
if (_token == wusdAddress) {
return _getWUSDPrice();
}
uint256 oldPrice = lastPrice[_token];
uint256 newPrice = _getRawPrice(_token);
// 价格波动检查
_validatePriceChange(_token, newPrice);
lastPrice[_token] = newPrice;
emit PriceUpdate(_token, oldPrice, newPrice, block.timestamp);
return newPrice;
}
/**
* @notice 直接读取YT代币的ytPrice变量
*/
function _getRawPrice(address _token) private view returns (uint256) {
return IYTToken(_token).ytPrice();
}
/**
* @notice 从配置的YTAssetVault读取wusdPrice
* @dev 如果未设置wusdPriceSource返回固定价格1.0
*/
function _getWUSDPrice() private view returns (uint256) {
if (wusdPriceSource == address(0)) {
return PRICE_PRECISION; // 默认1.0
}
return IYTToken(wusdPriceSource).wusdPrice();
}
/**
* @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
) {
if (_token == wusdAddress) {
uint256 wusdPrice = _getWUSDPrice();
currentPrice = wusdPrice;
cachedPrice = wusdPrice;
maxPrice = wusdPrice;
minPrice = wusdPrice;
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) {
if (_token == wusdAddress) {
// WUSD通常不需要价差直接返回原价格
return _getWUSDPrice();
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, true);
}
/**
* @notice 获取最小价格(下压价差)
*/
function getMinPrice(address _token) external view returns (uint256) {
if (_token == wusdAddress) {
// WUSD通常不需要价差直接返回原价格
return _getWUSDPrice();
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, false);
}
/**
* @dev 预留存储空间,用于未来升级时添加新的状态变量
* 50个slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}