For the complete documentation index, see llms.txt. This page is also available as Markdown.

Flash Loans

Curvance flash loans are available through BorrowableCToken.flashLoan(uint256 assets, bytes data). A receiver contract calls the cToken, receives the cToken's underlying asset, executes its callback, and must let the cToken pull back assets + fee before the transaction ends.

Curvance flash loans are ERC-3156-inspired, not fully ERC-3156-compatible. The callback returns bytes32, but Curvance's callback signature is onFlashLoan(uint256 assets, uint256 assetsReturned, bytes calldata data), not the ERC-3156 receiver signature. The cToken also does not validate the returned bytes32.

Flash loans are not normal borrows. flashLoan does not create account debt, does not check debtCaps, does not check account collateral, and does not call the MarketManager hold-period hooks. The amount check is based on the underlying tokens currently held by the cToken.


Key Concepts

What is a Flash Loan?

A flash loan is an uncollateralized loan that must be repaid inside the same transaction. If the receiver does not return the borrowed assets plus the fee, the repayment pull reverts and the whole transaction is unwound.

In Curvance, the caller is also the receiver. BorrowableCToken.flashLoan sends the underlying to msg.sender and then calls IFlashLoan(msg.sender).onFlashLoan(...). There is no separate receiver argument.

Flash Loan Fee

  • Fee rate: FLASHLOAN_FEE = 4, with BPS = 1e4, so the fee is 4 basis points, or 0.04%.

  • Rounding: flashFee(assets) computes ceil(assets * 4 / 10_000). For any nonzero flash loan, the fee is at least one smallest unit of the underlying asset, for example 1 wei for WETH or 1 micro-USDC for USDC.

  • Where the fee goes: after repayment, the cToken adds the fee to _totalAssets. That increases the assets backing cToken shares, so flash-loan fees accrue to lenders.

  • No hold-period effect: flashLoan does not set or check the 20-minute MIN_HOLD_PERIOD. If your callback separately borrows, posts collateral, repays, redeems, withdraws, or transfers cTokens, those calls still follow their normal Curvance checks.


Implementation Steps

Receiver Contract

The repository's IBorrowableCToken interface does not declare flashLoan or flashFee. For a Solidity receiver, define a small local cToken interface for the flash-loan surface, or import the concrete BorrowableCToken type if that fits your project.

Your contract must implement onFlashLoan() to receive the callback:

Note: IFlashLoan declares both flashFee and onFlashLoan. Curvance's cToken only invokes onFlashLoan on your contract; flashFee is declared for ERC-3156 parity. If you implement IFlashLoan and don't want to duplicate the math, delegate to the cToken's own flashFee as shown above.

Calling flashLoan()

Use placeholder addresses in examples and source real values from the deployment registry for the chain you're on.

Call flashLoan(uint256 assets, bytes calldata data) on the cToken:

Use the underlying token's balanceOf(cTokenAddress) for exact flash-loan availability. assetsHeld() is useful for borrow-market accounting, but it is not the flashLoan limit because it subtracts outstanding debt and the base reserve. The flash-loan implementation checks the cToken's raw underlying balance.


Flash Loan Execution Flow

Inside BorrowableCToken.flashLoan, execution proceeds in this order:

  1. Accrue pending interest with _accrueIfNeeded().

  2. Revert on assets == 0.

  3. Check assets <= asset.balanceOf(address(this)).

  4. Compute fee = flashFee(assets) and assetsReturned = assets + fee.

  5. Transfer assets of the underlying token to msg.sender.

  6. Call IFlashLoan(msg.sender).onFlashLoan(assets, assetsReturned, data).

  7. Pull assetsReturned from msg.sender with safeTransferFrom.

  8. Add fee to _totalAssets.

  9. Emit Flashloan(assets, fee, msg.sender).


Important Considerations

1. Available liquidity

Before initiating a flash loan, ensure the cToken holds enough underlying:

2. Fee Calculation

Always query the fee from the contract; don't recompute it off-chain. The on-chain implementation rounds up:

3. Token Approval

Your contract must have approved the cToken to pull assets + fee by the time onFlashLoan returns. The simplest option is to approve inside the callback:

If you fail to approve, or your contract's balance is insufficient at callback-end, the safeTransferFrom pull inside flashLoan reverts and the entire transaction is unwound.

4. Callback-spoofing protection

Any address can call onFlashLoan on your contract; the require-check on msg.sender == borrowableCToken is load-bearing. Without it, an attacker can trigger your business logic with attacker-controlled assets and data. This is the single most common flash-loan receiver mistake.

5. Reentrancy

flashLoan itself is not nonReentrant, but every deposit, withdraw, redeem, borrow, repay, and collateral entrypoint on the cToken is. Nested flash loans on the same cToken may be possible; nested deposits / redeems / borrows / repays from your callback revert with the reentrancy lock. Design flows to avoid reentering the cToken during the callback.

6. Return Value

Curvance does not validate the callback's returned bytes32. Returning the ERC-3156 magic value, returning another bytes32, or returning bytes32(0) does not change Curvance's repayment logic. Reverting from the callback still reverts the whole flash loan.


Error Handling

flashLoan itself is not marked nonReentrant. Your callback is therefore not automatically inside the cToken's reentrancy lock. However, deposit, mint, withdraw, redeem, borrow, repay, collateral, liquidation, and several admin entrypoints are nonReentrant; once your callback enters one of those functions, that function's normal reentrancy lock applies.

Treat callback-side Curvance calls as advanced flows. During the callback, the cToken's raw underlying balance is temporarily lower by the flash-loaned amount, while repayment has not happened yet. Any nested action still runs its own pause, liquidity, collateral, hold-period, and transfer checks.

Error
Meaning

BaseCToken__ZeroAmount()

flashLoan(0, data) was called.

BorrowableCToken__InsufficientAssetsHeld()

Requested assets exceeded the cToken's raw underlying token balance.

BorrowableCToken__DepositsNotInitialized()

The cToken has not been initialized and accrual reached the assetsHeld() path.

TransferFailed()

The outgoing underlying transfer from the cToken to the receiver failed.

TransferFromFailed()

The repayment pull failed, usually because the receiver's allowance or balance was too low.

ApproveFailed()

Your receiver's callback attempted a safe approval and the underlying token rejected it.

For ethers v6 integrations, use the iface.parseError(errData) pattern from Borrow: Error Handling. Include the cToken ABI, your receiver ABI, and small ABI fragments for Solady transfer errors if your generated ABI does not include them.


Last updated

Was this helpful?