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, withBPS = 1e4, so the fee is 4 basis points, or 0.04%.Rounding:
flashFee(assets)computesceil(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:
flashLoandoes not set or check the 20-minuteMIN_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.
The receiver must have enough underlying to cover the fee. The flash loan gives the receiver assets; the extra fee must come from profit made during the callback or from funds already held by the receiver.
Calling flashLoan()
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:
Accrue pending interest with
_accrueIfNeeded().Revert on
assets == 0.Check
assets <= asset.balanceOf(address(this)).Compute
fee = flashFee(assets)andassetsReturned = assets + fee.Transfer
assetsof the underlying token tomsg.sender.Call
IFlashLoan(msg.sender).onFlashLoan(assets, assetsReturned, data).Pull
assetsReturnedfrommsg.senderwithsafeTransferFrom.Add
feeto_totalAssets.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.
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?