Section 7 of 18

Build
+15 Lynx

CToken: Withdraw (redeem)

What You Are Building

You are adding the withdrawal mechanism to the CToken. Users burn cTokens to get their underlying tokens back. This is the reverse of mintFresh() from the previous section, but with a critical difference: the Comptroller can block the withdrawal if those cTokens are backing a borrow.

Concept: The Dual-Redeem Pattern

Compound V2 offers two ways to withdraw. First, "I want to burn exactly X cTokens" (redeemInternal). Second, "I want to receive exactly Y underlying tokens" (redeemUnderlyingInternal). Both funnel into a single redeemFresh() function that accepts two parameters, one of which must be zero.

This dual-parameter pattern exists because users think in different terms depending on context. A user closing their entire position wants to burn all their cTokens. A user who needs exactly 1,000 DAI for a payment wants to specify the underlying amount. Forcing everyone through one interface creates UX friction and rounding confusion.

The conversion between the two uses the exchange rate you built in section 5:

// Burning exact cTokens: calculate underlying received
redeemAmount = mul_ScalarTruncate(exchangeRate, redeemTokensIn);

// Receiving exact underlying: calculate cTokens to burn
redeemTokens = div_(redeemAmountIn, exchangeRate);

Concept: Comptroller Solvency Check

In Uniswap V2, you can always burn your LP tokens. Nobody can stop you from withdrawing liquidity. In Compound, withdrawal can be blocked.

When a user has entered a market as collateral and has outstanding borrows elsewhere, their cTokens are "working" as collateral. The Comptroller checks whether removing those cTokens would make the user's position undercollateralized. If it would, the redeem is rejected.

This is the line that enforces it:

uint256 allowed = comptroller.redeemAllowed(address(this), redeemer, redeemTokens);
require(allowed == 0, "CToken: redeem rejected by comptroller");

The Comptroller runs a hypothetical liquidity calculation: "What would this user's collateral ratio look like if they redeemed these tokens?" If the answer is "underwater," the transaction reverts. You will build redeemAllowed in section 11.

Concept: Cash Sufficiency

Even if the Comptroller approves the withdrawal, the protocol must have enough underlying tokens sitting in the contract. If most of the pool is currently borrowed out, there might not be enough cash. This is the utilization problem: high utilization means suppliers cannot withdraw freely.

require(getCashPrior() >= redeemAmount, "CToken: insufficient cash");

This is why Compound's interest rate model spikes rates at high utilization. It creates economic pressure for borrowers to repay, freeing up cash for suppliers who want to withdraw.

Your Task

Implement three functions in the CTokenRedeem contract that inherits from CTokenMint:

  1. redeemInternal(uint256 redeemTokens): Call accrueInterest(), then call redeemFresh(msg.sender, redeemTokens, 0).

  2. redeemUnderlyingInternal(uint256 redeemAmount): Call accrueInterest(), then call redeemFresh(msg.sender, 0, redeemAmount).

  3. redeemFresh(address redeemer, uint256 redeemTokensIn, uint256 redeemAmountIn):

    • Require that exactly one of the two inputs is zero.
    • Get the exchange rate via exchangeRateStoredInternal().
    • If redeemTokensIn > 0: set redeemTokens = redeemTokensIn and compute redeemAmount = mul_ScalarTruncate(exchangeRate, redeemTokensIn).
    • If redeemAmountIn > 0: set redeemTokens = div_(redeemAmountIn, exchangeRate) and redeemAmount = redeemAmountIn.
    • Call comptroller.redeemAllowed(address(this), redeemer, redeemTokens) and require it returns 0.
    • Require accrualBlockNumber == block.number.
    • Require getCashPrior() >= redeemAmount.
    • Subtract redeemTokens from totalSupply and accountTokens[redeemer].
    • Call doTransferOut(redeemer, redeemAmount).
    • Emit Redeem(redeemer, redeemAmount, redeemTokens) and Transfer(redeemer, address(0), redeemTokens).

Your Code

Solution.sol
Solidity
Loading editor...

Requirements

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