Aave V3 利率模型与数学原理
1. 数学库基础
1.1 精度定义
Aave V3 使用两种高精度定点数来避免浮点数精度问题:
// WadRayMath.sol
library WadRayMath {
uint256 internal constant WAD = 1e18; // 18 位精度 (Wad)
uint256 internal constant RAY = 1e27; // 27 位精度 (Ray)
uint256 internal constant WAD_RAY_RATIO = 1e9; // 转换比率
// 半精度值,用于四舍五入
uint256 internal constant HALF_WAD = 0.5e18;
uint256 internal constant HALF_RAY = 0.5e27;
}为什么需要两种精度?
| 精度 | 用途 | 示例 |
|---|---|---|
| WAD (1e18) | 代币金额、价格 | 1.5 ETH = 1.5e18 |
| RAY (1e27) | 利率、指数 | 年利率 5% = 0.05e27 |
1.2 核心数学运算
// Ray 乘法 (带四舍五入)
function rayMul(uint256 a, uint256 b) internal pure returns (uint256 c) {
// 为避免溢出,先除后乘
assembly {
if iszero(or(iszero(b), iszero(gt(a, div(sub(not(0), HALF_RAY), b))))) {
revert(0, 0)
}
c := div(add(mul(a, b), HALF_RAY), RAY)
}
}
// Ray 除法 (带四舍五入)
function rayDiv(uint256 a, uint256 b) internal pure returns (uint256 c) {
assembly {
if or(iszero(b), iszero(iszero(gt(a, div(sub(not(0), div(b, 2)), RAY))))) {
revert(0, 0)
}
c := div(add(mul(a, RAY), div(b, 2)), b)
}
}
// Wad 转 Ray
function wadToRay(uint256 a) internal pure returns (uint256 b) {
assembly {
b := mul(a, WAD_RAY_RATIO)
if iszero(eq(div(b, WAD_RAY_RATIO), a)) {
revert(0, 0)
}
}
}
// Ray 转 Wad
function rayToWad(uint256 a) internal pure returns (uint256 b) {
assembly {
b := div(a, WAD_RAY_RATIO)
}
}1.3 百分比运算
// PercentageMath.sol
library PercentageMath {
uint256 internal constant PERCENTAGE_FACTOR = 1e4; // 100.00%
uint256 internal constant HALF_PERCENTAGE_FACTOR = 0.5e4;
// 百分比乘法: value * percentage / 100
function percentMul(uint256 value, uint256 percentage) internal pure returns (uint256) {
if (value == 0 || percentage == 0) {
return 0;
}
return (value * percentage + HALF_PERCENTAGE_FACTOR) / PERCENTAGE_FACTOR;
}
// 百分比除法: value * 100 / percentage
function percentDiv(uint256 value, uint256 percentage) internal pure returns (uint256) {
return (value * PERCENTAGE_FACTOR + percentage / 2) / percentage;
}
}百分比表示:
- 100% = 10000 (1e4)
- 50% = 5000
- 5% = 500
- 0.01% = 1
2. 双斜率利率模型
2.1 模型概述
Aave V3 采用双斜率(Kinked)利率模型,根据资金利用率动态调整借贷利率:
利用率 (U) = 总借贷 / 总存款
利率
│
│ ╱
│ ╱
│ ╱ Slope2 (陡)
│ ╱
│ ╱---╱ ← 拐点 (U_optimal)
│ ╱
│ ╱ Slope1 (缓)
│ ╱
│╱
└────────────────────────── 利用率
U_optimal 100%
2.2 数学公式
当 U ≤ U_optimal 时(正常区间):
BorrowRate = BaseRate + (U / U_optimal) × Slope1
当 U > U_optimal 时(高利用率区间):
ExcessUtilization = (U - U_optimal) / (1 - U_optimal)
BorrowRate = BaseRate + Slope1 + ExcessUtilization × Slope2
存款利率:
SupplyRate = BorrowRate × U × (1 - ReserveFactor)
2.3 代码实现
// DefaultReserveInterestRateStrategy.sol
contract DefaultReserveInterestRateStrategy is IDefaultInterestRateStrategy {
// 常量定义
uint256 public immutable OPTIMAL_USAGE_RATIO; // 最优利用率
uint256 public immutable MAX_EXCESS_USAGE_RATIO; // 1 - OPTIMAL_USAGE_RATIO
// 利率参数
uint256 internal immutable _baseVariableBorrowRate; // 基础利率
uint256 internal immutable _variableRateSlope1; // 斜率1
uint256 internal immutable _variableRateSlope2; // 斜率2
uint256 internal immutable _stableRateSlope1; // 固定利率斜率1
uint256 internal immutable _stableRateSlope2; // 固定利率斜率2
uint256 internal immutable _baseStableRateOffset; // 固定利率偏移
function calculateInterestRates(
DataTypes.CalculateInterestRatesParams calldata params
) external view override returns (uint256, uint256, uint256) {
uint256 vars.totalDebt = params.totalStableDebt + params.totalVariableDebt;
// 计算利用率
uint256 currentLiquidityRate;
uint256 currentVariableBorrowRate;
uint256 currentStableBorrowRate;
uint256 availableLiquidity = IERC20(params.reserve).balanceOf(params.aToken) +
params.liquidityAdded - params.liquidityTaken;
uint256 totalLiquidity = availableLiquidity + vars.totalDebt;
// 利用率 = 总债务 / (可用流动性 + 总债务)
vars.currentUsageRatio = totalLiquidity == 0
? 0
: vars.totalDebt.rayDiv(totalLiquidity);
// 计算稳定利率
currentStableBorrowRate = _calculateStableRate(vars.currentUsageRatio, vars.totalDebt);
// 计算可变利率
if (vars.currentUsageRatio > OPTIMAL_USAGE_RATIO) {
// 超过最优利用率:使用陡峭的斜率2
uint256 excessUsageRatio = (vars.currentUsageRatio - OPTIMAL_USAGE_RATIO).rayDiv(
MAX_EXCESS_USAGE_RATIO
);
currentVariableBorrowRate = _baseVariableBorrowRate +
_variableRateSlope1 +
_variableRateSlope2.rayMul(excessUsageRatio);
} else {
// 正常利用率:使用平缓的斜率1
currentVariableBorrowRate = _baseVariableBorrowRate +
vars.currentUsageRatio.rayMul(_variableRateSlope1).rayDiv(OPTIMAL_USAGE_RATIO);
}
// 计算存款利率
// supplyRate = borrowRate × utilizationRate × (1 - reserveFactor)
currentLiquidityRate = _getOverallBorrowRate(
vars.totalDebt,
params.totalStableDebt,
params.totalVariableDebt,
vars.currentAvgStableBorrowRate,
currentVariableBorrowRate
).rayMul(vars.currentUsageRatio).percentMul(
PercentageMath.PERCENTAGE_FACTOR - params.reserveFactor
);
return (currentLiquidityRate, currentStableBorrowRate, currentVariableBorrowRate);
}
}2.4 典型参数配置
稳定币 (USDC/USDT/DAI):
| 参数 | 值 | 说明 |
|---|---|---|
| 最优利用率 | 90% | 高利用率容忍 |
| 基础利率 | 0% | 无基础成本 |
| Slope1 | 4% | 正常区间缓慢上升 |
| Slope2 | 60% | 超过 90% 后急剧上升 |
主流资产 (ETH/WBTC):
| 参数 | 值 | 说明 |
|---|---|---|
| 最优利用率 | 80% | 中等利用率 |
| 基础利率 | 0% | 无基础成本 |
| Slope1 | 4% | 正常区间 |
| Slope2 | 80% | 高利用率惩罚 |
波动性资产:
| 参数 | 值 | 说明 |
|---|---|---|
| 最优利用率 | 45% | 低利用率目标 |
| 基础利率 | 0% | 无基础成本 |
| Slope1 | 4% | 正常区间 |
| Slope2 | 300% | 极高惩罚 |
3. 复利计算
3.1 线性复利(存款)
存款利息使用简单的线性复利:
// MathUtils.sol
function calculateLinearInterest(
uint256 rate, // 年利率 (RAY)
uint40 lastUpdateTimestamp
) internal view returns (uint256) {
uint256 result = rate * (block.timestamp - uint256(lastUpdateTimestamp));
unchecked {
// rate × time / SECONDS_PER_YEAR + 1
result = result / SECONDS_PER_YEAR + WadRayMath.RAY;
}
return result;
}公式:
利息因子 = 1 + rate × Δt / SECONDS_PER_YEAR
新指数 = 旧指数 × 利息因子
3.2 复合复利(借贷)
借贷利息使用泰勒展开近似的复合复利:
function calculateCompoundedInterest(
uint256 rate,
uint40 lastUpdateTimestamp,
uint256 currentTimestamp
) internal pure returns (uint256) {
uint256 exp = currentTimestamp - uint256(lastUpdateTimestamp);
if (exp == 0) {
return WadRayMath.RAY;
}
uint256 expMinusOne;
uint256 expMinusTwo;
uint256 basePowerTwo;
uint256 basePowerThree;
unchecked {
expMinusOne = exp - 1;
expMinusTwo = exp > 2 ? exp - 2 : 0;
basePowerTwo = rate.rayMul(rate) / (SECONDS_PER_YEAR * SECONDS_PER_YEAR);
basePowerThree = basePowerTwo.rayMul(rate) / SECONDS_PER_YEAR;
}
// 泰勒展开: e^(rt) ≈ 1 + rt + (rt)²/2! + (rt)³/3!
uint256 secondTerm = exp * expMinusOne * basePowerTwo;
unchecked {
secondTerm /= 2;
}
uint256 thirdTerm = exp * expMinusOne * expMinusTwo * basePowerThree;
unchecked {
thirdTerm /= 6;
}
return WadRayMath.RAY + (rate * exp) / SECONDS_PER_YEAR + secondTerm + thirdTerm;
}泰勒展开公式:
e^(rt) ≈ 1 + rt + (rt)²/2! + (rt)³/3! + ...
其中:
- r = 年利率
- t = 时间(秒)
- rt = r × t / SECONDS_PER_YEAR
3.3 为什么使用泰勒展开?
- Gas 效率:避免指数运算的高成本
- 精度足够:对于短时间间隔(通常几秒到几小时),三阶展开精度足够
- 可预测性:确定性计算,无浮点误差
精度分析:
时间间隔 | 年利率 10% | 误差
1 秒 | 0.0000003% | < 1e-15
1 小时 | 0.00114% | < 1e-10
1 天 | 0.0274% | < 1e-8
1 周 | 0.192% | < 1e-6
4. 指数系统
4.1 流动性指数 (Liquidity Index)
// 更新流动性指数
function _updateIndexes(
DataTypes.ReserveData storage reserve,
DataTypes.ReserveCache memory reserveCache
) internal {
// 计算自上次更新以来的累积利息
uint256 cumulatedLiquidityInterest = MathUtils.calculateLinearInterest(
reserveCache.currLiquidityRate,
reserveCache.reserveLastUpdateTimestamp
);
// 新指数 = 旧指数 × 累积利息
reserveCache.nextLiquidityIndex = cumulatedLiquidityInterest.rayMul(
reserveCache.currLiquidityIndex
);
reserve.liquidityIndex = reserveCache.nextLiquidityIndex.toUint128();
}用途:
- 追踪存款的累积收益
- 计算 aToken 的实际余额
计算示例:
初始存款: 100 USDC
存款时指数: 1.05
当前指数: 1.10
实际余额 = 缩放余额 × 当前指数
= (100 / 1.05) × 1.10
= 95.24 × 1.10
= 104.76 USDC
收益 = 104.76 - 100 = 4.76 USDC
4.2 可变借贷指数 (Variable Borrow Index)
// 更新可变借贷指数
if (reserveCache.currScaledVariableDebt != 0) {
uint256 cumulatedVariableBorrowInterest = MathUtils.calculateCompoundedInterest(
reserveCache.currVariableBorrowRate,
reserveCache.reserveLastUpdateTimestamp,
block.timestamp
);
reserveCache.nextVariableBorrowIndex = cumulatedVariableBorrowInterest.rayMul(
reserveCache.currVariableBorrowIndex
);
reserve.variableBorrowIndex = reserveCache.nextVariableBorrowIndex.toUint128();
}用途:
- 追踪可变利率债务的累积利息
- 计算用户的实际债务
5. 健康因子计算
5.1 公式定义
健康因子 (HF) = Σ(抵押品价值_i × 清算阈值_i) / 总债务
其中:
- 抵押品价值 = 代币数量 × 价格
- 清算阈值 = 该资产可用于抵押的最大比例
5.2 代码实现
// GenericLogic.sol
function calculateUserAccountData(
mapping(address => DataTypes.ReserveData) storage reservesData,
mapping(uint256 => address) storage reservesList,
mapping(uint8 => DataTypes.EModeCategory) storage eModeCategories,
DataTypes.CalculateUserAccountDataParams memory params
) internal view returns (
uint256 totalCollateralInBaseCurrency,
uint256 totalDebtInBaseCurrency,
uint256 avgLtv,
uint256 avgLiquidationThreshold,
uint256 healthFactor,
bool hasZeroLtvCollateral
) {
CalculateUserAccountDataVars memory vars;
// 遍历用户所有储备
while (vars.i < params.reservesCount) {
if (!params.userConfig.isUsingAsCollateralOrBorrowing(vars.i)) {
unchecked { ++vars.i; }
continue;
}
vars.currentReserveAddress = reservesList[vars.i];
DataTypes.ReserveData storage currentReserve = reservesData[vars.currentReserveAddress];
DataTypes.ReserveConfigurationMap memory currentReserveConfig = currentReserve.configuration;
// 获取资产价格和单位
vars.assetPrice = IPriceOracleGetter(params.oracle).getAssetPrice(vars.currentReserveAddress);
vars.assetUnit = 10 ** currentReserveConfig.getDecimals();
// 获取风险参数
(vars.ltv, vars.liquidationThreshold, vars.liquidationBonus, , ) =
currentReserveConfig.getParams();
// E-Mode 参数覆盖
if (params.userEModeCategory != 0) {
DataTypes.EModeCategory storage eModeCategory = eModeCategories[params.userEModeCategory];
if (currentReserveConfig.getEModeCategory() == params.userEModeCategory) {
vars.ltv = eModeCategory.ltv;
vars.liquidationThreshold = eModeCategory.liquidationThreshold;
}
}
// 累加抵押品价值
if (params.userConfig.isUsingAsCollateral(vars.i)) {
vars.userBalanceInBaseCurrency = _getUserBalanceInBaseCurrency(
vars.currentReserveAddress,
currentReserve,
vars.assetPrice,
vars.assetUnit
);
totalCollateralInBaseCurrency += vars.userBalanceInBaseCurrency;
if (vars.ltv != 0) {
avgLtv += vars.userBalanceInBaseCurrency * vars.ltv;
} else {
hasZeroLtvCollateral = true;
}
avgLiquidationThreshold += vars.userBalanceInBaseCurrency * vars.liquidationThreshold;
}
// 累加债务价值
if (params.userConfig.isBorrowing(vars.i)) {
totalDebtInBaseCurrency += _getUserDebtInBaseCurrency(
vars.currentReserveAddress,
currentReserve,
vars.assetPrice,
vars.assetUnit
);
}
unchecked { ++vars.i; }
}
// 计算加权平均值
if (totalCollateralInBaseCurrency != 0) {
avgLtv = avgLtv / totalCollateralInBaseCurrency;
avgLiquidationThreshold = avgLiquidationThreshold / totalCollateralInBaseCurrency;
}
// 计算健康因子
healthFactor = totalDebtInBaseCurrency == 0
? type(uint256).max
: totalCollateralInBaseCurrency.percentMul(avgLiquidationThreshold).wadDiv(totalDebtInBaseCurrency);
}5.3 健康因子示例
用户资产:
- 10 ETH @ $2000 = $20,000 (清算阈值 82.5%)
- 5000 USDC @ $1 = $5,000 (清算阈值 85%)
用户债务:
- 15,000 USDT
计算:
抵押品价值加权 = 20,000 × 0.825 + 5,000 × 0.85 = 16,500 + 4,250 = 20,750
健康因子 = 20,750 / 15,000 = 1.383
结论:HF > 1,头寸安全
6. E-Mode 数学
6.1 E-Mode 资本效率
E-Mode 允许相关性资产获得更高的 LTV 和清算阈值:
普通模式:
- ETH LTV: 80%
- ETH 清算阈值: 82.5%
E-Mode (稳定币):
- USDC/USDT/DAI LTV: 97%
- 清算阈值: 98%
6.2 效率对比
场景:用户存入 10,000 USDC,想借出其他稳定币
普通模式:
- 最大借贷 = 10,000 × 80% = 8,000 USDC
- 杠杆率 = 1.8x
E-Mode (稳定币):
- 最大借贷 = 10,000 × 97% = 9,700 USDC
- 杠杆率 = 33x (理论最大)
资本效率提升 = 9,700 / 8,000 = 1.21x (21% 提升)
6.3 E-Mode 参数应用
// 在计算用户账户数据时应用 E-Mode 参数
if (EModeLogic.isInEModeCategory(params.userEModeCategory, vars.eModeAssetCategory)) {
DataTypes.EModeCategory storage eModeCategory = eModeCategories[params.userEModeCategory];
// 使用 E-Mode 的更高参数
vars.ltv = eModeCategory.ltv;
vars.liquidationThreshold = eModeCategory.liquidationThreshold;
// E-Mode 可能使用特定的价格预言机
if (eModeCategory.priceSource != address(0)) {
vars.assetPrice = IPriceOracleGetter(eModeCategory.priceSource)
.getAssetPrice(vars.currentReserveAddress);
}
}7. 清算奖励计算
7.1 奖励公式
基础抵押品 = 债务金额 × 债务价格 / 抵押品价格
清算人获得 = 基础抵押品 × 清算奖励比例
协议费用 = (清算人获得 - 基础抵押品) × 协议费率
7.2 代码实现
function _calculateAvailableCollateralToLiquidate(
DataTypes.ReserveData storage collateralReserve,
DataTypes.ReserveCache memory debtReserveCache,
address collateralAsset,
address debtAsset,
uint256 debtToCover,
uint256 userCollateralBalance,
uint256 liquidationBonus,
IPriceOracleGetter oracle
) internal view returns (uint256, uint256, uint256) {
// 获取价格
uint256 collateralPrice = oracle.getAssetPrice(collateralAsset);
uint256 debtAssetPrice = oracle.getAssetPrice(debtAsset);
// 基础抵押品 = 债务价值 / 抵押品价格
uint256 baseCollateral = (debtAssetPrice * debtToCover * collateralAssetUnit) /
(collateralPrice * debtAssetUnit);
// 含奖励的抵押品 = 基础抵押品 × 清算奖励
uint256 maxCollateralToLiquidate = baseCollateral.percentMul(liquidationBonus);
// 如果用户抵押品不足
if (maxCollateralToLiquidate > userCollateralBalance) {
collateralAmount = userCollateralBalance;
// 反推可清算债务
debtAmountNeeded = ((collateralPrice * collateralAmount * debtAssetUnit) /
(debtAssetPrice * collateralAssetUnit)).percentDiv(liquidationBonus);
} else {
collateralAmount = maxCollateralToLiquidate;
debtAmountNeeded = debtToCover;
}
// 协议费用 = 奖励部分 × 协议费率
uint256 bonusCollateral = collateralAmount - collateralAmount.percentDiv(liquidationBonus);
uint256 liquidationProtocolFee = bonusCollateral.percentMul(liquidationProtocolFeePercentage);
return (
collateralAmount - liquidationProtocolFee, // 清算人获得
debtAmountNeeded, // 需偿还债务
liquidationProtocolFee // 协议费用
);
}7.3 清算计算示例
场景:
- 债务: 7,500 USDC
- ETH 价格: $1,800
- 清算奖励: 5% (10500 = 105%)
- 协议费率: 10%
计算:
1. 基础抵押品 = 7,500 / 1,800 = 4.167 ETH
2. 含奖励抵押品 = 4.167 × 1.05 = 4.375 ETH
3. 奖励部分 = 4.375 - 4.167 = 0.208 ETH
4. 协议费用 = 0.208 × 10% = 0.0208 ETH
5. 清算人获得 = 4.375 - 0.0208 = 4.354 ETH
清算人利润 = 4.354 × $1,800 - $7,500 = $337.2 (4.5% 利润)
8. 小结
| 组件 | 数学基础 | 精度 |
|---|---|---|
| 利率计算 | 双斜率模型 | RAY (1e27) |
| 复利计算 | 泰勒展开 | RAY (1e27) |
| 健康因子 | 加权平均 | WAD (1e18) |
| 清算奖励 | 百分比运算 | 1e4 |
关键公式速查:
利用率: U = TotalDebt / TotalLiquidity
借贷利率: R = BaseRate + Slope × U
存款利率: S = R × U × (1 - ReserveFactor)
健康因子: HF = (Collateral × LiqThreshold) / Debt
流动性指数: LI_new = LI_old × (1 + rate × Δt)
下一章将详细讲解 aToken 和债务代币的代币化机制。