update ytLending

This commit is contained in:
2025-12-19 10:54:52 +08:00
parent 76b7f838db
commit 21674f86a9
14 changed files with 22 additions and 392 deletions

File diff suppressed because one or more lines are too long

View File

@@ -50,8 +50,7 @@ contract Lending is
// 常量:一年的秒数
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);
@@ -78,9 +77,9 @@ contract Lending is
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");
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);
@@ -263,6 +262,7 @@ contract Lending is
/**
* @notice 借款
* @dev baseBorrowMin 是用户借款的最小金额,如果用户借款后,余额小于 baseBorrowMin由正数变为负数同理则抛出 BorrowTooSmall 错误
*/
function borrow(uint256 amount) external override nonReentrant whenNotPaused {
accrueInterest();
@@ -294,7 +294,7 @@ contract Lending is
totalSupplyBase -= withdrawAmount;
totalBorrowBase += borrowAmount;
// 更新用户本金
// 更新用户本金,方便检查更新后的用户本金是否大于还是小于抵押品价值
userBasic[msg.sender].principal = newPrincipal;
// 检查抵押品是否充足
@@ -307,6 +307,7 @@ contract Lending is
/**
* @notice 清算不良债务(内部实现)
* @dev 当用户抵押品由于乘以liquidateCollateralFactor后小于债务价值时会进行清算清算后如果实际抵押品价值大于债务价值则将差额部分作为用户本金本金以baseToken显示否则将差额部分作为坏账由协议承担
*/
function _absorbInternal(address absorber, address borrower) internal {
if (!isLiquidatable(borrower)) revert NotLiquidatable();
@@ -438,7 +439,6 @@ contract Lending is
IERC20(asset).safeTransfer(recipient, collateralAmount);
// 注意:收入会自动体现在 getReserves() 中,因为 balance 增加了
emit BuyCollateral(msg.sender, asset, baseAmount, collateralAmount);
}
@@ -451,7 +451,7 @@ contract Lending is
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;

View File

@@ -32,6 +32,8 @@ interface ILending {
error NotLiquidatable();
error SupplyCapExceeded();
error InvalidLiquidationFactor();
error InvalidBorrowCollateralFactor();
error InvalidLiquidateCollateralFactor();
error InsufficientReserves();
// ========== Core Functions ==========

View File

@@ -1,374 +0,0 @@
# YT Lending 借贷池系统流程说明
## 系统概述
YT Lending 是基于 Compound V3 架构的简化借贷池系统,采用 OpenZeppelin UUPS 升级模式和 Ownable 权限管理。
### 核心特性
-**UUPS 可升级**:使用 OpenZeppelin 的 UUPS 代理模式
-**简化权限**:使用 Owner 权限管理,替代复杂的 Governor 治理
-**单一基础资产**:支持一种基础资产(如 USDC的借贷
-**多种抵押品**:支持多种抵押资产(如 ETH、WBTC
-**动态利率**:双拐点利率模型
-**清算机制**:支持不良贷款清算和抵押品拍卖
---
## 合约架构
```
├── Lending.sol # 核心借贷合约UUPS
├── LendingFactory.sol # 工厂合约
├── Configurator.sol # 配置管理合约UUPS
├── LendingConfiguration.sol # 配置结构体
├── LendingStorage.sol # 存储定义
├── LendingMath.sol # 数学计算库
├── ConfiguratorStorage.sol # 配置器存储
└── interfaces/
├── ILending.sol # 借贷接口
└── IPriceFeed.sol # 价格预言机接口
```
---
## 部署流程
### 步骤 1: 部署基础合约
运行部署脚本:
```bash
npx hardhat run scripts/deploy/07-deployLending.ts --network <network>
```
部署内容:
1. **LendingFactory** - 工厂合约
2. **Configurator Implementation** - 配置管理实现合约
3. **Configurator Proxy** - 配置管理代理合约
4. **Lending Implementation** - 借贷池实现合约
### 步骤 2: 配置市场参数
运行配置脚本:
```bash
npx hardhat run scripts/deploy/08-configureLending.ts --network <network>
```
配置内容:
1. 设置工厂合约地址
2. 配置市场参数(利率模型、清算参数等)
3. 添加抵押资产配置
4. 部署 Lending Proxy 代理合约
---
## 配置参数说明
### 利率模型参数
| 参数 | 说明 | 示例值 |
|------|------|--------|
| `supplyKink` | 供应利率拐点(利用率) | 80% |
| `supplyPerYearInterestRateSlopeLow` | 供应拐点前斜率 | 3% APY |
| `supplyPerYearInterestRateSlopeHigh` | 供应拐点后斜率 | 40% APY |
| `borrowKink` | 借款利率拐点(利用率) | 80% |
| `borrowPerYearInterestRateSlopeLow` | 借款拐点前斜率 | 5% APY |
| `borrowPerYearInterestRateSlopeHigh` | 借款拐点后斜率 | 150% APY |
### 抵押资产参数
| 参数 | 说明 | 示例值 |
|------|------|--------|
| `borrowCollateralFactor` | 借款抵押率LTV | 80% |
| `liquidateCollateralFactor` | 清算抵押率 | 85% |
| `liquidationFactor` | 清算激励 | 5% |
| `supplyCap` | 供应上限 | 100,000 ETH |
---
## 核心功能流程
### 1. 存款Supply
```solidity
lending.supply(1000 * 1e6); // 存入 1000 USDC
```
**流程**
1. 用户授权 USDC 给 Lending 合约
2. 调用 `supply()` 存入 USDC
3. 合约记录用户正余额
4. 开始计息
### 2. 存入抵押品Supply Collateral
```solidity
lending.supplyCollateral(WETH, 10 * 1e18); // 存入 10 ETH
```
**流程**
1. 用户授权抵押品(如 WETH给 Lending 合约
2. 调用 `supplyCollateral()` 存入抵押品
3. 合约记录用户抵押品余额
### 3. 借款Borrow
```solidity
lending.borrow(5000 * 1e6); // 借出 5000 USDC
```
**流程**
1. 用户已存入足够的抵押品
2. 调用 `borrow()` 借出 USDC
3. 合约检查抵押品是否充足(根据 borrowCollateralFactor
4. 用户余额变为负数(表示借款)
5. 开始计息
**借款能力计算**
```
借款能力 = Σ(抵押品价值 × borrowCollateralFactor)
```
### 4. 取出资产Withdraw
```solidity
lending.withdraw(500 * 1e6); // 取出 500 USDC
```
**流程**
1. 调用 `withdraw()` 取出 USDC
2. 如果用户有借款,检查抵押品是否充足
3. 转账 USDC 给用户
### 5. 取出抵押品Withdraw Collateral
```solidity
lending.withdrawCollateral(WETH, 2 * 1e18); // 取出 2 ETH
```
**流程**
1. 调用 `withdrawCollateral()` 取出抵押品
2. 如果用户有借款,检查剩余抵押品是否充足
3. 转账抵押品给用户
### 6. 清算Absorb
```solidity
lending.absorb(borrowerAddress); // 清算借款人
```
**触发条件**
```
债务 > Σ(抵押品价值 × liquidateCollateralFactor)
```
**流程**
1. 清算人调用 `absorb()` 清算不良贷款
2. 合约检查借款人是否可被清算
3. 将借款人的债务清零
4. 将借款人的抵押品转移到清算库存
### 7. 购买清算抵押品Buy Collateral
```solidity
lending.buyCollateral(WETH, 1 * 1e18, 2000 * 1e6);
// 用 2000 USDC 购买至少 1 ETH
```
**流程**
1. 购买者调用 `buyCollateral()`
2. 按折扣价storeFrontPriceFactor计算可购买的抵押品数量
3. 转账 USDC 给合约
4. 转账抵押品给购买者
**折扣价格计算**
```
折扣价 = 市场价 × storeFrontPriceFactor
可购买数量 = 支付 USDC 数量 / 折扣价
```
---
## 利率计算
### 利用率
```
利用率 = 总借款 / 总供应
```
### 双拐点利率模型
```
如果 利用率 <= Kink:
利率 = 基础利率 + 利用率 × 低斜率
如果 利用率 > Kink:
超额利用率 = 利用率 - Kink
利率 = 基础利率 + 低斜率 + 超额利用率 × 高斜率
```
**示例**借款利率Kink = 80%
| 利用率 | 借款利率 |
|--------|----------|
| 0% | 1.5% |
| 40% | 3.5% |
| 80% | 5.5% |
| 90% | 20.5% |
| 100% | 35.5% |
### 利率计算优化(采用 Compound V3 方式)
**关键优化**:配置时传入年化利率,初始化时自动转换为每秒利率
```solidity
// 初始化时(只计算一次)
uint256 SECONDS_PER_YEAR = 31,536,000;
supplyPerSecondRate = supplyPerYearRate / SECONDS_PER_YEAR;
borrowPerSecondRate = borrowPerYearRate / SECONDS_PER_YEAR;
// 每次计提利息(高效计算)
newIndex = oldIndex + (oldIndex * perSecondRate * timeElapsed) / 1e18;
```
**优化效果**
- ✅ 精度更高:每秒利率避免了重复除法导致的精度损失
- ✅ Gas 更低:每次计提节省 ~100 gas~66% Gas 优化)
- ✅ 计算更快:只需乘法,无需除以 31,536,000
- ✅ 完全兼容:配置文件仍使用易读的年化利率
---
## 权限管理
### Owner 权限
Owner所有者可以执行以下操作
1. **升级合约**:调用 `upgradeTo()` 升级 Lending 或 Configurator
2. **暂停/恢复**:调用 `pause()` / `unpause()` 暂停或恢复合约
3. **配置市场**:通过 Configurator 修改市场参数
4. **转移所有权**:调用 `transferOwnership()` 转移 Owner
### 推荐配置
建议使用多签钱包(如 Gnosis Safe作为 Owner
```typescript
// 部署后转移所有权到多签
await lending.transferOwnership(GNOSIS_SAFE_ADDRESS);
await configurator.transferOwnership(GNOSIS_SAFE_ADDRESS);
```
---
## 安全注意事项
### ⚠️ 关键风险
1. **价格预言机风险**:依赖外部价格源,需确保价格源可靠
2. **清算延迟风险**:市场剧烈波动时可能来不及清算
3. **Owner 权限过大**Owner 拥有所有管理权限,需使用多签
### 🔒 安全建议
1. **使用多签钱包**:将 Owner 设为 Gnosis Safe 等多签钱包
2. **设置合理参数**
- `borrowCollateralFactor` < `liquidateCollateralFactor`
- 合理的清算折扣storeFrontPriceFactor
- 适当的 supplyCap 防止单一资产风险过大
3. **监控系统**实时监控抵押率及时清算
4. **价格源多样化**考虑使用多个价格源取平均
---
## 升级流程
### 使用 upgrades 插件升级(推荐)
运行升级脚本
```bash
npx hardhat run scripts/deploy/09-upgradeLending.ts --network <network>
```
**脚本特性**
- 自动验证存储布局兼容性
- 自动部署新实现合约
- 自动更新代理指向
- 保留所有状态数据
**修改升级目标**
```typescript
// 在 09-upgradeLending.ts 中修改
const UPGRADE_LENDING = true; // true = 升级 Lending, false = 升级 Configurator
```
### 手动 UUPS 升级步骤(不推荐)
如果需要手动升级
1. **部署新实现合约**
```typescript
const NewLending = await ethers.getContractFactory("LendingV2");
const newImpl = await NewLending.deploy();
```
2. **升级代理**
```typescript
await lending.upgradeTo(newImpl.address);
```
### 注意事项
- 只有 Owner 可以升级
- 必须保持存储布局一致
- 只能在末尾添加新存储变量
- 升级前在测试网充分测试
- 🔧 推荐使用 upgrades 插件它会自动验证兼容性
---
## 监控指标
### 关键指标
1. **利用率**`totalBorrow / totalSupply`
2. **供应利率**`getSupplyRate()`
3. **借款利率**`getBorrowRate()`
4. **储备金**`reserves`
5. **清算库存**`collateralReserves[asset]`
### 健康检查
```typescript
// 检查利用率
const utilization = totalBorrow.mul(1e18).div(totalSupply);
console.log("Utilization:", utilization / 1e18 * 100, "%");
// 检查是否需要清算
const isLiquidatable = await lending.isLiquidatable(userAddress);
```
---
## 总结
YT Lending 提供了一个简化但功能完整的借贷池系统
- 核心借贷功能存款借款抵押品管理
- 清算机制absorb + buyCollateral
- 动态利率模型双拐点
- 可升级架构UUPS
- 简化权限管理Owner
**适用场景**
- 中小型借贷协议
- 需要快速上线的项目
- 中心化或半中心化管理的借贷池
**限制**
- 单一基础资产只能借贷一种资产
- 简化的权限模型不支持 DAO 治理
- 需要可靠的价格预言机

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
{"id":"873f71b8338f0e7a","source_id_to_path":{"0":"contracts/ytLending/Configurator.sol","1":"contracts/ytLending/ConfiguratorStorage.sol","2":"contracts/ytLending/Lending.sol","3":"contracts/ytLending/LendingConfiguration.sol","4":"contracts/ytLending/LendingFactory.sol","5":"contracts/ytLending/LendingMath.sol","6":"contracts/ytLending/LendingStorage.sol","7":"contracts/ytLending/interfaces/ILending.sol","8":"contracts/ytLending/interfaces/IPriceFeed.sol","9":"lib/forge-std/src/Base.sol","10":"lib/forge-std/src/StdAssertions.sol","11":"lib/forge-std/src/StdChains.sol","12":"lib/forge-std/src/StdCheats.sol","13":"lib/forge-std/src/StdConstants.sol","14":"lib/forge-std/src/StdError.sol","15":"lib/forge-std/src/StdInvariant.sol","16":"lib/forge-std/src/StdJson.sol","17":"lib/forge-std/src/StdMath.sol","18":"lib/forge-std/src/StdStorage.sol","19":"lib/forge-std/src/StdStyle.sol","20":"lib/forge-std/src/StdToml.sol","21":"lib/forge-std/src/StdUtils.sol","22":"lib/forge-std/src/Test.sol","23":"lib/forge-std/src/Vm.sol","24":"lib/forge-std/src/console.sol","25":"lib/forge-std/src/console2.sol","26":"lib/forge-std/src/interfaces/IMulticall3.sol","27":"lib/forge-std/src/safeconsole.sol","28":"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","29":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","30":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","31":"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol","32":"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol","33":"node_modules/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol","34":"node_modules/@openzeppelin/contracts/access/Ownable.sol","35":"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol","36":"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol","37":"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol","38":"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol","39":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol","40":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol","41":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol","42":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol","43":"node_modules/@openzeppelin/contracts/proxy/Proxy.sol","44":"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol","45":"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol","46":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","47":"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol","48":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","49":"node_modules/@openzeppelin/contracts/utils/Address.sol","50":"node_modules/@openzeppelin/contracts/utils/Context.sol","51":"node_modules/@openzeppelin/contracts/utils/Errors.sol","52":"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol","53":"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol","54":"test/YtLending.t.sol"},"language":"Solidity"}

View File

@@ -0,0 +1 @@
{"id":"d40601d3822c5f89","source_id_to_path":{"0":"contracts/ytLending/Configurator.sol","1":"contracts/ytLending/ConfiguratorStorage.sol","2":"contracts/ytLending/Lending.sol","3":"contracts/ytLending/LendingConfiguration.sol","4":"contracts/ytLending/LendingFactory.sol","5":"contracts/ytLending/LendingMath.sol","6":"contracts/ytLending/LendingStorage.sol","7":"contracts/ytLending/interfaces/ILending.sol","8":"contracts/ytLending/interfaces/IPriceFeed.sol","9":"lib/forge-std/src/Base.sol","10":"lib/forge-std/src/StdAssertions.sol","11":"lib/forge-std/src/StdChains.sol","12":"lib/forge-std/src/StdCheats.sol","13":"lib/forge-std/src/StdConstants.sol","14":"lib/forge-std/src/StdError.sol","15":"lib/forge-std/src/StdInvariant.sol","16":"lib/forge-std/src/StdJson.sol","17":"lib/forge-std/src/StdMath.sol","18":"lib/forge-std/src/StdStorage.sol","19":"lib/forge-std/src/StdStyle.sol","20":"lib/forge-std/src/StdToml.sol","21":"lib/forge-std/src/StdUtils.sol","22":"lib/forge-std/src/Test.sol","23":"lib/forge-std/src/Vm.sol","24":"lib/forge-std/src/console.sol","25":"lib/forge-std/src/console2.sol","26":"lib/forge-std/src/interfaces/IMulticall3.sol","27":"lib/forge-std/src/safeconsole.sol","28":"node_modules/@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol","29":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol","30":"node_modules/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol","31":"node_modules/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol","32":"node_modules/@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol","33":"node_modules/@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol","34":"node_modules/@openzeppelin/contracts/access/Ownable.sol","35":"node_modules/@openzeppelin/contracts/interfaces/IERC1363.sol","36":"node_modules/@openzeppelin/contracts/interfaces/IERC165.sol","37":"node_modules/@openzeppelin/contracts/interfaces/IERC1967.sol","38":"node_modules/@openzeppelin/contracts/interfaces/IERC20.sol","39":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC1822.sol","40":"node_modules/@openzeppelin/contracts/interfaces/draft-IERC6093.sol","41":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol","42":"node_modules/@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol","43":"node_modules/@openzeppelin/contracts/proxy/Proxy.sol","44":"node_modules/@openzeppelin/contracts/proxy/beacon/IBeacon.sol","45":"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol","46":"node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol","47":"node_modules/@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol","48":"node_modules/@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol","49":"node_modules/@openzeppelin/contracts/utils/Address.sol","50":"node_modules/@openzeppelin/contracts/utils/Context.sol","51":"node_modules/@openzeppelin/contracts/utils/Errors.sol","52":"node_modules/@openzeppelin/contracts/utils/StorageSlot.sol","53":"node_modules/@openzeppelin/contracts/utils/introspection/IERC165.sol","54":"test/YtLending.t.sol"},"language":"Solidity"}

View File

@@ -23,7 +23,7 @@ contract YtLendingTest is Test {
Configurator public configurator;
// 测试代币
MockERC20 public usdc; // 基础资产 (6 decimals)
MockERC20 public usdc; // 基础资产 (18 decimals)
MockERC20 public weth; // 抵押品 (18 decimals)
// 测试账户