This commit is contained in:
2025-12-18 13:07:35 +08:00
commit 76b7f838db
271 changed files with 88812 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IUSDY {
function mint(address _to, uint256 _amount) external;
function burn(address _from, uint256 _amount) external;
function totalSupply() external view returns (uint256);
}

View File

@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IYTLPToken {
function mint(address _to, uint256 _amount) external;
function burn(address _from, uint256 _amount) external;
}

View File

@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IYTPoolManager {
function addLiquidityForAccount(
address _fundingAccount,
address _account,
address _token,
uint256 _amount,
uint256 _minUsdy,
uint256 _minYtLP
) external returns (uint256);
function removeLiquidityForAccount(
address _account,
address _tokenOut,
uint256 _ytLPAmount,
uint256 _minOut,
address _receiver
) external returns (uint256);
function getPrice(bool _maximise) external view returns (uint256);
}

View File

@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IYTPriceFeed {
function getPrice(address _token, bool _maximise) external view returns (uint256);
}

View File

@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IYTToken {
function ytPrice() external view returns (uint256);
function wusdPrice() external view returns (uint256);
}

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IYTVault {
function buyUSDY(address _token, address _receiver) external returns (uint256);
function sellUSDY(address _token, address _receiver) external returns (uint256);
function swap(address _tokenIn, address _tokenOut, address _receiver) external returns (uint256);
function getPoolValue(bool _maximise) external view returns (uint256);
function getPrice(address _token, bool _maximise) external view returns (uint256);
function getMaxPrice(address _token) external view returns (uint256);
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);
}

View File

@@ -0,0 +1,443 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
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();
error VaultNotExists();
error InvalidHardCap();
/// @notice YTAssetVault实现合约地址
address public vaultImplementation;
/// @notice 所有创建的vault地址列表
address[] public allVaults;
/// @notice vault地址 => 是否存在
mapping(address => bool) public isVault;
/// @notice 默认硬顶值0表示无限制
uint256 public defaultHardCap;
event VaultCreated(
address indexed vault,
address indexed manager,
string name,
string symbol,
uint256 hardCap,
uint256 index
);
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 NextRedemptionTimeSet(address indexed vault, uint256 redemptionTime);
/**
* @notice 初始化工厂
* @param _vaultImplementation YTAssetVault实现合约地址
* @param _defaultHardCap 默认硬顶值
*/
function initialize(
address _vaultImplementation,
uint256 _defaultHardCap
) external initializer {
if (_vaultImplementation == address(0)) revert InvalidAddress();
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
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 _wusd WUSD代币地址传0使用默认地址
* @param _redemptionTime 赎回时间Unix时间戳
* @param _initialWusdPrice 初始WUSD价格精度1e30传0则使用默认值1.0
* @param _initialYtPrice 初始YT价格精度1e30传0则使用默认值1.0
* @return vault 新创建的vault地址
*/
function createVault(
string memory _name,
string memory _symbol,
address _manager,
uint256 _hardCap,
address _wusd,
uint256 _redemptionTime,
uint256 _initialWusdPrice,
uint256 _initialYtPrice
) 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,
_symbol,
_manager,
actualHardCap,
_wusd,
_redemptionTime,
_initialWusdPrice,
_initialYtPrice
);
// 部署代理合约
vault = address(new ERC1967Proxy(vaultImplementation, initData));
// 记录vault信息
allVaults.push(vault);
isVault[vault] = true;
emit VaultCreated(
vault,
_manager,
_name,
_symbol,
actualHardCap,
allVaults.length - 1
);
}
/**
* @notice 批量创建vault
* @param _names YT代币名称数组
* @param _symbols YT代币符号数组
* @param _managers 管理员地址数组
* @param _hardCaps 硬顶数组
* @param _wusd WUSD代币地址传0使用默认地址
* @param _redemptionTimes 赎回时间数组Unix时间戳
* @param _initialWusdPrices 初始WUSD价格数组精度1e30
* @param _initialYtPrices 初始YT价格数组精度1e30
* @return vaults 创建的vault地址数组
*/
function createVaultBatch(
string[] memory _names,
string[] memory _symbols,
address[] memory _managers,
uint256[] memory _hardCaps,
address _wusd,
uint256[] memory _redemptionTimes,
uint256[] memory _initialWusdPrices,
uint256[] memory _initialYtPrices
) 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"
);
vaults = new address[](_names.length);
for (uint256 i = 0; i < _names.length; i++) {
vaults[i] = this.createVault(
_names[i],
_symbols[i],
_managers[i],
_hardCaps[i],
_wusd,
_redemptionTimes[i],
_initialWusdPrices[i],
_initialYtPrices[i]
);
}
}
/**
* @notice 设置指定vault的硬顶
* @param _vault vault地址
* @param _hardCap 新的硬顶值
*/
function setHardCap(address _vault, uint256 _hardCap) external onlyOwner {
if (!isVault[_vault]) revert VaultNotExists();
YTAssetVault(_vault).setHardCap(_hardCap);
emit HardCapSet(_vault, _hardCap);
}
/**
* @notice 批量设置硬顶
* @param _vaults vault地址数组
* @param _hardCaps 硬顶值数组
*/
function setHardCapBatch(
address[] memory _vaults,
uint256[] memory _hardCaps
) external onlyOwner {
require(_vaults.length == _hardCaps.length, "Length mismatch");
for (uint256 i = 0; i < _vaults.length; i++) {
if (!isVault[_vaults[i]]) revert VaultNotExists();
YTAssetVault(_vaults[i]).setHardCap(_hardCaps[i]);
emit HardCapSet(_vaults[i], _hardCaps[i]);
}
}
/**
* @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();
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();
YTAssetVault(_vault).setNextRedemptionTime(_nextRedemptionTime);
emit NextRedemptionTimeSet(_vault, _nextRedemptionTime);
}
/**
* @notice 批量设置赎回时间
* @param _vaults vault地址数组
* @param _nextRedemptionTime 统一的赎回时间
*/
function setVaultNextRedemptionTimeBatch(
address[] memory _vaults,
uint256 _nextRedemptionTime
) external onlyOwner {
for (uint256 i = 0; i < _vaults.length; i++) {
if (!isVault[_vaults[i]]) revert VaultNotExists();
YTAssetVault(_vaults[i]).setNextRedemptionTime(_nextRedemptionTime);
emit NextRedemptionTimeSet(_vaults[i], _nextRedemptionTime);
}
}
/**
* @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();
YTAssetVault(_vaults[i]).pause();
}
}
/**
* @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();
YTAssetVault(_vaults[i]).unpause();
}
}
/**
* @notice 更新vault价格
* @param _vault vault地址
* @param _wusdPrice WUSD价格精度1e30
* @param _ytPrice YT价格精度1e30
*/
function updateVaultPrices(
address _vault,
uint256 _wusdPrice,
uint256 _ytPrice
) external onlyOwner {
if (!isVault[_vault]) revert VaultNotExists();
YTAssetVault(_vault).updatePrices(_wusdPrice, _ytPrice);
emit PricesUpdated(_vault, _wusdPrice, _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"
);
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]);
}
}
/**
* @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();
YTAssetVault(_vault).upgradeToAndCall(_newImplementation, "");
}
/**
* @notice 批量升级vault
* @param _vaults vault地址数组
* @param _newImplementation 新实现地址
*/
function upgradeVaultBatch(
address[] memory _vaults,
address _newImplementation
) external onlyOwner {
if (_newImplementation == address(0)) revert InvalidAddress();
for (uint256 i = 0; i < _vaults.length; i++) {
if (!isVault[_vaults[i]]) revert VaultNotExists();
YTAssetVault(_vaults[i]).upgradeToAndCall(_newImplementation, "");
}
}
/**
* @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
returns (address[] memory vaults)
{
require(_start < _end && _end <= allVaults.length, "Invalid range");
vaults = new address[](_end - _start);
for (uint256 i = _start; i < _end; i++) {
vaults[i - _start] = allVaults[i];
}
}
/**
* @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,
uint256 idleAssets,
uint256 managedAssets,
uint256 totalSupply,
uint256 hardCap,
uint256 wusdPrice,
uint256 ytPrice,
uint256 nextRedemptionTime
) {
exists = isVault[_vault];
if (!exists) return (false, 0, 0, 0, 0, 0, 0, 0, 0);
(
totalAssets,
idleAssets,
managedAssets,
totalSupply,
hardCap,
wusdPrice,
ytPrice,
nextRedemptionTime
) = YTAssetVault(_vault).getVaultInfo();
}
/**
* @dev 预留存储空间,用于未来升级时添加新的状态变量
* 50个slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}

View File

@@ -0,0 +1,375 @@
// 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价格精度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,
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 预留存储空间,用于未来升级时添加新的状态变量
* 50个slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}

View File

@@ -0,0 +1,204 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "./ConfiguratorStorage.sol";
import "./LendingFactory.sol";
/**
* @title Configurator
* @notice 借贷池配置管理合约
*/
contract Configurator is
ConfiguratorStorage,
UUPSUpgradeable,
OwnableUpgradeable
{
event SetFactory(address indexed lendingProxy, address indexed oldFactory, address indexed newFactory);
event SetConfiguration(address indexed lendingProxy, Configuration oldConfiguration, Configuration newConfiguration);
event AddAsset(address indexed lendingProxy, AssetConfig assetConfig);
event UpdateAsset(address indexed lendingProxy, AssetConfig oldAssetConfig, AssetConfig newAssetConfig);
event LendingDeployed(address indexed lendingProxy, address indexed newLending);
error AlreadyInitialized();
error AssetDoesNotExist();
error ConfigurationAlreadyExists();
error InvalidAddress();
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize() external initializer {
__UUPSUpgradeable_init();
__Ownable_init(msg.sender);
}
/**
* @dev 授权升级函数 - 只有 owner 可以升级
*/
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
/**
* @notice 设置工厂合约地址
* @param lendingProxy Lending 代理地址
* @param newFactory 新工厂地址
*/
function setFactory(address lendingProxy, address newFactory) external onlyOwner {
if (newFactory == address(0)) revert InvalidAddress();
address oldFactory = factory[lendingProxy];
factory[lendingProxy] = newFactory;
emit SetFactory(lendingProxy, oldFactory, newFactory);
}
/**
* @notice 设置市场配置
* @param lendingProxy Lending 代理地址
* @param newConfiguration 新配置
*/
function setConfiguration(address lendingProxy, Configuration calldata newConfiguration)
external
onlyOwner
{
Configuration memory oldConfiguration = configuratorParams[lendingProxy];
// 防止修改不可变参数
if (oldConfiguration.baseToken != address(0) &&
(oldConfiguration.baseToken != newConfiguration.baseToken ||
oldConfiguration.trackingIndexScale != newConfiguration.trackingIndexScale))
revert ConfigurationAlreadyExists();
// 删除旧的资产配置
delete configuratorParams[lendingProxy];
// 设置新配置
configuratorParams[lendingProxy].baseToken = newConfiguration.baseToken;
configuratorParams[lendingProxy].baseTokenPriceFeed = newConfiguration.baseTokenPriceFeed;
configuratorParams[lendingProxy].supplyKink = newConfiguration.supplyKink;
configuratorParams[lendingProxy].supplyPerYearInterestRateSlopeLow = newConfiguration.supplyPerYearInterestRateSlopeLow;
configuratorParams[lendingProxy].supplyPerYearInterestRateSlopeHigh = newConfiguration.supplyPerYearInterestRateSlopeHigh;
configuratorParams[lendingProxy].supplyPerYearInterestRateBase = newConfiguration.supplyPerYearInterestRateBase;
configuratorParams[lendingProxy].borrowKink = newConfiguration.borrowKink;
configuratorParams[lendingProxy].borrowPerYearInterestRateSlopeLow = newConfiguration.borrowPerYearInterestRateSlopeLow;
configuratorParams[lendingProxy].borrowPerYearInterestRateSlopeHigh = newConfiguration.borrowPerYearInterestRateSlopeHigh;
configuratorParams[lendingProxy].borrowPerYearInterestRateBase = newConfiguration.borrowPerYearInterestRateBase;
configuratorParams[lendingProxy].storeFrontPriceFactor = newConfiguration.storeFrontPriceFactor;
configuratorParams[lendingProxy].trackingIndexScale = newConfiguration.trackingIndexScale;
configuratorParams[lendingProxy].baseBorrowMin = newConfiguration.baseBorrowMin;
configuratorParams[lendingProxy].targetReserves = newConfiguration.targetReserves;
// 复制资产配置
for (uint i = 0; i < newConfiguration.assetConfigs.length; i++) {
configuratorParams[lendingProxy].assetConfigs.push(newConfiguration.assetConfigs[i]);
}
emit SetConfiguration(lendingProxy, oldConfiguration, newConfiguration);
}
/**
* @notice 添加抵押资产
* @param lendingProxy Lending 代理地址
* @param assetConfig 资产配置
*/
function addAsset(address lendingProxy, AssetConfig calldata assetConfig)
external
onlyOwner
{
configuratorParams[lendingProxy].assetConfigs.push(assetConfig);
emit AddAsset(lendingProxy, assetConfig);
}
/**
* @notice 更新资产配置
* @param lendingProxy Lending 代理地址
* @param newAssetConfig 新资产配置
*/
function updateAsset(address lendingProxy, AssetConfig calldata newAssetConfig)
external
onlyOwner
{
uint assetIndex = getAssetIndex(lendingProxy, newAssetConfig.asset);
AssetConfig memory oldAssetConfig = configuratorParams[lendingProxy].assetConfigs[assetIndex];
configuratorParams[lendingProxy].assetConfigs[assetIndex] = newAssetConfig;
emit UpdateAsset(lendingProxy, oldAssetConfig, newAssetConfig);
}
/**
* @notice 更新资产抵押率
* @param lendingProxy Lending 代理地址
* @param asset 资产地址
* @param newBorrowCF 新借款抵押率
*/
function updateAssetBorrowCollateralFactor(
address lendingProxy,
address asset,
uint64 newBorrowCF
)
external
onlyOwner
{
uint assetIndex = getAssetIndex(lendingProxy, asset);
configuratorParams[lendingProxy].assetConfigs[assetIndex].borrowCollateralFactor = newBorrowCF;
}
/**
* @notice 更新资产供应上限
* @param lendingProxy Lending 代理地址
* @param asset 资产地址
* @param newSupplyCap 新供应上限
*/
function updateAssetSupplyCap(
address lendingProxy,
address asset,
uint128 newSupplyCap
)
external
onlyOwner
{
uint assetIndex = getAssetIndex(lendingProxy, asset);
configuratorParams[lendingProxy].assetConfigs[assetIndex].supplyCap = newSupplyCap;
}
/**
* @notice 部署新的 Lending 实现
* @param lendingProxy Lending 代理地址
* @return 新实现合约地址
*/
function deploy(address lendingProxy) external onlyOwner returns (address) {
address newLending = LendingFactory(factory[lendingProxy]).deploy();
emit LendingDeployed(lendingProxy, newLending);
return newLending;
}
/**
* @notice 获取资产索引
* @param lendingProxy Lending 代理地址
* @param asset 资产地址
* @return 资产在配置数组中的索引
*/
function getAssetIndex(address lendingProxy, address asset) public view returns (uint) {
AssetConfig[] memory assetConfigs = configuratorParams[lendingProxy].assetConfigs;
uint numAssets = assetConfigs.length;
for (uint i = 0; i < numAssets; ) {
if (assetConfigs[i].asset == asset) {
return i;
}
unchecked { i++; }
}
revert AssetDoesNotExist();
}
/**
* @notice 获取市场配置
* @param lendingProxy Lending 代理地址
* @return 配置信息
*/
function getConfiguration(address lendingProxy) external view returns (Configuration memory) {
return configuratorParams[lendingProxy];
}
}

