Section 14 of 18
Core Lending Complete
Final Build
Submit your complete protocol and run the full test suite. Earn the "compound-v2-builder" badge on completion.
What You Built
You just wrote the core lending protocol. Every function that powers supply, withdraw, borrow, and repay is now implemented. This is the engine that held billions of dollars in Compound V2 and was forked by dozens of protocols. Here is everything you built across the core contracts:
ExponentialNoError (Fixed-Point Math)
| Function | Purpose |
|---|---|
truncate() | Convert Exp to integer (discard fractional part) |
mul_ScalarTruncate() | Multiply then truncate in one step |
mul_() overloads | Scale-aware multiplication (ExpExp, Expuint, uintExp, uintuint) |
div_() overloads | Scale-aware division (Exp/Exp, Exp/uint, uint/Exp) |
add_(), sub_() | Exp arithmetic |
JumpRateModel (Interest Rates)
| Function | Purpose |
|---|---|
utilizationRate() | Calculate pool utilization: borrows / (cash + borrows - reserves) |
getBorrowRate() | Piecewise linear rate with kink at 80% utilization |
getSupplyRate() | Derived from borrow rate: borrowRate * utilization * (1 - reserveFactor) |
CTokenStorage (State and Accounting)
| State Variable | Purpose |
|---|---|
totalSupply | Total cTokens in existence |
totalBorrows | Total underlying lent out (grows with interest) |
totalReserves | Protocol revenue from interest |
borrowIndex | Cumulative interest multiplier for lazy per-user calculation |
accountTokens | cToken balances per user |
accountBorrows | BorrowSnapshot per user (principal + interestIndex) |
CTokenInterest (Interest Accrual)
| Function | Purpose |
|---|---|
accrueInterest() | Update borrowIndex, totalBorrows, totalReserves for elapsed blocks |
exchangeRateStoredInternal() | Calculate cToken-to-underlying rate |
borrowBalanceStoredInternal() | Calculate a borrower's current debt with accrued interest |
getAccountSnapshot() | Return a user's cToken balance, borrow balance, and exchange rate |
CTokenMint and CTokenRedeem (Supply and Withdraw)
| Function | Purpose |
|---|---|
mintInternal() / mintFresh() | Deposit underlying, receive cTokens |
redeemInternal() / redeemFresh() | Burn cTokens, receive underlying |
redeemUnderlyingInternal() | Redeem by specifying underlying amount |
Comptroller (Risk Engine)
| Function | Purpose |
|---|---|
_supportMarket() | Register a new cToken market |
enterMarkets() | Opt into using markets as collateral |
getAccountLiquidity() | Calculate a user's total collateral vs total borrows |
mintAllowed() / redeemAllowed() / borrowAllowed() | Policy hooks enforcing solvency |
repayBorrowAllowed() | Minimal check (repaying is always safe) |
CTokenBorrow and CTokenRepay (Debt Management)
| Function | Purpose |
|---|---|
borrowInternal() / borrowFresh() | Take out a loan against collateral |
repayBorrowInternal() | Repay your own debt |
repayBorrowBehalfInternal() | Repay someone else's debt |
repayBorrowFresh() | Core repay logic with type(uint256).max support |
Key Concepts You Now Understand
Lazy interest accrual: A global borrowIndex grows each time accrueInterest is called. Individual borrow balances are computed on demand: principal * currentBorrowIndex / snapshotInterestIndex. No iteration over all borrowers.
The exchange rate: (totalCash + totalBorrows - totalReserves) / totalSupply. As borrowers pay interest, totalBorrows grows, and each cToken becomes worth more underlying. Suppliers earn interest without any transaction touching their balance.
Cross-market risk: The Comptroller aggregates a user's collateral and borrows across all markets. A user's ETH deposit in one market backs their USDC borrow in another. This cross-market view is why the Comptroller exists as a separate contract.
Hypothetical liquidity: Before allowing a risky action (redeem, borrow), the Comptroller simulates the outcome and checks whether the user would still be solvent. This prevents users from accidentally undercollateralizing themselves.
The BorrowSnapshot pattern: Each borrower stores their principal and the borrowIndex at the time of their last interaction. This avoids the impossible task of updating every borrower's balance every block.
What Comes Next
The core lending protocol handles normal operations: supply, earn interest, borrow, repay. But what happens when a borrower's collateral drops in value and their debt exceeds their collateral? That is where liquidation comes in. Part 3 builds the liquidation mechanism (both the Comptroller policy side and the CToken execution side) and the final CErc20 integration layer that ties everything into a deployable contract.
The Final Test
The test below covers all core functions across all contracts built so far. It verifies math operations, interest rate calculations, exchange rate formulas, the Comptroller's risk checks, and the full borrow/repay cycle. Run it to confirm your complete core lending protocol works correctly.