Section 12 of 18

Build
+20 Lynx

CToken: Borrow

What You Are Building

You are adding the borrow mechanism to the CToken. This is what makes Compound a lending protocol, not just a savings account. Users who have supplied collateral and entered markets can borrow underlying tokens from any market. The borrowed amount accrues interest over time through the BorrowSnapshot pattern you saw in section 5.

Uniswap V2 has no equivalent. There is nothing in Uniswap that lets you take tokens out of a pool and owe them back later. Borrowing is an entirely new concept relative to what you built in the DEX module.

Concept: How Borrowing Works

The flow is straightforward: the user requests to borrow X underlying tokens, the CToken asks the Comptroller for permission (cross-market solvency check), and if approved, transfers the tokens out and records the debt.

What makes it interesting is how the debt is tracked. The protocol does not update every borrower's balance every block. Instead, it uses the BorrowSnapshot pattern from section 4:

struct BorrowSnapshot {
    uint256 principal;      // Borrow balance at last interaction
    uint256 interestIndex;  // borrowIndex at last interaction
}

A borrower's current debt is computed lazily:

currentBorrow = snapshot.principal * currentBorrowIndex / snapshot.interestIndex

The global borrowIndex grows every time accrueInterest() runs. By dividing the current index by the snapshot's index, you get the interest multiplier since the borrower's last interaction. This is the same mathematical trick as Uniswap V2's cumulative price oracle: store a running accumulator, compute differences on demand.

Concept: The borrowFresh Flow

borrowFresh follows the same pattern as mintFresh and redeemFresh, calling the Comptroller first:

uint256 allowed = comptroller.borrowAllowed(address(this), borrower, borrowAmount);
require(allowed == 0, "CToken: borrow rejected by comptroller");

The Comptroller's borrowAllowed (which you built in section 11) runs the hypothetical liquidity calculation: "What would this user's solvency look like if they borrowed this additional amount?" If it creates a shortfall, the borrow is rejected.

After the Comptroller check, borrowFresh verifies cash availability. The protocol can only lend out tokens it actually holds. If utilization is near 100%, there might not be enough cash. This is the same check as redeemFresh, and it is the reason high utilization drives interest rates up.

Concept: The BorrowSnapshot Update

The most important part of borrowFresh is the snapshot update:

accountBorrows[borrower].principal = accountBorrowsNew;
accountBorrows[borrower].interestIndex = borrowIndex;

This "resets" the borrower's interest accumulation point. Before the update, borrowBalanceStoredInternal() computed their balance using the OLD snapshot. After the update, it uses the NEW snapshot with the current borrowIndex. Any interest accrued before this moment is already baked into accountBorrowsNew (computed via borrowBalanceStoredInternal). Future interest will be calculated relative to the current borrowIndex.

The totalBorrows update is equally important. This global variable affects the exchange rate (which determines how much suppliers earn) and the interest rate model (which determines how much borrowers pay). Every new borrow increases totalBorrows, pushing utilization up and rates higher.

Your Task

Build CTokenBorrow inheriting from CTokenRedeem:

  1. borrowInternal(uint256 borrowAmount): internal. Call accrueInterest(), then call borrowFresh(msg.sender, borrowAmount).

  2. borrowFresh(address borrower, uint256 borrowAmount): internal.

    • Call comptroller.borrowAllowed(address(this), borrower, borrowAmount) and require it returns 0.
    • Require accrualBlockNumber == block.number.
    • Require getCashPrior() >= borrowAmount.
    • Compute the borrower's previous borrow balance: uint256 accountBorrowsPrev = borrowBalanceStoredInternal(borrower).
    • Compute accountBorrowsNew = accountBorrowsPrev + borrowAmount.
    • Compute totalBorrowsNew = totalBorrows + borrowAmount.
    • Update the BorrowSnapshot: set accountBorrows[borrower].principal = accountBorrowsNew and accountBorrows[borrower].interestIndex = borrowIndex.
    • Update totalBorrows = totalBorrowsNew.
    • Call doTransferOut(borrower, borrowAmount).
    • Emit Borrow(borrower, borrowAmount, accountBorrowsNew, totalBorrowsNew).

Your Code

Solution.sol
Solidity
Loading editor...

Requirements

Write your implementation, then click Run Tests. Tests execute on the server.