Flash Loans

1. Overview

ERC-3156-style flash loans, onFlashLoan variant: borrow any flash-enabled asset of any pool, uncollateralized, for one transaction. A single Flash singleton keyed by pool serves the lending, so every call carries the target pool as its first argument and repayment goes to the pool rather than to the singleton. This page covers the loan flow, the fee split, the liquidity floor that bounds a loan, and the borrower callback contract.

Opt-in per leg. A leg is flash-enabled only while FLASH_ENABLED_BIT (layout, PoolConstantsLib.sol) is set; with it clear, Flash.flashLoan reverts FeatureDisabled(Err.Resource.FLASH) at its flags check. Arming a leg is an UPDATE_RISK write (§5.1); which legs are armed is in deployed instances. Note §6.2: initAsset writes minLiquidity = 0, so arming a leg without raising it makes the whole liquid reserve loanable.

2. Architecture

2.1. Implementation

The Flash singleton (Flash.sol) is a UUPS implementation behind an ERC-1967 proxy; Pool bakes the proxy address in as an immutable. It serves every pool, so each public function takes address pool as its first arg. Flash calls Pool’s restricted entry points (flashSend for the token push, flashAccount for the protocol-fee ledger update) via standard external calls, which Pool answers directly; the bodies run in the linked PoolLiquidityLib library. No runtime module trust: the DELEGATECALL target is compile-time fixed.

2.2. Key Features

FeatureShape
ERC-3156-styleonFlashLoan variant of the standard borrower callback, magic value keccak256("ERC3156FlashBorrower.onFlashLoan"), pool-keyed singleton
Multi-assetAny flash-enabled asset
Configurable feesPool-wide fee in pbps (flashFeePbps / 1_000_000)
Protocol revenueFee split between LPs and the protocol treasury
Liquidity constraintminLiquidity floor enforced

3. Technical Details

3.1. Flash Loan Flow

  1. Validation

    • Reject the native sentinel: the pool holds wnative as the asset, so borrow wnative directly
    • Check asset is flash-enabled (FLASH_ENABLED_BIT); an unlisted token reads flags 0 and fails here
    • Check asset is not halted (HALT_MASK = 0x0005, see Flow guards)
    • amount != 0
  2. Fee quote

    • fee = ceil(amount * flashFeePbps / PBPS), split into protoFee / LP share by protoSharePct
  3. Prepare and send

    • Capture the pool’s token balance, then flashSend pushes amount and checks the liquidity floor Rliq≥amount+minLiquidity at the push. Nothing is recalled from a yield hook: a loan draws on liquid reserves only
  4. Callback

    • Call the borrower’s onFlashLoan() (IERC3156-style) and require the magic value
  5. Repayment Verification

    • Post-callback pool balance must be at least balanceBefore + fee (the capture predates the debit, so the net delta is +fee)
    • flashAccount keeps the whole fee in reserves: the LP share raises the index and protoFee is booked as pending treasury shares

There are no pool hooks preFlashLoan / onFlashLoan, only the borrower’s IERC3156 callback. A loan never recalls invested reserves (dual ledger, Hooks), so on a hooked leg the capacity is the liquid part only and no borrower can force a venue redemption. maxFlashLoan =Rliq-minLiquidity is the ERC-3156 quote; the floor itself is enforced pool-side in flashSend. Flash.flashLoan refuses a pool the factory did not mint.

3.2. Fee Structure

fee = ceil(amount * flashFeePbps / 1_000_000) // rounds up, toward the pool protocolFee = (fee * protoSharePct) / 100 lpFee = fee - protocolFee
  • flashFeePbps: pool-wide, PBPS, floor MIN_FLASH_FEE_PBPS = 10 (0.001%), ceiling MAX_FLASH_FEE_PBPS = 10,000 (1%). Live values: Deployments
  • protoSharePct: percentage to the protocol (10 = 10%)
  • The LP fee increases reserves; the protocol fee is booked as treasury shares and claimed separately (Admin §8)

3.3. Security Features

Reentrancy protection is the nonReentrant modifier on flashLoan(). Repayment is a strict balance check, the callback must return the ERC-3156 magic value, and every step runs inside one transaction.

4. Usage Examples

4.1. Basic Flash Loan

The lender is the Flash singleton, not the pool, and every call takes the target pool as first arg. Repay by raising the pool’s token balance with a plain transfer to the pool: Flash verifies pool balance >= balanceBefore + fee after the callback. Repaying via deposit, donate or swap_qe is blocked during the callback by the flash-in-flight guard in PoolIOLib (Pool §8.2).

