Final ERC defining a unified single-asset flash-loan interface: `IERC3156FlashLender` (lender) and `IERC3156FlashBorrower` (borrower callback). Standardizes how protocols expose uncollateralized one-transaction loans so arbitrageurs, liquidators, and refinancers can interoperate across lenders.
- 01flash-loan-powered arbitrage
- 02atomic liquidations
- 03collateral-swap / debt-refinance flows
- 04cross-protocol composability
- 05lender-side flash-loan exposure
- pnpm add @openzeppelin/contracts
- forge install vectorized/solady
- forge install aave/aave-v3-core
Lenders implement `IERC3156FlashLender` with `maxFlashLoan(address token) view returns (uint256)`, `flashFee(address token, uint256 amount) view returns (uint256)`, and `flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes data) returns (bool)`. Borrowers implement `IERC3156FlashBorrower.onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes data) returns (bytes32)` and MUST return `keccak256('ERC3156FlashBorrower.onFlashLoan')` and approve the lender for `amount + fee` before returning. The lender pulls repayment via `transferFrom` after the callback. Use OpenZeppelin's `ERC20FlashMint` for ERC-20-mint-style flash loans or Aave V3's `flashLoanSimple` (3156-compatible) for pool-based loans. Always validate `msg.sender == trustedLender` and `initiator == address(this)` inside `onFlashLoan` to block arbitrary callers.
- ⚑Callback abuse: a malicious lender (or a spoofed call from any contract) can invoke your `onFlashLoan` directly — always check `require(msg.sender == trustedLender)` and `require(initiator == address(this) || initiator == trustedCaller)` first; missing these is the #1 flash-loan-borrower exploit.
- ⚑Return-value mismatch: the borrower MUST return the magic hash `keccak256('ERC3156FlashBorrower.onFlashLoan')` — returning `bytes32(0)` or `true` looks fine in tests but reverts on real lenders that check the magic value.
- ⚑Repayment uses `transferFrom`, not `transfer` — the borrower must `approve(lender, amount + fee)` inside the callback; forgetting the approval reverts after all your arbitrage logic ran.
- ⚑Reentrancy: flash loans turn every state-changing function into a potential reentrancy vector; combine `nonReentrant` modifiers with read-only-reentrancy guards on view functions used as oracles (Curve LP price, AMM TWAP).
- ⚑Fee-on-transfer / rebasing tokens silently break the standard — `amount + fee` is not what arrives at the lender; either reject these tokens explicitly or compute repayment from balance deltas.
- ⚑Flash-loan price-oracle manipulation: never read AMM spot prices in the same tx — flash loans are routinely used to bend Uniswap V2 prices and drain anything that quotes from `getReserves`; require multi-block TWAPs or Chainlink feeds.