文章目录
- [3. Comptroller - Compound的⻛控中枢](#3. Comptroller - Compound的⻛控中枢)
-
- [3.1 Comptroller核⼼职责](#3.1 Comptroller核⼼职责)
- [3.2 账户流动性计算](#3.2 账户流动性计算)
- [4. 利率模型详解](#4. 利率模型详解)
-
- [4.1 JumpRateModel - 跳跃利率模型](#4.1 JumpRateModel - 跳跃利率模型)
-
- [4.1.1 利率模型原理](#4.1.1 利率模型原理)
- [4.1.2 利率曲线可视化](#4.1.2 利率曲线可视化)
- [4.1.3 利率模型合约实现](#4.1.3 利率模型合约实现)
- [4.2 利率实时更新机制](#4.2 利率实时更新机制)
- [5. Compound V3 (Comet) - 新⼀代架构](#5. Compound V3 (Comet) - 新⼀代架构)
-
- [5.1 为什么需要V3?](#5.1 为什么需要V3?)
- [5.2 Comet核⼼设计](#5.2 Comet核⼼设计)
-
- [5.2.1 V2 vs V3 架构对⽐](#5.2.1 V2 vs V3 架构对⽐)
- [5.2.2 Comet核⼼合约](#5.2.2 Comet核⼼合约)
- [5.3 Comet的优势](#5.3 Comet的优势)
3. Comptroller - Compound的⻛控中枢
3.1 Comptroller核⼼职责
Comptroller是Compound协议的"⼤脑",负责所有⻛险控制和市场管理功能。它的核⼼职责包括:
- 市场准⼊控制:决定哪些资产可以作为抵押品
- 借款额度计算:实时计算⽤户的借款能⼒
- 清算判定:判断账户是否可以被清算
- ⻛险参数管理:设置和更新各种⻛险参数
- COMP奖励分发:管理流动性挖矿奖励
3.2 账户流动性计算
流动性计算是Comptroller最核⼼的功能,决定了⽤户能否借款、是否会被清算。
c
// Comptroller.sol - 账户流动性计算
contract Comptroller {
/**
* @notice 计算账户流动性
* @param account 账户地址
* @return (error, liquidity, shortfall)
*/
function getAccountLiquidity(address account)
public
view
returns (uint, uint, uint)
{
// 获取账户在所有市场的资产和负债
(uint err, uint liquidity, uint shortfall) =
getHypotheticalAccountLiquidityInternal(
account,
CToken(address(0)),
0,
0
);
return (err, liquidity, shortfall);
}
/**
* @notice 内部流动性计算函数
* @dev 核⼼算法:遍历所有市场,累加抵押品和债务
*/
function getHypotheticalAccountLiquidityInternal(
address account,
CToken cTokenModify,
uint redeemTokens,
uint borrowAmount
) internal view returns (uint, uint, uint) {
// 累加变量
uint sumCollateral = 0; // 总抵押品价值(加权)
uint sumBorrowPlusEffects = 0; // 总债务价值
// 遍历⽤户参与的所有市场
CToken[] memory assets = accountAssets[account];
for (uint i = 0; i < assets.length; i++) {
CToken asset = assets[i];
// 获取⽤户在该市场的cToken余额
uint cTokenBalance = asset.balanceOf(account);
// 获取汇率和价格
uint exchangeRate = asset.exchangeRateStored();
uint oraclePrice = oracle.getUnderlyingPrice(asset);
// 获取市场配置
(, uint collateralFactorMantissa) = markets[address(asset)];
// 计算该资产的抵押品价值
// tokenValue = cTokenBalance × exchangeRate × price
uint tokensToDenom = (cTokenBalance * exchangeRate) / 1e18;
uint collateralValue = (tokensToDenom * oraclePrice) / 1e18;
// 如果该资产启⽤了抵押,累加到总抵押品
if (collateralFactorMantissa > 0) {
sumCollateral += (collateralValue * collateralFactorMantissa) / 1e18;
}
// 计算该资产的借款价值
uint borrowBalance = asset.borrowBalanceStored(account);
uint borrowValue = (borrowBalance * oraclePrice) / 1e18;
sumBorrowPlusEffects += borrowValue;
// 如果是假设性操作,调整计算
if (asset == cTokenModify) {
// 假设赎回
if (redeemTokens > 0) {
uint redeemValue = (redeemTokens * exchangeRate * oraclePrice) / (1e18 * 1e18);
sumCollateral -= (redeemValue * collateralFactorMantissa) / 1e18;
}
// 假设借款
if (borrowAmount > 0) {
uint borrowValue = (borrowAmount * oraclePrice) / 1e18;
sumBorrowPlusEffects += borrowValue;
}
}
}
// 计算流动性和资⾦缺⼝
if (sumCollateral > sumBorrowPlusEffects) {
return (0, sumCollateral - sumBorrowPlusEffects, 0);
} else {
return (0, 0, sumBorrowPlusEffects - sumCollateral);
}
}
/**
* @notice 检查是否允许借款
*/
function borrowAllowed(
address cToken,
address borrower,
uint borrowAmount
) external returns (uint) {
// 1. 检查市场是否上线
if (!markets[cToken].isListed) {
return uint(Error.MARKET_NOT_LISTED);
}
// 2. 检查借款上限
if (!markets[cToken].accountMembership[borrower]) {
// 如果⽤户⾸次借款,⾃动加⼊市场
markets[cToken].accountMembership[borrower] = true;
accountAssets[borrower].push(CToken(cToken));
}
// 3. 计算假设性的账户流动性
(uint err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(
borrower,
CToken(cToken),
0,
borrowAmount
);
if (err != 0) {
return err;
}
// 如果会产⽣资⾦缺⼝,拒绝借款
if (shortfall > 0) {
return uint(Error.INSUFFICIENT_LIQUIDITY);
}
return uint(Error.NO_ERROR);
}
/**
* @notice 检查是否允许赎回
*/
function redeemAllowed(
address cToken,
address redeemer,
uint redeemTokens
) external returns (uint) {
// 检查赎回后是否会产⽣资⾦缺⼝
(uint err, , uint shortfall) = getHypotheticalAccountLiquidityInternal(
redeemer,
CToken(cToken),
redeemTokens,
0
);
if (err != 0) {
return err;
}
if (shortfall > 0) {
return uint(Error.INSUFFICIENT_LIQUIDITY);
}
return uint(Error.NO_ERROR);
}
}
3.3 ⻛险参数配置
主要⻛险参数:

不同资产的⻛险参数对⽐:

4. 利率模型详解
4.1 JumpRateModel - 跳跃利率模型
Compound采⽤JumpRateModel(跳跃利率模型),这是⼀种分段线性的利率曲线,在达到最优利⽤率前线性增⻓,超过后陡峭上升。
4.1.1 利率模型原理
核⼼思想:
通过利率调节市场供需,保护资⾦池流动性:
- 利⽤率低时:低利率吸引借款
- 利⽤率适中时:线性增⻓
- 利⽤率过⾼时:利率跳跃,抑制借款
利率计算公式:
利⽤率 U = Borrows / (Cash + Borrows - Reserves)
借款利率:
- 当 U ≤ Kink: BorrowRate = baseRate + multiplier × U
- 当 U > Kink: BorrowRate = baseRate + multiplier × Kink + jumpMultiplier × (U - Kink)
存款利率:SupplyRate = BorrowRate × U × (1 - ReserveFactor)
参数说明:
- baseRate :基础利率(通常为0或很⼩的值)
- multiplier :正常斜率系数
- jumpMultiplier :跳跃斜率系数(远⼤于multiplier)
- Kink :拐点,通常设置为80%
4.1.2 利率曲线可视化

USDC利率曲线示例:
参数配置:
- baseRatePerYear: 0%
- multiplierPerYear: 5%
- jumpMultiplierPerYear: 109%
- kink: 80%
利率计算:U = 50%(正常区间)
borrowAPY = 0% + 5% × (50%/80%) = 3.125%
supplyAPY = 3.125% × 50% × 90% = 1.41%
U = 80%(拐点)borrowAPY = 0% + 5% × 1 = 5%
supplyAPY = 5% × 80% × 90% = 3.6%
U = 90%(⾼利⽤率)borrowAPY = 0% + 5% + 109% × (10%/20%) = 59.5%
supplyAPY = 59.5% × 90% × 90% = 48.2%
U = 95%(接近枯竭)borrowAPY = 0% + 5% + 109% × (15%/20%) = 86.75%
supplyAPY = 86.75% × 95% × 90% = 74.1%
4.1.3 利率模型合约实现
c
// JumpRateModel.sol
pragma solidity ^0.8.10;
/**
* @title JumpRateModel
* @notice 实现分段线性利率模型
*/
contract JumpRateModel {
// ⼀年的区块数(假设12秒⼀个区块)
uint public constant blocksPerYear = 2628000;
// 基础利率(每区块)
uint public baseRatePerBlock;
// 正常斜率乘数(每区块)
uint public multiplierPerBlock;
// 跳跃斜率乘数(每区块)
uint public jumpMultiplierPerBlock;
// 拐点(利⽤率阈值)
uint public kink;
/**
* @notice 构造函数
* @param baseRatePerYear 年化基础利率
* @param multiplierPerYear 年化正常斜率
* @param jumpMultiplierPerYear 年化跳跃斜率
* @param kink_ 拐点
*/
constructor(
uint baseRatePerYear,
uint multiplierPerYear,
uint jumpMultiplierPerYear,
uint kink_
) {
baseRatePerBlock = baseRatePerYear / blocksPerYear;
multiplierPerBlock = multiplierPerYear / blocksPerYear;
jumpMultiplierPerBlock = jumpMultiplierPerYear / blocksPerYear;
kink = kink_;
}
/**
* @notice 计算利⽤率
* @param cash 可⽤现⾦
* @param borrows 总借款
* @param reserves 储备⾦
* @return 利⽤率(scaled by 1e18)
*/
function utilizationRate(
uint cash,
uint borrows,
uint reserves
) public pure returns (uint) {
// 如果没有借款,利⽤率为0
if (borrows == 0) {
return 0;
}
// U = borrows / (cash + borrows - reserves)
return (borrows * 1e18) / (cash + borrows - reserves);
}
/**
* @notice 计算每区块借款利率
* @param cash 可⽤现⾦
* @param borrows 总借款
* @param reserves 储备⾦
* @return 每区块借款利率
*/
function getBorrowRate(
uint cash,
uint borrows,
uint reserves
) public view returns (uint) {
uint util = utilizationRate(cash, borrows, reserves);
// 如果利⽤率 <= kink,使⽤正常斜率
if (util <= kink) {
// rate = base + multiplier × util
return ((util * multiplierPerBlock) / 1e18) + baseRatePerBlock;
} else {
// 如果利⽤率 > kink,使⽤跳跃斜率
// normalRate = base + multiplier × kink
uint normalRate = ((kink * multiplierPerBlock) / 1e18) + baseRatePerBlock;
// excessUtil = util - kink
uint excessUtil = util - kink;
// jumpRate = normalRate + jumpMultiplier × excessUtil
return ((excessUtil * jumpMultiplierPerBlock) / 1e18) + normalRate;
}
}
/**
* @notice 计算每区块存款利率
* @param cash 可⽤现⾦
* @param borrows 总借款
* @param reserves 储备⾦
* @param reserveFactorMantissa 储备因⼦
* @return 每区块存款利率
*/
function getSupplyRate(
uint cash,
uint borrows,
uint reserves,
uint reserveFactorMantissa
) public view returns (uint) {
uint oneMinusReserveFactor = 1e18 - reserveFactorMantissa;
uint borrowRate = getBorrowRate(cash, borrows, reserves);
uint util = utilizationRate(cash, borrows, reserves);
// supplyRate = borrowRate × util × (1 - reserveFactor)
uint rateToPool = (borrowRate * oneMinusReserveFactor) / 1e18;
return (util * rateToPool) / 1e18;
}
}
4.2 利率实时更新机制
区块级利息累积:
Compound的利息在每个区块累积,通过 accrueInterest 函数更新:
c
/**
* @notice 累积利息
* @dev 在任何状态变更操作前都必须调⽤
*/
function accrueInterest() public returns (uint) {
// 1. 获取当前区块号
uint currentBlockNumber = block.number;
uint accrualBlockNumberPrior = accrualBlockNumber;
// 如果在同⼀区块内,⽆需重复计算
if (accrualBlockNumberPrior == currentBlockNumber) {
return uint(Error.NO_ERROR);
}
// 2. 读取存储的状态
uint cashPrior = getCash();
uint borrowsPrior = totalBorrows;
uint reservesPrior = totalReserves;
uint borrowIndexPrior = borrowIndex;
// 3. 计算当前借款利率
uint borrowRateMantissa = interestRateModel.getBorrowRate(
cashPrior,
borrowsPrior,
reservesPrior
);
require(borrowRateMantissa <= borrowRateMaxMantissa, "Borrow rate too high");
// 4. 计算区块差
uint blockDelta = currentBlockNumber - accrualBlockNumberPrior;
// 5. 计算利息
// interestAccumulated = borrowRate × borrows × blockDelta
uint simpleInterestFactor = borrowRateMantissa * blockDelta;
uint interestAccumulated = (simpleInterestFactor * borrowsPrior) / 1e18;
// 6. 更新总借款(加上新增利息)
uint totalBorrowsNew = interestAccumulated + borrowsPrior;
// 7. 更新储备⾦(协议收⼊)
uint totalReservesNew = (interestAccumulated * reserveFactorMantissa) / 1e18 +
reservesPrior;
// 8. 更新借款索引
uint borrowIndexNew = (simpleInterestFactor * borrowIndexPrior) / 1e18 +
borrowIndexPrior;
// 9. 写⼊状态
accrualBlockNumber = currentBlockNumber;
borrowIndex = borrowIndexNew;
totalBorrows = totalBorrowsNew;
totalReserves = totalReservesNew;
emit AccrueInterest(
cashPrior,
interestAccumulated,
borrowIndexNew,
totalBorrowsNew
);
return uint(Error.NO_ERROR);
}
区块利息 vs 时间利息:

5. Compound V3 (Comet) - 新⼀代架构
5.1 为什么需要V3?
Compound V2虽然成功,但也暴露了⼀些问题:
- 资本效率低:每个资产独⽴的资⾦池导致流动性分散
- Gas成本⾼:复杂的跨市场计算消耗⼤量Gas
- ⻛险管理复杂:多抵押品组合增加了⻛险评估难度
- ⽤户体验差:借多种资产需要多次操作
5.2 Comet核⼼设计
Compound V3(代号Comet)采⽤了全新的架构设计:
核⼼改变:
- 单⼀借款资产:每个Comet市场只⽀持借⼀种资产(如USDC)
- 多种抵押品:但可以接受多种资产作为抵押
- 原⽣收益:借款资产⾃动赚取收益(如USDC存⼊Compound Treasury)
- 简化逻辑:⼤幅降低合约复杂度和Gas成本
5.2.1 V2 vs V3 架构对⽐

V2 vs V3 功能对⽐:

5.2.2 Comet核⼼合约
c
// Comet.sol - Compound V3核⼼合约
pragma solidity 0.8.15;
contract Comet {
// 基础资产(唯⼀可借资产)
address public immutable baseToken;
// 抵押品配置
struct AssetConfig {
address asset;
uint8 decimals;
uint64 borrowCollateralFactor; // 借款抵押因⼦
uint64 liquidateCollateralFactor; // 清算抵押因⼦
uint128 supplyCap; // 供应上限
}
AssetConfig[] public assetConfigs;
// ⽤户基础资产余额(可正可负)
mapping(address => int256) public baseBalances;
// ⽤户抵押品余额
mapping(address => mapping(address => uint256)) public collateralBalances;
// 利率模型
uint64 public baseIndexScale = 1e15;
/**
* @notice 供应基础资产(类似V2的mint)
* @param amount 供应数量
*/
function supply(address asset, uint amount) external {
if (asset == baseToken) {
supplyBase(msg.sender, amount);
} else {
supplyCollateral(msg.sender, asset, amount);
}
}
/**
* @notice 供应基础资产
*/
function supplyBase(address from, uint amount) internal {
// 转⼊资产
doTransferIn(baseToken, from, amount);
// 增加⽤户余额(可能从负变正)
baseBalances[from] += int256(amount);
emit Supply(from, baseToken, amount);
}
/**
* @notice 供应抵押品
*/
function supplyCollateral(address from, address asset, uint amount) internal {
// 检查是否是⽀持的抵押品
AssetConfig memory config = getAssetConfig(asset);
require(config.asset != address(0), "Unsupported asset");
// 转⼊抵押品
doTransferIn(asset, from, amount);
// 增加抵押品余额
collateralBalances[from][asset] += amount;
emit SupplyCollateral(from, asset, amount);
}
/**
* @notice 取出资产
* @param asset 资产地址
* @param amount 取出数量
*/
function withdraw(address asset, uint amount) external {
if (asset == baseToken) {
withdrawBase(msg.sender, amount);
} else {
withdrawCollateral(msg.sender, asset, amount);
}
}
/**
* @notice 取出基础资产
*/
function withdrawBase(address to, uint amount) internal {
// 减少⽤户余额(可能从正变负,即借款)
baseBalances[to] -= int256(amount);
// 检查是否有⾜够的借款能⼒
if (baseBalances[to] < 0) {
require(isBorrowCollateralized(to), "Undercollateralized");
}
// 转出资产
doTransferOut(baseToken, to, amount);
emit Withdraw(to, baseToken, amount);
}
/**
* @notice 检查借款是否有⾜够抵押
*/
function isBorrowCollateralized(address account) public view returns (bool) {
int256 baseBalance = baseBalances[account];
// 如果余额为正,说明是供应者,不需要抵押
if (baseBalance >= 0) {
return true;
}
// 计算借款价值
uint256 borrowValue = presentValue(uint256(-baseBalance));
// 计算抵押品总价值
uint256 collateralValue = 0;
for (uint i = 0; i < assetConfigs.length; i++) {
AssetConfig memory config = assetConfigs[i];
uint256 collateralBalance = collateralBalances[account][config.asset];
if (collateralBalance > 0) {
// 获取抵押品价格
uint256 price = getPrice(config.asset);
// 计算加权价值
uint256 value = (collateralBalance * price) / (10 ** config.decimals);
collateralValue += (value * config.borrowCollateralFactor) / 1e18;
}
}
// 抵押品价值必须⼤于借款价值
return collateralValue >= borrowValue;
}
/**
* @notice 清算函数
* @param borrower 借款⼈
* @param asset 抵押品资产
* @param baseAmount 偿还的基础资产数量
*/
function absorb(address borrower, address[] calldata assets) external {
// 检查是否可以清算
require(!isBorrowCollateralized(borrower), "Not liquidatable");
// 计算借款⼈的债务
uint256 debt = uint256(-baseBalances[borrower]);
// 吸收所有抵押品
for (uint i = 0; i < assets.length; i++) {
address asset = assets[i];
uint256 collateralBalance = collateralBalances[borrower][asset];
if (collateralBalance > 0) {
// 将抵押品转移到协议储备
collateralBalances[borrower][asset] = 0;
collateralBalances[address(this)][asset] += collateralBalance;
emit AbsorbCollateral(borrower, asset, collateralBalance);
}
}
// 清除借款⼈的债务
baseBalances[borrower] = 0;
emit Absorb(borrower, debt);
}
}
5.3 Comet的优势
- 更⾼的资本效率
V3中,所有抵押品共享同⼀个借款池,提⾼了资本利⽤率:
V2模式:
⽤户A: 在ETH市场存⼊,只能赚取ETH市场的利息
⽤户B: 在USDC市场存⼊,只能赚取USDC市场的利息
流动性分散
V3模式:所有USDC需求者共享同⼀个USDC池
⽆论抵押ETH、WBTC还是LINK,都从同⼀池⼦借USDC
流动性集中,效率更⾼
- 降低Gas成本
操作对⽐(以太坊主⽹):
存款:
V2: ~120,000 Gas
V3: ~75,000 Gas
节省: 37.5%
借款:V2: ~280,000 Gas
V3: ~160,000 Gas
节省: 42.9%
还款:V2: ~180,000 Gas
V3: ~110,000 Gas
节省: 38.9%
- 原⽣收益功能
V3将基础资产(如USDC)投资到其他收益协议,为⽤户创造额外收益:
USDC Comet市场:
- 闲置USDC⾃动存⼊Compound Treasury
- 赚取国债收益率(~4-5% APY)
- ⽤户获得:借款利息 + 国债收益
- 提⾼整体APY约1-2个百分点