contract Arbitrageur is IERC3156FlashBorrower { bytes32 constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); function executeArbitrage(address flash, address pool, address token, uint256 amount) external { bytes memory data = abi.encode(msg.sender, pool); IFlash(flash).flashLoan(pool, this, token, amount, data); } function onFlashLoan( address initiator, address token, uint256 amount, uint256 fee, bytes calldata data ) external returns (bytes32) { (, address pool) = abi.decode(data, (address, address)); // Perform arbitrage logic // ... // Repay by transferring amount + fee back to the POOL (not the Flash singleton) IERC20(token).transfer(pool, amount + fee); return CALLBACK_SUCCESS; } }

4.2. Query Available Liquidity

// Check maximum flash loan available (pool-keyed on the Flash singleton) uint256 maxLoan = flash.maxFlashLoan(pool, USDC); // Calculate fee for specific amount uint256 fee = flash.flashFee(pool, USDC, 1_000_000e6); // 1M USDC

5. Configuration

5.1. Enabling Flash Loans

The pool admin (seat or owner) enables flash loans per asset via risk configuration (Admin §5.3):

// Queue the flash-loan enable (TUNING tier, 1 hour under PROD_DELAYS) admin.requestOp( pool, uint8(IPool.OpType.UPDATE_RISK), bytes32(uint256(uint160(token))), abi.encode(riskConfig) ); // After the delay matures admin.executeUpdateRisk(pool, token);

RiskConfig flags:

riskConfig.flags |= FLASH_ENABLED_BIT; // Enable flash loans

5.2. Fee Parameters

Flash loan fees are pool-wide and ride the TUNING-tier timelock through UPDATE_FEES (Admin §3):

// UPDATE_FEES is pool-wide, so its subject is ignored. admin.requestOp( pool, uint8(IPool.OpType.UPDATE_FEES), bytes32(0), abi.encode(IPool.FeeParams({ protoSharePct: 10, // 10% to protocol flashFeePbps: 10, // 0.001% flowCooldownSecs: 15 // re-stated, not optional: the whole struct is written })) ); // Execute after the delay matures admin.executeUpdateFees(pool);

6. Emergency Controls

6.1. Asset Halt

One lever disables flash loans instantly, no timelock: HALT_BIT (bit 0). Halting is guardian, owner or seat; the owner clears it (Admin §2.1).

admin.haltAsset(pool, token, HALT_BIT); // guardian, owner or seat admin.unhaltAsset(pool, token, HALT_BIT); // owner; the seat after the guardian notice

6.2. Minimum Liquidity

Reserves below minLiquidity cannot be flash loaned, which bounds a drain. initAsset writes 0, so the floor is inert until a deploy config raises it.

7. Cost shape

The loan path does one balance read before the callback and one after, and never touches the asset hook: capacity is the liquid reserve above minLiquidity. The LP share and the protocol share are booked in the same flashAccount write.

8. Events

event FlashLoanExecuted( address indexed pool, address indexed initiator, address indexed receiver, address token, uint256 amount, uint256 fee );

9. Error Handling

ErrorTrigger
FeatureDisabled(Resource.FLASH)Flash loans not enabled for the asset, the singleton is paused, or the native sentinel was passed
FeatureDisabled(Resource.ASSET)Any HALT_MASK bit set on the asset
InvalidState()pool is not a clone the factory minted
ZeroValue()Requested amount is 0
InsufficientAmount(available, required)Liquid reserves short of amount + minLiquidity
OperationFailed()Callback did not return the magic value, or the pool balance did not grow by at least fee

10. Integration Notes

10.1. For Borrowers

  1. Implement IERC3156FlashBorrower interface
  2. Return correct magic value from onFlashLoan()
  3. Repay by transferring amount + fee to the pool before returning; no approval is involved
  4. Account for fees in arbitrage/liquidation logic

10.2. For Pool Operators

  1. Enable flash loans via risk config, timelocked (§5.1)
  2. Set fee parameters (§5.2)
  3. Monitor flash loan activity via events (§8, Observability)
  4. Set minLiquidity explicitly (§6.2); it defaults to 0
PageCovers
PoolflashSend / flashAccount, the flash-in-flight guard
AdminUPDATE_RISK and UPDATE_FEES ops, halt levers
HooksDual ledger; swaps and exits recall through preOutflow, flash does not
ComposabilityIntegrating the loan inside a larger transaction