Compound Protocol
Contents
TL;DR
A typical procedure:
- ETH suppliers (also USDT borrowers) provided ETH, got cETH, held cETH to earn interests;
- cETH becomes convertible into an increasing amount of ETH (underlying asset) as interest is accrued by borrowers of the asset.
- USDT borrowers frictionlessly borrow USDT from the protocol, using cETH as collateral at some collateral rate;
- Oracles are needed.
- The value of an account’s borrowing outstanding exceeded their borrowing capacity;
- A USDT holder arbitraged by invoking the liquidation function to exchange his USDT for the borrower’s cETH collateral at the current market price minus a liquidation discount, reducing the borrower’s exposure, eliminating the protocol’s risk.
DeFi Lending
传统的抵押贷款本质上是将房子、车、土地等低流动性资产作为抵押,借出钱这种高度流动的资产。DeFi 中的抵押贷款,抵押的资产和借出的资产都是高流动性的资产。
DeFi 借贷的主要场景:交易活动对资金的需求,如套利、杠杆、做市等。
- 杠杆:持有 ETH 且看好 ETH 接下来会涨,这时可以抵押 ETH 借出 USDT 买更多 ETH,买回来的 ETH 还可以再抵押借出 USDT,从而又可以买更多 ETH。
Compound 的流动性挖矿模式,用户借款也能挖矿获得平台币奖励,抵扣借款利息后还有净收益。
White Paper
Originally, blockchain assets have negative yield, resulting from significant storage costs and risks, without natural interest rates to offset those costs. This contributes to volatility, as holding is disincentivized.
Centralized exchanges allow customers to trade blockchain assets on margin(孖展), with “borrowing markets” built into the exchange. This is a trust-based approach only available for a limited number of customer groups. Besides, the number of tradable assets is also limited. Most importantly, balances and positions are virtual; you can’t move a position on-chain.
Supplying and Borrowing
Compound makes frictionlessly trading the time value of blockchain assets possible. It’s a protocol on the Ethereum blockchain that establishes money markets, which are pools of assets with algorithmically derived interest rates, based on the supply and demand for the asset.
- Suppliers (and borrowers) of an asset interact directly with the protocol, earning (and paying) a floating interest rate, without having to negotiate terms such as maturity, interest rate, or collateral with a peer or counterparty.
- Each money market is unique to an Ethereum asset (such as Ether, an ERC-20 stablecoin such as Dai, or an ERC-20 utility token such as Augur), and contains a transparent and publicly-inspectable ledger, with a record of all transactions and historical interest rates.
The Compound protocol aggregates the supply of each user; when a user supplies an asset, it becomes a fungible resource.
- This approach offers significantly more liquidity than direct lending; unless every asset in a market is borrowed, users can withdraw their assets at any time, without waiting for a specific loan to mature.
- Assets supplied to a market (held by the protocol) are represented by an ERC-20 token balance (cToken), which entitles the owner to an increasing quantity of the underlying asset.
- As the money market accrues interest(累积利息), which is a function of borrowing demand, cTokens become convertible into an increasing amount of the underlying asset. In this way, earning interest is as simple as holding a ERC-20 cToken.
Compound allows users to frictionlessly borrow from the protocol for use anywhere in the Ethereum ecosystem. cToken are used as collateral to borrow from the protocol.
- Each money market has a floating interest rate set by market forces that determines the borrowing cost for each asset.
- Each market has a collateral factor, ranging from 0 to 1, that represents the portion of the underlying asset value (not the underlying asset itself) that can be borrowed.
- The sum of the value of an account’s underlying token balances, multiplied by the collateral factors, equals a user’s borrowing capacity. Users are able to borrow up to, but not exceeding, their borrowing capacity, and an account can take no action (e.g. borrow, transfer cToken collateral, or redeem cToken collateral) that would raise the total value of borrowed assets above their borrowing capacity; this protects the protocol from default risk(违约风险).
- If the value of an account’s borrowing outstanding exceeds their borrowing capacity, a portion of the outstanding borrowing may be repaid in exchange for the user’s cToken collateral, at the current market price minus a liquidation discount.
- The proportion eligible to be closed, a close factor , is the portion of the borrowed asset that can be repaid, and ranges from 0 to 1.
- This liquidation discount incentives an ecosystem of arbitrageurs to quickly step in to reduce the borrower’s exposure, and eliminate the protocol’s risk.
- Any Ethereum address that possesses the borrowed asset may invoke the liquidation function, exchanging their asset for the borrower’s cToken collateral.
- As both users, both assets, and prices are all contained within the Compound protocol, liquidation is frictionless and does not rely on any outside systems or order-books.
- Use cases:
- Traders looking to short a token can borrow it, send it to an exchange and sell the token, profiting from declines in overvalued tokens.
Interest Rate Model
The Compound protocol utilizes an interest rate model that achieves an interest rate equilibrium based on supply and demand in each money market to incentivize liquidity.
Interest rates (the “price” of money) increase as a function of demand; when demand is low, interest rates should be low, and vise versa when demand is high.
The utilization ratio $$U$$ for each market $$a$$ unifies supply and demand into a single variable
The demand curve is codified through governance and is expressed as a function of utilization:
$$U_a = Borrows_a / (Cash_a + Borrows_a)$$
- $$U_a$$ stands for utilization rate of market $$a$$.
Borrowing interest rate of market $$a$$ may resemble the following:
-
$$Borrowing Interest Rate_a = baseRate + U_a * multiplier$$
- $$baseRate$$ is the y-intercept when utilization rate is 0;
- $$multiplier$$ is the slope of the interest rate.
-
$$Supplying Interest Rate_a = U_a * Borrowing Interest Rate_a * (1 - reserveFactor)$$
- A portion of the accrued interest is retained (set aside) as reserves, determined by a $$reserveFactor$$ , ranging from 0 to 1.
-
- Black stands for utilization rate; purple stands for borrowing interest rate; green stands for supplying interest rate.
- $$Borrowing Interest Rate_{BAT} = U_{BAT} * 38.5214\% + 2.53\%$$
- $$2.57/100 * 38.5214/100 + 2.53/100 ≈ 3.52\%$$
- $$Supplying Interest Rate_{BAT} = U_{BAT} * (U_{BAT} * 38.5214\% + 2.53\%) * (1 - 25\%)$$
- $$2.57/100 * (2.57/100 * 38.5214/100 + 2.53/100) * (1 - 25/100) ≈ 0.07\%$$
Architecture
Each money market is structured as a smart contract that implements the ERC-20.
- User’s balances are represented as cToken balances;
- Users can
mint(uint amountUnderlying)
cTokens by supplying assets to the market, orredeem(uint amount)
cTokens for the underlying asset; - The exchange rate is the price between cTokens and the underlying asset.
$$exchangeRate = (underlyingBalance + totalBorrowingBalance_a - reserves_a) / cTokenSupply_a$$
- reserves: 准备金
- The exchange rate increases over time as interest is accrued by borrowers of the asset.
The history of each interest rate for each money market is captured by an Interest Rate Index, calculated each time an interest rate changes, resulting from a user minting, redeeming, borrowing, repaying or liquidating the asset.
Contracts v2.8.1
Interest Rate
// https://github.com/compound-finance/compound-protocol/blob/v2.8.1/contracts/InterestRateModel.sol
contract InterestRateModel {
// Indicator that this is an InterestRateModel contract (for inspection)
bool public constant isInterestRateModel = true;
/**
* @notice Calculates the current borrow interest rate per block
* @param cash The total amount of cash the market has
* @param borrows The total amount of borrows the market has outstanding
* @param reserves The total amount of reserves the market has
* @return The borrow rate per block (as a percentage, and scaled by 1e18)
*/
function getBorrowRate(uint cash, uint borrows, uint reserves) external view returns (uint);
/**
* @notice Calculates the current supply interest rate per block
* @param cash The total amount of cash the market has
* @param borrows The total amount of borrows the market has outstanding
* @param reserves The total amount of reserves the market has
* @param reserveFactorMantissa The current reserve factor the market has
* @return The supply rate per block (as a percentage, and scaled by 1e18)
*/
function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) external view returns (uint);
}
WhitePaperInterestRateModel
// https://github.com/compound-finance/compound-protocol/blob/v2.8.1/contracts/WhitePaperInterestRateModel.sol
import "./InterestRateModel.sol";
import "./SafeMath.sol";
// The parameterized model described in section 2.4 of the original Compound Protocol whitepaper
contract WhitePaperInterestRateModel is InterestRateModel {
using SafeMath for uint;
event NewInterestParams(uint baseRatePerBlock, uint multiplierPerBlock);
// The approximate number of blocks per year (1 block per 15s) that is assumed by the interest rate model
uint public constant blocksPerYear = 2102400;
// The multiplier of utilization rate that gives the slope of the interest rate
uint public multiplierPerBlock;
// The base interest rate which is the y-intercept when utilization rate is 0
uint public baseRatePerBlock;
/**
* @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by 1e18)
* @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by 1e18)
*/
constructor(uint baseRatePerYear, uint multiplierPerYear) public {
baseRatePerBlock = baseRatePerYear.div(blocksPerYear);
multiplierPerBlock = multiplierPerYear.div(blocksPerYear);
emit NewInterestParams(baseRatePerBlock, multiplierPerBlock);
}
/**
* @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - reserves)`
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market (currently unused)
* @return The utilization rate as a mantissa between [0, 1e18]
*/
function utilizationRate(uint cash, uint borrows, uint reserves) public pure returns (uint) {
// Utilization rate is 0 when there are no borrows
if (borrows == 0) {
return 0;
}
return borrows.mul(1e18).div(cash.add(borrows).sub(reserves));
}
/**
* @notice Calculates the current borrow interest rate per block, with the error code expected by the market
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market
* @return The borrow rate percentage per block as a mantissa (scaled by 1e18)
*/
function getBorrowRate(uint cash, uint borrows, uint reserves) public view returns (uint) {
uint ur = utilizationRate(cash, borrows, reserves);
return ur.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock);
}
/**
* @notice Calculates the current supply interest rate per block
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market
* @param reserveFactorMantissa The current reserve factor for the market
* @return The supply rate percentage per block as a mantissa (scaled by 1e18)
*/
function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) public view returns (uint) {
uint oneMinusReserveFactor = uint(1e18).sub(reserveFactorMantissa);
uint borrowRate = getBorrowRate(cash, borrows, reserves);
uint rateToPool = borrowRate.mul(oneMinusReserveFactor).div(1e18);
return utilizationRate(cash, borrows, reserves).mul(rateToPool).div(1e18);
}
}
JumpRateModel
// https://github.com/compound-finance/compound-protocol/blob/v2.8.1/contracts/JumpRateModel.sol
// Compound's JumpRateModel Contract
contract JumpRateModel is InterestRateModel {
// ...
// The multiplierPerBlock after hitting a specified utilization point
uint public jumpMultiplierPerBlock;
// The utilization point at which the jump multiplier is applied
uint public kink;
/**
* @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by 1e18)
* @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by 1e18)
* @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point
* @param kink_ The utilization point at which the jump multiplier is applied
*/
constructor(uint baseRatePerYear, uint multiplierPerYear, uint jumpMultiplierPerYear, uint kink_) public {
// ...
jumpMultiplierPerBlock = jumpMultiplierPerYear.div(blocksPerYear);
kink = kink_;
emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink);
}
// function utilizationRate
/**
* @notice Calculates the current borrow rate per block, with the error code expected by the market
* @param cash The amount of cash in the market
* @param borrows The amount of borrows in the market
* @param reserves The amount of reserves in the market
* @return The borrow rate percentage per block as a mantissa (scaled by 1e18)
*/
function getBorrowRate(uint cash, uint borrows, uint reserves) public view returns (uint) {
uint util = utilizationRate(cash, borrows, reserves);
if (util <= kink) {
return util.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock);
} else {
uint normalRate = kink.mul(multiplierPerBlock).div(1e18).add(baseRatePerBlock);
uint excessUtil = util.sub(kink);
return excessUtil.mul(jumpMultiplierPerBlock).div(1e18).add(normalRate);
}
}
// function getSupplyRate
}
$$Borrowing Interest Rate Normal_a = baseRate + U_a * multiplier$$
$$Borrowing Interest Rate Jump_a = (baseRate + kink * multiplier) + (U_a - kink) * jumpMultiplier$$
cToken
ETH 的 cToken 交互入口是 CEther
合约;ERC20 的 cToken 交互入口是 CErc20Delegator
合约,每种 ERC20 资产各有一份入口合约。
代理合约会将调用请求重定向到目标合约中。代理合约实现了数据与逻辑的分离,是 cToken 合约可升级的基础。
References