Compound V3 每秒利率模型详解
目录
- 一、时间单位:每秒计息
- 二、精度与 FACTOR_SCALE
- 三、基于利用率的利率模型
- 四、供应利率与借款利率独立计算
- 五、利率计算的代码架构
- 六、每秒利率换算成年化
- 七、参数速查表
- 八、总结
- 九、动手练习项目:链上利率计算器 RateCalculator
一、时间单位:每秒计息
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() 应用分段模型:
- 用
mulFactor()把当前利用率乘以斜率参数; - 加上基础截距;
- 返回 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。
七、参数速查表
| 参数 | 供应 | 借款 | 说明 |
|---|---|---|---|
| 基础/截距 | 0 | 317097919(≈1% APY) | 0% 利用率时的 y 截距 |
| 拐点 kink | 可配置(USDC 93%) | 可配置 | 分段函数的拐点 |
| 斜率 | 拐点前后不同 | 拐点前后不同 | 决定利率对利用率的敏感度 |
| 精度 | 1e18 | 1e18 | 定点精度 |
| 时间单位 | 每秒 | 每秒 | 前端年化展示 |
所有曲线参数都可经治理调整。
八、总结
- V3 按每秒计息,用 SECONDS_PER_YEAR 年化;
- 所有计算用 1e18 定点精度,可直接运算;
- 利率是利用率的分段线性函数,拐点 kink 之后斜率陡增;
- 供应利率和借款利率独立计算(不像 V2 是推导关系),利差进储备;
- 每秒利率 × SECONDS_PER_YEAR = APR。
九、动手练习项目:链上利率计算器 RateCalculator
项目目标
亲手实现 Compound V3 的分段利率模型,验证拐点前后斜率切换、供需利率独立、每秒→年化换算。部署到 Sepolia,喂入不同利用率读出利率曲线。
合约要求
RateModel.sol
- immutable 参数:
supplyKink、supplyPerSecondInterestRateBase、supplyPerSecondInterestRateSlopeLow、supplyPerSecondInterestRateSlopeHigh,以及对应的 borrow 四个参数(照搬 Comet 命名); FACTOR_SCALE = 1e18、SECONDS_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)
test_Utilization:totalSupply=1000e18、totalBorrow≈904.87e18,断言利用率 ≈ 904869679838357231;test_BorrowBaseRate:utilization=0 时borrowAPR ≈ 0.01e18(1%);test_SupplyBaseRate:utilization=0 时 supplyRate = 0;test_KinkSlopeSwitch:分别取 kink 以下、等于、以上三个利用率,断言斜率正确切换(拐点后增速更快);test_RateMonotonic:利用率越高,借款利率越高(单调递增);test_SupplyVsBorrowIndependent:构造参数证明供应利率不是 borrowRate×utilization(独立计算);testFuzz_NoOverflow:任意利用率下计算不溢出。
Sepolia 部署与验证步骤
- 用真实 USDC 市场参数(kink 93%、borrow base 317097919 等)部署 RateModel;
- 在 Etherscan 调
getUtilization(1000e18, 900e18)读利用率; - 调
borrowAPR(util)、supplyAPR(util),对照 Compound 官网当前 USDC 市场的 APR 是否量级吻合; - 喂入拐点前后的利用率,画出利率曲线的两段斜率。
进阶挑战(可选)
- 写一个脚本扫描利用率 0%→100%,输出借款/供应利率曲线数据,用图表确认拐点;
- 把每秒利率正确复利成 APY(
(1 + ratePerSec)^SECONDS_PER_YEAR - 1),对比与 APR 的差异; - 写注释回答:为什么 V3 让供需利率独立而非推导?这对协议储备和资本效率有何影响?