Compound V3 协议以秒为单位衡量利息。为了便于人类阅读,Compound V3 前端将该数字放大至以年为单位。当我们查看 Etherscan 上 Compound V3 的利率参数 时,我们可以看到以下针对借款人的参数。在接下来的部分中,我们将把这些参数与我们在 Compound USDC Ethereum 前端看到的内容进行相互印证。
本文是一篇 Solidity 演练,逐行解释了资金利用率是如何驱动利率的。

前置知识
我们假设您已经阅读过我们关于 如何决定加密货币利率 的文章。
与 Compound V2 的主要区别
在 Compound V2(以及 Aave V3)中,存款利率是借款利率乘以资金利用率。而在 Compound V3 中,存款利率直接是资金利用率的函数,并不将借款利率作为计算因素。借款利率遵循其自身的利率曲线。
利率模型组件的变量名
Aave V3 所谓的“最佳利用率(optimal utilization)”,在 Compound V3 中被称为“拐点(kink)”。
变量 borrowPerSecondInterestRateBase 是“截距(intercept)”,目前的值为 317097919。本节的目标是证明此截距等同于 1% 的年化收益率(APY)。也就是说,当资金利用率为零时,借款人需要支付 1% 的利息(根据当前参数得出,治理层可能会对此进行更改)。

不考虑闰年和其他公历的特殊情况,每年有 31,536,000 秒(SECONDS_PER_YEAR)。如果我们将 31536000(SECONDS_PER_YEAR)乘以 317097919(borrowInterestRatePerSecond),我们将得到(大约)0.01e18(1e16)。在以 1e18 为 1 的比例尺下,这相当于 1% 的借款利率。
实际上,我们可以通过查看 以太坊上的 Compound USDC 市场 来验证这一点。
如果您将鼠标悬停在不同资金利用率水平的“Interest Rate Model”(利率模型)上,您可以看到预期的存款和借款利息是多少。前端不允许您将鼠标悬停在 0% 的资金利用率上。然而,您可以看到资金利用率每变化 1%,利息大约会变化 0.03%,因此在 0% 利用率时的预期利息将是 1%。请参见下方的动画。
因此,我们可以看到 y 轴截距确实是每年 1%。
这在撰写本文时是成立的,但对于其他 Layer 2 上的其他基础资产可能有所不同。
在上面的动画中,我们看到在 0% 资金利用率时,预期的“Earn APR”(即贷款人赚取的年化收益率)为 0%。这与我们在 Etherscan 上看到的结果相符。存款利率的 y 轴截距参数被称为 supplyPerSecondInterestRateBase。根据图表和 Etherscan 的数据,该截距均为零。

我们如何知道 0.01e18 等同于 1%?
在上面的示例中,我们推断出 0.01e18 相当于 1% 的利息。我们将通读代码库来展示这是如何推导出来的。这需要五个步骤。
第 1 步:引入 FACTOR_SCALE = 1e18
在 CometCore.sol 第 57 行 中,我们看到 Compound 采用的“FACTOR_SCALE”为 1e18(下方截图中的 蓝色框 )。
我们还可以看到在上文中提到的 SECONDS_PER_YEAR 常量(红色框)。

第 2 步:资金利用率以 FACTOR_SCALE (1e18) 衡量
让我们看一下 Comet.sol 中的 getUtilization() 函数。

目前我们还不知道 totalSupply_ 和 totalBorrow_(蓝色框 和 绿色框)使用的是什么比例,但可以合理地假设它们使用相同的比例(小数位数),因此它们的比例将会被抵消(见第 464 行的返回值)。由于分子乘以了 FACTOR_SCALE(红色框),资金利用率百分比将以 18 位小数进行衡量。
现在让我们将 Etherscan 上 getUtilization() 的当前值与 Compound App 上显示的值进行比较

当前的资金利用率为 904869679838357231,当除以 FACTOR_SCALE(并四舍五入到两位小数)后相当于 90.49%。它确实使用了 18 位小数。
presentValueSupply 和 presentValueBorrow 这两个函数不是我们预期读者已经熟悉的术语——但它们可以分别被看作是可用于出借的总资金的美元价值以及借出的总美元价值。
第 3 步:最佳利用率,即拐点(kink)—— 在 [0-1e18] 的范围内
在我们关于 DeFi 中的利率 的文章中,我们提到主流的利率模型都是分段函数,具有“最佳(optimal)”利用率的概念。Compound V3 将此称为“拐点(kink)”——此时利率开始更大幅度地上升。这两个不同的术语指的是完全相同的概念。
在撰写本文时,以太坊上 USDC 的最佳利用率是 93%,如下所示。

由于资金利用率是以 18 位小数的 定点数 进行衡量的,因此 borrowKink 和 supplyKink 也使用 18 位小数的定点数进行衡量。正如我们将在下一节中看到的,利率函数会直接对两者进行比较。
第 4 步:mulFactor()
熟悉其他定点数库中 mulDivDown 操作的程序员将很容易理解这个函数。
它接收两个定点数并将它们相乘,从而生成另一个定点数。该函数如下所示:

由于我们将两个带有 18 位小数的数字相乘,我们需要除以 FACTOR_SCALE 以避免输出变成 36 位小数。
只需将该函数视为“它将两个 18 位小数的数字相乘,并返回一个表示其乘积的 18 位小数即可。”
第 5 步:getSupplyRate() 返回因子比例的十进制数
现在我们可以看到 Compound V3 使用 1e18 的比例来衡量利率了。
正如我们在关于利率的文章中讨论的那样,Compound 使用的是一个分段线性函数。下面是 getSupplyRate() 函数,该函数返回由当前资金利用率决定的贷款人所赚取的当前利率。我们知道 mulFactor(...) 返回的是 FACTOR_SCALE(18 位小数)的数字。因此,我们知道 supplyPerSecondInterestRate(黄色圆圈)也必须是一个 FACTOR_SCALE 格式的数字,否则我们在相加时会导致小数位错位。因此,getSupplyRate() 函数返回的是具有 FACTOR_SCALE 小数位的数字。

函数 getBorrowRate 的运行逻辑也是相同的,所以我们不在这里赘述。
曲线的各项参数——截距、斜率和拐点——都可以由治理层进行调整。
计算假设的利率
为了将利率计算为资金利用率的函数,用户可以使用 getUtilization() 获取当前的利用率,并将其代入 getSupplyRate() 和 getBorrowRate() 中。
contract GetCurrentRatesComet {
function getRates(IComet comet)
external
returns (uint64, uint64) {
uint64 private constant SECONDS_PER_YEAR = 365 * 24 * 60 * 60;
uint256 utilization = comet.getUtilization();
// these are 18 decimal fixed point numbers
// measuring interest per second
uint64 supplyRate = comet.getSupplyRate();
uint64 borrowRate = comet.getBorrowRate();
// return them as APR
return (supplyRate * SECONDS_PER_YEAR,
borrowRate * SECONDS_PER_YEAR);
}
}
利息应计
我们目前只展示了 Compound 如何计算某个时间快照的利率。在后续文章中,我们将展示它是如何复利并进行利息应计的。
总结
利率的时间单位是秒。它们以 18 位小数的精度进行衡量。资金利用率同样也以 18 位小数的精度进行衡量。
通过 RareSkills 了解更多
请查看我们的 区块链训练营(Blockchain Bootcamp) 以了解更多信息。
原载于 2024 年 1 月 4 日