View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./LendingConfiguration.sol";
/**
* @title ConfiguratorStorage
* @notice Configurator 存储定义
*/
abstract contract ConfiguratorStorage is LendingConfiguration {
// Lending 代理地址 => 工厂合约地址
mapping(address => address) public factory;
// Lending 代理地址 => 配置参数
mapping(address => Configuration) public configuratorParams;
}

View File

@@ -0,0 +1,663 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.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 "./LendingStorage.sol";
import "./LendingMath.sol";
import "./interfaces/ILending.sol";
import "./interfaces/IPriceFeed.sol";
/**
* @title Lending
* @notice 借贷池核心合约
*/
contract Lending is
ILending,
LendingStorage,
UUPSUpgradeable,
OwnableUpgradeable,
PausableUpgradeable,
ReentrancyGuardUpgradeable
{
using SafeERC20 for IERC20;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/**
* @notice 初始化函数
* @param config 市场配置
*/
function initialize(Configuration calldata config) external initializer {
__UUPSUpgradeable_init();
__Ownable_init(msg.sender);
__Pausable_init();
__ReentrancyGuard_init();
// 设置基础配置
baseToken = config.baseToken;
baseTokenPriceFeed = config.baseTokenPriceFeed;
// 常量:一年的秒数
uint256 SECONDS_PER_YEAR = 365 * 24 * 60 * 60; // 31,536,000
// 设置利率参数(将年化利率转换为每秒利率,只计算一次)
// 这样可以大幅降低每次计提利息的 Gas 成本并提高精度
supplyKink = config.supplyKink;
supplyPerSecondInterestRateSlopeLow = uint64(config.supplyPerYearInterestRateSlopeLow / SECONDS_PER_YEAR);
supplyPerSecondInterestRateSlopeHigh = uint64(config.supplyPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR);
supplyPerSecondInterestRateBase = uint64(config.supplyPerYearInterestRateBase / SECONDS_PER_YEAR);
borrowKink = config.borrowKink;
borrowPerSecondInterestRateSlopeLow = uint64(config.borrowPerYearInterestRateSlopeLow / SECONDS_PER_YEAR);
borrowPerSecondInterestRateSlopeHigh = uint64(config.borrowPerYearInterestRateSlopeHigh / SECONDS_PER_YEAR);
borrowPerSecondInterestRateBase = uint64(config.borrowPerYearInterestRateBase / SECONDS_PER_YEAR);
// 设置其他参数
storeFrontPriceFactor = config.storeFrontPriceFactor;
trackingIndexScale = config.trackingIndexScale;
baseBorrowMin = config.baseBorrowMin;
targetReserves = config.targetReserves;
// 初始化利息累计因子
supplyIndex = 1e18;
borrowIndex = 1e18;
lastAccrualTime = block.timestamp;
// 设置抵押资产配置
for (uint i = 0; i < config.assetConfigs.length; i++) {
AssetConfig memory assetConfig = config.assetConfigs[i];
// 验证参数合法性(必须 < 1
require(assetConfig.liquidationFactor < 1e18, "Invalid liquidationFactor");
require(assetConfig.borrowCollateralFactor < 1e18, "Invalid borrowCF");
require(assetConfig.liquidateCollateralFactor < 1e18, "Invalid liquidateCF");
assetConfigs[assetConfig.asset] = assetConfig;
assetList.push(assetConfig.asset);
}
}
/**
* @dev 授权升级函数 - 只有 owner 可以升级
*/
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
/**
* @notice 暂停合约
*/
function pause() external onlyOwner {
_pause();
}
/**
* @notice 恢复合约
*/
function unpause() external onlyOwner {
_unpause();
}
/**
* @notice 计提利息
*/
function accrueInterest() public {
uint256 timeElapsed = block.timestamp - lastAccrualTime;
if (timeElapsed == 0) return;
// 计算实际的 totalSupply 和 totalBorrow含利息
// 注意totalSupplyBase 和 totalBorrowBase 都是正数本金
// supplyIndex 用于存款borrowIndex 用于借款
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
uint64 utilization = LendingMath.getUtilization(totalSupply, totalBorrow);
// 计算供应利率和借款利率(每秒利率)
uint64 supplyRate = LendingMath.getSupplyRate(
utilization,
supplyKink,
supplyPerSecondInterestRateSlopeLow,
supplyPerSecondInterestRateSlopeHigh,
supplyPerSecondInterestRateBase
);
uint64 borrowRate = LendingMath.getBorrowRate(
utilization,
borrowKink,
borrowPerSecondInterestRateSlopeLow,
borrowPerSecondInterestRateSlopeHigh,
borrowPerSecondInterestRateBase
);
// 更新利息累计因子(使用每秒利率,计算更精确且 Gas 更低)
supplyIndex = LendingMath.accrueInterest(supplyIndex, supplyRate, timeElapsed);
borrowIndex = LendingMath.accrueInterest(borrowIndex, borrowRate, timeElapsed);
lastAccrualTime = block.timestamp;
}
/**
* @notice 存入基础资产
*/
function supply(uint256 amount) external override nonReentrant whenNotPaused {
accrueInterest();
IERC20(baseToken).safeTransferFrom(msg.sender, address(this), amount);
// 获取用户当前本金
UserBasic memory user = userBasic[msg.sender];
int104 oldPrincipal = user.principal;
// 计算当前实际余额(含利息)
uint256 index = oldPrincipal >= 0 ? supplyIndex : borrowIndex;
int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, index);
// 计算新余额(增加存款)
int256 newBalance = oldBalance + int256(amount);
// 转换为新本金(可能从借款变为存款)
uint256 newIndex = newBalance >= 0 ? supplyIndex : borrowIndex;
int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, newIndex);
// 计算还款和存款金额
(uint104 repayAmount, uint104 supplyAmount) = LendingMath.repayAndSupplyAmount(oldPrincipal, newPrincipal);
// 更新全局状态
totalBorrowBase -= repayAmount;
totalSupplyBase += supplyAmount;
// 更新用户本金
userBasic[msg.sender].principal = newPrincipal;
emit Supply(msg.sender, msg.sender, amount);
}
/**
* @notice 取出基础资产(如果余额不足会自动借款)
*/
function withdraw(uint256 amount) external override nonReentrant whenNotPaused {
accrueInterest();
// 获取用户当前本金
UserBasic memory user = userBasic[msg.sender];
int104 oldPrincipal = user.principal;
// 计算当前实际余额(含利息)
uint256 index = oldPrincipal >= 0 ? supplyIndex : borrowIndex;
int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, index);
// 计算新余额
int256 newBalance = oldBalance - int256(amount);
// 转换为新本金
uint256 newIndex = newBalance >= 0 ? supplyIndex : borrowIndex;
int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, newIndex);
// 计算提取和借款金额
(uint104 withdrawAmount, uint104 borrowAmount) = LendingMath.withdrawAndBorrowAmount(oldPrincipal, newPrincipal);
// 更新全局状态
totalSupplyBase -= withdrawAmount;
totalBorrowBase += borrowAmount;
// 更新用户本金
userBasic[msg.sender].principal = newPrincipal;
// 如果变成负余额(借款),检查抵押品
if (newBalance < 0) {
if (uint256(-newBalance) < baseBorrowMin) revert BorrowTooSmall();
if (!_isSolvent(msg.sender)) revert InsufficientCollateral();
}
IERC20(baseToken).safeTransfer(msg.sender, amount);
emit Withdraw(msg.sender, msg.sender, amount);
}
/**
* @notice 存入抵押品
*/
function supplyCollateral(address asset, uint256 amount) external override nonReentrant whenNotPaused {
AssetConfig memory config = assetConfigs[asset];
if (config.asset == address(0)) revert Unauthorized();
uint256 newTotal = userCollateral[msg.sender][asset] + amount;
if (newTotal > config.supplyCap) revert SupplyCapExceeded();
IERC20(asset).safeTransferFrom(msg.sender, address(this), amount);
userCollateral[msg.sender][asset] += amount;
emit SupplyCollateral(msg.sender, msg.sender, asset, amount);
}
/**
* @notice 取出抵押品
*/
function withdrawCollateral(address asset, uint256 amount) external override nonReentrant whenNotPaused {
accrueInterest();
if (userCollateral[msg.sender][asset] < amount) revert InsufficientBalance();
userCollateral[msg.sender][asset] -= amount;
// 检查是否仍有足够的抵押品(如果有债务)
int104 principal = userBasic[msg.sender].principal;
if (principal < 0) {
if (!_isSolvent(msg.sender)) revert InsufficientCollateral();
}
IERC20(asset).safeTransfer(msg.sender, amount);
emit WithdrawCollateral(msg.sender, msg.sender, asset, amount);
}
/**
* @notice 借款
*/
function borrow(uint256 amount) external override nonReentrant whenNotPaused {
accrueInterest();
// 获取用户当前本金
UserBasic memory user = userBasic[msg.sender];
int104 oldPrincipal = user.principal;
// 计算当前实际余额(含利息)
// 如果 principal >= 0存款使用 supplyIndex
// 如果 principal < 0借款使用 borrowIndex
uint256 index = oldPrincipal >= 0 ? supplyIndex : borrowIndex;
int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, index);
// 计算新余额(减去借款额)
int256 newBalance = oldBalance - int256(amount);
// 检查最小借款额
if (newBalance < 0 && uint256(-newBalance) < baseBorrowMin) revert BorrowTooSmall();
// 转换为新本金(新状态可能从存款变为借款)
uint256 newIndex = newBalance >= 0 ? supplyIndex : borrowIndex;
int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, newIndex);
// 计算提取和借款金额
(uint104 withdrawAmount, uint104 borrowAmount) = LendingMath.withdrawAndBorrowAmount(oldPrincipal, newPrincipal);
// 更新全局状态
totalSupplyBase -= withdrawAmount;
totalBorrowBase += borrowAmount;
// 更新用户本金
userBasic[msg.sender].principal = newPrincipal;
// 检查抵押品是否充足
if (!_isSolvent(msg.sender)) revert InsufficientCollateral();
IERC20(baseToken).safeTransfer(msg.sender, amount);
emit Withdraw(msg.sender, msg.sender, amount);
}
/**
* @notice 清算不良债务(内部实现)
*/
function _absorbInternal(address absorber, address borrower) internal {
if (!isLiquidatable(borrower)) revert NotLiquidatable();
// 获取用户当前本金
UserBasic memory user = userBasic[borrower];
int104 oldPrincipal = user.principal;
// 计算当前实际余额(含利息累计的债务)
int256 oldBalance = LendingMath.principalToBalance(oldPrincipal, borrowIndex);
if (oldBalance >= 0) revert NotLiquidatable();
// 计算所有抵押品的总价值(按 liquidationFactor 折扣)
uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice();
uint256 totalCollateralValue = 0;
for (uint i = 0; i < assetList.length; i++) {
address asset = assetList[i];
uint256 collateralAmount = userCollateral[borrower][asset];
if (collateralAmount > 0) {
AssetConfig memory assetConfig = assetConfigs[asset];
uint256 assetPrice = IPriceFeed(assetConfig.priceFeed).getPrice();
// 计算抵押品价值USD8位精度
uint256 assetScale = 10 ** assetConfig.decimals;
uint256 collateralValueUSD = (collateralAmount * assetPrice) / assetScale;
// 应用 liquidationFactor 折扣
uint256 discountedValue = (collateralValueUSD * assetConfig.liquidationFactor) / 1e18;
totalCollateralValue += discountedValue;
// 将抵押品转移到清算库存
userCollateral[borrower][asset] = 0;
collateralReserves[asset] += collateralAmount;
// 发射抵押品吸收事件
emit AbsorbCollateral(absorber, borrower, asset, collateralAmount, collateralValueUSD);
}
}
// 将抵押品价值转换为 baseToken 数量
uint256 baseScale = 10 ** IERC20Metadata(baseToken).decimals();
uint256 collateralInBase = (totalCollateralValue * baseScale) / basePrice;
// 计算新余额oldBalance负数+ 抵押品价值
int256 newBalance = oldBalance + int256(collateralInBase);
// 如果新余额仍为负,强制归零(坏账由协议承担)
if (newBalance < 0) {
newBalance = 0;
}
// 转换为新本金
int104 newPrincipal = LendingMath.balanceToPrincipal(newBalance, supplyIndex);
// 更新用户本金
userBasic[borrower].principal = newPrincipal;
// 计算偿还和供应金额
(uint104 repayAmount, uint104 supplyAmount) = LendingMath.repayAndSupplyAmount(oldPrincipal, newPrincipal);
// 更新全局状态
// 储备金通过减少 totalBorrowBase 和增加 totalSupplyBase 来承担坏账
totalSupplyBase += supplyAmount;
totalBorrowBase -= repayAmount;
// 计算协议支付的债务(坏账部分)
uint256 basePaidOut = uint256(newBalance - oldBalance);
uint256 valueOfBasePaidOut = (basePaidOut * basePrice) / baseScale;
// 发射债务吸收事件
emit AbsorbDebt(absorber, borrower, basePaidOut, valueOfBasePaidOut);
}
/**
* @notice 清算不良债务(单个)
*/
function absorb(address borrower) external override nonReentrant whenNotPaused {
accrueInterest();
_absorbInternal(msg.sender, borrower);
}
/**
* @notice 批量清算不良债务
*/
function absorbMultiple(address absorber, address[] calldata accounts) external override nonReentrant whenNotPaused {
accrueInterest();
for (uint i = 0; i < accounts.length; ) {
_absorbInternal(absorber, accounts[i]);
unchecked { i++; }
}
}
/**
* @notice 购买清算后的抵押品
*/
function buyCollateral(
address asset,
uint256 minAmount,
uint256 baseAmount,
address recipient
) external override nonReentrant whenNotPaused {
if (collateralReserves[asset] == 0) revert InsufficientBalance();
// 计算可购买的抵押品数量
uint256 collateralAmount = quoteCollateral(asset, baseAmount);
// 验证数量
if (collateralAmount < minAmount) revert InsufficientBalance();
if (collateralAmount > collateralReserves[asset]) revert InsufficientBalance();
// 检查储备金是否充足(如果已达到目标,不再出售抵押品)
uint256 balance = IERC20(baseToken).balanceOf(address(this));
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
int256 currentReserves = int256(balance) - int256(totalSupply) + int256(totalBorrow);
if (currentReserves >= 0 && uint256(currentReserves) >= targetReserves) {
revert InsufficientBalance(); // 储备金充足,无需出售
}
// 收取清算人支付的资金
IERC20(baseToken).safeTransferFrom(msg.sender, address(this), baseAmount);
// 抵押品出库
collateralReserves[asset] -= collateralAmount;
// 转账抵押品到指定接收人
IERC20(asset).safeTransfer(recipient, collateralAmount);
// 注意:收入会自动体现在 getReserves() 中,因为 balance 增加了
emit BuyCollateral(msg.sender, asset, baseAmount, collateralAmount);
}
/**
* @notice 计算支付指定baseAmount可购买的抵押品数量
*/
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();
// 计算折扣率 - 使用 Compound V3 的 mulFactor 方式
// discountFactor = storeFrontPriceFactor * (FACTOR_SCALE - liquidationFactor) / FACTOR_SCALE
uint256 FACTOR_SCALE = 1e18;
uint256 discountFactor = (storeFrontPriceFactor * (FACTOR_SCALE - assetConfig.liquidationFactor)) / FACTOR_SCALE;
// 计算折扣后的资产价格
// assetPriceDiscounted = assetPrice * (FACTOR_SCALE - discountFactor) / FACTOR_SCALE
uint256 assetPriceDiscounted = (assetPrice * (FACTOR_SCALE - discountFactor)) / FACTOR_SCALE;
// 计算可购买的抵押品数量
// 公式:(basePrice * baseAmount * assetScale) / (assetPriceDiscounted * baseScale)
uint256 baseScale = 10 ** uint256(IERC20Metadata(baseToken).decimals());
uint256 assetScale = 10 ** uint256(assetConfig.decimals);
// 使用中间变量分步计算,避免潜在的溢出
// 先计算分子和分母,再进行除法
return (basePrice * baseAmount * assetScale) / (assetPriceDiscounted * baseScale);
}
/**
* @notice 检查账户偿付能力
*/
function _isSolvent(address account) internal view returns (bool) {
int104 principal = userBasic[account].principal;
if (principal >= 0) return true;
// 计算实际债务(含利息)- 使用 borrowIndex
int256 balance = LendingMath.principalToBalance(principal, borrowIndex);
uint256 debt = uint256(-balance);
// 将 debt 转换为美元价值(使用 baseToken 价格)
uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice();
uint256 baseDecimals = IERC20Metadata(baseToken).decimals();
uint256 debtValue = (debt * basePrice) / (10 ** baseDecimals);
// 计算借款能力(抵押品价值已经在 _getCollateralValue 中应用了借款系数)
uint256 borrowCapacity = _getCollateralValue(account);
// 比较:借款能力 >= 债务价值
return borrowCapacity >= debtValue;
}
/**
* @notice 计算账户抵押品总价值
*/
function _getCollateralValue(address account) internal view returns (uint256) {
uint256 totalValue = 0;
for (uint i = 0; i < assetList.length; i++) {
address asset = assetList[i];
uint256 amount = userCollateral[account][asset];
if (amount > 0) {
AssetConfig memory config = assetConfigs[asset];
uint256 price = IPriceFeed(config.priceFeed).getPrice();
uint256 value = LendingMath.getCollateralValue(amount, price, config.decimals);
totalValue += (value * config.borrowCollateralFactor) / 1e18;
}
}
return totalValue;
}
/**
* @notice 获取最小借款抵押率
*/
function _getMinBorrowCollateralFactor() internal view returns (uint64) {
uint64 minFactor = type(uint64).max;
for (uint i = 0; i < assetList.length; i++) {
uint64 factor = assetConfigs[assetList[i]].borrowCollateralFactor;
if (factor < minFactor) minFactor = factor;
}
return minFactor;
}
// ========== View Functions ==========
function getBalance(address account) external view override returns (int256) {
int104 principal = userBasic[account].principal;
// 使用 supplyIndex 计算实际余额(含利息)
return LendingMath.principalToBalance(principal, supplyIndex);
}
function balanceOf(address account) external view override returns (uint256) {
int104 principal = userBasic[account].principal;
if (principal <= 0) return 0;
// 只返回正余额(存款)
return uint256(LendingMath.principalToBalance(principal, supplyIndex));
}
function borrowBalanceOf(address account) external view override returns (uint256) {
int104 principal = userBasic[account].principal;
if (principal >= 0) return 0;
// 只返回负余额(借款),转为正数
int256 balance = LendingMath.principalToBalance(principal, borrowIndex);
return uint256(-balance);
}
function getCollateral(address account, address asset) external view override returns (uint256) {
return userCollateral[account][asset];
}
function isLiquidatable(address account) public view override returns (bool) {
int104 principal = userBasic[account].principal;
if (principal >= 0) return false;
// 计算实际债务(含利息)
int256 balance = LendingMath.principalToBalance(principal, borrowIndex);
uint256 debt = uint256(-balance);
// 将 debt 转换为美元价值(使用 baseToken 价格和 price feed 精度)
uint256 basePrice = IPriceFeed(baseTokenPriceFeed).getPrice();
uint256 baseDecimals = IERC20Metadata(baseToken).decimals();
uint256 debtValue = (debt * basePrice) / (10 ** baseDecimals);
// 计算抵押品总价值(清算阈值)
uint256 collateralValue = 0;
for (uint i = 0; i < assetList.length; i++) {
address asset = assetList[i];
uint256 amount = userCollateral[account][asset];
if (amount > 0) {
AssetConfig memory config = assetConfigs[asset];
uint256 price = IPriceFeed(config.priceFeed).getPrice();
uint256 value = LendingMath.getCollateralValue(amount, price, config.decimals);
collateralValue += (value * config.liquidateCollateralFactor) / 1e18;
}
}
// 比较:债务价值 > 抵押品清算阈值价值
return debtValue > collateralValue;
}
function getTotalSupply() external view returns (uint256) {
return (uint256(totalSupplyBase) * supplyIndex) / 1e18;
}
function getTotalBorrow() external view returns (uint256) {
return (uint256(totalBorrowBase) * borrowIndex) / 1e18;
}
function getCollateralReserves(address asset) external view override returns (uint256) {
return collateralReserves[asset];
}
function getReserves() external view override returns (int256) {
// 计算实际总供应和总借款(含利息)
uint256 balance = IERC20(baseToken).balanceOf(address(this));
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
// reserves = balance - totalSupply + totalBorrow
return int256(balance) - int256(totalSupply) + int256(totalBorrow);
}
function getUtilization() external view override returns (uint256) {
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
return LendingMath.getUtilization(totalSupply, totalBorrow);
}
function getSupplyRate() external view override returns (uint64) {
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
uint64 utilization = LendingMath.getUtilization(totalSupply, totalBorrow);
uint64 perSecondRate = LendingMath.getSupplyRate(
utilization,
supplyKink,
supplyPerSecondInterestRateSlopeLow,
supplyPerSecondInterestRateSlopeHigh,
supplyPerSecondInterestRateBase
);
// 转换为年化利率APY
return perSecondRate * 31536000; // SECONDS_PER_YEAR
}
/**
* @notice 提取协议储备金(仅 owner
*/
function withdrawReserves(address to, uint256 amount) external override onlyOwner nonReentrant {
uint256 balance = IERC20(baseToken).balanceOf(address(this));
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
int256 currentReserves = int256(balance) - int256(totalSupply) + int256(totalBorrow);
// 检查储备金是否充足
if (currentReserves < 0 || amount > uint256(currentReserves)) {
revert InsufficientReserves();
}
// 转账储备金
IERC20(baseToken).safeTransfer(to, amount);
emit WithdrawReserves(to, amount);
}
function getBorrowRate() external view override returns (uint64) {
uint256 totalSupply = (uint256(totalSupplyBase) * supplyIndex) / 1e18;
uint256 totalBorrow = (uint256(totalBorrowBase) * borrowIndex) / 1e18;
uint64 utilization = LendingMath.getUtilization(totalSupply, totalBorrow);
uint64 perSecondRate = LendingMath.getBorrowRate(
utilization,
borrowKink,
borrowPerSecondInterestRateSlopeLow,
borrowPerSecondInterestRateSlopeHigh,
borrowPerSecondInterestRateBase
);
// 转换为年化利率APY
return perSecondRate * 31536000; // SECONDS_PER_YEAR
}
}

