Flash Loans
1. Overview
ERC-3156-style flash loans, postFlashLoan variant: borrow any flash-enabled asset of any pool, uncollateralized, for the duration of one transaction. Lending is served by a single Flash singleton keyed by pool, so every call carries the target pool as its first argument and repayment is made 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.
Dormant on Arc. No leg is flash-enabled. Live
RiskConfig.flagsis0x06(SWAP_ENABLED_BIT | LIABILITY_SWAP_ENABLED_BIT) on all 37 legs;FLASH_ENABLED_BIT(bit 4,PoolConstantsLib.sol) is set nowhere, soFlash.flashLoanrevertsFeatureDisabled(Err.Resource.FLASH)fleet-wide at its flags check. The contract is deployed and the code below is the shipped behaviour; arming it on any leg is anUPDATE_RISKwrite (§5.1), and whether the feature ships at all is an open decision. Note the interaction with §6.2:initAssetwritesminLiquidity = 0and no deploy script raises it, so enabling the bit today would make 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 PoolLiquidity library. No runtime module trust: the DELEGATECALL target is compile-time fixed, not a pluggable module.
2.2. Key Features
- ERC-3156-style:
postFlashLoanvariant of the standard borrower callback (magic valuekeccak256("ERC3156FlashBorrower.postFlashLoan")), pool-keyed singleton - Multi-Asset Support: any flash-enabled asset
- Configurable Fees: pool-wide fee in pbps (
flashFeePbps / 1_000_000) - Protocol Revenue: fees split between LPs and protocol treasury
- Liquidity Constraints:
minLiquidityfloor 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=HALT_RISK_BIT|HALT_GUARDIAN_BIT= 0x0041) amount != 0
Fee quote
fee = amount * flashFeePbps / PBPS, split intoprotoFee/ LP share byprotoSharePct
Prepare and send
flashPreparerecalls from the asset hook to- Capture the pool’s token balance, then
flashSendpushesamountand re-checks the liquidity floor at the push
Callback
- Call the borrower’s
postFlashLoan()(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) flashAccountcredits the LP share to reserves andprotoFeeto the protocol ledger
- Post-callback pool balance must be at least
There are no pool hooks preFlashLoan / postFlashLoan, only the borrower’s IERC3156 callback. The step-3 recall is flashPrepare CALLing the asset hook’s preOutflow (dual ledger). maxFlashLoan , an advisory ERC-3156 quote: the floor itself is enforced pool-side at both flashPrepare and flashSend. See Hooks.
3.2. Fee Structure
fee = (amount * flashFeePbps) / 1_000_000
protocolFee = (fee * protoSharePct) / 100
lpFee = fee - protocolFee- flashFeePbps: Pool-wide parameter, PBPS, ceiling
MAX_FLASH_FEE_PBPS= 10,000 (1%). Live values: 2. Deployments - protoSharePct: Percentage to protocol (e.g., 20 = 20%)
- LP fee increases reserves, protocol fee collected separately
3.3. Security Features
- Reentrancy Protection:
nonReentrantmodifier onflashLoan() - Balance Verification: Strict check on repayment amount
- Callback Validation: ERC-3156 magic value must be returned
- Atomic Execution: All operations within single transaction
4. Usage Examples
4.1. Basic Flash Loan
The lender is the Flash singleton, not the pool, every call takes the target pool as first arg. Repayment is by raising the pool’s token balance (a plain transfer to the pool): Flash verifies pool balance >= balanceBefore + fee after the callback. Repaying via deposit/donate/swap is blocked during the callback (flash-in-flight guard in PoolIOLib).
contract Arbitrageur is IERC3156FlashBorrower {
bytes32 constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.postFlashLoan");
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 postFlashLoan(
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
Pool owner must enable flash loans per asset via risk configuration. No leg is enabled today (§1):
// Queue the flash-loan enable (LOW 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.executeUpdateRiskConfig(pool, token);RiskConfig flags:
riskConfig.flags |= FLASH_ENABLED_BIT; // Enable flash loans5.2. Fee Parameters
Flash loan fees are pool-wide and subject to the LOW-tier timelock, 1 hour (Admin._tier maps UPDATE_FEES to DELAY_LOW; Constants.sol sets LOW to 1 hours in both the PROD and the TESTNET tables):
// 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: 20, // 20% to protocol
flashFeePbps: 100 // 0.01%, the shipped value
}))
);
// Execute after the delay matures
admin.executeUpdateFeeParams(pool);6. Emergency Controls
6.1. Asset Halt
One lever disables flash loans instantly (no timelock). src names the HALT_MASK bit being set: HALT_RISK_BIT (bit 0, owner risk halt) or HALT_GUARDIAN_BIT (bit 6, guardian emergency halt). Halting is guardian-or-owner; unhaltAsset is owner-only and clears only the bit it names, so a leg is loanable again once every source that halted it has been lifted.
admin.haltAsset(pool, token, HALT_RISK_BIT); // owner risk halt
admin.haltAsset(pool, token, HALT_GUARDIAN_BIT); // guardian emergency halt
admin.unhaltAsset(pool, token, HALT_RISK_BIT); // owner only6.2. Minimum Liquidity
Reserves below minLiquidity cannot be flash loaned, protecting against liquidity drain attacks.
7. Cost shape
The loan path does one balance read before the callback and one after, and touches the asset hook only when liquid reserves are short of amount + 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 |
ZeroValue() | Requested amount is 0 |
InsufficientAmount(available, required) | Liquid reserves short of amount + minLiquidity after recall |
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
postFlashLoan() - 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 (requires timelock)
- Set fee parameters (§5.2)
- Monitor flash loan activity via events
- Set
minLiquiditydeliberately:initAssetwrites 0, so the floor is inert until a deploy config raises it