Section 6 of 18
CToken: Supply (mint)
What You Are Building
The mint function is how assets enter the lending protocol. When a user supplies underlying tokens (like DAI), they receive cTokens (like cDAI) in return. These cTokens represent their share of the pool and grow in value over time as borrowers pay interest.
This is the first user-facing operation you build. Everything from the previous sections (fixed-point math, interest rates, state variables, exchange rates, and interest accrual) converges here. The mint flow exercises the Comptroller hook, the exchange rate calculation, and the state update pattern that every subsequent operation will follow.
How Minting Works
The mint process has two layers: mintInternal and mintFresh. This separation mirrors a pattern you will see throughout Compound V2. The "Internal" function handles setup (accruing interest), then calls the "Fresh" function for the actual logic. The "Fresh" function requires that interest has already been accrued to the current block.
This is different from Uniswap V2's approach, where mint() is a single function that reads balances directly. Compound needs the two-layer pattern because interest must be accrued before any calculation. In Uniswap, there is no time-dependent state to update before processing.
The Comptroller Check
Before any tokens move, mintFresh calls comptroller.mintAllowed(). This is the Comptroller's policy hook. It checks whether the market is listed (registered), whether the market is paused, and whether any protocol-level constraints would prevent this mint.
If the Comptroller returns anything other than 0, the mint reverts. This is the first line of defense. You will build the Comptroller side of this hook in section 11. For now, just know that the CToken always asks permission first.
This pattern is analogous to Uniswap V2's Factory controlling which pairs can exist. But Compound's Comptroller is more powerful: it can block specific operations on specific markets at runtime, not just at deployment time.
The Exchange Rate Conversion
The core calculation converts underlying tokens to cTokens using the exchange rate:
Exp memory exchangeRate = Exp({ mantissa: exchangeRateStoredInternal() });
uint256 mintTokens = div_(actualMintAmount, exchangeRate);
If the exchange rate is 0.02e18, depositing 100 DAI gives you 100e18 / 0.02e18 = 5000 cTokens. As the exchange rate grows (because borrowers pay interest), the same 100 DAI buys fewer cTokens. This is correct: each cToken is now worth more underlying.
The div_ function from the math library handles the fixed-point division. It scales the numerator by expScale before dividing, preserving 18 decimals of precision.
State Updates
After computing mintTokens, the function updates two state variables:
totalSupply += mintTokens;
accountTokens[minter] += mintTokens;
These are the only state changes. The underlying token transfer is handled separately by CErc20 (section 17), which calls mintInternal after transferring tokens in. For now, the CToken assumes the tokens have already arrived. The actualMintAmount parameter represents how many tokens actually arrived (accounting for potential fee-on-transfer tokens).
The Interest Accrual Requirement
mintFresh includes an explicit check:
require(accrualBlockNumber == block.number, "CToken: interest not accrued");
This guarantees that mintInternal called accrueInterest() before calling mintFresh. If someone called mintFresh directly without accruing interest first, the exchange rate would be stale (based on the last accrual, not the current block), and the minter would receive an incorrect number of cTokens. This check makes that impossible.
Events
Two events are emitted: Mint (a Compound-specific event with the minter address, underlying amount, and cTokens minted) and Transfer (the standard ERC-20 event, from address(0) to the minter). The Transfer from address(0) follows the ERC-20 convention for token creation, ensuring wallet interfaces display the mint correctly.
Your Task
Build the CTokenMint contract that inherits from CTokenInterest.
- Implement
mintInternal(uint256 mintAmount): callaccrueInterest()first, then callmintFresh(msg.sender, mintAmount). This function is internal because CErc20 calls it after handling the token transfer. - Implement
mintFresh(address minter, uint256 mintAmount):- Call
comptroller.mintAllowed(address(this), minter, mintAmount)and require the result is 0 - Require
accrualBlockNumber == block.number(interest must be current) - Get the exchange rate via
exchangeRateStoredInternal(), wrapped in anExpstruct - Set
actualMintAmount = mintAmount(CErc20 will handle fee-on-transfer accounting later) - Compute
mintTokens = div_(actualMintAmount, exchangeRate)using the math library - Update
totalSupplyandaccountTokens[minter] - Emit
Mint(minter, actualMintAmount, mintTokens)andTransfer(address(0), minter, mintTokens)
- Call