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.flashLoanrevertsFeatureDisabled(Err.Resource.FLASH)at its flags check. Arming a leg is anUPDATE_RISKwrite (§5.1); which legs are armed is in deployed instances. Note §6.2:initAssetwritesminLiquidity = 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
| Feature | Shape |
|---|---|
| ERC-3156-style | onFlashLoan variant of the standard borrower callback, magic value keccak256("ERC3156FlashBorrower.onFlashLoan"), pool-keyed singleton |
| Multi-asset | Any flash-enabled asset |
| Configurable fees | Pool-wide fee in pbps (flashFeePbps / 1_000_000) |
| Protocol revenue | Fee split between LPs and the protocol treasury |
| Liquidity constraint | minLiquidity floor enforced |
3. Technical Details
3.1. Flash Loan Flow
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
Fee quote
fee = ceil(amount * flashFeePbps / PBPS), split intoprotoFee/ LP share byprotoSharePct
Prepare and send
- Capture the pool’s token balance, then
flashSendpushesamountand checks the liquidity floor at the push. Nothing is recalled from a yield hook: a loan draws on liquid reserves only
- Capture the pool’s token balance, then
Callback
- Call the borrower’s
onFlashLoan()(IERC3156-style) and require the magic value
- Call the borrower’s
Repayment Verification
- Post-callback pool balance must be at least
balanceBefore + fee(the capture predates the debit, so the net delta is+fee) flashAccountkeeps the whole fee in reserves: the LP share raises the index andprotoFeeis booked as pending treasury shares
- Post-callback pool balance must be at least
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 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 - protocolFeeflashFeePbps: pool-wide, PBPS, floorMIN_FLASH_FEE_PBPS= 10 (0.001%), ceilingMAX_FLASH_FEE_PBPS= 10,000 (1%). Live values: DeploymentsprotoSharePct: 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 USDC5. 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 loans5.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 notice6.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
| Error | Trigger |
|---|---|
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
- Implement
IERC3156FlashBorrowerinterface - Return correct magic value from
onFlashLoan() - Repay by transferring
amount + feeto the pool before returning; no approval is involved - Account for fees in arbitrage/liquidation logic
10.2. For Pool Operators
- Enable flash loans via risk config, timelocked (§5.1)
- Set fee parameters (§5.2)
- Monitor flash loan activity via events (§8, Observability)
- Set
minLiquidityexplicitly (§6.2); it defaults to 0
11. Related
| Page | Covers |
|---|---|
| Pool | flashSend / flashAccount, the flash-in-flight guard |
| Admin | UPDATE_RISK and UPDATE_FEES ops, halt levers |
| Hooks | Dual ledger; swaps and exits recall through preOutflow, flash does not |
| Composability | Integrating the loan inside a larger transaction |