change WUSD payment to USDC
This commit is contained in:
@@ -1,12 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
/**
|
||||
* @title IPriceFeed
|
||||
* @notice 价格预言机接口
|
||||
*/
|
||||
interface IPriceFeed {
|
||||
function getPrice() external view returns (uint256 price);
|
||||
function decimals() external view returns (uint8);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IYTToken {
|
||||
interface IYTAssetVault {
|
||||
function ytPrice() external view returns (uint256);
|
||||
function wusdPrice() external view returns (uint256);
|
||||
}
|
||||
}
|
||||
6
contracts/interfaces/IYTLendingPriceFeed.sol
Normal file
6
contracts/interfaces/IYTLendingPriceFeed.sol
Normal file
@@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
interface IYTLendingPriceFeed {
|
||||
function getPrice(address _token) external view returns (uint256);
|
||||
}
|
||||
@@ -11,5 +11,7 @@ interface IYTVault {
|
||||
function getMinPrice(address _token) external view returns (uint256);
|
||||
function getSwapFeeBasisPoints(address _tokenIn, address _tokenOut, uint256 _usdyAmount) external view returns (uint256);
|
||||
function getRedemptionFeeBasisPoints(address _token, uint256 _usdyAmount) external view returns (uint256);
|
||||
function ytPrice() external view returns (uint256);
|
||||
function wusdPrice() external view returns (uint256);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ contract Configurator is
|
||||
|
||||
// 设置新配置
|
||||
configuratorParams[lendingProxy].baseToken = newConfiguration.baseToken;
|
||||
configuratorParams[lendingProxy].baseTokenPriceFeed = newConfiguration.baseTokenPriceFeed;
|
||||
configuratorParams[lendingProxy].lendingPriceSource = newConfiguration.lendingPriceSource;
|
||||
configuratorParams[lendingProxy].supplyKink = newConfiguration.supplyKink;
|
||||
configuratorParams[lendingProxy].supplyPerYearInterestRateSlopeLow = newConfiguration.supplyPerYearInterestRateSlopeLow;
|
||||
configuratorParams[lendingProxy].supplyPerYearInterestRateSlopeHigh = newConfiguration.supplyPerYearInterestRateSlopeHigh;
|
||||
|
||||
@@ -12,7 +12,7 @@ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import "./LendingStorage.sol";
|
||||
import "./LendingMath.sol";
|
||||
import "../interfaces/ILending.sol";
|
||||
import "../interfaces/IPriceFeed.sol";
|
||||
import "../interfaces/IYTLendingPriceFeed.sol";
|
||||
|
||||
/**
|
||||
* @title Lending
|
||||
@@ -45,7 +45,7 @@ contract Lending is
|
||||
|
||||
// 设置基础配置
|
||||
baseToken = config.baseToken;
|
||||
baseTokenPriceFeed = config.baseTokenPriceFeed;
|
||||
lendingPriceSource = config.lendingPriceSource;
|
||||
|
||||
// 常量:一年的秒数
|
||||
uint64 SECONDS_PER_YEAR = 365 * 24 * 60 * 60; // 31,536,000
|
||||
@@ -327,7 +327,7 @@ contract Lending is
|
||||
if (oldBalance >= 0) revert NotLiquidatable();
|
||||
|
||||
// 计算所有抵押品的总价值(按 liquidationFactor 折扣)
|
||||
uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice();
|
||||
uint256 basePrice = IYTLendingPriceFeed(lendingPriceSource).getPrice(baseToken);
|
||||
uint256 totalCollateralValue = 0;
|
||||
|
||||
for (uint i = 0; i < assetList.length; i++) {
|
||||
@@ -336,7 +336,7 @@ contract Lending is
|
||||
|
||||
if (collateralAmount > 0) {
|
||||
AssetConfig memory assetConfig = assetConfigs[asset];
|
||||
uint256 assetPrice = IPriceFeed(assetConfig.priceFeed).getPrice();
|
||||
uint256 assetPrice = IYTLendingPriceFeed(lendingPriceSource).getPrice(asset);
|
||||
|
||||
// 计算抵押品价值(USD,8位精度)- 用于事件记录
|
||||
uint256 assetScale = 10 ** assetConfig.decimals;
|
||||
@@ -454,24 +454,44 @@ contract Lending is
|
||||
|
||||
/**
|
||||
* @notice 计算支付指定baseAmount可购买的抵押品数量
|
||||
* @dev 重新设计以避免在 1e30 价格精度下溢出
|
||||
*/
|
||||
function quoteCollateral(address asset, uint256 baseAmount) public view override returns (uint256) {
|
||||
AssetConfig memory assetConfig = assetConfigs[asset];
|
||||
|
||||
uint256 assetPrice = IPriceFeed(assetConfig.priceFeed).getPrice();
|
||||
uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice();
|
||||
uint256 assetPrice = IYTLendingPriceFeed(lendingPriceSource).getPrice(asset);
|
||||
uint256 basePrice = IYTLendingPriceFeed(lendingPriceSource).getPrice(baseToken);
|
||||
|
||||
uint256 FACTOR_SCALE = 1e18;
|
||||
uint256 baseScale = 10 ** uint256(IERC20Metadata(baseToken).decimals());
|
||||
uint256 assetScale = 10 ** uint256(assetConfig.decimals);
|
||||
|
||||
// 折扣因子
|
||||
// 计算折扣因子
|
||||
uint256 discountFactor = (storeFrontPriceFactor * (FACTOR_SCALE - assetConfig.liquidationFactor)) / FACTOR_SCALE;
|
||||
|
||||
// 分子: basePrice * baseAmount * assetScale * FACTOR_SCALE
|
||||
// 分母: assetPrice * (FACTOR_SCALE - discountFactor) * baseScale
|
||||
return (basePrice * baseAmount * assetScale * FACTOR_SCALE) /
|
||||
(assetPrice * (FACTOR_SCALE - discountFactor) * baseScale);
|
||||
// 计算折扣后的资产价格 (保持 1e30 精度)
|
||||
uint256 effectiveAssetPrice = (assetPrice * (FACTOR_SCALE - discountFactor)) / FACTOR_SCALE;
|
||||
|
||||
// 为了避免溢出,我们需要重新排列计算:
|
||||
// result = (basePrice * baseAmount * assetScale) / (effectiveAssetPrice * baseScale)
|
||||
//
|
||||
// 由于所有价格都是 1e30 精度,我们可以先约简价格:
|
||||
// priceRatio = basePrice / effectiveAssetPrice (保持精度)
|
||||
// result = (baseAmount * priceRatio * assetScale) / (1e30 * baseScale)
|
||||
//
|
||||
// 但为了避免精度损失,我们分步计算:
|
||||
// step1 = baseAmount * assetScale / baseScale (token amount conversion)
|
||||
// step2 = step1 * basePrice / effectiveAssetPrice (price conversion)
|
||||
|
||||
// 如果 baseScale 和 assetScale 相同(都是18),可以简化
|
||||
if (baseScale == assetScale) {
|
||||
// result = baseAmount * basePrice / effectiveAssetPrice
|
||||
return (baseAmount * basePrice) / effectiveAssetPrice;
|
||||
} else {
|
||||
// 一般情况:分步计算避免溢出
|
||||
uint256 adjustedAmount = (baseAmount * assetScale) / baseScale;
|
||||
return (adjustedAmount * basePrice) / effectiveAssetPrice;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -486,7 +506,7 @@ contract Lending is
|
||||
uint256 debt = uint256(-balance);
|
||||
|
||||
// 将 debt 转换为美元价值(使用 baseToken 价格)
|
||||
uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice();
|
||||
uint256 basePrice = IYTLendingPriceFeed(lendingPriceSource).getPrice(baseToken);
|
||||
uint256 baseDecimals = IERC20Metadata(baseToken).decimals();
|
||||
uint256 debtValue = (debt * basePrice) / (10 ** baseDecimals);
|
||||
|
||||
@@ -508,7 +528,7 @@ contract Lending is
|
||||
uint256 amount = userCollateral[account][asset];
|
||||
if (amount > 0) {
|
||||
AssetConfig memory config = assetConfigs[asset];
|
||||
uint256 price = IPriceFeed(config.priceFeed).getPrice();
|
||||
uint256 price = IYTLendingPriceFeed(lendingPriceSource).getPrice(asset);
|
||||
uint256 value = LendingMath.getCollateralValue(amount, price, config.decimals);
|
||||
totalValue += (value * config.borrowCollateralFactor) / 1e18;
|
||||
}
|
||||
@@ -553,7 +573,7 @@ contract Lending is
|
||||
uint256 debt = uint256(-balance);
|
||||
|
||||
// 将 debt 转换为美元价值(使用 baseToken 价格和 price feed 精度)
|
||||
uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice();
|
||||
uint256 basePrice = IYTLendingPriceFeed(lendingPriceSource).getPrice(baseToken);
|
||||
uint256 baseDecimals = IERC20Metadata(baseToken).decimals();
|
||||
uint256 debtValue = (debt * basePrice) / (10 ** baseDecimals);
|
||||
|
||||
@@ -564,7 +584,7 @@ contract Lending is
|
||||
uint256 amount = userCollateral[account][asset];
|
||||
if (amount > 0) {
|
||||
AssetConfig memory config = assetConfigs[asset];
|
||||
uint256 price = IPriceFeed(config.priceFeed).getPrice();
|
||||
uint256 price = IYTLendingPriceFeed(lendingPriceSource).getPrice(asset);
|
||||
uint256 value = LendingMath.getCollateralValue(amount, price, config.decimals);
|
||||
collateralValue += (value * config.liquidateCollateralFactor) / 1e18;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ pragma solidity ^0.8.0;
|
||||
contract LendingConfiguration {
|
||||
struct AssetConfig {
|
||||
address asset; // 资产地址
|
||||
address priceFeed; // 价格预言机地址
|
||||
uint8 decimals; // 小数位数
|
||||
uint64 borrowCollateralFactor; // 借款抵押率
|
||||
uint64 liquidateCollateralFactor; // 清算抵押率
|
||||
@@ -18,7 +17,7 @@ contract LendingConfiguration {
|
||||
|
||||
struct Configuration {
|
||||
address baseToken; // 基础资产
|
||||
address baseTokenPriceFeed; // 基础资产价格预言机
|
||||
address lendingPriceSource; // 借贷价格源
|
||||
|
||||
// 利率模型参数
|
||||
uint64 supplyKink; // 供应拐点利用率
|
||||
|
||||
@@ -7,7 +7,6 @@ pragma solidity ^0.8.0;
|
||||
*/
|
||||
library LendingMath {
|
||||
uint256 internal constant FACTOR_SCALE = 1e18;
|
||||
uint256 internal constant PRICE_SCALE = 1e8;
|
||||
uint256 internal constant SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
|
||||
|
||||
/**
|
||||
|
||||
39
contracts/ytLending/LendingPriceFeed.sol
Normal file
39
contracts/ytLending/LendingPriceFeed.sol
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "../interfaces/IYTVault.sol";
|
||||
import "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
contract LendingPriceFeed is Ownable {
|
||||
address public ytVault;
|
||||
address public wusdAddress;
|
||||
|
||||
error InvalidYTVaultAddress();
|
||||
error InvalidWUSDAddress();
|
||||
|
||||
constructor(address _ytVault, address _wusdAddress) Ownable(msg.sender) {
|
||||
if (_ytVault == address(0)) revert InvalidYTVaultAddress();
|
||||
if (_wusdAddress == address(0)) revert InvalidWUSDAddress();
|
||||
ytVault = _ytVault;
|
||||
wusdAddress = _wusdAddress;
|
||||
}
|
||||
|
||||
function setYTVault(address _ytVault) external onlyOwner {
|
||||
if (_ytVault == address(0)) revert InvalidYTVaultAddress();
|
||||
ytVault = _ytVault;
|
||||
}
|
||||
|
||||
function setWUSDAddress(address _wusdAddress) external onlyOwner {
|
||||
if (_wusdAddress == address(0)) revert InvalidWUSDAddress();
|
||||
wusdAddress = _wusdAddress;
|
||||
}
|
||||
|
||||
function getPrice(address _token) external view returns (uint256) {
|
||||
if (_token == wusdAddress) {
|
||||
return IYTVault(ytVault).wusdPrice();
|
||||
} else {
|
||||
return IYTVault(_token).ytPrice();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ abstract contract LendingStorage is LendingConfiguration {
|
||||
|
||||
// 市场配置
|
||||
address public baseToken;
|
||||
address public baseTokenPriceFeed;
|
||||
address public lendingPriceSource;
|
||||
|
||||
// 利率参数(每秒利率,已从年化利率转换)
|
||||
uint64 public supplyKink;
|
||||
|
||||
@@ -257,7 +257,7 @@ contract YTPoolManager is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrade
|
||||
function getAumInUsdy(bool _maximise) public view returns (uint256) {
|
||||
uint256 aum = IYTVault(ytVault).getPoolValue(_maximise);
|
||||
|
||||
aum += aumAddition;
|
||||
aum += aumAddition; // aumAddition是协议额外增加的AUM,用来“预留风险缓冲 / 扣除潜在负债”
|
||||
if (aum > aumDeduction) {
|
||||
aum -= aumDeduction;
|
||||
} else {
|
||||
|
||||
@@ -3,7 +3,8 @@ pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
||||
import "../../interfaces/IYTToken.sol";
|
||||
import "../../interfaces/IYTAssetVault.sol";
|
||||
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
|
||||
|
||||
/**
|
||||
* @title YTPriceFeed
|
||||
@@ -22,6 +23,7 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
error PriceChangeTooLarge();
|
||||
error SpreadTooHigh();
|
||||
error InvalidAddress();
|
||||
error InvalidChainlinkPrice();
|
||||
|
||||
address public gov;
|
||||
|
||||
@@ -29,14 +31,13 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
uint256 public constant BASIS_POINTS_DIVISOR = 10000;
|
||||
uint256 public constant MAX_SPREAD_BASIS_POINTS = 200; // 最大2%价差
|
||||
|
||||
// WUSD固定价格
|
||||
address public wusdAddress;
|
||||
|
||||
// WUSD价格来源
|
||||
address public wusdPriceSource;
|
||||
address public usdcAddress;
|
||||
|
||||
// 价格保护参数
|
||||
uint256 public maxPriceChangeBps; // 5% 最大价格变动
|
||||
|
||||
/// @notice USDC价格Feed
|
||||
AggregatorV3Interface internal usdcPriceFeed;
|
||||
|
||||
// 价差配置(每个代币可以有不同的价差)
|
||||
mapping(address => uint256) public spreadBasisPoints;
|
||||
@@ -64,13 +65,31 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
*/
|
||||
function initialize(address _wusdAddress) external initializer {
|
||||
function initialize(address _usdcAddress, address _usdcPriceFeed) external initializer {
|
||||
__UUPSUpgradeable_init();
|
||||
if (_wusdAddress == address(0)) revert InvalidAddress();
|
||||
wusdAddress = _wusdAddress;
|
||||
if (_usdcAddress == address(0)) revert InvalidAddress();
|
||||
usdcAddress = _usdcAddress;
|
||||
usdcPriceFeed = AggregatorV3Interface(_usdcPriceFeed);
|
||||
gov = msg.sender;
|
||||
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可调用)
|
||||
@@ -78,14 +97,6 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
|
||||
|
||||
/**
|
||||
* @notice 设置WUSD价格来源(YTAssetVault地址)
|
||||
* @param _wusdPriceSource YTAssetVault合约地址
|
||||
*/
|
||||
function setWusdPriceSource(address _wusdPriceSource) external onlyGov {
|
||||
wusdPriceSource = _wusdPriceSource;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 设置keeper权限
|
||||
* @param _keeper keeper地址
|
||||
@@ -132,6 +143,30 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
emit SpreadUpdate(_tokens[i], _spreadBasisPoints[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 更新并缓存代币价格(keeper调用)
|
||||
* @param _token 代币地址
|
||||
* @return 更新后的价格
|
||||
*/
|
||||
function updatePrice(address _token) external onlyKeeper returns (uint256) {
|
||||
if (_token == usdcAddress) {
|
||||
return _getUSDCPrice();
|
||||
}
|
||||
|
||||
uint256 oldPrice = lastPrice[_token];
|
||||
uint256 newPrice = _getRawPrice(_token);
|
||||
|
||||
// 价格波动检查
|
||||
_validatePriceChange(_token, newPrice);
|
||||
|
||||
// 更新缓存价格
|
||||
lastPrice[_token] = newPrice;
|
||||
|
||||
emit PriceUpdate(_token, oldPrice, newPrice, block.timestamp);
|
||||
|
||||
return newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 强制更新价格(紧急情况)
|
||||
@@ -159,8 +194,8 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
* - swap时tokenOut:_maximise=true(高估输出)
|
||||
*/
|
||||
function getPrice(address _token, bool _maximise) external view returns (uint256) {
|
||||
if (_token == wusdAddress) {
|
||||
return _getWUSDPrice();
|
||||
if (_token == usdcAddress) {
|
||||
return _getUSDCPrice();
|
||||
}
|
||||
|
||||
uint256 basePrice = _getRawPrice(_token);
|
||||
@@ -172,47 +207,31 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
return _applySpread(_token, basePrice, _maximise);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 更新价格并返回(由keeper调用)
|
||||
* @param _token 代币地址
|
||||
* @return 新价格
|
||||
*/
|
||||
function updatePrice(address _token) external onlyKeeper returns (uint256) {
|
||||
if (_token == wusdAddress) {
|
||||
return _getWUSDPrice();
|
||||
}
|
||||
|
||||
uint256 oldPrice = lastPrice[_token];
|
||||
uint256 newPrice = _getRawPrice(_token);
|
||||
|
||||
// 价格波动检查
|
||||
_validatePriceChange(_token, newPrice);
|
||||
|
||||
lastPrice[_token] = newPrice;
|
||||
|
||||
emit PriceUpdate(_token, oldPrice, newPrice, block.timestamp);
|
||||
|
||||
return newPrice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 直接读取YT代币的ytPrice变量
|
||||
*/
|
||||
function _getRawPrice(address _token) private view returns (uint256) {
|
||||
return IYTToken(_token).ytPrice();
|
||||
return IYTAssetVault(_token).ytPrice();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 从配置的YTAssetVault读取wusdPrice
|
||||
* @dev 如果未设置wusdPriceSource,返回固定价格1.0
|
||||
* @notice 获取并验证USDC价格(从Chainlink)
|
||||
* @return 返回uint256格式的USDC价格,精度为1e30
|
||||
*/
|
||||
function _getWUSDPrice() private view returns (uint256) {
|
||||
if (wusdPriceSource == address(0)) {
|
||||
return PRICE_PRECISION; // 默认1.0
|
||||
}
|
||||
return IYTToken(wusdPriceSource).wusdPrice();
|
||||
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) * 1e22; // 1e22 = 10^(30-8)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice 应用价差
|
||||
* @param _token 代币地址
|
||||
@@ -269,12 +288,12 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
uint256 minPrice,
|
||||
uint256 spread
|
||||
) {
|
||||
if (_token == wusdAddress) {
|
||||
uint256 wusdPrice = _getWUSDPrice();
|
||||
currentPrice = wusdPrice;
|
||||
cachedPrice = wusdPrice;
|
||||
maxPrice = wusdPrice;
|
||||
minPrice = wusdPrice;
|
||||
if (_token == usdcAddress) {
|
||||
uint256 usdcPrice = _getUSDCPrice();
|
||||
currentPrice = usdcPrice;
|
||||
cachedPrice = usdcPrice;
|
||||
maxPrice = usdcPrice;
|
||||
minPrice = usdcPrice;
|
||||
spread = 0;
|
||||
} else {
|
||||
currentPrice = _getRawPrice(_token);
|
||||
@@ -289,9 +308,9 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
* @notice 获取最大价格(上浮价差)
|
||||
*/
|
||||
function getMaxPrice(address _token) external view returns (uint256) {
|
||||
if (_token == wusdAddress) {
|
||||
// WUSD通常不需要价差,直接返回原价格
|
||||
return _getWUSDPrice();
|
||||
if (_token == usdcAddress) {
|
||||
// USDC通常不需要价差,直接返回原价格
|
||||
return _getUSDCPrice();
|
||||
}
|
||||
uint256 basePrice = _getRawPrice(_token);
|
||||
_validatePriceChange(_token, basePrice);
|
||||
@@ -302,9 +321,9 @@ contract YTPriceFeed is Initializable, UUPSUpgradeable {
|
||||
* @notice 获取最小价格(下压价差)
|
||||
*/
|
||||
function getMinPrice(address _token) external view returns (uint256) {
|
||||
if (_token == wusdAddress) {
|
||||
// WUSD通常不需要价差,直接返回原价格
|
||||
return _getWUSDPrice();
|
||||
if (_token == usdcAddress) {
|
||||
// USDC通常不需要价差,直接返回原价格
|
||||
return _getUSDCPrice();
|
||||
}
|
||||
uint256 basePrice = _getRawPrice(_token);
|
||||
_validatePriceChange(_token, basePrice);
|
||||
|
||||
@@ -72,7 +72,6 @@ contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrad
|
||||
|
||||
gov = msg.sender;
|
||||
|
||||
|
||||
usdy = _usdy;
|
||||
ytLP = _ytLP;
|
||||
ytPoolManager = _ytPoolManager;
|
||||
@@ -102,7 +101,7 @@ contract YTRewardRouter is Initializable, UUPSUpgradeable, ReentrancyGuardUpgrad
|
||||
|
||||
/**
|
||||
* @notice 添加流动性
|
||||
* @param _token YT代币或WUSD地址
|
||||
* @param _token YT代币或USDC地址
|
||||
* @param _amount 代币数量
|
||||
* @param _minUsdy 最小USDY数量
|
||||
* @param _minYtLP 最小ytLP数量
|
||||
|
||||
@@ -390,7 +390,7 @@ 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);
|
||||
@@ -509,7 +509,7 @@ contract YTVault is Initializable, UUPSUpgradeable, ReentrancyGuardUpgradeable {
|
||||
address _tokenOut,
|
||||
uint256 _usdyAmount
|
||||
) private view returns (uint256) {
|
||||
// 稳定币交换是指两个代币都是稳定币(如 WUSD <-> USDC)
|
||||
// 稳定币交换是指两个代币都是稳定币(如 USDC <-> USDT)
|
||||
bool isStableSwap = stableTokens[_tokenIn] && stableTokens[_tokenOut];
|
||||
uint256 baseBps = isStableSwap ? stableSwapFeeBasisPoints : swapFeeBasisPoints;
|
||||
uint256 taxBps = isStableSwap ? stableTaxBasisPoints : taxBasisPoints;
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
// 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/access/OwnableUpgradeable.sol";
|
||||
|
||||
/**
|
||||
* @title WUSD
|
||||
* @notice Wrapped USD - 简单的ERC20代币
|
||||
*/
|
||||
contract WUSD is Initializable, ERC20Upgradeable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 初始化合约
|
||||
* @param _name 代币名称
|
||||
* @param _symbol 代币符号
|
||||
*/
|
||||
function initialize(string memory _name, string memory _symbol) external initializer {
|
||||
__ERC20_init(_name, _symbol);
|
||||
__UUPSUpgradeable_init();
|
||||
__Ownable_init(msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 授权升级(仅owner可调用)
|
||||
* @param newImplementation 新实现合约地址
|
||||
*/
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
|
||||
|
||||
/**
|
||||
* @notice 铸造代币
|
||||
* @param _to 接收地址
|
||||
* @param _amount 铸造数量
|
||||
*/
|
||||
function mint(address _to, uint256 _amount) external onlyOwner {
|
||||
_mint(_to, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice 销毁代币
|
||||
* @param _from 销毁地址
|
||||
* @param _amount 销毁数量
|
||||
*/
|
||||
function burn(address _from, uint256 _amount) external onlyOwner {
|
||||
_burn(_from, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev 预留存储空间,用于未来升级时添加新的状态变量
|
||||
*/
|
||||
uint256[50] private __gap;
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user