View File

@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title LendingConfiguration
* @notice 借贷池配置结构体定义
*/
contract LendingConfiguration {
struct AssetConfig {
address asset; // 资产地址
address priceFeed; // 价格预言机地址
uint8 decimals; // 小数位数
uint64 borrowCollateralFactor; // 借款抵押率 (例: 0.8e18 = 80%)
uint64 liquidateCollateralFactor; // 清算抵押率 (例: 0.85e18 = 85%)
uint64 liquidationFactor; // 清算激励 (例: 1.05e18 = 5%折扣)
uint128 supplyCap; // 供应上限
}
struct Configuration {
address baseToken; // 基础资产(借出的资产,如 USDC
address baseTokenPriceFeed; // 基础资产价格预言机
// 利率模型参数
uint64 supplyKink; // 供应拐点利用率
uint64 supplyPerYearInterestRateSlopeLow; // 供应拐点前斜率
uint64 supplyPerYearInterestRateSlopeHigh; // 供应拐点后斜率
uint64 supplyPerYearInterestRateBase; // 供应基础利率
uint64 borrowKink; // 借款拐点利用率
uint64 borrowPerYearInterestRateSlopeLow; // 借款拐点前斜率
uint64 borrowPerYearInterestRateSlopeHigh; // 借款拐点后斜率
uint64 borrowPerYearInterestRateBase; // 借款基础利率
// 其他核心参数
uint64 storeFrontPriceFactor; // 清算价格折扣
uint64 trackingIndexScale; // 追踪索引比例
uint104 baseBorrowMin; // 最小借款额
uint104 targetReserves; // 目标储备金
AssetConfig[] assetConfigs; // 抵押资产配置数组
}
}

