357 lines
12 KiB
Solidity
357 lines
12 KiB
Solidity
// 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/IYTAssetVault.sol";
|
||
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.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();
|
||
error InvalidChainlinkPrice();
|
||
error StalePrice();
|
||
|
||
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%价差
|
||
|
||
address public usdcAddress;
|
||
|
||
// 价格保护参数
|
||
uint256 public maxPriceChangeBps; // 5% 最大价格变动
|
||
uint256 public priceStalenesThreshold; // 价格过期阈值(秒)
|
||
|
||
/// @notice USDC价格Feed
|
||
AggregatorV3Interface internal usdcPriceFeed;
|
||
|
||
// 价差配置(每个代币可以有不同的价差)
|
||
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 _usdcAddress, address _usdcPriceFeed) external initializer {
|
||
__UUPSUpgradeable_init();
|
||
if (_usdcAddress == address(0)) revert InvalidAddress();
|
||
usdcAddress = _usdcAddress;
|
||
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
|
||
gov = msg.sender;
|
||
maxPriceChangeBps = 500; // 5% 最大价格变动
|
||
priceStalenesThreshold = 3600; // 默认1小时
|
||
}
|
||
|
||
/**
|
||
* @notice 授权升级(仅gov可调用)
|
||
* @param newImplementation 新实现合约地址
|
||
*/
|
||
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
|
||
|
||
/**
|
||
* @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);
|
||
}
|
||
|
||
/**
|
||
* @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 _threshold 阈值(秒),例如:3600 = 1小时,86400 = 24小时
|
||
*/
|
||
function setPriceStalenessThreshold(uint256 _threshold) external onlyGov {
|
||
require(_threshold > 0 && _threshold <= 7 days, "Invalid threshold");
|
||
priceStalenesThreshold = _threshold;
|
||
}
|
||
|
||
/**
|
||
* @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 更新并缓存代币价格(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;
|
||
}
|
||
|
||
/**
|
||
* @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 == usdcAddress) {
|
||
return _getUSDCPrice();
|
||
}
|
||
|
||
uint256 basePrice = _getRawPrice(_token);
|
||
|
||
// 价格波动检查
|
||
_validatePriceChange(_token, basePrice);
|
||
|
||
// 应用价差
|
||
return _applySpread(_token, basePrice, _maximise);
|
||
}
|
||
|
||
/**
|
||
* @notice 直接读取YT代币的ytPrice变量
|
||
*/
|
||
function _getRawPrice(address _token) private view returns (uint256) {
|
||
return IYTAssetVault(_token).ytPrice();
|
||
}
|
||
|
||
/**
|
||
* @notice 获取并验证USDC价格(从Chainlink)
|
||
* @return 返回uint256格式的USDC价格,精度为1e30
|
||
*/
|
||
function _getUSDCPrice() internal view returns (uint256) {
|
||
(
|
||
uint80 roundId,
|
||
int256 price,
|
||
/* uint256 startedAt */,
|
||
uint256 updatedAt,
|
||
uint80 answeredInRound
|
||
) = usdcPriceFeed.latestRoundData();
|
||
|
||
// 价格有效性检查
|
||
if (price <= 0) revert InvalidChainlinkPrice();
|
||
|
||
// 新鲜度检查:确保价格数据不过期
|
||
if (updatedAt == 0) revert StalePrice();
|
||
if (answeredInRound < roundId) revert StalePrice();
|
||
if (block.timestamp - updatedAt > priceStalenesThreshold) revert StalePrice();
|
||
|
||
return uint256(price) * 1e22; // 1e22 = 10^(30-8)
|
||
}
|
||
|
||
/**
|
||
* @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 == usdcAddress) {
|
||
uint256 usdcPrice = _getUSDCPrice();
|
||
currentPrice = usdcPrice;
|
||
cachedPrice = usdcPrice;
|
||
maxPrice = usdcPrice;
|
||
minPrice = usdcPrice;
|
||
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 == usdcAddress) {
|
||
// USDC通常不需要价差,直接返回原价格
|
||
return _getUSDCPrice();
|
||
}
|
||
uint256 basePrice = _getRawPrice(_token);
|
||
_validatePriceChange(_token, basePrice);
|
||
return _applySpread(_token, basePrice, true);
|
||
}
|
||
|
||
/**
|
||
* @notice 获取最小价格(下压价差)
|
||
*/
|
||
function getMinPrice(address _token) external view returns (uint256) {
|
||
if (_token == usdcAddress) {
|
||
// USDC通常不需要价差,直接返回原价格
|
||
return _getUSDCPrice();
|
||
}
|
||
uint256 basePrice = _getRawPrice(_token);
|
||
_validatePriceChange(_token, basePrice);
|
||
return _applySpread(_token, basePrice, false);
|
||
}
|
||
|
||
/**
|
||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||
*/
|
||
uint256[50] private __gap;
|
||
}
|