aave v3.4 利率计算详解
aave v3.4 的代码库中将利率策略地址interestRateStrategyAddress从每个资产的储备配置中移出,改为在Pool合约中作为不可变变量统一管理。这意味着在新版本中,所有资产共享同一个利率策略合约,使用不同的参数配置,本文对v3.4版本利率计算做个解析。合约代码如下:
function calculateInterestRates(DataTypes.CalculateInterestRatesParams calldata params) external view virtual override returns (uint256, uint256) {InterestRateDataRay memory rateData = _rayifyRateData(_interestRateData[params.reserve]);CalcInterestRatesLocalVars memory vars;vars.currentLiquidityRate = 0;vars.currentVariableBorrowRate = rateData.baseVariableBorrowRate;if (params.totalDebt != 0) {vars.availableLiquidity =params.virtualUnderlyingBalance +params.liquidityAdded -params.liquidityTaken;vars.availableLiquidityPlusDebt = vars.availableLiquidity + params.totalDebt;vars.borrowUsageRatio = params.totalDebt.rayDiv(vars.availableLiquidityPlusDebt);vars.supplyUsageRatio = params.totalDebt.rayDiv(vars.availableLiquidityPlusDebt + params.unbacked);} else {return (0, vars.currentVariableBorrowRate);}if (vars.borrowUsageRatio > rateData.optimalUsageRatio) {uint256 excessBorrowUsageRatio = (vars.borrowUsageRatio - rateData.optimalUsageRatio).rayDiv(WadRayMath.RAY - rateData.optimalUsageRatio);vars.currentVariableBorrowRate +=rateData.variableRateSlope1 +rateData.variableRateSlope2.rayMul(excessBorrowUsageRatio);} else {vars.currentVariableBorrowRate += rateData.variableRateSlope1.rayMul(vars.borrowUsageRatio).rayDiv(rateData.optimalUsageRatio);}vars.currentLiquidityRate = vars.currentVariableBorrowRate.rayMul(vars.supplyUsageRatio).percentMul(PercentageMath.PERCENTAGE_FACTOR - params.reserveFactor);return (vars.currentLiquidityRate, vars.currentVariableBorrowRate);}
参数解析
入参结构如下:
struct CalculateInterestRatesParams {uint256 unbacked;uint256 liquidityAdded;uint256 liquidityTaken;uint256 totalDebt;uint256 reserveFactor;address reserve;// @notice DEPRECATED in 3.4, but kept for backwards compatibilitybool usingVirtualBalance;uint256 virtualUnderlyingBalance;}
unbacked(废弃):未抵押的资产数量;这个在 Aave 中是一个特殊概念,与普通的借贷抵押完全不同。这个概念主要是为了支持跨链操作而设计的;用户在链 A 上锁定资产--》跨链桥在链 B 上的 Aave 协议中先创建"未抵押资产"--》这些资产可以立即在链 B 上使用--》当链 A 的确认最终到达时,这些资产会被"支持"(backed)
Aave 设计了一个特殊的"Bridge"角色,只有被授权的桥接合约可以创建未抵押资产,这需要特殊权限,普通用户无法创建。
这个功能实际上从未在生产环境中使用过,并已在 V3.4 版本中被移除。虽然代码中仍然保留了这个参数,但在实际运行中,这个参数的值应该总是为 0,仅为了保持向后兼容性。这里大概说下,不用太关注。
liquidityAdded:添加到储备中的流动性数量
liquidityTaken:从储备中提取的流动性数量
totalDebt:储备的总债务指的是资产池中所有用户借出的资产总量
reserveFactor:协议收取的费用比例,比如 10% 意味着利息收入的 10% 归协议
reserve:储备资产的地址,比如usdt的地址
usingVirtualBalance(废弃):是否启用虚拟余额(已废弃,不必关注)
virtualUnderlyingBalance:虚拟余额,用户 supply(存款)、repay(还款)时, virtualUnderlyingBalance 增加;用户 withdraw(提取)、borrow(借款)时, virtualUnderlyingBalance 减少
virtualUnderlyingBalance =总存款金额 - 总提取金额 - 总借出金额 + 总还款金额
看似是资金池总的剩余金额,但其实不完全一样,具体看aave v3 合约解析 状态存储相关部分的介绍。
第一步是获取当前资产的利率配置参数
InterestRateDataRay memory rateData = _rayifyRateData(_interestRateData[params.reserve]);
返回结构如下:
struct InterestRateDataRay {uint256 optimalUsageRatio;uint256 baseVariableBorrowRate;uint256 variableRateSlope1;uint256 variableRateSlope2;
}
optimalUsageRatio :最佳使用率,这是资金池的理想使用率,当实际使用率接近这个值时,利率会保持在相对稳定的水平
baseVariableBorrowRate :基础可变借款利率;即使使用率为零,借款人也需要支付的最低利率
variableRateSlope1 :第一阶段可变利率斜率;当使用率低于最佳使用率时,利率增长的斜率
variableRateSlope2 :第二阶段可变利率斜率;当使用率超过最佳使用率时,利率增长的斜率
计算的一开始定义了一个中间变量CalcInterestRatesLocalVars 结构如下
struct CalcInterestRatesLocalVars {uint256 availableLiquidity;uint256 currentVariableBorrowRate;uint256 currentLiquidityRate;uint256 borrowUsageRatio;uint256 supplyUsageRatio;uint256 availableLiquidityPlusDebt;}
然后对这些参数进行了初始化
if (params.totalDebt != 0) {vars.availableLiquidity =params.virtualUnderlyingBalance +params.liquidityAdded -params.liquidityTaken;vars.availableLiquidityPlusDebt = vars.availableLiquidity + params.totalDebt;vars.borrowUsageRatio = params.totalDebt.rayDiv(vars.availableLiquidityPlusDebt);vars.supplyUsageRatio = params.totalDebt.rayDiv(vars.availableLiquidityPlusDebt + params.unbacked);
} else {return (0, vars.currentVariableBorrowRate);
}
availableLiquidity:可用流动性,表示资产池中当前可供借出的资产数量,计算公式: virtualUnderlyingBalance + liquidityAdded - liquidityTaken
currentVariableBorrowRate: 当前可变借款利率;表示资产池当前的可变借款利率,初始值为基础可变借款利率( baseVariableBorrowRate ),根据使用率动态调整,使用率越高,利率越高
currentLiquidityRate: 当前流动性利率,表示存款人获得的利率
计算公式: currentVariableBorrowRate * supplyUsageRatio * (100% - reserveFactor)
borrowUsageRatio: 借款使用率;表示已借出资产占总可用资产的比例,用于确定借款利率的调整幅度
计算公式: totalDebt / (availableLiquidity + totalDebt)
supplyUsageRatio:供应使用率,表示已借出资产占总供应资产的比例,用于计算存款利率
计算公式: totalDebt / (availableLiquidity + totalDebt + unbacked)
availableLiquidityPlusDebt:可用流动性加债务总额,表示资产池的总规模(可用资金加已借出资金)
- 计算公式: availableLiquidity + totalDebt
借款利息
if (vars.borrowUsageRatio > rateData.optimalUsageRatio) {uint256 excessBorrowUsageRatio = (vars.borrowUsageRatio - rateData.optimalUsageRatio).rayDiv(WadRayMath.RAY - rateData.optimalUsageRatio);vars.currentVariableBorrowRate +=rateData.variableRateSlope1 +rateData.variableRateSlope2.rayMul(excessBorrowUsageRatio);
} else {vars.currentVariableBorrowRate += rateData.variableRateSlope1.rayMul(vars.borrowUsageRatio).rayDiv(rateData.optimalUsageRatio);
}
当借款使用率超过最佳使用率时,计算超额使用率比例:
接下来计算借款利息:
currentVariableBorrowRate = baseVariableBorrowRate + variableRateSlope1 + (variableRateSlope2 * excessBorrowUsageRatio)
此时的利率由三部分组成:
- 基础利率(baseVariableBorrowRate)
- 第一斜率(variableRateSlope1)全额计入
- 第二斜率(variableRateSlope2)按超额使用率比例计入
当借款使用率不超过最佳使用率时(低使用率场景)
在这种情况下,利率由两部分组成:
- 基础利率(baseVariableBorrowRate)
- 第一斜率(variableRateSlope1)按使用率与最佳使用率的比例计入
存款利息
vars.currentLiquidityRate = vars.currentVariableBorrowRate.rayMul(vars.supplyUsageRatio).percentMul(PercentageMath.PERCENTAGE_FACTOR - params.reserveFactor);
翻译成公式就是:存款利率 = 借款利率 × 供应使用率 × (1 - 储备因子)
举个例子:借款利息是5%,此时资金池中80%的资金被借出,代表这个资金池只有80%的资金产生了利息,那么存款利息就是5% × 80% = 4%;
如果储备因子为10%,代表这4%的利息中有10%归协议所有,那么存款利息为
存款利率 = 4% × (1 - 10%) = 4% × 90% = 3.6%