change WUSD payment to USDC

This commit is contained in:
2025-12-24 16:41:26 +08:00
parent d2e9377f78
commit e21ee7a5df
160 changed files with 6038 additions and 4050 deletions

View File

@@ -46,7 +46,7 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
event VaultImplementationUpdated(address indexed newImplementation);
event DefaultHardCapSet(uint256 newDefaultHardCap);
event HardCapSet(address indexed vault, uint256 newHardCap);
event PricesUpdated(address indexed vault, uint256 wusdPrice, uint256 ytPrice);
event PricesUpdated(address indexed vault, uint256 ytPrice);
event NextRedemptionTimeSet(address indexed vault, uint256 redemptionTime);
/**
@@ -98,10 +98,10 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
* @param _symbol YT代币符号
* @param _manager 管理员地址
* @param _hardCap 硬顶限制0表示使用默认值
* @param _wusd WUSD代币地址传0使用默认地址
* @param _usdc USDC代币地址传0使用默认地址
* @param _redemptionTime 赎回时间Unix时间戳
* @param _initialWusdPrice 初始WUSD价格精度1e30传0则使用默认值1.0
* @param _initialYtPrice 初始YT价格精度1e30传0则使用默认值1.0
* @param _usdcPriceFeed Chainlink USDC价格Feed地址
* @return vault 新创建的vault地址
*/
function createVault(
@@ -109,10 +109,10 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
string memory _symbol,
address _manager,
uint256 _hardCap,
address _wusd,
address _usdc,
uint256 _redemptionTime,
uint256 _initialWusdPrice,
uint256 _initialYtPrice
uint256 _initialYtPrice,
address _usdcPriceFeed
) external onlyOwner returns (address vault) {
if (_manager == address(0)) revert InvalidAddress();
@@ -126,10 +126,10 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
_symbol,
_manager,
actualHardCap,
_wusd,
_usdc,
_redemptionTime,
_initialWusdPrice,
_initialYtPrice
_initialYtPrice,
_usdcPriceFeed
);
// 部署代理合约
@@ -155,10 +155,10 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
* @param _symbols YT代币符号数组
* @param _managers 管理员地址数组
* @param _hardCaps 硬顶数组
* @param _wusd WUSD代币地址传0使用默认地址
* @param _usdc USDC代币地址传0使用默认地址
* @param _redemptionTimes 赎回时间数组Unix时间戳
* @param _initialWusdPrices 初始WUSD价格数组精度1e30
* @param _initialYtPrices 初始YT价格数组精度1e30
* @param _usdcPriceFeed Chainlink USDC价格Feed地址
* @return vaults 创建的vault地址数组
*/
function createVaultBatch(
@@ -166,17 +166,16 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
string[] memory _symbols,
address[] memory _managers,
uint256[] memory _hardCaps,
address _wusd,
address _usdc,
uint256[] memory _redemptionTimes,
uint256[] memory _initialWusdPrices,
uint256[] memory _initialYtPrices
uint256[] memory _initialYtPrices,
address _usdcPriceFeed
) external returns (address[] memory vaults) {
require(
_names.length == _symbols.length &&
_names.length == _managers.length &&
_names.length == _hardCaps.length &&
_names.length == _redemptionTimes.length &&
_names.length == _initialWusdPrices.length &&
_names.length == _initialYtPrices.length,
"Length mismatch"
);
@@ -189,10 +188,10 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
_symbols[i],
_managers[i],
_hardCaps[i],
_wusd,
_usdc,
_redemptionTimes[i],
_initialWusdPrices[i],
_initialYtPrices[i]
_initialYtPrices[i],
_usdcPriceFeed
);
}
}
@@ -312,41 +311,33 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
/**
* @notice 更新vault价格
* @param _vault vault地址
* @param _wusdPrice WUSD价格精度1e30
* @param _ytPrice YT价格精度1e30
*/
function updateVaultPrices(
address _vault,
uint256 _wusdPrice,
address _vault,
uint256 _ytPrice
) external onlyOwner {
if (!isVault[_vault]) revert VaultNotExists();
YTAssetVault(_vault).updatePrices(_wusdPrice, _ytPrice);
emit PricesUpdated(_vault, _wusdPrice, _ytPrice);
YTAssetVault(_vault).updatePrices(_ytPrice);
emit PricesUpdated(_vault, _ytPrice);
}
/**
* @notice 批量更新价格
* @param _vaults vault地址数组
* @param _wusdPrices WUSD价格数组精度1e30
* @param _ytPrices YT价格数组精度1e30
*/
function updateVaultPricesBatch(
address[] memory _vaults,
uint256[] memory _wusdPrices,
uint256[] memory _ytPrices
) external onlyOwner {
require(
_vaults.length == _wusdPrices.length &&
_vaults.length == _ytPrices.length,
"Length mismatch"
);
require(_vaults.length == _ytPrices.length, "Length mismatch");
for (uint256 i = 0; i < _vaults.length; i++) {
if (!isVault[_vaults[i]]) revert VaultNotExists();
YTAssetVault(_vaults[i]).updatePrices(_wusdPrices[i], _ytPrices[i]);
emit PricesUpdated(_vaults[i], _wusdPrices[i], _ytPrices[i]);
YTAssetVault(_vaults[i]).updatePrices(_ytPrices[i]);
emit PricesUpdated(_vaults[i], _ytPrices[i]);
}
}
@@ -422,7 +413,7 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
uint256 managedAssets,
uint256 totalSupply,
uint256 hardCap,
uint256 wusdPrice,
uint256 usdcPrice,
uint256 ytPrice,
uint256 nextRedemptionTime
) {
@@ -434,7 +425,7 @@ contract YTAssetFactory is Initializable, UUPSUpgradeable, OwnableUpgradeable {
managedAssets,
totalSupply,
hardCap,
wusdPrice,
usdcPrice,
ytPrice,
nextRedemptionTime
) = YTAssetVault(_vault).getVaultInfo();

View File

@@ -7,11 +7,13 @@ 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";
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 基于价格的资产金库,用户根据WUSD和YT代币价格进行兑换
* @notice 基于价格的资产金库用户根据USDC和YT代币价格进行兑换
* @dev UUPS可升级合约YT是份额代币
*/
contract YTAssetVault is
@@ -33,12 +35,14 @@ contract YTAssetVault is
error InvalidAmount();
error InvalidHardCap();
error InvalidPrice();
error InsufficientWUSD();
error InsufficientUSDC();
error InsufficientYTA();
error StillInLockPeriod();
error RequestNotFound();
error RequestAlreadyProcessed();
error InvalidBatchSize();
error InvalidPriceFeed();
error InvalidChainlinkPrice();
/// @notice 工厂合约地址
address public factory;
@@ -49,14 +53,14 @@ contract YTAssetVault is
/// @notice YT代币硬顶最大可铸造的YT数量
uint256 public hardCap;
/// @notice 已提取用于管理的WUSD数量
/// @notice 已提取用于管理的USDC数量
uint256 public managedAssets;
/// @notice WUSD代币地址
address public wusdAddress;
/// @notice USDC代币地址
address public usdcAddress;
/// @notice WUSD价格精度1e30
uint256 public wusdPrice;
/// @notice USDC代币精度从代币合约读取
uint8 public usdcDecimals;
/// @notice YT价格精度1e30
uint256 public ytPrice;
@@ -64,14 +68,20 @@ contract YTAssetVault is
/// @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 wusdAmount; // 应得WUSD数量
uint256 usdcAmount; // 应得USDC数量
uint256 requestTime; // 请求时间
uint256 queueIndex; // 队列位置
bool processed; // 是否已处理
@@ -96,13 +106,13 @@ contract YTAssetVault is
event ManagerSet(address indexed newManager);
event AssetsWithdrawn(address indexed to, uint256 amount);
event AssetsDeposited(uint256 amount);
event PriceUpdated(uint256 wusdPrice, uint256 ytPrice, uint256 timestamp);
event Buy(address indexed user, uint256 wusdAmount, uint256 ytAmount);
event Sell(address indexed user, uint256 ytAmount, uint256 wusdAmount);
event PriceUpdated(uint256 ytPrice, uint256 timestamp);
event Buy(address indexed user, uint256 usdcAmount, uint256 ytAmount);
event Sell(address indexed user, uint256 ytAmount, uint256 usdcAmount);
event NextRedemptionTimeSet(uint256 newRedemptionTime);
event WithdrawRequestCreated(uint256 indexed requestId, address indexed user, uint256 ytAmount, uint256 wusdAmount, uint256 queueIndex);
event WithdrawRequestProcessed(uint256 indexed requestId, address indexed user, uint256 wusdAmount);
event BatchProcessed(uint256 startIndex, uint256 endIndex, uint256 processedCount, uint256 totalWusdDistributed);
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);
modifier onlyFactory() {
if (msg.sender != factory) revert Forbidden();
@@ -120,38 +130,37 @@ contract YTAssetVault is
* @param _symbol YT代币符号
* @param _manager 管理员地址
* @param _hardCap 硬顶限制
* @param _wusd WUSD代币地址可选传0则使用默认地址
* @param _usdc USDC代币地址可选传0则使用默认地址
* @param _redemptionTime 赎回时间Unix时间戳
* @param _initialWusdPrice 初始WUSD价格精度1e30传0则使用默认值1.0
* @param _initialYtPrice 初始YT价格精度1e30传0则使用默认值1.0
*
* @dev 价格精度为1e30
*/
function initialize(
string memory _name,
string memory _symbol,
address _manager,
uint256 _hardCap,
address _wusd,
address _usdc,
uint256 _redemptionTime,
uint256 _initialWusdPrice,
uint256 _initialYtPrice
uint256 _initialYtPrice,
address _usdcPriceFeed
) external initializer {
wusdAddress = _wusd == address(0)
? 0x7Cd017ca5ddb86861FA983a34b5F495C6F898c41
: _wusd;
__ERC20_init(_name, _symbol);
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
__Pausable_init();
if (_usdcPriceFeed == address(0)) revert InvalidPriceFeed();
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
usdcAddress = _usdc;
// 获取USDC的decimals
usdcDecimals = IERC20Metadata(usdcAddress).decimals();
factory = msg.sender;
manager = _manager;
hardCap = _hardCap;
// 使用传入的初始价格如果为0则使用默认值1.0
wusdPrice = _initialWusdPrice == 0 ? PRICE_PRECISION : _initialWusdPrice;
ytPrice = _initialYtPrice == 0 ? PRICE_PRECISION : _initialYtPrice;
// 设置赎回时间
@@ -164,6 +173,42 @@ contract YTAssetVault is
*/
function _authorizeUpgrade(address newImplementation) internal override onlyFactory {}
/**
* @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;
}
/**
* @notice 设置硬顶
* @param _hardCap 新的硬顶值
@@ -210,54 +255,56 @@ contract YTAssetVault is
/**
* @notice 更新价格仅manager可调用
* @param _wusdPrice WUSD价格精度1e30
* @param _ytPrice YT价格精度1e30
*/
function updatePrices(uint256 _wusdPrice, uint256 _ytPrice) external onlyFactory {
if (_wusdPrice == 0 || _ytPrice == 0) revert InvalidPrice();
function updatePrices(uint256 _ytPrice) external onlyFactory {
if (_ytPrice == 0) revert InvalidPrice();
wusdPrice = _wusdPrice;
ytPrice = _ytPrice;
emit PriceUpdated(_wusdPrice, _ytPrice, block.timestamp);
emit PriceUpdated(_ytPrice, block.timestamp);
}
/**
* @notice 用WUSD购买YT
* @param _wusdAmount 支付的WUSD数量
* @notice 用USDC购买YT
* @param _usdcAmount 支付的USDC数量
* @return ytAmount 实际获得的YT数量
* @dev 首次购买时YT价格 = WUSD价格1:1兑换
* @dev 首次购买时YT价格 = USDC价格1:1兑换
*/
function depositYT(uint256 _wusdAmount)
external
function depositYT(uint256 _usdcAmount)
external
nonReentrant
whenNotPaused
returns (uint256 ytAmount)
{
if (_wusdAmount == 0) revert InvalidAmount();
if (_usdcAmount == 0) revert InvalidAmount();
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
// 计算可以购买的YT数量
ytAmount = (_wusdAmount * wusdPrice) / ytPrice;
// ytAmount = _usdcAmount * usdcPrice * conversionFactor / ytPrice
ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
// 检查硬顶
if (hardCap > 0 && totalSupply() + ytAmount > hardCap) {
revert HardCapExceeded();
}
// 转入WUSD
IERC20(wusdAddress).safeTransferFrom(msg.sender, address(this), _wusdAmount);
// 转入USDC
IERC20(usdcAddress).safeTransferFrom(msg.sender, address(this), _usdcAmount);
// 铸造YT
_mint(msg.sender, ytAmount);
emit Buy(msg.sender, _wusdAmount, ytAmount);
emit Buy(msg.sender, _usdcAmount, ytAmount);
}
/**
* @notice 提交YT提现请求需要等到统一赎回时间
* @param _ytAmount 卖出的YT数量
* @return requestId 提现请求ID
* @dev 用户提交请求后YT会立即销毁但WUSD需要等待批量处理后才能领取
* @dev 用户提交请求后YT会立即销毁
*/
function withdrawYT(uint256 _ytAmount)
external
@@ -272,9 +319,13 @@ contract YTAssetVault is
if (block.timestamp < nextRedemptionTime) {
revert StillInLockPeriod();
}
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
// 计算可以换取的WUSD数量
uint256 wusdAmount = (_ytAmount * ytPrice) / wusdPrice;
// 计算可以换取的USDC数量
// usdcAmount = _ytAmount * ytPrice / (usdcPrice * conversionFactor)
uint256 usdcAmount = (_ytAmount * ytPrice) / (usdcPrice * conversionFactor);
// 销毁YT代币
_burn(msg.sender, _ytAmount);
@@ -284,7 +335,7 @@ contract YTAssetVault is
withdrawRequests[requestId] = WithdrawRequest({
user: msg.sender,
ytAmount: _ytAmount,
wusdAmount: wusdAmount,
usdcAmount: usdcAmount,
requestTime: block.timestamp,
queueIndex: requestId,
processed: false
@@ -299,14 +350,14 @@ contract YTAssetVault is
// 增加待处理请求计数
pendingRequestsCount++;
emit WithdrawRequestCreated(requestId, msg.sender, _ytAmount, wusdAmount, requestId);
emit WithdrawRequestCreated(requestId, msg.sender, _ytAmount, usdcAmount, requestId);
}
/**
* @notice 批量处理提现请求仅manager或factory可调用
* @param _batchSize 本批次最多处理的请求数量
* @return processedCount 实际处理的请求数量
* @return totalDistributed 实际分发的WUSD总量
* @return totalDistributed 实际分发的USDC总量
* @dev 按照请求ID顺序即时间先后依次处理遇到资金不足时停止
*/
function processBatchWithdrawals(uint256 _batchSize)
@@ -322,7 +373,7 @@ contract YTAssetVault is
if (_batchSize == 0) revert InvalidBatchSize();
uint256 availableWUSD = IERC20(wusdAddress).balanceOf(address(this));
uint256 availableUSDC = IERC20(usdcAddress).balanceOf(address(this));
uint256 startIndex = processedUpToIndex;
for (uint256 i = processedUpToIndex; i < requestIdCounter && processedCount < _batchSize; i++) {
@@ -333,25 +384,25 @@ contract YTAssetVault is
continue;
}
// 检查是否有足够的WUSD
if (availableWUSD >= request.wusdAmount) {
// 转账WUSD给用户
IERC20(wusdAddress).safeTransfer(request.user, request.wusdAmount);
// 检查是否有足够的USDC
if (availableUSDC >= request.usdcAmount) {
// 转账USDC给用户
IERC20(usdcAddress).safeTransfer(request.user, request.usdcAmount);
// 标记为已处理
request.processed = true;
// 更新统计
availableWUSD -= request.wusdAmount;
totalDistributed += request.wusdAmount;
availableUSDC -= request.usdcAmount;
totalDistributed += request.usdcAmount;
processedCount++;
// 减少待处理请求计数
pendingRequestsCount--;
emit WithdrawRequestProcessed(i, request.user, request.wusdAmount);
emit WithdrawRequestProcessed(i, request.user, request.usdcAmount);
} else {
// WUSD不足停止处理
// USDC不足,停止处理
break;
}
}
@@ -467,18 +518,18 @@ contract YTAssetVault is
}
/**
* @notice 提取WUSD用于外部投资
* @notice 提取USDC用于外部投资
* @param _to 接收地址
* @param _amount 提取数量
*/
function withdrawForManagement(address _to, uint256 _amount) external onlyManager nonReentrant whenNotPaused {
if (_amount == 0) revert InvalidAmount();
uint256 availableAssets = IERC20(wusdAddress).balanceOf(address(this));
uint256 availableAssets = IERC20(usdcAddress).balanceOf(address(this));
if (_amount > availableAssets) revert InvalidAmount();
managedAssets += _amount;
IERC20(wusdAddress).safeTransfer(_to, _amount);
IERC20(usdcAddress).safeTransfer(_to, _amount);
emit AssetsWithdrawn(_to, _amount);
}
@@ -499,8 +550,8 @@ contract YTAssetVault is
managedAssets -= _amount;
}
// 从manager转入WUSD到合约
IERC20(wusdAddress).safeTransferFrom(msg.sender, address(this), _amount);
// 从manager转入USDC到合约
IERC20(usdcAddress).safeTransferFrom(msg.sender, address(this), _amount);
emit AssetsDeposited(_amount);
}
@@ -510,33 +561,37 @@ contract YTAssetVault is
* @return 总资产 = 合约余额 + 被管理的资产
*/
function totalAssets() public view returns (uint256) {
return IERC20(wusdAddress).balanceOf(address(this)) + managedAssets;
return IERC20(usdcAddress).balanceOf(address(this)) + managedAssets;
}
/**
* @notice 获取空闲资产(可用于提取的资产)
* @return 合约中实际持有的WUSD数量
* @return 合约中实际持有的USDC数量
*/
function idleAssets() public view returns (uint256) {
return IERC20(wusdAddress).balanceOf(address(this));
return IERC20(usdcAddress).balanceOf(address(this));
}
/**
* @notice 预览购买:计算支付指定WUSD可获得的YT数量
* @param _wusdAmount 支付的WUSD数量
* @notice 预览购买计算支付指定USDC可获得的YT数量
* @param _usdcAmount 支付的USDC数量
* @return ytAmount 可获得的YT数量
*/
function previewBuy(uint256 _wusdAmount) external view returns (uint256 ytAmount) {
ytAmount = (_wusdAmount * wusdPrice) / ytPrice;
function previewBuy(uint256 _usdcAmount) external view returns (uint256 ytAmount) {
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
ytAmount = (_usdcAmount * usdcPrice * conversionFactor) / ytPrice;
}
/**
* @notice 预览卖出计算卖出指定YT可获得的WUSD数量
* @notice 预览卖出计算卖出指定YT可获得的USDC数量
* @param _ytAmount 卖出的YT数量
* @return wusdAmount 可获得的WUSD数量
* @return usdcAmount 可获得的USDC数量
*/
function previewSell(uint256 _ytAmount) external view returns (uint256 wusdAmount) {
wusdAmount = (_ytAmount * ytPrice) / wusdPrice;
function previewSell(uint256 _ytAmount) external view returns (uint256 usdcAmount) {
uint256 usdcPrice = _getUSDCPrice();
uint256 conversionFactor = _getPriceConversionFactor();
usdcAmount = (_ytAmount * ytPrice) / (usdcPrice * conversionFactor);
}
/**
@@ -548,16 +603,16 @@ contract YTAssetVault is
uint256 _managedAssets,
uint256 _totalSupply,
uint256 _hardCap,
uint256 _wusdPrice,
uint256 _usdcPrice,
uint256 _ytPrice,
uint256 _nextRedemptionTime
) {
_usdcPrice = _getUSDCPrice();
_totalAssets = totalAssets();
_idleAssets = idleAssets();
_managedAssets = managedAssets;
_totalSupply = totalSupply();
_hardCap = hardCap;
_wusdPrice = wusdPrice;
_ytPrice = ytPrice;
_nextRedemptionTime = nextRedemptionTime;
}