View File

@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./Lending.sol";
import "./LendingConfiguration.sol";
/**
* @title LendingFactory
* @notice 工厂合约 - 用于部署新的 Lending 实现
*/
contract LendingFactory is LendingConfiguration, Ownable {
constructor() Ownable(msg.sender) {}
event LendingDeployed(address indexed lending);
/**
* @notice 部署新的 Lending 实现合约
* @return 新 Lending 合约地址
*/
function deploy() external onlyOwner returns (address) {
Lending lending = new Lending();
emit LendingDeployed(address(lending));
return address(lending);
}
}

View File

@@ -0,0 +1,160 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title LendingMath
* @notice 借贷池数学计算库
*/
library LendingMath {
uint256 internal constant FACTOR_SCALE = 1e18;
uint256 internal constant PRICE_SCALE = 1e8;
uint256 internal constant SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
/**
* @notice 将本金转换为实际余额(含利息)
* @param principal 本金(正数或负数)
* @param index 利息索引
* @return 实际余额
*/
function principalToBalance(int104 principal, uint256 index) internal pure returns (int256) {
return int256(principal) * int256(index) / int256(FACTOR_SCALE);
}
/**
* @notice 将实际余额转换为本金
* @param balance 实际余额(正数或负数)
* @param index 利息索引
* @return 本金
*/
function balanceToPrincipal(int256 balance, uint256 index) internal pure returns (int104) {
return int104((balance * int256(FACTOR_SCALE)) / int256(index));
}
/**
* @notice 计算供应方本金变化和借款方本金变化
* @dev 用于 absorb 时计算账户状态变化
*/
function repayAndSupplyAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) {
// 如果新本金小于旧本金,没有偿还或供应
if (newPrincipal < oldPrincipal) return (0, 0);
if (newPrincipal <= 0) {
// 从负数变得更接近0偿还债务
return (uint104(newPrincipal - oldPrincipal), 0);
} else if (oldPrincipal >= 0) {
// 两个都是正数(增加存款)
return (0, uint104(newPrincipal - oldPrincipal));
} else {
// 从负数变正数(偿还所有债务并存款)
return (uint104(-oldPrincipal), uint104(newPrincipal));
}
}
/**
* @notice 计算提取金额和借款金额
* @dev 用于 withdraw/borrow 时计算账户状态变化
*/
function withdrawAndBorrowAmount(int104 oldPrincipal, int104 newPrincipal) internal pure returns (uint104, uint104) {
// 如果新本金大于旧本金,没有提取或借款
if (newPrincipal > oldPrincipal) return (0, 0);
if (newPrincipal >= 0) {
// 还是正数(提取存款)
return (uint104(oldPrincipal - newPrincipal), 0);
} else if (oldPrincipal <= 0) {
// 两个都是负数(增加借款)
return (0, uint104(oldPrincipal - newPrincipal));
} else {
// 从正数变负数(提取所有存款并借款)
return (uint104(oldPrincipal), uint104(-newPrincipal));
}
}
/**
* @notice 计算利用率
* @param totalSupply 总供应量
* @param totalBorrow 总借款量
* @return 利用率 (scaled by 1e18)
*/
function getUtilization(uint256 totalSupply, uint256 totalBorrow) internal pure returns (uint64) {
if (totalSupply == 0) return 0;
return uint64((totalBorrow * FACTOR_SCALE) / totalSupply);
}
/**
* @notice 计算供应利率(每秒利率)
*/
function getSupplyRate(
uint256 utilization,
uint64 supplyKink,
uint64 supplyPerSecondInterestRateSlopeLow,
uint64 supplyPerSecondInterestRateSlopeHigh,
uint64 supplyPerSecondInterestRateBase
) internal pure returns (uint64) {
if (utilization <= supplyKink) {
return supplyPerSecondInterestRateBase + uint64((utilization * supplyPerSecondInterestRateSlopeLow) / FACTOR_SCALE);
} else {
uint256 excessUtil = utilization - supplyKink;
return supplyPerSecondInterestRateBase + supplyPerSecondInterestRateSlopeLow +
uint64((excessUtil * supplyPerSecondInterestRateSlopeHigh) / FACTOR_SCALE);
}
}
/**
* @notice 计算借款利率(每秒利率)
*/
function getBorrowRate(
uint256 utilization,
uint64 borrowKink,
uint64 borrowPerSecondInterestRateSlopeLow,
uint64 borrowPerSecondInterestRateSlopeHigh,
uint64 borrowPerSecondInterestRateBase
) internal pure returns (uint64) {
if (utilization <= borrowKink) {
return borrowPerSecondInterestRateBase + uint64((utilization * borrowPerSecondInterestRateSlopeLow) / FACTOR_SCALE);
} else {
uint256 excessUtil = utilization - borrowKink;
return borrowPerSecondInterestRateBase + borrowPerSecondInterestRateSlopeLow +
uint64((excessUtil * borrowPerSecondInterestRateSlopeHigh) / FACTOR_SCALE);
}
}
/**
* @notice 计算复利后的利息累计因子
* @param index 当前利息累计因子
* @param interestRatePerSecond 每秒利率
* @param timeElapsed 经过的秒数
* @return 新的利息累计因子
*/
function accrueInterest(
uint256 index,
uint64 interestRatePerSecond,
uint256 timeElapsed
) internal pure returns (uint256) {
// 优化:每秒利率直接乘以时间,只需一次除法
uint256 interestAccrued = (index * interestRatePerSecond * timeElapsed) / FACTOR_SCALE;
return index + interestAccrued;
}
/**
* @notice 计算抵押品价值
*/
function getCollateralValue(
uint256 collateralAmount,
uint256 collateralPrice,
uint8 collateralDecimals
) internal pure returns (uint256) {
return (collateralAmount * collateralPrice) / (10 ** collateralDecimals);
}
/**
* @notice 计算借款能力
*/
function getBorrowCapacity(
uint256 collateralValue,
uint64 borrowCollateralFactor
) internal pure returns (uint256) {
return (collateralValue * borrowCollateralFactor) / FACTOR_SCALE;
}
}

