Compound V3 每秒利率模型详解

理解基于利用率的拐点(kink)分段利率、供需利率独立计算与每秒到年化的换算

6 分钟阅读
Compound V3 每秒利率模型详解

Compound V3 每秒利率模型详解

目录


一、时间单位:每秒计息

Compound V3 按每秒计息,而不是 V2 的每区块。这样做的好处:跨链一致(不同链出块时间不同,但秒是统一的)。

协议用常量 SECONDS_PER_YEAR = 31,536,000(= 365 × 24 × 3600)把每秒利率年化,方便前端展示成人类可读的 APR/APY。

二、精度与 FACTOR_SCALE

协议用 FACTOR_SCALE = 1e18 作为所有利率计算的标准精度(18 位定点小数)。这个精度统一用于:利用率、每秒利率、模型参数(斜率、截距、拐点)。

统一精度的好处:所有参与运算的数都是同一刻度,可以直接相乘相加,不必反复转换。

三、基于利用率的利率模型

3.1 利用率计算

利用率 = (totalBorrow × FACTOR_SCALE) / totalSupply

getUtilization() 返回 0 到 1e18 之间的值,代表 0%–100%。

例子:返回值 904869679838357231,除以 1e18 ≈ 90.49% 利用率。

直觉:借出去的越多(相对供应),利用率越高,利率也越高——激励更多人来存、抑制借款。

3.2 带拐点的分段线性函数

Compound V3 用两段斜率的利率模型,中间有个拐点叫 kink(相当于 Aave V3 的”最优利用率”)。写作时 USDC on Ethereum 的 kink 是 93%

利率曲线分两段:

  • 拐点以下:较缓的斜率,利率涨得慢——鼓励正常借贷;
  • 拐点以上:陡峭的斜率,利率飙升——反映高利用率的风险(流动性快被借光了),强力激励还款和存款。
若 利用率 ≤ kink:
    rate = base + slopeLow × 利用率
若 利用率 > kink:
    rate = base + slopeLow × kink + slopeHigh × (利用率 − kink)

四、供应利率与借款利率独立计算

关键架构差异:V2 和 Aave V3 里,供应利率 = 借款利率 × 利用率(推导出来的)。而 Compound V3 的供应利率和借款利率都是利用率的独立直接函数,各有各的斜率和截距。

这让协议能更灵活地设定利差(借款利率和供应利率之间的差就是协议储备的来源)。

4.1 借款利率模型

borrowPerSecondInterestRateBase(截距)当前 = 317097919。乘以 SECONDS_PER_YEAR ≈ 0.01e18,即 0% 利用率时 1% 的年化基础借款利率

验证:317097919 × 31,536,000 ≈ 1.0e16 = 0.01e18 = 1%。这就是借款利率曲线的 y 轴截距。

4.2 供应利率模型

supplyPerSecondInterestRateBase 当前 = 0,意味着 0% 利用率时出借人赚不到钱(没人借,自然没利息)——合理。

五、利率计算的代码架构

getSupplyRate() 应用分段模型:

  1. mulFactor() 把当前利用率乘以斜率参数;
  2. 加上基础截距;
  3. 返回 18 位定点的每秒利率。

mulFactor(a, b) = (a × b) / FACTOR_SCALE——两个 1e18 刻度的数相乘后除以 1e18,保持精度一致。

getBorrowRate() 逻辑相同,只是用不同的斜率和截距参数。

核心原则:函数内所有比较的值共享相同的小数刻度,才能直接做数学运算。

六、每秒利率换算成年化

APR = 每秒利率 × SECONDS_PER_YEAR

合约示例:

uint256 utilization = comet.getUtilization();
uint64 supplyRate = comet.getSupplyRate(utilization);
uint64 supplyAPR = supplyRate * SECONDS_PER_YEAR;

注意 APR(单利年化)和 APY(复利年化)的区别:因为指数按每秒复利累加,实际 APY 略高于 APR。前端展示常用 APY。

七、参数速查表

参数供应借款说明
基础/截距0317097919(≈1% APY)0% 利用率时的 y 截距
拐点 kink可配置(USDC 93%)可配置分段函数的拐点
斜率拐点前后不同拐点前后不同决定利率对利用率的敏感度
精度1e181e18定点精度
时间单位每秒每秒前端年化展示

所有曲线参数都可经治理调整。

八、总结

  • V3 按每秒计息,用 SECONDS_PER_YEAR 年化;
  • 所有计算用 1e18 定点精度,可直接运算;
  • 利率是利用率的分段线性函数,拐点 kink 之后斜率陡增;
  • 供应利率和借款利率独立计算(不像 V2 是推导关系),利差进储备;
  • 每秒利率 × SECONDS_PER_YEAR = APR。

九、动手练习项目:链上利率计算器 RateCalculator

项目目标

亲手实现 Compound V3 的分段利率模型,验证拐点前后斜率切换、供需利率独立、每秒→年化换算。部署到 Sepolia,喂入不同利用率读出利率曲线。

合约要求

RateModel.sol

  • immutable 参数:supplyKinksupplyPerSecondInterestRateBasesupplyPerSecondInterestRateSlopeLowsupplyPerSecondInterestRateSlopeHigh,以及对应的 borrow 四个参数(照搬 Comet 命名);
  • FACTOR_SCALE = 1e18SECONDS_PER_YEAR = 31536000
  • mulFactor(uint a, uint b) internal pure returns (uint)a * b / FACTOR_SCALE
  • getUtilization(uint totalSupply, uint totalBorrow) public pure returns (uint)
  • getSupplyRate(uint utilization) public view returns (uint64):实现分段逻辑(≤kink 用 slopeLow,>kink 加 slopeHigh 部分);
  • getBorrowRate(uint utilization) public view returns (uint64):同理;
  • supplyAPR(uint util) / borrowAPR(uint util):× SECONDS_PER_YEAR。

测试要求(Foundry)

  1. test_Utilization:totalSupply=1000e18、totalBorrow≈904.87e18,断言利用率 ≈ 904869679838357231;
  2. test_BorrowBaseRate:utilization=0 时 borrowAPR ≈ 0.01e18(1%);
  3. test_SupplyBaseRate:utilization=0 时 supplyRate = 0;
  4. test_KinkSlopeSwitch:分别取 kink 以下、等于、以上三个利用率,断言斜率正确切换(拐点后增速更快);
  5. test_RateMonotonic:利用率越高,借款利率越高(单调递增);
  6. test_SupplyVsBorrowIndependent:构造参数证明供应利率不是 borrowRate×utilization(独立计算);
  7. testFuzz_NoOverflow:任意利用率下计算不溢出。

Sepolia 部署与验证步骤

  1. 用真实 USDC 市场参数(kink 93%、borrow base 317097919 等)部署 RateModel;
  2. 在 Etherscan 调 getUtilization(1000e18, 900e18) 读利用率;
  3. borrowAPR(util)supplyAPR(util),对照 Compound 官网当前 USDC 市场的 APR 是否量级吻合;
  4. 喂入拐点前后的利用率,画出利率曲线的两段斜率。

进阶挑战(可选)

  • 写一个脚本扫描利用率 0%→100%,输出借款/供应利率曲线数据,用图表确认拐点;
  • 把每秒利率正确复利成 APY((1 + ratePerSec)^SECONDS_PER_YEAR - 1),对比与 APR 的差异;
  • 写注释回答:为什么 V3 让供需利率独立而非推导?这对协议储备和资本效率有何影响?

💬 评论