audit
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title ILending
|
||||
* @notice 借贷池核心接口
|
||||
*/
|
||||
interface ILending {
|
||||
event Supply(address indexed from, address indexed dst, uint256 amount);
|
||||
event Withdraw(address indexed src, address indexed to, uint256 amount);
|
||||
|
||||
@@ -10,11 +10,6 @@ import "../../interfaces/IYTVault.sol";
|
||||
import "../../interfaces/IYTLPToken.sol";
|
||||
import "../../interfaces/IUSDY.sol";
|
||||
|
||||
/**
|
||||
* @title YTPoolManager
|
||||
* @notice 管理ytLP的铸造和赎回,计算池子AUM
|
||||
* @dev UUPS可升级合约
|
||||
*/
|
||||
contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
@@ -82,13 +77,6 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
* @param _ytVault YTVault合约地址
|
||||
* @param _usdy USDY代币地址
|
||||
* @param _ytLP ytLP代币地址
|
||||
* @param _cooldownDuration 冷却时间(秒)
|
||||
*/
|
||||
function initialize(
|
||||
address _ytVault,
|
||||
address _usdy,
|
||||
@@ -108,10 +96,6 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
cooldownDuration = _cooldownDuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅gov可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
|
||||
|
||||
function setGov(address _gov) external onlyGov {
|
||||
@@ -138,9 +122,6 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
emit AumAdjustmentChanged(_addition, _deduction);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 为指定账户添加流动性(Handler调用)
|
||||
*/
|
||||
function addLiquidityForAccount(
|
||||
address _fundingAccount,
|
||||
address _account,
|
||||
@@ -186,9 +167,6 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
return mintAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 为指定账户移除流动性(Handler调用)
|
||||
*/
|
||||
function removeLiquidityForAccount(
|
||||
address _account,
|
||||
address _tokenOut,
|
||||
@@ -235,11 +213,6 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
return amountOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取ytLP价格
|
||||
* @param _maximise 是否取最大值
|
||||
* @return ytLP价格(18位精度)
|
||||
*/
|
||||
function getPrice(bool _maximise) external view returns (uint256) {
|
||||
uint256 aum = getAumInUsdy(_maximise);
|
||||
uint256 supply = IERC20(ytLP).totalSupply();
|
||||
@@ -249,11 +222,6 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
return aum * YTLP_PRECISION / supply;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取池子总价值(AUM)
|
||||
* @param _maximise true=使用最大价格(添加流动性时), false=使用最小价格(移除流动性时)
|
||||
* @return USDY计价的总价值
|
||||
*/
|
||||
function getAumInUsdy(bool _maximise) public view returns (uint256) {
|
||||
uint256 aum = IYTVault(ytVault).getPoolValue(_maximise);
|
||||
|
||||
@@ -267,10 +235,5 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
return aum;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,11 +6,6 @@ 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
|
||||
@@ -33,19 +28,14 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
|
||||
address public usdcAddress;
|
||||
|
||||
// 价格保护参数
|
||||
uint256 public maxPriceChangeBps; // 5% 最大价格变动
|
||||
|
||||
/// @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);
|
||||
@@ -62,9 +52,6 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
*/
|
||||
function initialize(address _usdcAddress, address _usdcPriceFeed) external initializer {
|
||||
__UUPSUpgradeable_init();
|
||||
if (_usdcAddress == address(0)) revert InvalidAddress();
|
||||
@@ -74,64 +61,33 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
maxPriceChangeBps = 500; // 5% 最大价格变动
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 授权升级(仅gov可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
|
||||
|
||||
/**
|
||||
* @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
|
||||
@@ -144,11 +100,6 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 更新并缓存代币价格(keeper调用)
|
||||
* @param _token 代币地址
|
||||
* @return 更新后的价格
|
||||
*/
|
||||
function updatePrice(address _token) external onlyKeeper returns (uint256) {
|
||||
if (_token == usdcAddress) {
|
||||
return _getUSDCPrice();
|
||||
@@ -157,10 +108,8 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
uint256 oldPrice = lastPrice[_token];
|
||||
uint256 newPrice = _getRawPrice(_token);
|
||||
|
||||
// 价格波动检查
|
||||
_validatePriceChange(_token, newPrice);
|
||||
|
||||
// 更新缓存价格
|
||||
lastPrice[_token] = newPrice;
|
||||
|
||||
emit PriceUpdate(_token, oldPrice, newPrice, block.timestamp);
|
||||
@@ -168,31 +117,13 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
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();
|
||||
@@ -200,24 +131,15 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
|
||||
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 */,
|
||||
@@ -232,13 +154,6 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
return uint256(price) * 1e22; // 1e22 = 10^(30-8)
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 应用价差
|
||||
* @param _token 代币地址
|
||||
* @param _basePrice 基础价格
|
||||
* @param _maximise true=上浮价格,false=下压价格
|
||||
* @return 应用价差后的价格
|
||||
*/
|
||||
function _applySpread(
|
||||
address _token,
|
||||
uint256 _basePrice,
|
||||
@@ -246,41 +161,30 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
) 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,
|
||||
@@ -303,13 +207,9 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
minPrice = _applySpread(_token, currentPrice, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取最大价格(上浮价差)
|
||||
*/
|
||||
|
||||
function getMaxPrice(address _token) external view returns (uint256) {
|
||||
if (_token == usdcAddress) {
|
||||
// USDC通常不需要价差,直接返回原价格
|
||||
return _getUSDCPrice();
|
||||
}
|
||||
uint256 basePrice = _getRawPrice(_token);
|
||||
@@ -317,12 +217,8 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
return _applySpread(_token, basePrice, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取最小价格(下压价差)
|
||||
*/
|
||||
function getMinPrice(address _token) external view returns (uint256) {
|
||||
if (_token == usdcAddress) {
|
||||
// USDC通常不需要价差,直接返回原价格
|
||||
return _getUSDCPrice();
|
||||
}
|
||||
uint256 basePrice = _getRawPrice(_token);
|
||||
@@ -330,9 +226,5 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
return _applySpread(_token, basePrice, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,6 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "../../interfaces/IYTPoolManager.sol";
|
||||
import "../../interfaces/IYTVault.sol";
|
||||
|
||||
/**
|
||||
* @title YTRewardRouter
|
||||
* @notice 用户交互入口
|
||||
* @dev UUPS可升级合约
|
||||
*/
|
||||
contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable, PausableUpgradeable {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
@@ -48,13 +43,6 @@ contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrad
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
* @param _usdy USDY代币地址
|
||||
* @param _ytLP ytLP代币地址
|
||||
* @param _ytPoolManager YTPoolManager地址
|
||||
* @param _ytVault YTVault地址
|
||||
*/
|
||||
function initialize(
|
||||
address _usdy,
|
||||
address _ytLP,
|
||||
@@ -78,35 +66,16 @@ contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrad
|
||||
ytVault = _ytVault;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅gov可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
|
||||
|
||||
/**
|
||||
* @notice 暂停合约(仅gov可调用)
|
||||
* @dev 暂停后,所有资金流动操作将被禁止
|
||||
*/
|
||||
function pause() external onlyGov {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 恢复合约(仅gov可调用)
|
||||
*/
|
||||
function unpause() external onlyGov {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 添加流动性
|
||||
* @param _token YT代币或USDC地址
|
||||
* @param _amount 代币数量
|
||||
* @param _minUsdy 最小USDY数量
|
||||
* @param _minYtLP 最小ytLP数量
|
||||
* @return ytLPAmount 获得的ytLP数量
|
||||
*/
|
||||
function addLiquidity(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
@@ -132,14 +101,6 @@ contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrad
|
||||
return ytLPAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 移除流动性
|
||||
* @param _tokenOut 输出代币地址
|
||||
* @param _ytLPAmount ytLP数量
|
||||
* @param _minOut 最小输出数量
|
||||
* @param _receiver 接收地址
|
||||
* @return amountOut 获得的代币数量
|
||||
*/
|
||||
function removeLiquidity(
|
||||
address _tokenOut,
|
||||
uint256 _ytLPAmount,
|
||||
@@ -161,15 +122,6 @@ contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrad
|
||||
return amountOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice YT代币互换
|
||||
* @param _tokenIn 输入代币地址
|
||||
* @param _tokenOut 输出代币地址
|
||||
* @param _amountIn 输入数量
|
||||
* @param _minOut 最小输出数量
|
||||
* @param _receiver 接收地址
|
||||
* @return amountOut 获得的代币数量
|
||||
*/
|
||||
function swapYT(
|
||||
address _tokenIn,
|
||||
address _tokenOut,
|
||||
@@ -192,29 +144,15 @@ contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrad
|
||||
return amountOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取ytLP价格
|
||||
* @return ytLP价格(18位精度)
|
||||
*/
|
||||
function getYtLPPrice() external view returns (uint256) {
|
||||
return IYTPoolManager(ytPoolManager).getPrice(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取账户价值
|
||||
* @param _account 账户地址
|
||||
* @return 账户持有的ytLP价值(USDY计价)
|
||||
*/
|
||||
function getAccountValue(address _account) external view returns (uint256) {
|
||||
uint256 ytLPBalance = IERC20(ytLP).balanceOf(_account);
|
||||
uint256 ytLPPrice = IYTPoolManager(ytPoolManager).getPrice(true);
|
||||
return ytLPBalance * ytLPPrice / (10 ** 18);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,11 +9,6 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "../../interfaces/IUSDY.sol";
|
||||
import "../../interfaces/IYTPriceFeed.sol";
|
||||
|
||||
/**
|
||||
* @title YTVault
|
||||
* @notice 核心资金池,处理YT代币的存储、交换和动态手续费
|
||||
* @dev UUPS可升级合约
|
||||
*/
|
||||
contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
@@ -50,38 +45,32 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
address public priceFeed;
|
||||
address public usdy;
|
||||
|
||||
mapping(address => bool) public isSwapper; // 授权的swap调用者
|
||||
mapping(address => bool) public isSwapper;
|
||||
|
||||
bool public isSwapEnabled;
|
||||
bool public emergencyMode;
|
||||
|
||||
// 代币白名单
|
||||
address[] public allWhitelistedTokens;
|
||||
mapping(address => bool) public whitelistedTokens;
|
||||
mapping(address => bool) public stableTokens; // 稳定币标记
|
||||
mapping(address => bool) public stableTokens;
|
||||
mapping(address => uint256) public tokenDecimals;
|
||||
mapping(address => uint256) public tokenWeights;
|
||||
uint256 public totalTokenWeights;
|
||||
|
||||
// 池子资产
|
||||
mapping(address => uint256) public poolAmounts;
|
||||
mapping(address => uint256) public tokenBalances; // 跟踪实际代币余额
|
||||
mapping(address => uint256) public tokenBalances;
|
||||
|
||||
// USDY债务追踪(用于动态手续费)
|
||||
mapping(address => uint256) public usdyAmounts;
|
||||
mapping(address => uint256) public maxUsdyAmounts;
|
||||
|
||||
// 手续费配置
|
||||
uint256 public swapFeeBasisPoints;
|
||||
uint256 public stableSwapFeeBasisPoints;
|
||||
uint256 public taxBasisPoints;
|
||||
uint256 public stableTaxBasisPoints;
|
||||
bool public hasDynamicFees;
|
||||
|
||||
// 全局滑点保护
|
||||
uint256 public maxSwapSlippageBps; // 10% 最大滑点
|
||||
uint256 public maxSwapSlippageBps;
|
||||
|
||||
// 单笔交易限额
|
||||
mapping(address => uint256) public maxSwapAmount;
|
||||
|
||||
event Swap(
|
||||
@@ -129,11 +118,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
* @param _usdy USDY代币地址
|
||||
* @param _priceFeed 价格预言机地址
|
||||
*/
|
||||
function initialize(address _usdy, address _priceFeed) external initializer {
|
||||
if (_usdy == address(0) || _priceFeed == address(0)) revert InvalidAddress();
|
||||
|
||||
@@ -144,7 +128,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
usdy = _usdy;
|
||||
priceFeed = _priceFeed;
|
||||
|
||||
// 初始化默认值
|
||||
isSwapEnabled = true;
|
||||
emergencyMode = false;
|
||||
swapFeeBasisPoints = 30;
|
||||
@@ -152,16 +135,11 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
taxBasisPoints = 50;
|
||||
stableTaxBasisPoints = 20;
|
||||
hasDynamicFees = true;
|
||||
maxSwapSlippageBps = 1000; // 10% 最大滑点
|
||||
maxSwapSlippageBps = 1000;
|
||||
|
||||
// 将 USDY 标记为稳定币,这样 USDY ↔ 稳定币的互换可以享受低费率
|
||||
stableTokens[_usdy] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅gov可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
|
||||
|
||||
function setGov(address _gov) external onlyGov {
|
||||
@@ -248,7 +226,7 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
}
|
||||
|
||||
function setMaxSwapSlippageBps(uint256 _slippageBps) external onlyGov {
|
||||
if (_slippageBps > 2000) revert SlippageTooHigh(); // 最大20%
|
||||
if (_slippageBps > 2000) revert SlippageTooHigh();
|
||||
maxSwapSlippageBps = _slippageBps;
|
||||
}
|
||||
|
||||
@@ -256,12 +234,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
maxSwapAmount[_token] = _amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 用YT代币购买USDY(添加流动性时调用)
|
||||
* @param _token YT代币地址
|
||||
* @param _receiver USDY接收地址
|
||||
* @return usdyAmountAfterFees 实际获得的USDY数量
|
||||
*/
|
||||
function buyUSDY(address _token, address _receiver)
|
||||
external
|
||||
onlyPoolManager
|
||||
@@ -298,12 +270,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return usdyAmountAfterFees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 用USDY卖出换取YT代币(移除流动性时调用)
|
||||
* @param _token YT代币地址
|
||||
* @param _receiver YT代币接收地址
|
||||
* @return amountOutAfterFees 实际获得的YT代币数量
|
||||
*/
|
||||
function sellUSDY(address _token, address _receiver)
|
||||
external
|
||||
onlyPoolManager
|
||||
@@ -319,29 +285,23 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
|
||||
uint256 price = _getPrice(_token, true);
|
||||
|
||||
// 计算赎回金额(扣费前)
|
||||
uint256 redemptionAmount = usdyAmount * PRICE_PRECISION / price;
|
||||
redemptionAmount = _adjustForDecimals(redemptionAmount, usdy, _token);
|
||||
if (redemptionAmount == 0) revert InvalidAmount();
|
||||
|
||||
// 计算手续费和实际转出金额
|
||||
uint256 feeBasisPoints = _getSwapFeeBasisPoints(usdy, _token, redemptionAmount);
|
||||
uint256 amountOut = redemptionAmount * (BASIS_POINTS_DIVISOR - feeBasisPoints) / BASIS_POINTS_DIVISOR;
|
||||
if (amountOut == 0) revert InvalidAmount();
|
||||
if (poolAmounts[_token] < amountOut) revert InsufficientPool();
|
||||
|
||||
// 计算实际转出的代币对应的USDY价值(用于减少usdyAmount记账)
|
||||
uint256 usdyAmountOut = amountOut * price / PRICE_PRECISION;
|
||||
usdyAmountOut = _adjustForDecimals(usdyAmountOut, _token, usdy);
|
||||
|
||||
// 手续费留在池子:只减少实际转出的部分
|
||||
_decreasePoolAmount(_token, amountOut);
|
||||
_decreaseUsdyAmount(_token, usdyAmountOut);
|
||||
|
||||
// 销毁USDY
|
||||
IUSDY(usdy).burn(address(this), usdyAmount);
|
||||
|
||||
// 转出代币
|
||||
IERC20(_token).safeTransfer(_receiver, amountOut);
|
||||
_updateTokenBalance(_token);
|
||||
|
||||
@@ -350,13 +310,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return amountOut;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice YT代币互换
|
||||
* @param _tokenIn 输入代币地址
|
||||
* @param _tokenOut 输出代币地址
|
||||
* @param _receiver 接收地址
|
||||
* @return amountOutAfterFees 实际获得的输出代币数量
|
||||
*/
|
||||
function swap(
|
||||
address _tokenIn,
|
||||
address _tokenOut,
|
||||
@@ -370,7 +323,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
uint256 amountIn = _transferIn(_tokenIn);
|
||||
if (amountIn == 0) revert InvalidAmount();
|
||||
|
||||
// 检查单笔交易限额
|
||||
if (maxSwapAmount[_tokenIn] > 0) {
|
||||
if (amountIn > maxSwapAmount[_tokenIn]) revert AmountExceedsLimit();
|
||||
}
|
||||
@@ -390,7 +342,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
if (amountOutAfterFees == 0) revert InvalidAmount();
|
||||
if (poolAmounts[_tokenOut] < amountOutAfterFees) revert InsufficientPool();
|
||||
|
||||
// 全局滑点保护(10%)
|
||||
_validateSwapSlippage(amountIn, amountOutAfterFees, priceIn, priceOut);
|
||||
|
||||
_increasePoolAmount(_tokenIn, amountIn);
|
||||
@@ -407,26 +358,14 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return amountOutAfterFees;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取代币价格(带价差)
|
||||
* @param _token 代币地址
|
||||
* @param _maximise true=最大价格, false=最小价格
|
||||
* @return 价格(30位精度)
|
||||
*/
|
||||
function getPrice(address _token, bool _maximise) external view returns (uint256) {
|
||||
return _getPrice(_token, _maximise);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取最大价格
|
||||
*/
|
||||
function getMaxPrice(address _token) external view returns (uint256) {
|
||||
return _getPrice(_token, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取最小价格
|
||||
*/
|
||||
function getMinPrice(address _token) external view returns (uint256) {
|
||||
return _getPrice(_token, false);
|
||||
}
|
||||
@@ -435,11 +374,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return allWhitelistedTokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取池子总价值
|
||||
* @param _maximise true=使用最大价格(对协议有利), false=使用最小价格(对用户有利)
|
||||
* @return 池子总价值(USDY计价)
|
||||
*/
|
||||
function getPoolValue(bool _maximise) external view returns (uint256) {
|
||||
uint256 totalValue = 0;
|
||||
for (uint256 i = 0; i < allWhitelistedTokens.length; i++) {
|
||||
@@ -476,13 +410,6 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
usdyAmounts[_token] = value - _amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取swap手续费率(公开方法,供前端调用)
|
||||
* @param _tokenIn 输入代币
|
||||
* @param _tokenOut 输出代币
|
||||
* @param _usdyAmount USDY数量
|
||||
* @return 手续费率(basis points)
|
||||
*/
|
||||
function getSwapFeeBasisPoints(
|
||||
address _tokenIn,
|
||||
address _tokenOut,
|
||||
@@ -549,14 +476,11 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
? nextAmount - targetAmount
|
||||
: targetAmount - nextAmount;
|
||||
|
||||
// 改善平衡 → 降低手续费
|
||||
if (nextDiff < initialDiff) {
|
||||
uint256 rebateBps = _taxBasisPoints * initialDiff / targetAmount;
|
||||
return rebateBps > _feeBasisPoints ? 0 : _feeBasisPoints - rebateBps;
|
||||
}
|
||||
|
||||
// 恶化平衡 → 提高手续费
|
||||
// taxBps = tax * (a + b) / (2 * target)
|
||||
uint256 sumDiff = initialDiff + nextDiff;
|
||||
if (sumDiff / 2 > targetAmount) {
|
||||
sumDiff = targetAmount * 2;
|
||||
@@ -596,10 +520,8 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
uint256 _priceIn,
|
||||
uint256 _priceOut
|
||||
) private view {
|
||||
// 计算预期输出(不含手续费)
|
||||
uint256 expectedOut = _amountIn * _priceIn / _priceOut;
|
||||
|
||||
// 计算实际滑点
|
||||
if (expectedOut > _amountOut) {
|
||||
uint256 slippage = (expectedOut - _amountOut) * BASIS_POINTS_DIVISOR / expectedOut;
|
||||
if (slippage > maxSwapSlippageBps) revert SlippageTooHigh();
|
||||
@@ -629,10 +551,5 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
return _amount * (10 ** (decimalsTo - decimalsFrom));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,11 +6,6 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
||||
|
||||
/**
|
||||
* @title USDY Token
|
||||
* @notice 统一计价代币
|
||||
* @dev 只有授权的Vault可以铸造和销毁,UUPS可升级合约
|
||||
*/
|
||||
contract USDY is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
@@ -31,62 +26,32 @@ contract USDY is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgrad
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
*/
|
||||
function initialize() external initializer {
|
||||
__ERC20_init("YT USD", "USDY");
|
||||
__Ownable_init(msg.sender);
|
||||
__UUPSUpgradeable_init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅owner可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
|
||||
|
||||
/**
|
||||
* @notice 添加授权的Vault地址
|
||||
* @param _vault Vault合约地址
|
||||
*/
|
||||
function addVault(address _vault) external onlyOwner {
|
||||
if (_vault == address(0)) revert InvalidVault();
|
||||
vaults[_vault] = true;
|
||||
emit VaultAdded(_vault);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 移除授权的Vault地址
|
||||
* @param _vault Vault合约地址
|
||||
*/
|
||||
function removeVault(address _vault) external onlyOwner {
|
||||
vaults[_vault] = false;
|
||||
emit VaultRemoved(_vault);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 铸造USDY代币
|
||||
* @param _account 接收地址
|
||||
* @param _amount 铸造数量
|
||||
*/
|
||||
function mint(address _account, uint256 _amount) external onlyVault {
|
||||
_mint(_account, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 销毁USDY代币
|
||||
* @param _account 销毁地址
|
||||
* @param _amount 销毁数量
|
||||
*/
|
||||
function burn(address _account, uint256 _amount) external onlyVault {
|
||||
_burn(_account, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,11 +6,6 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
||||
|
||||
/**
|
||||
* @title YTLPToken
|
||||
* @notice LP代币,代表用户在池子中的份额
|
||||
* @dev 只有授权的Minter(YTPoolManager)可以铸造和销毁,UUPS可升级合约
|
||||
*/
|
||||
contract YTLPToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
@@ -25,19 +20,12 @@ contract YTLPToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSU
|
||||
|
||||
event MinterSet(address indexed minter, bool isActive);
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
*/
|
||||
function initialize() external initializer {
|
||||
__ERC20_init("YT Liquidity Provider", "ytLP");
|
||||
__Ownable_init(msg.sender);
|
||||
__UUPSUpgradeable_init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅owner可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
|
||||
|
||||
modifier onlyMinter() {
|
||||
@@ -45,39 +33,19 @@ contract YTLPToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSU
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置铸造权限
|
||||
* @param _minter 铸造者地址
|
||||
* @param _isActive 是否激活
|
||||
*/
|
||||
function setMinter(address _minter, bool _isActive) external onlyOwner {
|
||||
if (_minter == address(0)) revert InvalidMinter();
|
||||
isMinter[_minter] = _isActive;
|
||||
emit MinterSet(_minter, _isActive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 铸造ytLP代币
|
||||
* @param _to 接收地址
|
||||
* @param _amount 铸造数量
|
||||
*/
|
||||
function mint(address _to, uint256 _amount) external onlyMinter {
|
||||
_mint(_to, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 销毁ytLP代币
|
||||
* @param _from 销毁地址
|
||||
* @param _amount 销毁数量
|
||||
*/
|
||||
function burn(address _from, uint256 _amount) external onlyMinter {
|
||||
_burn(_from, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,11 +7,6 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
|
||||
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "./YTAssetVault.sol";
|
||||
|
||||
/**
|
||||
* @title YTAssetFactory
|
||||
* @notice 用于批量创建和管理YT资产金库合约的工厂
|
||||
* @dev UUPS可升级合约
|
||||
*/
|
||||
contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
|
||||
error InvalidAddress();
|
||||
@@ -23,16 +18,9 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
/// @notice YTAssetVault实现合约地址
|
||||
address public vaultImplementation;
|
||||
|
||||
/// @notice 所有创建的vault地址列表
|
||||
address[] public allVaults;
|
||||
|
||||
/// @notice vault地址 => 是否存在
|
||||
mapping(address => bool) public isVault;
|
||||
|
||||
/// @notice 默认硬顶值(0表示无限制)
|
||||
uint256 public defaultHardCap;
|
||||
|
||||
event VaultCreated(
|
||||
@@ -49,11 +37,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
event PricesUpdated(address indexed vault, uint256 ytPrice);
|
||||
event NextRedemptionTimeSet(address indexed vault, uint256 redemptionTime);
|
||||
|
||||
/**
|
||||
* @notice 初始化工厂
|
||||
* @param _vaultImplementation YTAssetVault实现合约地址
|
||||
* @param _defaultHardCap 默认硬顶值
|
||||
*/
|
||||
function initialize(
|
||||
address _vaultImplementation,
|
||||
uint256 _defaultHardCap
|
||||
@@ -66,44 +49,20 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
vaultImplementation = _vaultImplementation;
|
||||
defaultHardCap = _defaultHardCap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅owner可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
|
||||
|
||||
/**
|
||||
* @notice 更新YTAssetVault实现合约
|
||||
* @param _newImplementation 新的实现合约地址
|
||||
*/
|
||||
|
||||
function setVaultImplementation(address _newImplementation) external onlyOwner {
|
||||
if (_newImplementation == address(0)) revert InvalidAddress();
|
||||
vaultImplementation = _newImplementation;
|
||||
emit VaultImplementationUpdated(_newImplementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置默认硬顶
|
||||
* @param _defaultHardCap 新的默认硬顶值
|
||||
*/
|
||||
function setDefaultHardCap(uint256 _defaultHardCap) external onlyOwner {
|
||||
defaultHardCap = _defaultHardCap;
|
||||
emit DefaultHardCapSet(_defaultHardCap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 创建新的YTAssetVault
|
||||
* @param _name YT代币名称
|
||||
* @param _symbol YT代币符号
|
||||
* @param _manager 管理员地址
|
||||
* @param _hardCap 硬顶限制(0表示使用默认值)
|
||||
* @param _usdc USDC代币地址(传0使用默认地址)
|
||||
* @param _redemptionTime 赎回时间(Unix时间戳)
|
||||
* @param _initialYtPrice 初始YT价格(精度1e30,传0则使用默认值1.0)
|
||||
* @param _usdcPriceFeed Chainlink USDC价格Feed地址
|
||||
* @return vault 新创建的vault地址
|
||||
*/
|
||||
function createVault(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
@@ -115,11 +74,9 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
address _usdcPriceFeed
|
||||
) external onlyOwner returns (address vault) {
|
||||
if (_manager == address(0)) revert InvalidAddress();
|
||||
|
||||
// 如果传入0,使用默认硬顶
|
||||
|
||||
uint256 actualHardCap = _hardCap == 0 ? defaultHardCap : _hardCap;
|
||||
|
||||
// 编码初始化数据
|
||||
bytes memory initData = abi.encodeWithSelector(
|
||||
YTAssetVault.initialize.selector,
|
||||
_name,
|
||||
@@ -132,10 +89,8 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
_usdcPriceFeed
|
||||
);
|
||||
|
||||
// 部署代理合约
|
||||
vault = address(new ERC1967Proxy(vaultImplementation, initData));
|
||||
|
||||
// 记录vault信息
|
||||
allVaults.push(vault);
|
||||
isVault[vault] = true;
|
||||
|
||||
@@ -149,18 +104,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量创建vault
|
||||
* @param _names YT代币名称数组
|
||||
* @param _symbols YT代币符号数组
|
||||
* @param _managers 管理员地址数组
|
||||
* @param _hardCaps 硬顶数组
|
||||
* @param _usdc USDC代币地址(传0使用默认地址)
|
||||
* @param _redemptionTimes 赎回时间数组(Unix时间戳)
|
||||
* @param _initialYtPrices 初始YT价格数组(精度1e30)
|
||||
* @param _usdcPriceFeed Chainlink USDC价格Feed地址
|
||||
* @return vaults 创建的vault地址数组
|
||||
*/
|
||||
function createVaultBatch(
|
||||
string[] memory _names,
|
||||
string[] memory _symbols,
|
||||
@@ -196,11 +139,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置指定vault的硬顶
|
||||
* @param _vault vault地址
|
||||
* @param _hardCap 新的硬顶值
|
||||
*/
|
||||
function setHardCap(address _vault, uint256 _hardCap) external onlyOwner {
|
||||
if (!isVault[_vault]) revert VaultNotExists();
|
||||
|
||||
@@ -208,11 +146,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
emit HardCapSet(_vault, _hardCap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量设置硬顶
|
||||
* @param _vaults vault地址数组
|
||||
* @param _hardCaps 硬顶值数组
|
||||
*/
|
||||
function setHardCapBatch(
|
||||
address[] memory _vaults,
|
||||
uint256[] memory _hardCaps
|
||||
@@ -226,11 +159,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置vault的管理员
|
||||
* @param _vault vault地址
|
||||
* @param _manager 新管理员地址
|
||||
*/
|
||||
function setVaultManager(address _vault, address _manager) external onlyOwner {
|
||||
if (!isVault[_vault]) revert VaultNotExists();
|
||||
if (_manager == address(0)) revert InvalidAddress();
|
||||
@@ -238,11 +166,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
YTAssetVault(_vault).setManager(_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置vault的下一个赎回时间
|
||||
* @param _vault vault地址
|
||||
* @param _nextRedemptionTime 赎回时间(Unix时间戳)
|
||||
*/
|
||||
function setVaultNextRedemptionTime(address _vault, uint256 _nextRedemptionTime) external onlyOwner {
|
||||
if (!isVault[_vault]) revert VaultNotExists();
|
||||
|
||||
@@ -250,11 +173,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
emit NextRedemptionTimeSet(_vault, _nextRedemptionTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量设置赎回时间
|
||||
* @param _vaults vault地址数组
|
||||
* @param _nextRedemptionTime 统一的赎回时间
|
||||
*/
|
||||
function setVaultNextRedemptionTimeBatch(
|
||||
address[] memory _vaults,
|
||||
uint256 _nextRedemptionTime
|
||||
@@ -266,30 +184,18 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 暂停vault(紧急情况)
|
||||
* @param _vault vault地址
|
||||
*/
|
||||
function pauseVault(address _vault) external onlyOwner {
|
||||
if (!isVault[_vault]) revert VaultNotExists();
|
||||
|
||||
YTAssetVault(_vault).pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 恢复vault
|
||||
* @param _vault vault地址
|
||||
*/
|
||||
function unpauseVault(address _vault) external onlyOwner {
|
||||
if (!isVault[_vault]) revert VaultNotExists();
|
||||
|
||||
YTAssetVault(_vault).unpause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量暂停vaults
|
||||
* @param _vaults vault地址数组
|
||||
*/
|
||||
function pauseVaultBatch(address[] memory _vaults) external onlyOwner {
|
||||
for (uint256 i = 0; i < _vaults.length; i++) {
|
||||
if (!isVault[_vaults[i]]) revert VaultNotExists();
|
||||
@@ -297,10 +203,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量恢复vaults
|
||||
* @param _vaults vault地址数组
|
||||
*/
|
||||
function unpauseVaultBatch(address[] memory _vaults) external onlyOwner {
|
||||
for (uint256 i = 0; i < _vaults.length; i++) {
|
||||
if (!isVault[_vaults[i]]) revert VaultNotExists();
|
||||
@@ -308,11 +210,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 更新vault价格
|
||||
* @param _vault vault地址
|
||||
* @param _ytPrice YT价格(精度1e30)
|
||||
*/
|
||||
function updateVaultPrices(
|
||||
address _vault,
|
||||
uint256 _ytPrice
|
||||
@@ -323,11 +220,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
emit PricesUpdated(_vault, _ytPrice);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量更新价格
|
||||
* @param _vaults vault地址数组
|
||||
* @param _ytPrices YT价格数组(精度1e30)
|
||||
*/
|
||||
function updateVaultPricesBatch(
|
||||
address[] memory _vaults,
|
||||
uint256[] memory _ytPrices
|
||||
@@ -341,11 +233,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 升级指定vault
|
||||
* @param _vault vault地址
|
||||
* @param _newImplementation 新实现地址
|
||||
*/
|
||||
function upgradeVault(address _vault, address _newImplementation) external onlyOwner {
|
||||
if (!isVault[_vault]) revert VaultNotExists();
|
||||
if (_newImplementation == address(0)) revert InvalidAddress();
|
||||
@@ -353,11 +240,6 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
YTAssetVault(_vault).upgradeToAndCall(_newImplementation, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量升级vault
|
||||
* @param _vaults vault地址数组
|
||||
* @param _newImplementation 新实现地址
|
||||
*/
|
||||
function upgradeVaultBatch(
|
||||
address[] memory _vaults,
|
||||
address _newImplementation
|
||||
@@ -370,18 +252,10 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取所有vault数量
|
||||
*/
|
||||
function getVaultCount() external view returns (uint256) {
|
||||
return allVaults.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取指定范围的vault地址
|
||||
* @param _start 起始索引
|
||||
* @param _end 结束索引(不包含)
|
||||
*/
|
||||
function getVaults(uint256 _start, uint256 _end)
|
||||
external
|
||||
view
|
||||
@@ -395,17 +269,10 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取所有vault地址
|
||||
*/
|
||||
function getAllVaults() external view returns (address[] memory) {
|
||||
return allVaults;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取vault详细信息
|
||||
* @param _vault vault地址
|
||||
*/
|
||||
function getVaultInfo(address _vault) external view returns (
|
||||
bool exists,
|
||||
uint256 totalAssets,
|
||||
@@ -431,9 +298,5 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
) = YTAssetVault(_vault).getVaultInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,6 @@ import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
|
||||
|
||||
/**
|
||||
* @title YTAssetVault
|
||||
* @notice 基于价格的资产金库,用户根据USDC和YT代币价格进行兑换
|
||||
* @dev UUPS可升级合约,YT是份额代币
|
||||
*/
|
||||
contract YTAssetVault is
|
||||
Initializable,
|
||||
ERC20Upgradeable,
|
||||
@@ -44,62 +39,39 @@ contract YTAssetVault is
|
||||
error InvalidPriceFeed();
|
||||
error InvalidChainlinkPrice();
|
||||
|
||||
/// @notice 工厂合约地址
|
||||
address public factory;
|
||||
|
||||
/// @notice 管理员地址
|
||||
address public manager;
|
||||
|
||||
/// @notice YT代币硬顶(最大可铸造的YT数量)
|
||||
uint256 public hardCap;
|
||||
|
||||
/// @notice 已提取用于管理的USDC数量
|
||||
uint256 public managedAssets;
|
||||
|
||||
/// @notice USDC代币地址
|
||||
address public usdcAddress;
|
||||
|
||||
/// @notice USDC代币精度(从代币合约读取)
|
||||
uint8 public usdcDecimals;
|
||||
|
||||
/// @notice YT价格(精度1e30)
|
||||
uint256 public ytPrice;
|
||||
|
||||
/// @notice 价格精度
|
||||
uint256 public constant PRICE_PRECISION = 1e30;
|
||||
|
||||
/// @notice Chainlink价格精度
|
||||
uint256 public constant CHAINLINK_PRICE_PRECISION = 1e8;
|
||||
|
||||
/// @notice 下一个赎回开放时间(所有用户统一)
|
||||
uint256 public nextRedemptionTime;
|
||||
|
||||
/// @notice USDC价格Feed
|
||||
AggregatorV3Interface internal usdcPriceFeed;
|
||||
|
||||
/// @notice 提现请求结构体
|
||||
struct WithdrawRequest {
|
||||
address user; // 用户地址
|
||||
uint256 ytAmount; // YT数量
|
||||
uint256 usdcAmount; // 应得USDC数量
|
||||
uint256 requestTime; // 请求时间
|
||||
uint256 queueIndex; // 队列位置
|
||||
bool processed; // 是否已处理
|
||||
address user;
|
||||
uint256 ytAmount;
|
||||
uint256 usdcAmount;
|
||||
uint256 requestTime;
|
||||
uint256 queueIndex;
|
||||
bool processed;
|
||||
}
|
||||
|
||||
/// @notice 请求ID => 请求详情
|
||||
mapping(uint256 => WithdrawRequest) public withdrawRequests;
|
||||
|
||||
/// @notice 用户地址 => 用户的所有请求ID列表
|
||||
mapping(address => uint256[]) private userRequestIds;
|
||||
|
||||
/// @notice 请求ID计数器
|
||||
uint256 public requestIdCounter;
|
||||
|
||||
/// @notice 已处理到的队列位置
|
||||
uint256 public processedUpToIndex;
|
||||
|
||||
/// @notice 当前待处理的请求数量(实时维护,避免循环计算)
|
||||
uint256 public pendingRequestsCount;
|
||||
|
||||
event HardCapSet(uint256 newHardCap);
|
||||
@@ -124,16 +96,6 @@ contract YTAssetVault is
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化金库
|
||||
* @param _name YT代币名称
|
||||
* @param _symbol YT代币符号
|
||||
* @param _manager 管理员地址
|
||||
* @param _hardCap 硬顶限制
|
||||
* @param _usdc USDC代币地址(可选,传0则使用默认地址)
|
||||
* @param _redemptionTime 赎回时间(Unix时间戳)
|
||||
* @param _initialYtPrice 初始YT价格(精度1e30,传0则使用默认值1.0)
|
||||
*/
|
||||
function initialize(
|
||||
string memory _name,
|
||||
string memory _symbol,
|
||||
@@ -153,30 +115,19 @@ contract YTAssetVault is
|
||||
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
|
||||
usdcAddress = _usdc;
|
||||
|
||||
// 获取USDC的decimals
|
||||
usdcDecimals = IERC20Metadata(usdcAddress).decimals();
|
||||
|
||||
factory = msg.sender;
|
||||
manager = _manager;
|
||||
hardCap = _hardCap;
|
||||
|
||||
// 使用传入的初始价格,如果为0则使用默认值1.0
|
||||
ytPrice = _initialYtPrice == 0 ? PRICE_PRECISION : _initialYtPrice;
|
||||
|
||||
// 设置赎回时间
|
||||
nextRedemptionTime = _redemptionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅factory可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyFactory {}
|
||||
|
||||
/**
|
||||
* @notice 获取并验证USDC价格(从Chainlink)
|
||||
* @return 返回uint256格式的USDC价格,精度为1e8
|
||||
*/
|
||||
function _getUSDCPrice() internal view returns (uint256) {
|
||||
(
|
||||
/* uint80 roundId */,
|
||||
@@ -191,72 +142,40 @@ contract YTAssetVault is
|
||||
return uint256(price);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 计算价格转换因子
|
||||
* @dev 转换因子 = 10^(ytDecimals) * PRICE_PRECISION / (10^(usdcDecimals) * CHAINLINK_PRICE_PRECISION)
|
||||
* @return 价格转换因子
|
||||
*/
|
||||
function _getPriceConversionFactor() internal view returns (uint256) {
|
||||
uint8 ytDecimals = decimals();
|
||||
|
||||
// 分子: 10^ytDecimals * PRICE_PRECISION (1e30)
|
||||
uint256 numerator = (10 ** ytDecimals) * PRICE_PRECISION;
|
||||
|
||||
// 分母: 10^usdcDecimals * CHAINLINK_PRICE_PRECISION (1e8)
|
||||
uint256 denominator = (10 ** usdcDecimals) * CHAINLINK_PRICE_PRECISION;
|
||||
|
||||
// 返回转换因子
|
||||
return numerator / denominator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置硬顶
|
||||
* @param _hardCap 新的硬顶值
|
||||
*/
|
||||
function setHardCap(uint256 _hardCap) external onlyFactory {
|
||||
if (_hardCap < totalSupply()) revert InvalidHardCap();
|
||||
hardCap = _hardCap;
|
||||
emit HardCapSet(_hardCap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置管理员
|
||||
* @param _manager 新管理员地址
|
||||
*/
|
||||
function setManager(address _manager) external onlyFactory {
|
||||
manager = _manager;
|
||||
emit ManagerSet(_manager);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 暂停合约(仅factory可调用)
|
||||
* @dev 暂停后,所有资金流动操作将被禁止
|
||||
*/
|
||||
function pause() external onlyFactory {
|
||||
_pause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 恢复合约(仅factory可调用)
|
||||
*/
|
||||
function unpause() external onlyFactory {
|
||||
_unpause();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置下一个赎回开放时间(仅factory可调用)
|
||||
* @param _nextRedemptionTime 下一个赎回时间(Unix时间戳)
|
||||
* @dev 所有用户统一在此时间后才能赎回,类似基金的赎回日
|
||||
*/
|
||||
function setNextRedemptionTime(uint256 _nextRedemptionTime) external onlyFactory {
|
||||
nextRedemptionTime = _nextRedemptionTime;
|
||||
emit NextRedemptionTimeSet(_nextRedemptionTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 更新价格(仅manager可调用)
|
||||
* @param _ytPrice YT价格(精度1e30)
|
||||
*/
|
||||
function updatePrices(uint256 _ytPrice) external onlyFactory {
|
||||
if (_ytPrice == 0) revert InvalidPrice();
|
||||
|
||||
@@ -265,12 +184,6 @@ contract YTAssetVault is
|
||||
emit PriceUpdated(_ytPrice, block.timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 用USDC购买YT
|
||||
* @param _usdcAmount 支付的USDC数量
|
||||
* @return ytAmount 实际获得的YT数量
|
||||
* @dev 首次购买时,YT价格 = USDC价格(1:1兑换)
|
||||
*/
|
||||
function depositYT(uint256 _usdcAmount)
|
||||
external
|
||||
nonReentrant
|
||||
@@ -282,30 +195,19 @@ contract YTAssetVault is
|
||||
uint256 usdcPrice = _getUSDCPrice();
|
||||
uint256 conversionFactor = _getPriceConversionFactor();
|
||||
|
||||
// 计算可以购买的YT数量
|
||||
// ytAmount = _usdcAmount * usdcPrice * conversionFactor / ytPrice
|
||||
ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
|
||||
|
||||
// 检查硬顶
|
||||
if (hardCap > 0 && totalSupply() + ytAmount > hardCap) {
|
||||
revert HardCapExceeded();
|
||||
}
|
||||
|
||||
// 转入USDC
|
||||
IERC20(usdcAddress).safeTransferFrom(msg.sender, address(this), _usdcAmount);
|
||||
|
||||
// 铸造YT
|
||||
_mint(msg.sender, ytAmount);
|
||||
|
||||
emit Buy(msg.sender, _usdcAmount, ytAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 提交YT提现请求(需要等到统一赎回时间)
|
||||
* @param _ytAmount 卖出的YT数量
|
||||
* @return requestId 提现请求ID
|
||||
* @dev 用户提交请求后,YT会立即销毁
|
||||
*/
|
||||
function withdrawYT(uint256 _ytAmount)
|
||||
external
|
||||
nonReentrant
|
||||
@@ -315,7 +217,6 @@ contract YTAssetVault is
|
||||
if (_ytAmount == 0) revert InvalidAmount();
|
||||
if (balanceOf(msg.sender) < _ytAmount) revert InsufficientYTA();
|
||||
|
||||
// 检查是否到达统一赎回时间
|
||||
if (block.timestamp < nextRedemptionTime) {
|
||||
revert StillInLockPeriod();
|
||||
}
|
||||
@@ -323,14 +224,10 @@ contract YTAssetVault is
|
||||
uint256 usdcPrice = _getUSDCPrice();
|
||||
uint256 conversionFactor = _getPriceConversionFactor();
|
||||
|
||||
// 计算可以换取的USDC数量
|
||||
// usdcAmount = _ytAmount * ytPrice / (usdcPrice * conversionFactor)
|
||||
uint256 usdcAmount = (_ytAmount * ytPrice) / (usdcPrice * conversionFactor);
|
||||
|
||||
// 销毁YT代币
|
||||
_burn(msg.sender, _ytAmount);
|
||||
|
||||
// 创建提现请求
|
||||
requestId = requestIdCounter;
|
||||
withdrawRequests[requestId] = WithdrawRequest({
|
||||
user: msg.sender,
|
||||
@@ -341,32 +238,21 @@ contract YTAssetVault is
|
||||
processed: false
|
||||
});
|
||||
|
||||
// 记录用户的请求ID
|
||||
userRequestIds[msg.sender].push(requestId);
|
||||
|
||||
// 递增计数器
|
||||
requestIdCounter++;
|
||||
|
||||
// 增加待处理请求计数
|
||||
pendingRequestsCount++;
|
||||
|
||||
emit WithdrawRequestCreated(requestId, msg.sender, _ytAmount, usdcAmount, requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 批量处理提现请求(仅manager或factory可调用)
|
||||
* @param _batchSize 本批次最多处理的请求数量
|
||||
* @return processedCount 实际处理的请求数量
|
||||
* @return totalDistributed 实际分发的USDC总量
|
||||
* @dev 按照请求ID顺序(即时间先后)依次处理,遇到资金不足时停止
|
||||
*/
|
||||
function processBatchWithdrawals(uint256 _batchSize)
|
||||
external
|
||||
nonReentrant
|
||||
whenNotPaused
|
||||
returns (uint256 processedCount, uint256 totalDistributed)
|
||||
{
|
||||
// 权限检查:只有manager或factory可以调用
|
||||
if (msg.sender != manager && msg.sender != factory) {
|
||||
revert Forbidden();
|
||||
}
|
||||
@@ -379,43 +265,33 @@ contract YTAssetVault is
|
||||
for (uint256 i = processedUpToIndex; i < requestIdCounter && processedCount < _batchSize; i++) {
|
||||
WithdrawRequest storage request = withdrawRequests[i];
|
||||
|
||||
// 跳过已处理的请求
|
||||
if (request.processed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否有足够的USDC
|
||||
if (availableUSDC >= request.usdcAmount) {
|
||||
// 转账USDC给用户
|
||||
IERC20(usdcAddress).safeTransfer(request.user, request.usdcAmount);
|
||||
|
||||
// 标记为已处理
|
||||
request.processed = true;
|
||||
|
||||
// 更新统计
|
||||
availableUSDC -= request.usdcAmount;
|
||||
totalDistributed += request.usdcAmount;
|
||||
processedCount++;
|
||||
|
||||
// 减少待处理请求计数
|
||||
pendingRequestsCount--;
|
||||
|
||||
emit WithdrawRequestProcessed(i, request.user, request.usdcAmount);
|
||||
} else {
|
||||
// USDC不足,停止处理
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新处理进度(跳到下一个未处理的位置)
|
||||
if (processedCount > 0) {
|
||||
// 找到下一个未处理的位置
|
||||
for (uint256 i = processedUpToIndex; i < requestIdCounter; i++) {
|
||||
if (!withdrawRequests[i].processed) {
|
||||
processedUpToIndex = i;
|
||||
break;
|
||||
}
|
||||
// 如果所有请求都已处理完
|
||||
if (i == requestIdCounter - 1) {
|
||||
processedUpToIndex = requestIdCounter;
|
||||
}
|
||||
@@ -425,43 +301,22 @@ contract YTAssetVault is
|
||||
emit BatchProcessed(startIndex, processedUpToIndex, processedCount, totalDistributed);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 查询用户的所有提现请求ID
|
||||
* @param _user 用户地址
|
||||
* @return 用户的所有请求ID数组
|
||||
*/
|
||||
function getUserRequestIds(address _user) external view returns (uint256[] memory) {
|
||||
return userRequestIds[_user];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 查询指定请求的详情
|
||||
* @param _requestId 请求ID
|
||||
* @return request 请求详情
|
||||
*/
|
||||
function getRequestDetails(uint256 _requestId) external view returns (WithdrawRequest memory request) {
|
||||
if (_requestId >= requestIdCounter) revert RequestNotFound();
|
||||
return withdrawRequests[_requestId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取待处理的请求数量
|
||||
* @return 待处理的请求总数
|
||||
* @dev 使用实时维护的计数器,O(1)复杂度,避免gas爆炸
|
||||
*/
|
||||
function getPendingRequestsCount() external view returns (uint256) {
|
||||
return pendingRequestsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取用户待处理的请求
|
||||
* @param _user 用户地址
|
||||
* @return pendingRequests 用户待处理的请求详情数组
|
||||
*/
|
||||
function getUserPendingRequests(address _user) external view returns (WithdrawRequest[] memory pendingRequests) {
|
||||
uint256[] memory requestIds = userRequestIds[_user];
|
||||
|
||||
// 先计算有多少待处理的请求
|
||||
uint256 pendingCount = 0;
|
||||
for (uint256 i = 0; i < requestIds.length; i++) {
|
||||
if (!withdrawRequests[requestIds[i]].processed) {
|
||||
@@ -469,7 +324,6 @@ contract YTAssetVault is
|
||||
}
|
||||
}
|
||||
|
||||
// 构造返回数组
|
||||
pendingRequests = new WithdrawRequest[](pendingCount);
|
||||
uint256 index = 0;
|
||||
for (uint256 i = 0; i < requestIds.length; i++) {
|
||||
@@ -481,13 +335,6 @@ contract YTAssetVault is
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取队列处理进度
|
||||
* @return currentIndex 当前处理到的位置
|
||||
* @return totalRequests 总请求数
|
||||
* @return pendingRequests 待处理请求数
|
||||
* @dev 使用实时维护的计数器,避免循环计算
|
||||
*/
|
||||
function getQueueProgress() external view returns (
|
||||
uint256 currentIndex,
|
||||
uint256 totalRequests,
|
||||
@@ -498,10 +345,6 @@ contract YTAssetVault is
|
||||
pendingRequests = pendingRequestsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 查询距离下次赎回开放还需等待多久
|
||||
* @return remainingTime 剩余时间(秒),0表示可以赎回
|
||||
*/
|
||||
function getTimeUntilNextRedemption() external view returns (uint256 remainingTime) {
|
||||
if (block.timestamp >= nextRedemptionTime) {
|
||||
return 0;
|
||||
@@ -509,19 +352,10 @@ contract YTAssetVault is
|
||||
return nextRedemptionTime - block.timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 检查当前是否可以赎回
|
||||
* @return 是否可以赎回
|
||||
*/
|
||||
function canRedeemNow() external view returns (bool) {
|
||||
return block.timestamp >= nextRedemptionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 提取USDC用于外部投资
|
||||
* @param _to 接收地址
|
||||
* @param _amount 提取数量
|
||||
*/
|
||||
function withdrawForManagement(address _to, uint256 _amount) external onlyManager nonReentrant whenNotPaused {
|
||||
if (_amount == 0) revert InvalidAmount();
|
||||
|
||||
@@ -534,69 +368,40 @@ contract YTAssetVault is
|
||||
emit AssetsWithdrawn(_to, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 将管理的资产归还到金库(可以归还更多,产生收益)
|
||||
* @param _amount 归还数量
|
||||
*/
|
||||
function depositManagedAssets(uint256 _amount) external onlyManager nonReentrant whenNotPaused {
|
||||
if (_amount == 0) revert InvalidAmount();
|
||||
|
||||
// 先更新状态(遵循CEI模式)
|
||||
if (_amount >= managedAssets) {
|
||||
// 归还金额 >= 已管理资产,managedAssets归零,多余部分是收益
|
||||
managedAssets = 0;
|
||||
} else {
|
||||
// 归还金额 < 已管理资产,部分归还
|
||||
managedAssets -= _amount;
|
||||
}
|
||||
|
||||
// 从manager转入USDC到合约
|
||||
IERC20(usdcAddress).safeTransferFrom(msg.sender, address(this), _amount);
|
||||
|
||||
emit AssetsDeposited(_amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取总资产(包含被管理的资产)
|
||||
* @return 总资产 = 合约余额 + 被管理的资产
|
||||
*/
|
||||
|
||||
function totalAssets() public view returns (uint256) {
|
||||
return IERC20(usdcAddress).balanceOf(address(this)) + managedAssets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取空闲资产(可用于提取的资产)
|
||||
* @return 合约中实际持有的USDC数量
|
||||
*/
|
||||
function idleAssets() public view returns (uint256) {
|
||||
return IERC20(usdcAddress).balanceOf(address(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 预览购买:计算支付指定USDC可获得的YT数量
|
||||
* @param _usdcAmount 支付的USDC数量
|
||||
* @return ytAmount 可获得的YT数量
|
||||
*/
|
||||
function previewBuy(uint256 _usdcAmount) external view returns (uint256 ytAmount) {
|
||||
uint256 usdcPrice = _getUSDCPrice();
|
||||
uint256 conversionFactor = _getPriceConversionFactor();
|
||||
ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 预览卖出:计算卖出指定YT可获得的USDC数量
|
||||
* @param _ytAmount 卖出的YT数量
|
||||
* @return usdcAmount 可获得的USDC数量
|
||||
*/
|
||||
function previewSell(uint256 _ytAmount) external view returns (uint256 usdcAmount) {
|
||||
uint256 usdcPrice = _getUSDCPrice();
|
||||
uint256 conversionFactor = _getPriceConversionFactor();
|
||||
usdcAmount = (_ytAmount * ytPrice) / (usdcPrice * conversionFactor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 获取金库信息
|
||||
*/
|
||||
|
||||
function getVaultInfo() external view returns (
|
||||
uint256 _totalAssets,
|
||||
uint256 _idleAssets,
|
||||
@@ -617,9 +422,5 @@ contract YTAssetVault is
|
||||
_nextRedemptionTime = nextRedemptionTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
* 50个slot = 50 * 32 bytes = 1600 bytes
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user