Files
assetxContracts/contracts/ytLending/Lending.sol
2025-12-22 13:25:06 +08:00

658 lines
27 KiB
Solidity
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;
// 常量:一年的秒数
uint64 SECONDS_PER_YEAR = 365 * 24 * 60 * 60; // 31,536,000
// 设置利率参数
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
if(assetConfig.liquidationFactor >= 1e18) revert InvalidLiquidationFactor();
if(assetConfig.borrowCollateralFactor >= 1e18) revert InvalidBorrowCollateralFactor();
if(assetConfig.liquidateCollateralFactor >= 1e18) revert InvalidLiquidateCollateralFactor();
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
);
// 更新利息累计因子(使用每秒利率)
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 取出基础资产(如果余额不足会自动借款)
* @dev 如果用户余额不足,会自动借款,借款金额为 amount借款利率为 borrowRate借款期限为 borrowPeriod
*/
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 存入抵押品
* @dev 由于不涉及债务计算,存入抵押品反而会让账户更安全,所以不用更新利息因子
*/
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 借款
* @dev baseBorrowMin 是用户借款的最小金额,如果用户借款后,余额小于 baseBorrowMin由正数变为负数同理则抛出 BorrowTooSmall 错误
*/
function borrow(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);
// 检查最小借款额
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 清算不良债务(内部实现)
* @dev 当用户抵押品由于乘以liquidateCollateralFactor后小于债务价值时会进行清算清算后如果实际抵押品价值乘以liquidateCollateralFactor大于债务价值则将差额部分作为用户本金本金以baseToken显示否则将差额部分作为坏账由协议承担
*/
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 = 0;
if (int256(collateralInBase) < -oldBalance) {
// 抵押品不足以覆盖债务,差额由协议储备金承担
basePaidOut = uint256(-oldBalance) - collateralInBase;
}
// 如果 collateralInBase >= -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 NotForSale(); // 储备金充足,无需出售
}
// 收取清算人支付的资金
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();
// 计算折扣率
// 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;
}
// ========== 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
}
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
}
/**
* @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);
}
}