View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./LendingConfiguration.sol";
/**
* @title LendingStorage
* @notice 借贷池存储变量定义
*/
abstract contract LendingStorage is LendingConfiguration {
// 市场配置
address public baseToken;
address public baseTokenPriceFeed;
// 利率参数(每秒利率,已从年化利率转换)
uint64 public supplyKink;
uint64 public supplyPerSecondInterestRateSlopeLow;
uint64 public supplyPerSecondInterestRateSlopeHigh;
uint64 public supplyPerSecondInterestRateBase;
uint64 public borrowKink;
uint64 public borrowPerSecondInterestRateSlopeLow;
uint64 public borrowPerSecondInterestRateSlopeHigh;
uint64 public borrowPerSecondInterestRateBase;
// 清算参数
uint64 public storeFrontPriceFactor;
uint64 public trackingIndexScale;
uint104 public baseBorrowMin;
uint104 public targetReserves;
// 资产映射
mapping(address => AssetConfig) public assetConfigs;
address[] public assetList;
// 用户账户信息
struct UserBasic {
int104 principal; // 本金(正数=存款本金,负数=借款本金)
}
mapping(address => UserBasic) public userBasic;
// 用户抵押品余额
mapping(address => mapping(address => uint256)) public userCollateral;
// 总存款本金和总借款本金
uint104 public totalSupplyBase;
uint104 public totalBorrowBase;
// 利息索引
uint256 public supplyIndex;
uint256 public borrowIndex;
uint256 public lastAccrualTime;
// 清算后的抵押品库存(不同于 reserves
// reserves 通过公式动态计算balance - totalSupply + totalBorrow
mapping(address => uint256) public collateralReserves;
}

View File

@@ -0,0 +1,177 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title ILending
* @notice 借贷池核心接口
*/
interface ILending {
// ========== Events ==========
event Supply(address indexed from, address indexed dst, uint256 amount);
event Withdraw(address indexed src, address indexed to, uint256 amount);
event SupplyCollateral(address indexed from, address indexed dst, address indexed asset, uint256 amount);
event WithdrawCollateral(address indexed src, address indexed to, address indexed asset, uint256 amount);
/// @notice 清算债务事件
event AbsorbDebt(address indexed absorber, address indexed borrower, uint256 basePaidOut, uint256 usdValue);
/// @notice 清算抵押品事件
event AbsorbCollateral(address indexed absorber, address indexed borrower, address indexed asset, uint256 collateralAbsorbed, uint256 usdValue);
event BuyCollateral(address indexed buyer, address indexed asset, uint256 baseAmount, uint256 collateralAmount);
/// @notice 储备金提取事件
event WithdrawReserves(address indexed to, uint256 amount);
// ========== Errors ==========
error Unauthorized();
error InsufficientBalance();
error InsufficientCollateral();
error BorrowTooSmall();
error NotLiquidatable();
error SupplyCapExceeded();
error InvalidLiquidationFactor();
error InsufficientReserves();
// ========== Core Functions ==========
/**
* @notice 存入基础资产
* @param amount 存入金额
*/
function supply(uint256 amount) external;
/**
* @notice 取出基础资产
* @param amount 取出金额
*/
function withdraw(uint256 amount) external;
/**
* @notice 存入抵押品
* @param asset 抵押品地址
* @param amount 抵押品数量
*/
function supplyCollateral(address asset, uint256 amount) external;
/**
* @notice 取出抵押品
* @param asset 抵押品地址
* @param amount 抵押品数量
*/
function withdrawCollateral(address asset, uint256 amount) external;
/**
* @notice 借款(通过取出超过存款的基础资产实现)
* @param amount 借款金额
*/
function borrow(uint256 amount) external;
/**
* @notice 清算不良债务(单个)
* @param borrower 待清算的借款人地址
*/
function absorb(address borrower) external;
/**
* @notice 批量清算不良债务
* @param absorber 清算发起人地址
* @param accounts 待清算的借款人地址数组
*/
function absorbMultiple(address absorber, address[] calldata accounts) external;
/**
* @notice 购买清算后的抵押品
* @param asset 抵押品地址
* @param minAmount 最小购买量
* @param baseAmount 支付的基础资产数量
* @param recipient 接收抵押品的地址
*/
function buyCollateral(address asset, uint256 minAmount, uint256 baseAmount, address recipient) external;
// ========== View Functions ==========
/**
* @notice 获取用户基础资产余额
* @param account 用户地址
* @return 余额(正数=存款,负数=借款)
*/
function getBalance(address account) external view returns (int256);
/**
* @notice 获取用户抵押品余额
* @param account 用户地址
* @param asset 抵押品地址
* @return 抵押品数量
*/
function getCollateral(address account, address asset) external view returns (uint256);
/**
* @notice 检查账户是否可被清算
* @param account 用户地址
* @return 是否可清算
*/
function isLiquidatable(address account) external view returns (bool);
/**
* @notice 获取当前供应利率
* @return 供应利率 (年化scaled by 1e18)
*/
function getSupplyRate() external view returns (uint64);
/**
* @notice 获取当前借款利率
* @return 借款利率 (年化scaled by 1e18)
*/
function getBorrowRate() external view returns (uint64);
/**
* @notice 获取用户存款余额只返回正数部分ERC20兼容
* @param account 用户地址
* @return 存款余额
*/
function balanceOf(address account) external view returns (uint256);
/**
* @notice 获取用户借款余额(只返回债务部分)
* @param account 用户地址
* @return 借款余额
*/
function borrowBalanceOf(address account) external view returns (uint256);
/**
* @notice 计算支付指定baseAmount可购买的抵押品数量
* @param asset 抵押品地址
* @param baseAmount 支付的基础资产数量
* @return 可购买的抵押品数量
*/
function quoteCollateral(address asset, uint256 baseAmount) external view returns (uint256);
/**
* @notice 获取协议储备金
* @return 储备金余额(可能为负)
*/
function getReserves() external view returns (int256);
/**
* @notice 获取抵押品库存
* @param asset 抵押品地址
* @return 库存数量
*/
function getCollateralReserves(address asset) external view returns (uint256);
/**
* @notice 获取市场利用率
* @return 利用率scaled by 1e18
*/
function getUtilization() external view returns (uint256);
/**
* @notice 提取协议储备金(仅 owner
* @param to 接收地址
* @param amount 提取数量
*/
function withdrawReserves(address to, uint256 amount) external;
}

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title IPriceFeed
* @notice 价格预言机接口
*/
interface IPriceFeed {
/**
* @notice 获取资产价格
* @return price 价格 (scaled by 1e8)
*/
function getPrice() external view returns (uint256 price);
/**
* @notice 获取价格精度
* @return 价格小数位数
*/
function decimals() external view returns (uint8);
}

View File

