EIP-3156: 闪电贷标准 (Flash Loans Standard)
EIP-3156 定义了以太坊上**闪电贷(Flash Loan)**的标准化接口与回调规范。
在 EIP-3156 出台前,不同借贷与做市协议(如 Aave、dYdX、Uniswap V2 Flash Swap)各自实现了一套私有的闪电贷方法签名与回调函数。借贷套利机器人、自动清算合约以及抵押品置换协议必须针对不同平台开发繁杂的适配器。EIP-3156 通过规范放贷方接口 IERC3156FlashLender 与借贷方接口 IERC3156FlashBorrower,确立了原子化无抵押借贷的行业标准。
核心接口定义
EIP-3156 严格解耦了放贷方(Lender)与借款方(Borrower)的职责:
sequenceDiagram
autonumber
actor User as 交易发起者 / 套利者
participant Borrower as 闪电借款合约 (Borrower)
participant Lender as 闪电放贷协议 (Lender)
participant DEX as 外部套利 / 清算市场
User->>Borrower: 触发套利策略
Borrower->>Lender: 调用 flashLoan(borrower, token, amount, data)
Note over Lender: 1. 记账前置:确认资金池有足够余额并锁定互斥状态
Lender->>Borrower: 转出指定代币 (无抵押物理放款)
Lender->>Borrower: 触发回调 onFlashLoan(initiator, token, amount, fee, data)
Note over Borrower: 2. 执行套利: 调动流动性在外部市场清算/兑换
Borrower->>DEX: 交换资产获取利润
Borrower->>Borrower: 确保自身持有 >= 本金 + 手续费
Borrower-->>Lender: 返回魔数 keccak256("ERC3156FlashBorrower.onFlashLoan")
Note over Lender: 3. 闭环验证:断言魔数匹配并通过 transferFrom 扣回本息
Lender->>Lender: 若扣回失败或金额不足,整笔交易 Revert
1. 放贷方接口:IERC3156FlashLender
interface IERC3156FlashLender {
/**
* @notice 查询指定代币当前最大允许借出的闪电贷额度
* @param token 拟借出的代币合约地址
* @return 若代币不支持则返回 0,否则返回池内可用最大流动性
*/
function maxFlashLoan(address token) external view returns (uint256);
/**
* @notice 查询借出指定额度所需支付的手续费
* @param token 拟借出的代币合约地址
* @param amount 拟借出的资产数量
* @return 应支付的手续费数量(若不支持则强制 Revert)
*/
function flashFee(address token, uint256 amount) external view returns (uint256);
/**
* @notice 发起闪电贷
* @param receiver 接收代币并执行回调的借款合约地址
* @param token 拟借出的代币地址
* @param amount 借贷本金
* @param data 传递给借款合约回调的任意业务参数
*/
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}
2. 借贷方接口:IERC3156FlashBorrower
interface IERC3156FlashBorrower {
/**
* @notice 借款方接收代币后的执行回调钩子
* @param initiator 触发该笔 flashLoan 的直接发起者地址
* @param token 借入的代币地址
* @param amount 借入的本金数额
* @param fee 需额外归还的手续费数额
* @param data 透传的自定义参数
* @return 必须返回固定的 32 字节魔数:keccak256("ERC3156FlashBorrower.onFlashLoan")
*/
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}
生产级状态机逻辑与闭环校验
1. 魔数确认机制 (Magic Value Verification)
标准规定成功回调必须返回确定的哈希魔数:
$$ \text{CALLBACK_SUCCESS} = \text{keccak256}(\texttt{“ERC3156FlashBorrower.onFlashLoan”}) $$
设计初衷:防止放贷合约在向任意普通合约或仅拥有回退函数(fallback)的合约转账时,将意外成功的空调用误判为合法借款逻辑完成。
2. 标准放贷合约参考实现
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC3156FlashLender} from "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol";
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract StandardFlashLender is IERC3156FlashLender, ReentrancyGuard {
bytes32 public constant CALLBACK_SUCCESS =
keccak256("ERC3156FlashBorrower.onFlashLoan");
// 费率基点 (例如 9 bps = 0.09%)
uint256 public constant FEE_BPS = 9;
uint256 public constant BPS_DENOMINATOR = 10000;
function maxFlashLoan(address token) external view override returns (uint256) {
return IERC20(token).balanceOf(address(this));
}
function flashFee(address token, uint256 amount) public view override returns (uint256) {
return (amount * FEE_BPS) / BPS_DENOMINATOR;
}
function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external override nonReentrant returns (bool) {
uint256 fee = flashFee(token, amount);
uint256 balanceBefore = IERC20(token).balanceOf(address(this));
require(amount <= balanceBefore, "IERC3156: exceed max loan");
// 1. 无抵押借出本金
IERC20(token).transfer(address(receiver), amount);
// 2. 触发借款方回调逻辑
bytes32 callbackResult = receiver.onFlashLoan(
msg.sender,
token,
amount,
fee,
data
);
require(callbackResult == CALLBACK_SUCCESS, "IERC3156: invalid callback return");
// 3. 强制扣回本金与手续费
IERC20(token).transferFrom(address(receiver), address(this), amount + fee);
// 4. 严格断言资金闭环不变量 (Invariant Check)
require(
IERC20(token).balanceOf(address(this)) >= balanceBefore + fee,
"IERC3156: balance check failed"
);
return true;
}
}
生产安全与审计威胁模型
- 回调伪造风险(Borrower 视角):
- 借款合约的
onFlashLoan是公开接口(external)。攻击者可自行直接向借款合约调用onFlashLoan,试图触发借款合约内部的资金批准与代扣逻辑。 - 防御准则:Borrower 必须在回调函数首行严格校验:
require(msg.sender == TRUSTED_LENDER, "Untrusted lender"); require(initiator == address(this), "Untrusted initiator");
- 借款合约的
- 重入与操纵(Lender 视角):
- 放贷合约在向 Borrower 转移资产时发生了外部执行流转移,必须配备严密的防重入机制(
nonReentrant),防止借款人在回调中反向进入协议的其他质押、存取接口,利用瞬态失真账本套利。
- 放贷合约在向 Borrower 转移资产时发生了外部执行流转移,必须配备严密的防重入机制(
- 手续费向下截断套利:
- 手续费计算
(amount * fee) / 10000若借贷极微小数量,截断为 0,可能导致无手续费调用放大借款频次。协议应设定最小绝对手续费。
- 手续费计算