Compound V3 的利率模型是一個非常簡單的模型,可以以治理的方式更新模型的參數。具體怎麼運作,只需要明白模型的幾個術語和定義就幾乎能完全理解。

TL;DR

首先 Compound V3 的借貸市場其利率模型同時會有兩個:supply-side 和 borrow-side,提供流動性的有一個利率模型,而借貸行為的有另外一個利率模型。兩個利率模型是一樣的,除了參數有差異之外。如圖:

Terminology & Formula

utilization

意義為資金利用率,是資產總借出和總供給的一個比率,用以反映出借貸市場的資金使用情況。在 Compound V3 utilization 的計算公式如下:

kink

意義為最佳資金利用率。這是一個假定參數,設定經濟效益最好的借貸市場其 utilization 應該為多少。假設 kink 訂定為 90%,那這個借貸市場經濟效益最好的時候是將供給的資金借出 90%,另外 10% 用於儲備。在借貸市場中,不會將所有供給的資金全部借出,會有一部分用作準備金用以做贖回。在利用率高於 kink 的時候,利率會急劇攀升以刺激借款方還款。

interest rate

利率為 utilization 的函數,公式如下:

利用率低於 kink 的時候有一個定義了一個斜率,而利用率高於 kink 的時候會採用另外一個斜率,讓利率急劇上升。

base 可能為 0。base 不為零則表示當沒有人借款的時候,系統需要支付供給者利息,而借貸市場獲益的方式就是放款出去從借貸者賺取利息。在無人借款的情況下,系統自然沒有收益去承擔供給者利息。

Implementation

先看 storage 的部分,supply-side 和 borrow-side 所有的參數都是 scaled by 1e18 的 fixed number:

/// @@dev https://github.com/compound-finance/comet/blob/51c2ad5e02a74b6a824cf3f864ffbb7ec498d6c8/contracts/Comet.sol#L13
uint64 public immutable supplyKink;
uint64 public immutable supplyPerSecondInterestRateSlopeLow;
uint64 public immutable supplyPerSecondInterestRateSlopeHigh;
uint64 public immutable supplyPerSecondInterestRateBase;
 
uint64 public immutable borrowKink;
uint64 public immutable borrowPerSecondInterestRateSlopeLow;
uint64 public immutable borrowPerSecondInterestRateSlopeHigh;
uint64 public immutable borrowPerSecondInterestRateBase;

計算 utilization 的實作如下,totalSupply_totalBorrow_ 兩個變數意義分別為「總供給資產數量加上利息」和「總借出資產數量加上利息」,可以參考 Compound V3 借貸計息實作:

uint64 internal constant FACTOR_SCALE = 1e18;
 
function getUtilization() override public view returns (uint) {
    // meaning that `totalSupplyBase + interest`
    uint totalSupply_ = presentValueSupply(baseSupplyIndex, totalSupplyBase);
    // meaning that `totalSupplyBase + interest`
    uint totalBorrow_ = presentValueBorrow(baseBorrowIndex, totalBorrowBase);
    if (totalSupply_ == 0) {
        return 0;
    } else {
        return totalBorrow_ * FACTOR_SCALE / totalSupply_;
    }
}

計算「供給利率」和「借貸利率」:

/// @dev multiplication for fixed number scaled by 1e18
function mulFactor(uint n, uint factor) internal pure returns (uint) {
    return n * factor / FACTOR_SCALE;
}
 
function getSupplyRate(uint utilization) override public view returns (uint64) {
    if (utilization <= supplyKink) {
        // interestRateBase + interestRateSlopeLow * utilization
        return safe64(supplyPerSecondInterestRateBase + mulFactor(supplyPerSecondInterestRateSlopeLow, utilization));
    } else {
        // interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink)
        return safe64(supplyPerSecondInterestRateBase + mulFactor(supplyPerSecondInterestRateSlopeLow, supplyKink) + mulFactor(supplyPerSecondInterestRateSlopeHigh, (utilization - supplyKink)));
    }
}
 