@@ -0,0 +1,266 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
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;
error Forbidden();
error InvalidAddress();
error InvalidDuration();
error PrivateMode();
error InvalidAmount();
error InsufficientOutput();
error CooldownNotPassed();
uint256 public constant PRICE_PRECISION = 10 ** 30;
uint256 public constant YTLP_PRECISION = 10 ** 18;
uint256 public constant BASIS_POINTS_DIVISOR = 10000;
uint256 public constant MAX_COOLDOWN_DURATION = 48 hours;
address public gov;
address public ytVault;
address public usdy;
address public ytLP;
uint256 public cooldownDuration;
mapping(address => uint256) public lastAddedAt;
mapping(address => bool) public isHandler;
uint256 public aumAddition;
uint256 public aumDeduction;
event AddLiquidity(
address indexed account,
address indexed token,
uint256 amount,
uint256 aumInUsdy,
uint256 ytLPSupply,
uint256 usdyAmount,
uint256 mintAmount
);
event RemoveLiquidity(
address indexed account,
address indexed token,
uint256 ytLPAmount,
uint256 aumInUsdy,
uint256 ytLPSupply,
uint256 usdyAmount,
uint256 amountOut
);
event CooldownDurationSet(uint256 duration);
event HandlerSet(address indexed handler, bool isActive);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
_;
}
modifier onlyHandler() {
if (!isHandler[msg.sender] && msg.sender != gov) revert Forbidden();
_;
}
/**
* @notice 初始化合约
* @param _ytVault YTVault合约地址
* @param _usdy USDY代币地址
* @param _ytLP ytLP代币地址
* @param _cooldownDuration 冷却时间(秒)
*/
function initialize(
address _ytVault,
address _usdy,
address _ytLP,
uint256 _cooldownDuration
) external initializer {
if (_ytVault == address(0) || _usdy == address(0) || _ytLP == address(0)) revert InvalidAddress();
if (_cooldownDuration > MAX_COOLDOWN_DURATION) revert InvalidDuration();
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
gov = msg.sender;
ytVault = _ytVault;
usdy = _usdy;
ytLP = _ytLP;
cooldownDuration = _cooldownDuration;
}
/**
* @notice 授权升级仅gov可调用
* @param newImplementation 新实现合约地址
*/
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
function setGov(address _gov) external onlyGov {
if (_gov == address(0)) revert InvalidAddress();
gov = _gov;
}
function setHandler(address _handler, bool _isActive) external onlyGov {
isHandler[_handler] = _isActive;
emit HandlerSet(_handler, _isActive);
}
function setCooldownDuration(uint256 _duration) external onlyGov {
if (_duration > MAX_COOLDOWN_DURATION) revert InvalidDuration();
cooldownDuration = _duration;
emit CooldownDurationSet(_duration);
}
function setAumAdjustment(uint256 _addition, uint256 _deduction) external onlyGov {
aumAddition = _addition;
aumDeduction = _deduction;
}
/**
* @notice 为指定账户添加流动性Handler调用
*/
function addLiquidityForAccount(
address _fundingAccount,
address _account,
address _token,
uint256 _amount,
uint256 _minUsdy,
uint256 _minYtLP
) external onlyHandler nonReentrant returns (uint256) {
return _addLiquidity(_fundingAccount, _account, _token, _amount, _minUsdy, _minYtLP);
}
function _addLiquidity(
address _fundingAccount,
address _account,
address _token,
uint256 _amount,
uint256 _minUsdy,
uint256 _minYtLP
) private returns (uint256) {
if (_amount == 0) revert InvalidAmount();
uint256 aumInUsdy = getAumInUsdy(true);
uint256 ytLPSupply = IERC20(ytLP).totalSupply();
IERC20(_token).safeTransferFrom(_fundingAccount, ytVault, _amount);
uint256 usdyAmount = IYTVault(ytVault).buyUSDY(_token, address(this));
if (usdyAmount < _minUsdy) revert InsufficientOutput();
uint256 mintAmount;
if (ytLPSupply == 0) {
mintAmount = usdyAmount;
} else {
mintAmount = usdyAmount * ytLPSupply / aumInUsdy;
}
if (mintAmount < _minYtLP) revert InsufficientOutput();
IYTLPToken(ytLP).mint(_account, mintAmount);
lastAddedAt[_account] = block.timestamp;
emit AddLiquidity(_account, _token, _amount, aumInUsdy, ytLPSupply, usdyAmount, mintAmount);
return mintAmount;
}
/**
* @notice 为指定账户移除流动性Handler调用
*/
function removeLiquidityForAccount(
address _account,
address _tokenOut,
uint256 _ytLPAmount,
uint256 _minOut,
address _receiver
) external onlyHandler nonReentrant returns (uint256) {
return _removeLiquidity(_account, _tokenOut, _ytLPAmount, _minOut, _receiver);
}
function _removeLiquidity(
address _account,
address _tokenOut,
uint256 _ytLPAmount,
uint256 _minOut,
address _receiver
) private returns (uint256) {
if (_ytLPAmount == 0) revert InvalidAmount();
if (lastAddedAt[_account] + cooldownDuration > block.timestamp) revert CooldownNotPassed();
uint256 aumInUsdy = getAumInUsdy(false);
uint256 ytLPSupply = IERC20(ytLP).totalSupply();
uint256 usdyAmount = _ytLPAmount * aumInUsdy / ytLPSupply;
// 先销毁ytLP
IYTLPToken(ytLP).burn(_account, _ytLPAmount);
// 检查余额,只铸造差额部分
uint256 usdyBalance = IERC20(usdy).balanceOf(address(this));
if (usdyAmount > usdyBalance) {
IUSDY(usdy).mint(address(this), usdyAmount - usdyBalance);
}
// 转账USDY到Vault并换回代币
IERC20(usdy).safeTransfer(ytVault, usdyAmount);
uint256 amountOut = IYTVault(ytVault).sellUSDY(_tokenOut, _receiver);
if (amountOut < _minOut) revert InsufficientOutput();
emit RemoveLiquidity(_account, _tokenOut, _ytLPAmount, aumInUsdy, ytLPSupply, usdyAmount, amountOut);
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();
if (supply == 0) return YTLP_PRECISION;
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);
aum += aumAddition;
if (aum > aumDeduction) {
aum -= aumDeduction;
} else {
aum = 0;
}
return aum;
}
/**
* @dev 预留存储空间,用于未来升级时添加新的状态变量
* 50个slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}

View File

@@ -0,0 +1,314 @@
// SPDX-License-Identifier: MIT
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";
/**
* @title YTPriceFeed
* @notice 价格读取器直接从YT合约读取价格变量带保护机制和价差
* @dev UUPS可升级合约
*/
contract YTPriceFeed is Initializable, UUPSUpgradeable {
error Forbidden();
error MaxChangeTooHigh();
error PriceChangeTooLarge();
error SpreadTooHigh();
error InvalidAddress();
address public gov;
uint256 public constant PRICE_PRECISION = 10 ** 30;
uint256 public constant BASIS_POINTS_DIVISOR = 10000;
uint256 public constant MAX_SPREAD_BASIS_POINTS = 200; // 最大2%价差
// WUSD固定价格
address public wusdAddress;
// WUSD价格来源
address public wusdPriceSource;
// 价格保护参数
uint256 public maxPriceChangeBps; // 5% 最大价格变动
// 价差配置(每个代币可以有不同的价差)
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);
event SpreadUpdate(address indexed token, uint256 spreadBps);
event KeeperSet(address indexed keeper, bool isActive);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
_;
}
modifier onlyKeeper() {
if (!isKeeper[msg.sender] && msg.sender != gov) revert Forbidden();
_;
}
/**
* @notice 初始化合约
*/
function initialize(address _wusdAddress) external initializer {
__UUPSUpgradeable_init();
if (_wusdAddress == address(0)) revert InvalidAddress();
wusdAddress = _wusdAddress;
gov = msg.sender;
maxPriceChangeBps = 500; // 5% 最大价格变动
}
/**
* @notice 授权升级仅gov可调用
* @param newImplementation 新实现合约地址
*/
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地址
* @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
) external onlyGov {
require(_tokens.length == _spreadBasisPoints.length, "length mismatch");
for (uint256 i = 0; i < _tokens.length; i++) {
if (_spreadBasisPoints[i] > MAX_SPREAD_BASIS_POINTS) revert SpreadTooHigh();
spreadBasisPoints[_tokens[i]] = _spreadBasisPoints[i];
emit SpreadUpdate(_tokens[i], _spreadBasisPoints[i]);
}
}
/**
* @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 == wusdAddress) {
return _getWUSDPrice();
}
uint256 basePrice = _getRawPrice(_token);
// 价格波动检查
_validatePriceChange(_token, basePrice);
// 应用价差
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();
}
/**
* @notice 从配置的YTAssetVault读取wusdPrice
* @dev 如果未设置wusdPriceSource返回固定价格1.0
*/
function _getWUSDPrice() private view returns (uint256) {
if (wusdPriceSource == address(0)) {
return PRICE_PRECISION; // 默认1.0
}
return IYTToken(wusdPriceSource).wusdPrice();
}
/**
* @notice 应用价差
* @param _token 代币地址
* @param _basePrice 基础价格
* @param _maximise true=上浮价格false=下压价格
* @return 应用价差后的价格
*/
function _applySpread(
address _token,
uint256 _basePrice,
bool _maximise
) 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,
uint256 maxPrice,
uint256 minPrice,
uint256 spread
) {
if (_token == wusdAddress) {
uint256 wusdPrice = _getWUSDPrice();
currentPrice = wusdPrice;
cachedPrice = wusdPrice;
maxPrice = wusdPrice;
minPrice = wusdPrice;
spread = 0;
} else {
currentPrice = _getRawPrice(_token);
cachedPrice = lastPrice[_token];
spread = spreadBasisPoints[_token];
maxPrice = _applySpread(_token, currentPrice, true);
minPrice = _applySpread(_token, currentPrice, false);
}
}
/**
* @notice 获取最大价格(上浮价差)
*/
function getMaxPrice(address _token) external view returns (uint256) {
if (_token == wusdAddress) {
// WUSD通常不需要价差直接返回原价格
return _getWUSDPrice();
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, true);
}
/**
* @notice 获取最小价格(下压价差)
*/
function getMinPrice(address _token) external view returns (uint256) {
if (_token == wusdAddress) {
// WUSD通常不需要价差直接返回原价格
return _getWUSDPrice();
}
uint256 basePrice = _getRawPrice(_token);
_validatePriceChange(_token, basePrice);
return _applySpread(_token, basePrice, false);
}
/**
* @dev 预留存储空间,用于未来升级时添加新的状态变量
* 50个slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}

View File

@@ -0,0 +1,216 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
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;
error Forbidden();
error AlreadyInitialized();
error InvalidAddress();
error InvalidAmount();
error InsufficientOutput();
address public gov;
address public usdy;
address public ytLP;
address public ytPoolManager;
address public ytVault;
event Swap(
address indexed account,
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 amountOut
);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
_;
}
/**
* @notice 初始化合约
* @param _usdy USDY代币地址
* @param _ytLP ytLP代币地址
* @param _ytPoolManager YTPoolManager地址
* @param _ytVault YTVault地址
*/
function initialize(
address _usdy,
address _ytLP,
address _ytPoolManager,
address _ytVault
) external initializer {
if (_usdy == address(0)) revert InvalidAddress();
if (_ytLP == address(0)) revert InvalidAddress();
if (_ytPoolManager == address(0)) revert InvalidAddress();
if (_ytVault == address(0)) revert InvalidAddress();
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
__Pausable_init();
gov = msg.sender;
usdy = _usdy;
ytLP = _ytLP;
ytPoolManager = _ytPoolManager;
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代币或WUSD地址
* @param _amount 代币数量
* @param _minUsdy 最小USDY数量
* @param _minYtLP 最小ytLP数量
* @return ytLPAmount 获得的ytLP数量
*/
function addLiquidity(
address _token,
uint256 _amount,
uint256 _minUsdy,
uint256 _minYtLP
) external nonReentrant whenNotPaused returns (uint256) {
if (_amount == 0) revert InvalidAmount();
address account = msg.sender;
IERC20(_token).safeTransferFrom(account, address(this), _amount);
IERC20(_token).approve(ytPoolManager, _amount);
uint256 ytLPAmount = IYTPoolManager(ytPoolManager).addLiquidityForAccount(
address(this),
account,
_token,
_amount,
_minUsdy,
_minYtLP
);
return ytLPAmount;
}
/**
* @notice 移除流动性
* @param _tokenOut 输出代币地址
* @param _ytLPAmount ytLP数量
* @param _minOut 最小输出数量
* @param _receiver 接收地址
* @return amountOut 获得的代币数量
*/
function removeLiquidity(
address _tokenOut,
uint256 _ytLPAmount,
uint256 _minOut,
address _receiver
) external nonReentrant whenNotPaused returns (uint256) {
if (_ytLPAmount == 0) revert InvalidAmount();
address account = msg.sender;
uint256 amountOut = IYTPoolManager(ytPoolManager).removeLiquidityForAccount(
account,
_tokenOut,
_ytLPAmount,
_minOut,
_receiver
);
return amountOut;
}
/**
* @notice YT代币互换
* @param _tokenIn 输入代币地址
* @param _tokenOut 输出代币地址
* @param _amountIn 输入数量
* @param _minOut 最小输出数量
* @param _receiver 接收地址
* @return amountOut 获得的代币数量
*/
function swapYT(
address _tokenIn,
address _tokenOut,
uint256 _amountIn,
uint256 _minOut,
address _receiver
) external nonReentrant whenNotPaused returns (uint256) {
if (_amountIn == 0) revert InvalidAmount();
address account = msg.sender;
IERC20(_tokenIn).safeTransferFrom(account, ytVault, _amountIn);
uint256 amountOut = IYTVault(ytVault).swap(_tokenIn, _tokenOut, _receiver);
if (amountOut < _minOut) revert InsufficientOutput();
emit Swap(account, _tokenIn, _tokenOut, _amountIn, amountOut);
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;
}

View File

@@ -0,0 +1,626 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
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;
error Forbidden();
error OnlyPoolManager();
error NotSwapper();
error EmergencyMode();
error InvalidAddress();
error TokenNotWhitelisted();
error InvalidFee();
error NotInEmergency();
error SlippageTooHigh();
error SwapDisabled();
error InvalidAmount();
error InsufficientPool();
error SameToken();
error AmountExceedsLimit();
error MaxUSDYExceeded();
error InsufficientUSDYAmount();
error InvalidPoolAmount();
error DailyLimitExceeded();
uint256 public constant PRICE_PRECISION = 10 ** 30;
uint256 public constant BASIS_POINTS_DIVISOR = 10000;
uint256 public constant USDY_DECIMALS = 18;
address public gov;
address public ytPoolManager;
address public priceFeed;
address public usdy;
mapping(address => bool) public isSwapper; // 授权的swap调用者
bool public isSwapEnabled;
bool public emergencyMode;
// 代币白名单
address[] public allWhitelistedTokens;
mapping(address => bool) public whitelistedTokens;
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; // 跟踪实际代币余额
// 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% 最大滑点
// 单笔交易限额
mapping(address => uint256) public maxSwapAmount;
event Swap(
address indexed account,
address indexed tokenIn,
address indexed tokenOut,
uint256 amountIn,
uint256 amountOut,
uint256 feeBasisPoints
);
event AddLiquidity(
address indexed account,
address indexed token,
uint256 amount,
uint256 usdyAmount
);
event RemoveLiquidity(
address indexed account,
address indexed token,
uint256 usdyAmount,
uint256 amountOut
);
event EmergencyModeSet(bool enabled);
event SwapEnabledSet(bool enabled);
modifier onlyGov() {
if (msg.sender != gov) revert Forbidden();
_;
}
modifier onlyPoolManager() {
if (msg.sender != ytPoolManager) revert OnlyPoolManager();
_;
}
modifier onlySwapper() {
if (!isSwapper[msg.sender] && msg.sender != ytPoolManager) revert NotSwapper();
_;
}
modifier notInEmergency() {
if (emergencyMode) revert EmergencyMode();
_;
}
/**
* @notice 初始化合约
* @param _usdy USDY代币地址
* @param _priceFeed 价格预言机地址
*/
function initialize(address _usdy, address _priceFeed) external initializer {
if (_usdy == address(0) || _priceFeed == address(0)) revert InvalidAddress();
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
gov = msg.sender;
usdy = _usdy;
priceFeed = _priceFeed;
// 初始化默认值
isSwapEnabled = true;
emergencyMode = false;
swapFeeBasisPoints = 30;
stableSwapFeeBasisPoints = 4;
taxBasisPoints = 50;
stableTaxBasisPoints = 20;
hasDynamicFees = true;
maxSwapSlippageBps = 1000; // 10% 最大滑点
// 将 USDY 标记为稳定币,这样 USDY ↔ 稳定币的互换可以享受低费率
stableTokens[_usdy] = true;
}
/**
* @notice 授权升级仅gov可调用
* @param newImplementation 新实现合约地址
*/
function _authorizeUpgrade(address newImplementation) internal override onlyGov {}
function setGov(address _gov) external onlyGov {
if (_gov == address(0)) revert InvalidAddress();
gov = _gov;
}
function setPoolManager(address _manager) external onlyGov {
if (_manager == address(0)) revert InvalidAddress();
ytPoolManager = _manager;
}
function setSwapper(address _swapper, bool _isActive) external onlyGov {
if (_swapper == address(0)) revert InvalidAddress();
isSwapper[_swapper] = _isActive;
}
function setWhitelistedToken(
address _token,
uint256 _decimals,
uint256 _weight,
uint256 _maxUsdyAmount,
bool _isStable
) external onlyGov {
if (_token == address(0)) revert InvalidAddress();
if (!whitelistedTokens[_token]) {
allWhitelistedTokens.push(_token);
whitelistedTokens[_token] = true;
}
totalTokenWeights = totalTokenWeights - tokenWeights[_token] + _weight;
tokenDecimals[_token] = _decimals;
tokenWeights[_token] = _weight;
maxUsdyAmounts[_token] = _maxUsdyAmount;
stableTokens[_token] = _isStable;
}
function clearWhitelistedToken(address _token) external onlyGov {
if (!whitelistedTokens[_token]) revert TokenNotWhitelisted();
totalTokenWeights = totalTokenWeights - tokenWeights[_token];
delete whitelistedTokens[_token];
delete stableTokens[_token];
delete tokenDecimals[_token];
delete tokenWeights[_token];
delete maxUsdyAmounts[_token];
}
function setSwapFees(
uint256 _swapFee,
uint256 _stableSwapFee,
uint256 _taxBasisPoints,
uint256 _stableTaxBasisPoints
) external onlyGov {
if (_swapFee > 100 || _stableSwapFee > 50) revert InvalidFee();
swapFeeBasisPoints = _swapFee;
stableSwapFeeBasisPoints = _stableSwapFee;
taxBasisPoints = _taxBasisPoints;
stableTaxBasisPoints = _stableTaxBasisPoints;
}
function setDynamicFees(bool _hasDynamicFees) external onlyGov {
hasDynamicFees = _hasDynamicFees;
}
function setEmergencyMode(bool _emergencyMode) external onlyGov {
emergencyMode = _emergencyMode;
emit EmergencyModeSet(_emergencyMode);
}
function setSwapEnabled(bool _isSwapEnabled) external onlyGov {
isSwapEnabled = _isSwapEnabled;
emit SwapEnabledSet(_isSwapEnabled);
}
function withdrawToken(address _token, address _receiver, uint256 _amount) external onlyGov {
if (!emergencyMode) revert NotInEmergency();
IERC20(_token).safeTransfer(_receiver, _amount);
_updateTokenBalance(_token);
}
function setMaxSwapSlippageBps(uint256 _slippageBps) external onlyGov {
if (_slippageBps > 2000) revert SlippageTooHigh(); // 最大20%
maxSwapSlippageBps = _slippageBps;
}
function setMaxSwapAmount(address _token, uint256 _amount) external onlyGov {
maxSwapAmount[_token] = _amount;
}
/**
* @notice 用YT代币购买USDY添加流动性时调用
* @param _token YT代币地址
* @param _receiver USDY接收地址
* @return usdyAmountAfterFees 实际获得的USDY数量
*/
function buyUSDY(address _token, address _receiver)
external
onlyPoolManager
nonReentrant
notInEmergency
returns (uint256)
{
if (!whitelistedTokens[_token]) revert TokenNotWhitelisted();
if (!isSwapEnabled) revert SwapDisabled();
uint256 tokenAmount = _transferIn(_token);
if (tokenAmount == 0) revert InvalidAmount();
uint256 price = _getPrice(_token, false);
uint256 usdyAmount = tokenAmount * price / PRICE_PRECISION;
usdyAmount = _adjustForDecimals(usdyAmount, _token, usdy);
if (usdyAmount == 0) revert InvalidAmount();
uint256 feeBasisPoints = _getSwapFeeBasisPoints(_token, usdy, usdyAmount);
uint256 feeAmount = tokenAmount * feeBasisPoints / BASIS_POINTS_DIVISOR;
uint256 amountAfterFees = tokenAmount - feeAmount;
uint256 usdyAmountAfterFees = amountAfterFees * price / PRICE_PRECISION;
usdyAmountAfterFees = _adjustForDecimals(usdyAmountAfterFees, _token, usdy);
// 手续费直接留在池子中全部代币加入poolAmount但只铸造扣费后的USDY
_increasePoolAmount(_token, tokenAmount);
_increaseUsdyAmount(_token, usdyAmountAfterFees);
IUSDY(usdy).mint(_receiver, usdyAmountAfterFees);
emit AddLiquidity(_receiver, _token, tokenAmount, usdyAmountAfterFees);
return usdyAmountAfterFees;
}
/**
* @notice 用USDY卖出换取YT代币移除流动性时调用
* @param _token YT代币地址
* @param _receiver YT代币接收地址
* @return amountOutAfterFees 实际获得的YT代币数量
*/
function sellUSDY(address _token, address _receiver)
external
onlyPoolManager
nonReentrant
notInEmergency
returns (uint256)
{
if (!whitelistedTokens[_token]) revert TokenNotWhitelisted();
if (!isSwapEnabled) revert SwapDisabled();
uint256 usdyAmount = _transferIn(usdy);
if (usdyAmount == 0) revert InvalidAmount();
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);
emit RemoveLiquidity(_receiver, _token, usdyAmount, amountOut);
return amountOut;
}
/**
* @notice YT代币互换
* @param _tokenIn 输入代币地址
* @param _tokenOut 输出代币地址
* @param _receiver 接收地址
* @return amountOutAfterFees 实际获得的输出代币数量
*/
function swap(
address _tokenIn,
address _tokenOut,
address _receiver
) external onlySwapper nonReentrant notInEmergency returns (uint256) {
if (!isSwapEnabled) revert SwapDisabled();
if (!whitelistedTokens[_tokenIn]) revert TokenNotWhitelisted();
if (!whitelistedTokens[_tokenOut]) revert TokenNotWhitelisted();
if (_tokenIn == _tokenOut) revert SameToken();
uint256 amountIn = _transferIn(_tokenIn);
if (amountIn == 0) revert InvalidAmount();
// 检查单笔交易限额
if (maxSwapAmount[_tokenIn] > 0) {
if (amountIn > maxSwapAmount[_tokenIn]) revert AmountExceedsLimit();
}
uint256 priceIn = _getPrice(_tokenIn, false);
uint256 priceOut = _getPrice(_tokenOut, true);
uint256 usdyAmount = amountIn * priceIn / PRICE_PRECISION;
usdyAmount = _adjustForDecimals(usdyAmount, _tokenIn, usdy);
uint256 amountOut = usdyAmount * PRICE_PRECISION / priceOut;
amountOut = _adjustForDecimals(amountOut, usdy, _tokenOut);
uint256 feeBasisPoints = _getSwapFeeBasisPoints(_tokenIn, _tokenOut, usdyAmount);
uint256 amountOutAfterFees = amountOut * (BASIS_POINTS_DIVISOR - feeBasisPoints) / BASIS_POINTS_DIVISOR;
if (amountOutAfterFees == 0) revert InvalidAmount();
if (poolAmounts[_tokenOut] < amountOutAfterFees) revert InsufficientPool();
// 全局滑点保护
_validateSwapSlippage(amountIn, amountOutAfterFees, priceIn, priceOut);
_increasePoolAmount(_tokenIn, amountIn);
_decreasePoolAmount(_tokenOut, amountOutAfterFees);
_increaseUsdyAmount(_tokenIn, usdyAmount);
_decreaseUsdyAmount(_tokenOut, usdyAmount);
IERC20(_tokenOut).safeTransfer(_receiver, amountOutAfterFees);
_updateTokenBalance(_tokenOut);
emit Swap(msg.sender, _tokenIn, _tokenOut, amountIn, amountOutAfterFees, feeBasisPoints);
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);
}
function getAllPoolTokens() external view returns (address[] memory) {
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++) {
address token = allWhitelistedTokens[i];
if (!whitelistedTokens[token]) continue;
uint256 amount = poolAmounts[token];
uint256 price = _getPrice(token, _maximise);
uint256 value = amount * price / PRICE_PRECISION;
value = _adjustForDecimals(value, token, usdy);
totalValue += value;
}
return totalValue;
}
function getTargetUsdyAmount(address _token) public view returns (uint256) {
uint256 supply = IERC20(usdy).totalSupply();
if (supply == 0) { return 0; }
uint256 weight = tokenWeights[_token];
return weight * supply / totalTokenWeights;
}
function _increaseUsdyAmount(address _token, uint256 _amount) private {
usdyAmounts[_token] = usdyAmounts[_token] + _amount;
uint256 maxUsdyAmount = maxUsdyAmounts[_token];
if (maxUsdyAmount != 0) {
if (usdyAmounts[_token] > maxUsdyAmount) revert MaxUSDYExceeded();
}
}
function _decreaseUsdyAmount(address _token, uint256 _amount) private {
uint256 value = usdyAmounts[_token];
if (value < _amount) revert InsufficientUSDYAmount();
usdyAmounts[_token] = value - _amount;
}
/**
* @notice 获取swap手续费率公开方法供前端调用
* @param _tokenIn 输入代币
* @param _tokenOut 输出代币
* @param _usdyAmount USDY数量
* @return 手续费率basis points
*/
function getSwapFeeBasisPoints(
address _tokenIn,
address _tokenOut,
uint256 _usdyAmount
) public view returns (uint256) {
return _getSwapFeeBasisPoints(_tokenIn, _tokenOut, _usdyAmount);
}
/**
* @notice 获取赎回手续费率sellUSDY时使用
* @param _token 代币地址
* @param _usdyAmount USDY数量
* @return 手续费率basis points
*/
function getRedemptionFeeBasisPoints(
address _token,
uint256 _usdyAmount
) public view returns (uint256) {
return _getSwapFeeBasisPoints(usdy, _token, _usdyAmount);
}
function _getSwapFeeBasisPoints(
address _tokenIn,
address _tokenOut,
uint256 _usdyAmount
) private view returns (uint256) {
// 稳定币交换是指两个代币都是稳定币(如 WUSD <-> USDC
bool isStableSwap = stableTokens[_tokenIn] && stableTokens[_tokenOut];
uint256 baseBps = isStableSwap ? stableSwapFeeBasisPoints : swapFeeBasisPoints;
uint256 taxBps = isStableSwap ? stableTaxBasisPoints : taxBasisPoints;
if (!hasDynamicFees) {
return baseBps;
}
uint256 feesBasisPoints0 = getFeeBasisPoints(_tokenIn, _usdyAmount, baseBps, taxBps, true);
uint256 feesBasisPoints1 = getFeeBasisPoints(_tokenOut, _usdyAmount, baseBps, taxBps, false);
return feesBasisPoints0 > feesBasisPoints1 ? feesBasisPoints0 : feesBasisPoints1;
}
function getFeeBasisPoints(
address _token,
uint256 _usdyDelta,
uint256 _feeBasisPoints,
uint256 _taxBasisPoints,
bool _increment
) public view returns (uint256) {
if (!hasDynamicFees) { return _feeBasisPoints; }
uint256 initialAmount = usdyAmounts[_token];
uint256 nextAmount = initialAmount + _usdyDelta;
if (!_increment) {
nextAmount = _usdyDelta > initialAmount ? 0 : initialAmount - _usdyDelta;
}
uint256 targetAmount = getTargetUsdyAmount(_token);
if (targetAmount == 0) { return _feeBasisPoints; }
uint256 initialDiff = initialAmount > targetAmount
? initialAmount - targetAmount
: targetAmount - initialAmount;
uint256 nextDiff = nextAmount > targetAmount
? nextAmount - targetAmount
: targetAmount - nextAmount;
// 改善平衡 → 降低手续费
if (nextDiff < initialDiff) {
uint256 rebateBps = _taxBasisPoints * initialDiff / targetAmount;
return rebateBps > _feeBasisPoints ? 0 : _feeBasisPoints - rebateBps;
}
// 恶化平衡 → 提高手续费
uint256 averageDiff = (initialDiff + nextDiff) / 2;
if (averageDiff > targetAmount) {
averageDiff = targetAmount;
}
uint256 taxBps = _taxBasisPoints * averageDiff / targetAmount;
return _feeBasisPoints + taxBps;
}
function _transferIn(address _token) private returns (uint256) {
uint256 prevBalance = tokenBalances[_token];
uint256 nextBalance = IERC20(_token).balanceOf(address(this));
tokenBalances[_token] = nextBalance;
return nextBalance - prevBalance;
}
function _updateTokenBalance(address _token) private {
tokenBalances[_token] = IERC20(_token).balanceOf(address(this));
}
function _increasePoolAmount(address _token, uint256 _amount) private {
poolAmounts[_token] += _amount;
_validatePoolAmount(_token);
}
function _decreasePoolAmount(address _token, uint256 _amount) private {
if (poolAmounts[_token] < _amount) revert InsufficientPool();
poolAmounts[_token] -= _amount;
}
function _validatePoolAmount(address _token) private view {
if (poolAmounts[_token] > tokenBalances[_token]) revert InvalidPoolAmount();
}
function _validateSwapSlippage(
uint256 _amountIn,
uint256 _amountOut,
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();
}
}
function _getPrice(address _token, bool _maximise) private view returns (uint256) {
return IYTPriceFeed(priceFeed).getPrice(_token, _maximise);
}
function _adjustForDecimals(
uint256 _amount,
address _tokenFrom,
address _tokenTo
) private view returns (uint256) {
uint256 decimalsFrom = _tokenFrom == usdy ? USDY_DECIMALS : tokenDecimals[_tokenFrom];
uint256 decimalsTo = _tokenTo == usdy ? USDY_DECIMALS : tokenDecimals[_tokenTo];
if (decimalsFrom == decimalsTo) {
return _amount;
}
if (decimalsFrom > decimalsTo) {
return _amount / (10 ** (decimalsFrom - decimalsTo));
}
return _amount * (10 ** (decimalsTo - decimalsFrom));
}
/**
* @dev 预留存储空间,用于未来升级时添加新的状态变量
* 50个slot = 50 * 32 bytes = 1600 bytes
*/
uint256[50] private __gap;
}

