Files
assetxContracts/contracts/vault/YTAssetVault.sol

376 lines
12 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";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title YTAssetVault
* @notice WUSD和YT代币价格进行兑换
* @dev UUPS可升级合约YT是份额代币
*/
contract YTAssetVault is
Initializable,
ERC20Upgradeable,
UUPSUpgradeable,
ReentrancyGuardUpgradeable,
PausableUpgradeable
{
using SafeERC20 for IERC20;
error Forbidden();
error HardCapExceeded();
error InvalidAmount();
error InvalidHardCap();
error InvalidPrice();
error InsufficientWUSD();
error InsufficientYTA();
error StillInLockPeriod();
/// @notice 工厂合约地址
address public factory;
/// @notice 管理员地址
address public manager;
/// @notice YT代币硬顶最大可铸造的YT数量
uint256 public hardCap;
/// @notice 已提取用于管理的WUSD数量
uint256 public managedAssets;
/// @notice WUSD代币地址
address public wusdAddress;
/// @notice WUSD价格精度1e30
uint256 public wusdPrice;
/// @notice YT价格精度1e30
uint256 public ytPrice;
/// @notice 价格精度
uint256 public constant PRICE_PRECISION = 1e30;
/// @notice 下一个赎回开放时间(所有用户统一)
uint256 public nextRedemptionTime;
event HardCapSet(uint256 newHardCap);
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 NextRedemptionTimeSet(uint256 newRedemptionTime);
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
* @param _wusd WUSD代币地址0使
* @param _redemptionTime Unix时间戳
* @param _initialWusdPrice WUSD价格1e300使1.0
* @param _initialYtPrice YT价格1e300使1.0
*
* @dev 1e30
*/
function initialize(
string memory _name,
string memory _symbol,
address _manager,
uint256 _hardCap,
address _wusd,
uint256 _redemptionTime,
uint256 _initialWusdPrice,
uint256 _initialYtPrice
) external initializer {
wusdAddress = _wusd == address(0)
? 0x7Cd017ca5ddb86861FA983a34b5F495C6F898c41
: _wusd;
__ERC20_init(_name, _symbol);
__UUPSUpgradeable_init();
__ReentrancyGuard_init();
__Pausable_init();
factory = msg.sender;
manager = _manager;
hardCap = _hardCap;
// 使用传入的初始价格如果为0则使用默认值1.0
wusdPrice = _initialWusdPrice == 0 ? PRICE_PRECISION : _initialWusdPrice;
ytPrice = _initialYtPrice == 0 ? PRICE_PRECISION : _initialYtPrice;
// 设置赎回时间
nextRedemptionTime = _redemptionTime;
}
/**
* @notice factory可调用
* @param newImplementation
*/
function _authorizeUpgrade(address newImplementation) internal override onlyFactory {}
/**
* @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 _wusdPrice WUSD价格1e30
* @param _ytPrice YT价格1e30
*/
function updatePrices(uint256 _wusdPrice, uint256 _ytPrice) external onlyFactory {
if (_wusdPrice == 0 || _ytPrice == 0) revert InvalidPrice();
wusdPrice = _wusdPrice;
ytPrice = _ytPrice;
emit PriceUpdated(_wusdPrice, _ytPrice, block.timestamp);
}
/**
* @notice WUSD购买YT
* @param _wusdAmount WUSD数量
* @return ytAmount YT数量
* @dev YT价格 = WUSD价格1:1
*/
function depositYT(uint256 _wusdAmount)
external
nonReentrant
whenNotPaused
returns (uint256 ytAmount)
{
if (_wusdAmount == 0) revert InvalidAmount();
// 计算可以购买的YT数量
ytAmount = (_wusdAmount * wusdPrice) / ytPrice;
// 检查硬顶
if (hardCap > 0 && totalSupply() + ytAmount > hardCap) {
revert HardCapExceeded();
}
// 转入WUSD
IERC20(wusdAddress).safeTransferFrom(msg.sender, address(this), _wusdAmount);
// 铸造YT
_mint(msg.sender, ytAmount);
emit Buy(msg.sender, _wusdAmount, ytAmount);
}
/**
* @notice YT换取WUSD
* @param _ytAmount YT数量
* @return wusdAmount WUSD数量
*/
function withdrawYT(uint256 _ytAmount)
external
nonReentrant
whenNotPaused
returns (uint256 wusdAmount)
{
if (_ytAmount == 0) revert InvalidAmount();
if (balanceOf(msg.sender) < _ytAmount) revert InsufficientYTA();
// 检查是否到达统一赎回时间
if (block.timestamp < nextRedemptionTime) {
revert StillInLockPeriod();
}
// 计算可以换取的WUSD数量
wusdAmount = (_ytAmount * ytPrice) / wusdPrice;
// 检查合约是否有足够的WUSD
uint256 availableWUSD = IERC20(wusdAddress).balanceOf(address(this));
if (wusdAmount > availableWUSD) revert InsufficientWUSD();
// 销毁YT
_burn(msg.sender, _ytAmount);
// 转出WUSD
IERC20(wusdAddress).safeTransfer(msg.sender, wusdAmount);
emit Sell(msg.sender, _ytAmount, wusdAmount);
}
/**
* @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;
}
/**
* @notice WUSD用于外部投资
* @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));
if (_amount > availableAssets) revert InvalidAmount();
managedAssets += _amount;
IERC20(wusdAddress).safeTransfer(_to, _amount);
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转入WUSD到合约
IERC20(wusdAddress).safeTransferFrom(msg.sender, address(this), _amount);
emit AssetsDeposited(_amount);
}
/**
* @notice
* @return = +
*/
function totalAssets() public view returns (uint256) {
return IERC20(wusdAddress).balanceOf(address(this)) + managedAssets;
}
/**
* @notice
* @return WUSD数量
*/
function idleAssets() public view returns (uint256) {
return IERC20(wusdAddress).balanceOf(address(this));
}
/**
* @notice WUSD可获得的YT数量
* @param _wusdAmount WUSD数量
* @return ytAmount YT数量
*/
function previewBuy(uint256 _wusdAmount) external view returns (uint256 ytAmount) {
ytAmount = (_wusdAmount * wusdPrice) / ytPrice;
}
/**
* @notice YT可获得的WUSD数量
* @param _ytAmount YT数量
* @return wusdAmount WUSD数量
*/
function previewSell(uint256 _ytAmount) external view returns (uint256 wusdAmount) {
wusdAmount = (_ytAmount * ytPrice) / wusdPrice;
}
/**
* @notice
*/
function getVaultInfo() external view returns (
uint256 _totalAssets,
uint256 _idleAssets,
uint256 _managedAssets,
uint256 _totalSupply,
uint256 _hardCap,
uint256 _wusdPrice,
uint256 _ytPrice,
uint256 _nextRedemptionTime
) {
_totalAssets = totalAssets();
_idleAssets = idleAssets();
_managedAssets = managedAssets;
_totalSupply = totalSupply();
_hardCap = hardCap;
_wusdPrice = wusdPrice;
_ytPrice = ytPrice;
_nextRedemptionTime = nextRedemptionTime;
}
/**
* @dev
* 50slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}