BorrowableCToken
Overview
BorrowableCTokens are Curvance's lending market tokens that enable users to earn interest on deposited assets while simultaneously creating borrowing liquidity for the protocol. These tokens extend the base cToken functionality with comprehensive lending market features, including borrowing, interest accrual, liquidations, and flash loans. Users who deposit underlying assets receive BorrowableCTokens representing their share of the lending pool, which increase in value as borrowers pay interest.
Core Architecture
BorrowableCTokens build on the base cToken foundation by extending BaseCTokenWithYield, which itself extends BaseCToken.
BaseCToken: Provides the ERC4626-style vault mechanics, collateral accounting, Market Manager integration, and transfer/redemption checks.
BaseCTokenWithYield: Adds the packed vesting state used by strategy yield and BorrowableCToken interest accrual. For BorrowableCTokens, the packed data also stores the market debt index.
BorrowableCToken: Adds lending market functionality including:
Borrow against collateral
Interest rate management
Debt tracking and liquidations
Flash loan capabilities
Protocol fee collection
Key Integrations
BorrowableCTokens operate within a sophisticated network of contracts:
MarketManager: Manages risk parameters, collateral requirements, borrowing authorization, and liquidation processes.
DynamicIRM (Interest Rate Model): Determines borrowing and lending rates based on market utilization and other factors.
CentralRegistry: Provides protocol-wide configuration, permissions, and fee settings.
Oracle System: Supplies accurate asset pricing for collateral valuation and liquidation triggers.
Position Managers: Enable complex leveraged positions and automated strategies.
State Management
BorrowableCTokens maintain sophisticated state tracking for lending operations:
Interest Rate Mechanism
BorrowableCTokens employ a dynamic interest rate system that responds to market conditions:
Utilization-Based Rates: Interest rates adjust based on market utilization, computed as
debt / (assetsHeld + debt).assetsHeld()is the borrowable liquidity available after excluding outstanding debt and the base underlying reserve.Accrual-Driven Adjustments: The DynamicIRM does not monitor markets autonomously.
BorrowableCToken._accrueIfNeeded()callsIRM.adjustedBorrowRate(...)when the current vesting window has ended, and the new per-second borrow rate is packed into_vestingData.Dual Rate System:
Borrow Rate: Interest rate paid by borrowers, in WAD per second. Computed by the DynamicIRM from current utilization, configured base and vertex rates, and the current vertex multiplier. The next rate is applied when a cToken accrual crosses an adjustment window.
Supply Rate: Interest rate earned by lenders, in
WADper second. Implicit in the rising exchange rate. Equal toborrowRate * utilization * (1 - interestFee), whereinterestFeeis the protocol's cut.
Data Flow
Lending (Deposit) Flow
User calls
deposit()ormint()with underlying assets.Contract accrues any pending interest.
Contract calculates shares based on current exchange rate.
Underlying tokens are transferred from the user to the contract.
User's share of the lending pool increases.
Tokens begin earning interest from borrower payments.
Borrowing Flow
The borrower must already have collateral posted in at least one OTHER cToken inside the same isolated market. Collateral posted in the BorrowableCToken being borrowed from is not eligible: a single account cannot simultaneously hold debt and collateral in the same cToken (
BorrowableCToken__CollateralPositionActivereverts the borrow).User calls
borrow()specifying desired amounts.Contract accrues pending interest and updates rates.
MarketManager validates borrowing capacity against collateral.
Debt is recorded with current debt index for interest calculations.
Underlying assets are transferred to the borrower.
Borrowing sets the account's
cooldownTimestamp. During the 20-minute hold window, the Market Manager blocks repayment, transfers, and redeem or withdraw paths that callcanRedeemWithCollateralRemovalorcanTransfer. Deposits are not blocked by this hold period.
Repayment Flow
User calls
repay()orrepayFor()with repayment amount.Contract accrues pending interest to capture latest debt amount.
Interest is calculated based on time elapsed and current rates.
Underlying tokens are transferred from
msg.sender(the payer) to the contract.The cToken transfers underlying from
msg.sender, then the Market Manager'scanRepayWithReviewhook validates that repayment is allowed before debt storage is updated. If the hook reverts, the whole transaction reverts.The borrower's
_debtOfis updated andmarketOutstandingDebtis decreased.Repayment does not notify or mutate Market Manager position state.
canRepayWithReviewis a validation hook: it checks the hold period, confirms the borrower has an active debt position for the cToken, and enforces the minimum remaining loan size unless the repayment fully clears the debt.
Interest Accrual Flow
_accrueIfNeeded() is called before most operations.
Time elapsed since last accrual is calculated.
The packed borrow rate in
_vestingDatais used to accrue pending interest. The cToken callsIRM.adjustedBorrowRate(...)only whenblock.timestamp >= vestingEnd; otherwise accrual uses the stored rate and does not fetch a new rate from the DynamicIRM.Interest is applied to all outstanding debt.
Interest increases the value of BorrowableCTokens.
Debt indices are updated for accurate individual debt tracking.
Liquidation Flow
Liquidator identifies underwater positions.
Calls liquidateExact() or liquidate() functions
Contract accrues pending interest to get current debt values.
MarketManager calculates liquidation amounts and collateral seizure.
Liquidator repays portion of debt.
Collateral is seized from borrower and transferred to liquidator.
Any bad debt is recognized and removed from total assets.
Debt Index System
BorrowableCTokens use a sophisticated debt index system for accurate interest calculations:
Market Debt Index: Global index that increases as interest accrues, stored in the upper bits of
_vestingData.Account Debt Index: Personal index recorded when debt is created or modified, stored with each user's debt amount.
Interest Calculation: User's accrued interest =
(current market index / user's debt index - 1) * principal debt.
This system ensures accurate interest calculations regardless of when users borrow or repay.
Liquidation Mechanics
BorrowableCTokens support comprehensive liquidation capabilities:
Solvency Checks: Borrow paths and collateral-removing redemption or transfer paths run Market Manager liquidity checks. Repayments do not recompute a collateral-to-debt solvency check;
canRepayWithReviewenforces the hold period, confirms an active debt position, and checks the minimum remaining loan size unless the repayment fully clears the debt. Liquidation eligibility is checked on-chain throughcanLiquidate. There is no protocol-internal off-chain monitor; liquidators discover underwater positions and submit liquidation transactions themselves.Liquidation Triggers: Positions become liquidatable when collateral value falls below required thresholds.
Partial Liquidations:
liquidateExact(debtAmounts, accounts, collateralToken)lets a liquidator specify per-account repayment amounts smaller than the maximum permitted;liquidate(accounts, collateralToken)liquidates each account up to the protocol-permitted maximum.Bad Debt: Independently of partial vs full liquidation, when the seized collateral cannot cover the debt being liquidated, the shortfall is recognized as bad debt and
_totalAssetsis reduced by that amount (lenders absorb the loss). ABadDebtRecognizedevent is emitted.Incentive Structure: Liquidators receive collateral bonuses for maintaining market health.
Flash Loan Functionality
BorrowableCTokens provide flash loan capabilities for advanced users:
Uncollateralized Borrowing: Temporary loans without collateral requirements.
Atomic Transactions: Borrowed funds must be returned within the same transaction.
Fee Structure: 0.04% fee (4 basis points) on borrowed amounts.
Callback Integration: The borrower's contract (i.e.
msg.sender) must implementIFlashLoan.onFlashLoan(uint256 assets, uint256 assetsReturned, bytes data). The cToken does not implementIFlashLoan; it calls into the borrower's implementation during the loan.Use Cases: Arbitrage, liquidations, collateral swapping, and other DeFi strategies.
Position Manager Integration
BorrowableCTokens integrate with Position Managers for sophisticated strategies:
Leveraged Positions: Automated borrowing and collateral posting for leverage.
Cross-Asset Operations: Coordinated actions across multiple cTokens within the same isolated market (e.g. borrow
cUSDCand postcWETHcollateral atomically, against aWETH | USDCmarket).Slippage Protection: Position Manager entrypoints use
checkSlippageas an oracle-priced pre/post sanity check. It is not a full replacement for user-side swap-route slippage controls; users choose the slippage parameter and should validate route-level execution before using leverage or deleverage operations.Callback Mechanisms: Position Managers can execute custom logic during borrow operations.
Security Features
BorrowableCTokens implement multiple security layers:
Debt-Collateral Separation: An account cannot have debt and posted collateral in the same cToken. Borrowing from a BorrowableCToken reverts if the account has collateral posted in that same cToken, and posting collateral to a BorrowableCToken reverts if the account has debt in that same cToken. Accounts can still post one listed cToken as collateral while borrowing another listed cToken in the same isolated market.
Pause Mechanisms: Market Managers expose per-token
mintPaused,collateralizationPaused, andborrowPausedflags throughactionsPaused(cToken), plus market-wideliquidationPaused,redeemPaused, andtransferPausedflags.Reentrancy Protection: Deposit, mint, withdraw, redeem, collateral, borrow, repay, liquidation, and accrual entrypoints are protected with
nonReentrant.flashLoan()is not markednonReentrant; it accrues before transferring assets, callsmsg.sender.onFlashLoan(...), then pulls backassets + fee.Interest Rate Limits: Maximum bounds on interest rates to prevent exploitation.
Oracle Integration: Each priced asset is sourced through a single Chainlink-like adaptor configured per asset, with heartbeat and staleness checks on every read. Pegged assets (LSTs and stablecoins) carry an additional price-guard layer that constrains the reported price to a bounded range relative to a reference feed.
Flash Loan Protection: Borrowing or posting collateral starts a 20-minute hold window for the account. During this window the account cannot repay debt, transfer cToken shares, redeem, or withdraw through cToken paths that consult the Market Manager. Plain deposits are unaffected by the hold period.
Protocol Revenue
BorrowableCTokens generate protocol revenue through multiple mechanisms:
Interest Fees: A configurable percentage (
interestFee, max 60%, in BPS) of each interest accrual is captured by minting new BorrowableCToken shares directly tocentralRegistry.daoAddress(). The fee is paid in shares, not in underlying, so the DAO's claim grows pari passu with lenders' claims.Flash Loan Fees: Charged in underlying. The 4-bp fee is added to
_totalAssets, so the entire flash-loan fee accrues to lenders, not to the DAO.Excess Recovery: The DAO can call
skim()to sweep excess underlying, such as rounding dust or donations, tocentralRegistry.daoAddress().skimAvailable()returnsmarketOutstandingDebt + underlyingBalance - _totalAssets, but reverts withBaseCToken__ZeroAmount()if there is no recoverable excess.
Risk Management
Debt Caps: Maximum borrowing limits per market to control protocol exposure.
Collateral Factors: Loan-to-value ratios based on asset risk profiles.
Liquidation Thresholds: Dynamic thresholds that trigger liquidations before insolvency.
Oracle Safeguards: Curvance routes asset pricing through the Oracle Manager. In current live deployments, each priced asset uses a single Chainlink-like adaptor with heartbeat and staleness checks. Pegged or reference-priced assets can also use price guards that constrain reported prices to configured bounds.
Interest Rate Bounds: DynamicIRM bounds the annual base rate, annual vertex rate, vertex start, adjustment velocity, decay rate, and maximum vertex multiplier. It does not enforce a generic minimum borrow or supply rate.
Integration Considerations
State Updates: Always call functions that update interest before relying on debt or balance information.
Market Conditions: Interest rates can change based on utilization and DynamicIRM vertex multiplier updates. Liquidation requirements, incentives, close factors, collateral caps, and debt caps are Market Manager token configuration, with optional transient liquidation values during auction-based liquidations; they are not utilization-driven thresholds.
User Interaction Functions
Borrow Functions
borrow()
Contract: BorrowableCToken.sol
Description: Borrows underlying tokens from lenders, based on collateral posted inside this market.
Function signature:
uint256
assets
The amount of the underlying asset to borrow.
address
receiver
The address that will receive the borrowed assets.
Event:
borrowFor()
Contract: BorrowableCToken.sol
Description: Used by a delegated user to borrow underlying tokens from lenders, based on collateral posted inside this market by account
Function signature:
uint256
amount
The amount of the underlying asset to borrow.
address
receiver
The account that will receive the borrowed assets.
address
owner
The account whose collateral backs the new debt. The caller must be approved as owner's delegate.
Event:
Borrow Functions
flashLoan()
Contract: BorrowableCToken.sol
Description: Lends underlying assets to msg.sender for the duration of a single transaction. The borrower's contract must implement IFlashLoan.onFlashLoan(uint256 assets, uint256 assetsReturned, bytes calldata data) external returns (bytes32) and, before flashLoan returns, repay assets + flashFee(assets) to the cToken. The callback return value is ignored by the cToken. The fee is 4 bps (0.04%) and accrues to lenders by being added to _totalAssets. flashLoan checks the cToken's actual underlying ERC20 balance, not assetsHeld(), and reverts with BorrowableCToken__InsufficientAssetsHeld() if the requested amount exceeds that balance.
Function signature:
uint256
assets
The amount of asset() loaned during the flashloan.
bytes
data
Arbitrary calldata passed to flashloan callback to execute desired action during the flashloan.
Event:
Repay Functions
repay()
Contract: BorrowableCToken.sol
Description: Repays underlying tokens to lenders, freeing up their collateral posted inside this market and updates interest before executing the repayment.
Function signature:
uint256
assets
The amount of the underlying asset to repay, or 0 for the full outstanding amount
Event:
repay() does not emit PositionUpdated.
repayFor()
Contract: BorrowableCToken.sol
Description: Repays underlying tokens to lenders, on behalf of account, freeing up their collateral posted inside this market.
Function signature:
uint256
assets
The amount to repay, or 0 for the full outstanding amount.
address
account
The account address to repay on behalf of. repayFor is not delegate-gated; any payer can repay debt for owner.
Event:
Debt Tracking
debtBalance()
Contract: BorrowableCToken.sol
Description: Returns the current debt balance for a given account without accruing pending interest. This view uses the account's stored debt amount and debt index with the market debt index packed in _vestingData; it does not use the cToken exchange rate.
Function signature:
address
account
The address whose debt balance should be calculated
Return data:
uint256
The current balance of debt for the account
debtBalanceUpdated()
Contract: BorrowableCToken.sol
Description: Returns the current outstanding debt balance of an account with pending interest applied via _accrueIfNeeded().
Function signature:
address
account
The address whose debt balance should be calculated
Return data:
uint256
The current outstanding debt for account after accruing pending interest in the current transaction.
Liquidation Functions
liquidate()
Contract: BorrowableCToken.sol
Description: Liquidates each underwater account in accounts for as much debt as the protocol permits, transferring the seized collateral from collateralToken to the liquidator. Accrues pending interest before computing liquidation amounts. Reverts if collateralToken == address(this), since debt and collateral cannot share a cToken. Any shortfall between seized collateral and debt is realized as bad debt and absorbed by lenders via a reduction in _totalAssets.
Function signature:
Event:
liquidateExact()
Contract: BorrowableCToken.sol
Description: Liquidates underwater accounts by repaying exactly debtAmounts[i] of debt for accounts[i]. The two arrays must be the same length or the call reverts. The accounts array must be sorted in strictly ascending address order; canLiquidate reverts if any account is less than or equal to the previous account. Each exact amount must be no larger than the protocol-permitted maximum and must be backed by enough collateral shares for that exact liquidation. Use liquidate() when you want each account liquidated to the maximum permitted; use liquidateExact() when you need fine-grained control over per-account repayment, such as liquidating multiple accounts atomically with a fixed debt-token budget.
Function signature:
Event: Same as liquidate() above.
Flash Loan Helpers
flashFee()
Contract: BorrowableCToken.sol
Description: Returns the fee (in underlying asset units) that will be charged for a flash loan of assets. The fee is assets * 4 / 10000 rounded up, and accrues entirely to lenders by being added to _totalAssets at the end of the loan. Pure view; safe to call off-chain or from inside other contracts.
Function signature:
Event: None (pure function).
Market State Views
assetsHeld()
Contract: BorrowableCToken.sol
Description: Returns the amount of underlying currently available for borrows and ordinary withdrawals that check borrowable liquidity. Computed as _totalAssets - marketOutstandingDebt - _BASE_UNDERLYING_RESERVE, so it excludes the protocol's permanent reserve (77,777 wei of underlying) that backs the initial mint. Flash loans do not use assetsHeld(); flashLoan checks the cToken's actual underlying ERC20 balance. assetsHeld() reverts with BorrowableCToken__DepositsNotInitialized() if the market has not yet been started by the Market Manager.
Function signature:
Event: None (view function).
marketOutstandingDebtUpdated()
Contract: BorrowableCToken.sol
Description: Accrues any pending interest, then returns the current market-wide outstanding debt in underlying asset units. Use this when an integration needs an up-to-the-block debt total, e.g. for utilization or APY calculations. The non-accruing analog is the public state variable marketOutstandingDebt, which reads the last-accrued value without paying for the storage write.
Function signature:
Event:
isBorrowable()
Contract: BorrowableCToken.sol
Description: Returns true for any contract inheriting from BorrowableCToken, and false for SimpleCToken and StrategyCToken. Useful for integrators that handle cToken addresses generically and need to dispatch on whether a token supports borrowing, debt views, or flash loans. Pure function; the result is fixed at deployment.
Function signature:
Event: None (pure function).
Last updated
Was this helpful?