View File

@@ -0,0 +1,87 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
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 {
error Forbidden();
error InvalidVault();
mapping(address => bool) public vaults;
event VaultAdded(address indexed vault);
event VaultRemoved(address indexed vault);
modifier onlyVault() {
if (!vaults[msg.sender]) revert Forbidden();
_;
}
/**
* @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;
}

View File

@@ -0,0 +1,54 @@
// 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 {
/**
* @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;
}

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
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 只有授权的MinterYTPoolManager可以铸造和销毁UUPS可升级合约
*/
contract YTLPToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
error NotMinter();
error InvalidMinter();
mapping(address => bool) public isMinter;
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() {
if (!isMinter[msg.sender]) revert NotMinter();
_;
}
/**
* @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;
}

View File

@@ -0,0 +1,161 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title YTToken
* @notice YT代币示例实现Yield Token
* @dev 展示如何实现价格接口供YTPriceFeed读取
*/
contract YTToken is ERC20, Ownable {
error NotUpdater();
error InvalidUpdater();
error IntervalTooLong();
error UpdateTooFrequent();
error InvalidYield();
error InvalidAmount();
error InsufficientAssets();
uint256 public constant PRICE_PRECISION = 10 ** 30;
uint256 public totalAssets;
uint256 public accumulatedYield;
// 价格变量
uint256 public assetPrice;
uint256 public lastPriceUpdate;
// 价格更新控制
address public priceUpdater;
uint256 public minUpdateInterval = 5 minutes; // 最小更新间隔
event PriceUpdated(uint256 oldPrice, uint256 newPrice, uint256 timestamp);
event YieldAccumulated(uint256 amount, uint256 timestamp);
event PriceUpdaterSet(address indexed updater);
event MinUpdateIntervalSet(uint256 interval);
modifier onlyPriceUpdater() {
if (msg.sender != priceUpdater && msg.sender != owner()) revert NotUpdater();
_;
}
constructor(
string memory name,
string memory symbol,
address _priceUpdater
) ERC20(name, symbol) Ownable(msg.sender) {
assetPrice = PRICE_PRECISION; // 初始价格为1
lastPriceUpdate = block.timestamp;
priceUpdater = _priceUpdater;
}
/**
* @notice 设置价格更新者
*/
function setPriceUpdater(address _updater) external onlyOwner {
if (_updater == address(0)) revert InvalidUpdater();
priceUpdater = _updater;
emit PriceUpdaterSet(_updater);
}
/**
* @notice 设置最小更新间隔
*/
function setMinUpdateInterval(uint256 _interval) external onlyOwner {
if (_interval > 1 hours) revert IntervalTooLong();
minUpdateInterval = _interval;
emit MinUpdateIntervalSet(_interval);
}
/**
* @notice 更新代币价格
* @dev 只能由授权的updater调用有最小时间间隔限制
*/
function updatePrice() public onlyPriceUpdater {
if (block.timestamp < lastPriceUpdate + minUpdateInterval) revert UpdateTooFrequent();
uint256 oldPrice = assetPrice;
uint256 supply = totalSupply();
if (supply == 0) {
assetPrice = PRICE_PRECISION;
} else {
uint256 totalValue = totalAssets + accumulatedYield;
// 计算每个token对应的USDC价值18位精度
uint256 usdcPerToken = totalValue * 1e18 / supply;
// 转换为30位精度的价格
assetPrice = usdcPerToken * PRICE_PRECISION / 1e18;
}
lastPriceUpdate = block.timestamp;
emit PriceUpdated(oldPrice, assetPrice, block.timestamp);
}
/**
* @notice 累积收益并更新价格
* @dev 当从收益策略中收到新收益时调用
*/
function updateYield(uint256 _newYield) external onlyPriceUpdater {
if (_newYield == 0) revert InvalidYield();
accumulatedYield += _newYield;
emit YieldAccumulated(_newYield, block.timestamp);
// 收益更新后立即更新价格
if (block.timestamp >= lastPriceUpdate + minUpdateInterval) {
uint256 oldPrice = assetPrice;
uint256 supply = totalSupply();
if (supply > 0) {
uint256 totalValue = totalAssets + accumulatedYield;
uint256 usdcPerToken = totalValue * 1e18 / supply;
assetPrice = usdcPerToken * PRICE_PRECISION / 1e18;
}
lastPriceUpdate = block.timestamp;
emit PriceUpdated(oldPrice, assetPrice, block.timestamp);
}
}
/**
* @notice 存入资产(模拟)
* @dev 实际实现中应该处理真实的USDC存款
*/
function deposit(uint256 _amount) external onlyOwner {
if (_amount == 0) revert InvalidAmount();
totalAssets += _amount;
_mint(msg.sender, _amount);
}
/**
* @notice 提取资产(模拟)
* @dev 实际实现中应该处理真实的USDC提款
*/
function withdraw(uint256 _amount) external onlyOwner {
if (_amount == 0) revert InvalidAmount();
if (totalAssets < _amount) revert InsufficientAssets();
totalAssets -= _amount;
_burn(msg.sender, _amount);
}
/**
* @notice 获取当前价格信息
*/
function getPriceInfo() external view returns (
uint256 price,
uint256 lastUpdate,
uint256 timeSinceUpdate,
uint256 totalVal
) {
price = assetPrice;
lastUpdate = lastPriceUpdate;
timeSinceUpdate = block.timestamp - lastPriceUpdate;
totalVal = totalAssets + accumulatedYield;
}
}