function getBorrowRate(uint utilization) override public view returns (uint64) {
    if (utilization <= borrowKink) {
        // interestRateBase + interestRateSlopeLow * utilization
        return safe64(borrowPerSecondInterestRateBase + mulFactor(borrowPerSecondInterestRateSlopeLow, utilization));
    } else {
        // interestRateBase + interestRateSlopeLow * kink + interestRateSlopeHigh * (utilization - kink)
        return safe64(borrowPerSecondInterestRateBase + mulFactor(borrowPerSecondInterestRateSlopeLow, borrowKink) + mulFactor(borrowPerSecondInterestRateSlopeHigh, (utilization - borrowKink)));
    }
}

實際理解利率參數

首先需要知道資料的單位和型別:

  • kink, slope low, slope high, base rate,四個都是 scaled by 1e18 的 fixed number
  • Compound V3 以 per second 為時間單位計算利息,所以利率模型的參數都是以 per second 為時間單位的利率

這裡以一個 USDC 借貸市場實際試算,首先先取得特定區塊 supply-side 的資料:

cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "totalSupply()(uint256)" --block 21466495 --rpc-url mainnet
# totalSupply 476852844078057 [4.768e14]
 
cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "totalBorrow()(uint256)" --block 21466495 --rpc-url mainnet
# totalBorrow 435600946895498 [4.356e14]
 
cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "supplyKink()(uint256)" --block 21466495 --rpc-url mainnet
# supplyKink 900000000000000000 [9e17]
 
cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "supplyPerSecondInterestRateSlopeLow()(uint256)" --block 21466495 --rpc-url mainnet
# supplyPerSecondInterestRateSlopeLow 1712328767 [1.712e9]
 
cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "supplyPerSecondInterestRateSlopeHigh()(uint256)" --block 21466495 --rpc-url mainnet
# supplyPerSecondInterestRateSlopeHigh 96207508878 [9.62e10]
 
cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "supplyPerSecondInterestRateBase()(uint256)" --block 21466495 --rpc-url mainnet
# supplyPerSecondInterestRateBase 0

計算 utilization 並和鏈上資料比對:

chisel
 
 uint256 totalSupply = 435600946895498;
 uint256 totalBorrow = 476852844078057;
 (totalSupply * 1e18) / totalBorrow;
Type: uint256
 Hex: 0xcad5f8a500f3d6d
 Hex (full word): 0xcad5f8a500f3d6d
 Decimal: 913491347079380333
 
# utilization = 913491347079380333 [9.134e17] = 91.34%
# fetch utilization on-chain
cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "getUtilization()(uint256)" --block 21466495 --rpc-url mainnet
# 913491347079380333 [9.134e17]

計算 supply interest rate 並和鏈上資料比對 (borrow interest rate 計算方式也相同):

chisel
 
 uint256 baseRate = 0;
 uint256 slopeLow = 1712328767;
 uint256 slopeHigh = 96207508878;
 uint256 kink = 900000000000000000;
 uint256 curr_utils = 913491347079380333;
 baseRate + (slopeLow * kink / 1e18) + (slopeHigh * (curr_utils - kink) / 1e18)
Type: uint256
 Hex: 0xa938b0cf
 Hex (full word): 0xa938b0cf
 Decimal: 2839064783
 
# supply_rate = 2839064783
# fetch supply rate on-chain
cast call 0xc3d688B66703497DAA19211EEdff47f25384cdc3 "getSupplyRate(uint256)(uint256)" 913491347079380333  --block 21466495 --rpc-url mainnet
# 2839064783 [2.839e9]

先前提到過,Compound V3 是以 per second 為時間單位計算利息,將 supply_rate 乘以 365 * 24 * 60 * 60 就可以得到 Annual Percentage Rate,也就是 compound V3 UI 上所呈現的利率。最後試算可得,在利用率 91.34% 的利用率下,其 supply interest rate APR 大約為 8.9%。

2839064783 * 365 * 24 * 60 * 60 / 1e18 = 8.9532747%

Reference