Files
assetxContracts/contracts/ytVault/YTAssetVault.sol

626 lines
21 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/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
2025-12-24 16:41:26 +08:00
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
2025-12-18 13:07:35 +08:00
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
2025-12-24 16:41:26 +08:00
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
2025-12-18 13:07:35 +08:00
/**
* @title YTAssetVault
2025-12-24 16:41:26 +08:00
* @notice USDC和YT代币价格进行兑换
2025-12-18 13:07:35 +08:00
* @dev UUPS可升级合约YT是份额代币
*/
contract YTAssetVault is
Initializable,
ERC20Upgradeable,
UUPSUpgradeable,
ReentrancyGuardUpgradeable,
PausableUpgradeable
{
using SafeERC20 for IERC20;
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 HardCapExceeded();
error InvalidAmount();
error InvalidHardCap();
error InvalidPrice();
2025-12-24 16:41:26 +08:00
error InsufficientUSDC();
2025-12-18 13:07:35 +08:00
error InsufficientYTA();
error StillInLockPeriod();
error RequestNotFound();
error RequestAlreadyProcessed();
error InvalidBatchSize();
2025-12-24 16:41:26 +08:00
error InvalidPriceFeed();
error InvalidChainlinkPrice();
2025-12-18 13:07:35 +08:00
/// @notice 工厂合约地址
address public factory;
/// @notice 管理员地址
address public manager;
/// @notice YT代币硬顶最大可铸造的YT数量
uint256 public hardCap;
2025-12-24 16:41:26 +08:00
/// @notice 已提取用于管理的USDC数量
2025-12-18 13:07:35 +08:00
uint256 public managedAssets;
2025-12-24 16:41:26 +08:00
/// @notice USDC代币地址
address public usdcAddress;
2025-12-18 13:07:35 +08:00
2025-12-24 16:41:26 +08:00
/// @notice USDC代币精度从代币合约读取
uint8 public usdcDecimals;
2025-12-18 13:07:35 +08:00
/// @notice YT价格精度1e30
uint256 public ytPrice;
/// @notice 价格精度
uint256 public constant PRICE_PRECISION = 1e30;
2025-12-24 16:41:26 +08:00
/// @notice Chainlink价格精度
uint256 public constant CHAINLINK_PRICE_PRECISION = 1e8;
2025-12-18 13:07:35 +08:00
/// @notice 下一个赎回开放时间(所有用户统一)
uint256 public nextRedemptionTime;
2025-12-24 16:41:26 +08:00
/// @notice USDC价格Feed
AggregatorV3Interface internal usdcPriceFeed;
2025-12-18 13:07:35 +08:00
/// @notice 提现请求结构体
struct WithdrawRequest {
address user; // 用户地址
uint256 ytAmount; // YT数量
2025-12-24 16:41:26 +08:00
uint256 usdcAmount; // 应得USDC数量
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;
2025-12-18 13:07:35 +08:00
event HardCapSet(uint256 newHardCap);
event ManagerSet(address indexed newManager);
event AssetsWithdrawn(address indexed to, uint256 amount);
event AssetsDeposited(uint256 amount);
2025-12-24 16:41:26 +08:00
event PriceUpdated(uint256 ytPrice, uint256 timestamp);
event Buy(address indexed user, uint256 usdcAmount, uint256 ytAmount);
event Sell(address indexed user, uint256 ytAmount, uint256 usdcAmount);
2025-12-18 13:07:35 +08:00
event NextRedemptionTimeSet(uint256 newRedemptionTime);
2025-12-24 16:41:26 +08:00
event WithdrawRequestCreated(uint256 indexed requestId, address indexed user, uint256 ytAmount, uint256 usdcAmount, uint256 queueIndex);
event WithdrawRequestProcessed(uint256 indexed requestId, address indexed user, uint256 usdcAmount);
event BatchProcessed(uint256 startIndex, uint256 endIndex, uint256 processedCount, uint256 totalUsdcDistributed);
2025-12-18 13:07:35 +08:00
modifier onlyFactory() {
if (msg.sender != factory) revert Forbidden();
_;
}
modifier onlyManager() {
if (msg.sender != manager) revert Forbidden();
_;
}
/**
* @notice
* @param _name YT代币名称
* @param _symbol YT代币符号
* @param _manager
* @param _hardCap
2025-12-24 16:41:26 +08:00
* @param _usdc USDC代币地址0使
2025-12-18 13:07:35 +08:00
* @param _redemptionTime Unix时间戳
* @param _initialYtPrice YT价格1e300使1.0
*/
function initialize(
string memory _name,
string memory _symbol,
address _manager,
uint256 _hardCap,
2025-12-24 16:41:26 +08:00
address _usdc,
2025-12-18 13:07:35 +08:00
uint256 _redemptionTime,
2025-12-24 16:41:26 +08:00
uint256 _initialYtPrice,
address _usdcPriceFeed
2025-12-18 13:07:35 +08:00
) external initializer {
__ERC20_init(_name, _symbol);
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
__Pausable_init();
2025-12-24 16:41:26 +08:00
if (_usdcPriceFeed == address(0)) revert InvalidPriceFeed();
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
usdcAddress = _usdc;
// 获取USDC的decimals
usdcDecimals = IERC20Metadata(usdcAddress).decimals();
2025-12-18 13:07:35 +08:00
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 {}
2025-12-24 16:41:26 +08:00
/**
* @notice USDC价格Chainlink
* @return uint256格式的USDC价格1e8
*/
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);
}
/**
* @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;
}
2025-12-18 13:07:35 +08:00
/**
* @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
*/
2025-12-24 16:41:26 +08:00
function updatePrices(uint256 _ytPrice) external onlyFactory {
if (_ytPrice == 0) revert InvalidPrice();
2025-12-18 13:07:35 +08:00
ytPrice = _ytPrice;
2025-12-24 16:41:26 +08:00
emit PriceUpdated(_ytPrice, block.timestamp);
2025-12-18 13:07:35 +08:00
}
/**
2025-12-24 16:41:26 +08:00
* @notice USDC购买YT
* @param _usdcAmount USDC数量
2025-12-18 13:07:35 +08:00
* @return ytAmount YT数量
2025-12-24 16:41:26 +08:00
* @dev YT价格 = USDC价格1:1
2025-12-18 13:07:35 +08:00
*/
2025-12-24 16:41:26 +08:00
function depositYT(uint256 _usdcAmount)
external
2025-12-18 13:07:35 +08:00
nonReentrant
whenNotPaused
returns (uint256 ytAmount)
{
2025-12-24 16:41:26 +08:00
if (_usdcAmount == 0) revert InvalidAmount();
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
2025-12-18 13:07:35 +08:00
// 计算可以购买的YT数量
2025-12-24 16:41:26 +08:00
// ytAmount = _usdcAmount * usdcPrice * conversionFactor / ytPrice
ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
2025-12-18 13:07:35 +08:00
// 检查硬顶
if (hardCap > 0 && totalSupply() + ytAmount > hardCap) {
revert HardCapExceeded();
}
2025-12-24 16:41:26 +08:00
// 转入USDC
IERC20(usdcAddress).transferFrom(msg.sender, address(this), _usdcAmount);
2025-12-18 13:07:35 +08:00
// 铸造YT
_mint(msg.sender, ytAmount);
2025-12-24 16:41:26 +08:00
emit Buy(msg.sender, _usdcAmount, ytAmount);
2025-12-18 13:07:35 +08:00
}
/**
* @notice YT提现请求
2025-12-18 13:07:35 +08:00
* @param _ytAmount YT数量
* @return requestId ID
2025-12-24 16:41:26 +08:00
* @dev YT会立即销毁
2025-12-18 13:07:35 +08:00
*/
function withdrawYT(uint256 _ytAmount)
external
nonReentrant
whenNotPaused
returns (uint256 requestId)
2025-12-18 13:07:35 +08:00
{
if (_ytAmount == 0) revert InvalidAmount();
if (balanceOf(msg.sender) < _ytAmount) revert InsufficientYTA();
// 检查是否到达统一赎回时间
if (block.timestamp < nextRedemptionTime) {
revert StillInLockPeriod();
}
2025-12-24 16:41:26 +08:00
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
2025-12-18 13:07:35 +08:00
2025-12-24 16:41:26 +08:00
// 计算可以换取的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,
ytAmount: _ytAmount,
2025-12-24 16:41:26 +08:00
usdcAmount: usdcAmount,
requestTime: block.timestamp,
queueIndex: requestId,
processed: false
});
// 记录用户的请求ID
userRequestIds[msg.sender].push(requestId);
// 递增计数器
requestIdCounter++;
// 增加待处理请求计数
pendingRequestsCount++;
2025-12-24 16:41:26 +08:00
emit WithdrawRequestCreated(requestId, msg.sender, _ytAmount, usdcAmount, requestId);
}
/**
* @notice manager或factory可调用
* @param _batchSize
* @return processedCount
2025-12-24 16:41:26 +08:00
* @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();
}
if (_batchSize == 0) revert InvalidBatchSize();
2025-12-18 13:07:35 +08:00
2025-12-24 16:41:26 +08:00
uint256 availableUSDC = IERC20(usdcAddress).balanceOf(address(this));
uint256 startIndex = processedUpToIndex;
2025-12-18 13:07:35 +08:00
for (uint256 i = processedUpToIndex; i < requestIdCounter && processedCount < _batchSize; i++) {
WithdrawRequest storage request = withdrawRequests[i];
// 跳过已处理的请求
if (request.processed) {
continue;
}
2025-12-24 16:41:26 +08:00
// 检查是否有足够的USDC
if (availableUSDC >= request.usdcAmount) {
// 转账USDC给用户
IERC20(usdcAddress).safeTransfer(request.user, request.usdcAmount);
// 标记为已处理
request.processed = true;
// 更新统计
2025-12-24 16:41:26 +08:00
availableUSDC -= request.usdcAmount;
totalDistributed += request.usdcAmount;
processedCount++;
// 减少待处理请求计数
pendingRequestsCount--;
2025-12-24 16:41:26 +08:00
emit WithdrawRequestProcessed(i, request.user, request.usdcAmount);
} else {
2025-12-24 16:41:26 +08:00
// USDC不足停止处理
break;
}
}
2025-12-18 13:07:35 +08:00
// 更新处理进度(跳到下一个未处理的位置)
if (processedCount > 0) {
// 找到下一个未处理的位置
for (uint256 i = processedUpToIndex; i < requestIdCounter; i++) {
if (!withdrawRequests[i].processed) {
processedUpToIndex = i;
break;
}
// 如果所有请求都已处理完
if (i == requestIdCounter - 1) {
processedUpToIndex = requestIdCounter;
}
}
}
2025-12-18 13:07:35 +08:00
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) {
pendingCount++;
}
}
// 构造返回数组
pendingRequests = new WithdrawRequest[](pendingCount);
uint256 index = 0;
for (uint256 i = 0; i < requestIds.length; i++) {
uint256 requestId = requestIds[i];
if (!withdrawRequests[requestId].processed) {
pendingRequests[index] = withdrawRequests[requestId];
index++;
}
}
}
/**
* @notice
* @return currentIndex
* @return totalRequests
* @return pendingRequests
* @dev 使
*/
function getQueueProgress() external view returns (
uint256 currentIndex,
uint256 totalRequests,
uint256 pendingRequests
) {
currentIndex = processedUpToIndex;
totalRequests = requestIdCounter;
pendingRequests = pendingRequestsCount;
2025-12-18 13:07:35 +08:00
}
/**
* @notice
* @return remainingTime 0
*/
function getTimeUntilNextRedemption() external view returns (uint256 remainingTime) {
if (block.timestamp >= nextRedemptionTime) {
return 0;
}
return nextRedemptionTime - block.timestamp;
}
/**
* @notice
* @return
*/
function canRedeemNow() external view returns (bool) {
return block.timestamp >= nextRedemptionTime;
}
/**
2025-12-24 16:41:26 +08:00
* @notice USDC用于外部投资
2025-12-18 13:07:35 +08:00
* @param _to
* @param _amount
*/
function withdrawForManagement(address _to, uint256 _amount) external onlyManager nonReentrant whenNotPaused {
if (_amount == 0) revert InvalidAmount();
2025-12-24 16:41:26 +08:00
uint256 availableAssets = IERC20(usdcAddress).balanceOf(address(this));
2025-12-18 13:07:35 +08:00
if (_amount > availableAssets) revert InvalidAmount();
managedAssets += _amount;
2025-12-24 16:41:26 +08:00
IERC20(usdcAddress).safeTransfer(_to, _amount);
2025-12-18 13:07:35 +08:00
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;
}
2025-12-24 16:41:26 +08:00
// 从manager转入USDC到合约
IERC20(usdcAddress).transferFrom(msg.sender, address(this), _amount);
2025-12-18 13:07:35 +08:00
emit AssetsDeposited(_amount);
}
/**
* @notice
* @return = +
*/
function totalAssets() public view returns (uint256) {
2025-12-24 16:41:26 +08:00
return IERC20(usdcAddress).balanceOf(address(this)) + managedAssets;
2025-12-18 13:07:35 +08:00
}
/**
* @notice
2025-12-24 16:41:26 +08:00
* @return USDC数量
2025-12-18 13:07:35 +08:00
*/
function idleAssets() public view returns (uint256) {
2025-12-24 16:41:26 +08:00
return IERC20(usdcAddress).balanceOf(address(this));
2025-12-18 13:07:35 +08:00
}
/**
2025-12-24 16:41:26 +08:00
* @notice USDC可获得的YT数量
* @param _usdcAmount USDC数量
2025-12-18 13:07:35 +08:00
* @return ytAmount YT数量
*/
2025-12-24 16:41:26 +08:00
function previewBuy(uint256 _usdcAmount) external view returns (uint256 ytAmount) {
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
2025-12-18 13:07:35 +08:00
}
/**
2025-12-24 16:41:26 +08:00
* @notice YT可获得的USDC数量
2025-12-18 13:07:35 +08:00
* @param _ytAmount YT数量
2025-12-24 16:41:26 +08:00
* @return usdcAmount USDC数量
2025-12-18 13:07:35 +08:00
*/
2025-12-24 16:41:26 +08:00
function previewSell(uint256 _ytAmount) external view returns (uint256 usdcAmount) {
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
usdcAmount = (_ytAmount * ytPrice) / (usdcPrice * conversionFactor);
2025-12-18 13:07:35 +08:00
}
/**
* @notice
*/
function getVaultInfo() external view returns (
uint256 _totalAssets,
uint256 _idleAssets,
uint256 _managedAssets,
uint256 _totalSupply,
uint256 _hardCap,
2025-12-24 16:41:26 +08:00
uint256 _usdcPrice,
2025-12-18 13:07:35 +08:00
uint256 _ytPrice,
uint256 _nextRedemptionTime
) {
2025-12-24 16:41:26 +08:00
_usdcPrice = _getUSDCPrice();
2025-12-18 13:07:35 +08:00
_totalAssets = totalAssets();
_idleAssets = idleAssets();
_managedAssets = managedAssets;
_totalSupply = totalSupply();
_hardCap = hardCap;
_ytPrice = ytPrice;
_nextRedemptionTime = nextRedemptionTime;
}
/**
* @dev
* 50slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}