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,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);
}