ERC-7399: ⚡ 闪电贷 ⚡
闪电贷的接口和流程
Authors | Alberto Cuesta Cañada (@alcueca), Michael Amadi (@AmadiMichaels), Devtooligan (@devtooligan), Ultrasecr.eth (@ultrasecreth), Sam Bacha (@sambacha) |
---|---|
Created | 2023-07-25 |
Requires | EIP-20 |
Table of Contents
摘要
闪电贷是贷款方和借款方智能合约之间的一种贷款,必须在交易结束前偿还,并可选择支付费用。 此 ERC 指定了贷款方接受闪电贷请求的接口,以及借款方在贷款方执行过程中临时控制交易的接口。 还指定了安全执行闪电贷的流程。
动机
当前闪电贷生态系统的状态是分散的,缺乏标准化,这给贷款方和借款方都带来了一些挑战。 缺乏通用接口导致集成工作增加,因为每个闪电贷提供商都实现了自己独特的方法。 预计随着生态系统的发展,这种缺乏标准化的情况会变得更加严重,需要更多资源来维持兼容性。
对现有闪电贷协议的全面分析揭示了其实现方式的显着差异,包括:
- 在不同平台上启动闪电贷的语法不一致。
- 贷款接收方和回调接收方之间的关系存在差异,有些协议允许每个角色使用不同的地址,而另一些协议则不允许。
- 还款机制各不相同,有些贷款方从贷款接收方提取本金和费用,而另一些贷款方则要求贷款接收方手动返还资金。
- 对闪电铸造的处理方式存在差异,有些贷款方允许创建任意数量的本地资产而不收取费用,从而有效地允许受计算约束而非资产所有权限制约束的闪电贷。
为了解决这些不一致之处并促进更高效、更易于访问的闪电贷生态系统,此 ERC 指定了一个标准化接口,该接口涵盖了贷款方和借款方所需的最大灵活性。 通过将各种方法整合到一个统一的标准中,本提案旨在简化集成过程,使借款方能够无缝地在闪电贷方之间切换,而无需修改代码。
规范
本文档中的关键词“必须(MUST)”、“禁止(MUST NOT)”、“需要(REQUIRED)”、“应该(SHALL)”、“不应该(SHALL NOT)”、“应当(SHOULD)”、“不应当(SHOULD NOT)”、“推荐(RECOMMENDED)”、“不推荐(NOT RECOMMENDED)”、“可以(MAY)”和“可选(OPTIONAL)”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
根据此标准,闪电贷是指从 lender
借出的 ERC-20 asset
的 amount
贷款。 此贷款可以在 lender
中的单个 flash
调用期间保持开放。
此 amount
加上 fee
(由 lender
以相同的 asset
定义)必须在 flash
调用结束之前,在由 lender
定义的 repayment receiver 地址处偿还。
flash
函数由 initiator
调用,后者定义 loan receiver、callback receiver、callback function、asset
和 amount
。
当 initiator
在 lender
中调用 flash
时,lender
会将 amount
的 asset
转移到 loan receiver。
lender
在将 amount
的 asset
转移到 loan receiver 后,将在 callback receiver 上执行 callback function。 lender
将在此 callback function 调用中包含与此标准中定义的贷款相关的多个参数。
amount
和 fee
需要在 flash
调用结束之前转移到 repayment receiver
。 fee
可以设置为零 asset
。
lender 决定支持哪些资产。 lender 可以决定支持所有可能的资产。
贷款方规范
lender
必须实现 ERC-7399 接口。
// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.6.0 <0.9.0;
import { IERC20 } from "./IERC20.sol";
interface IERC7399 {
/// @dev 可供借出的货币数量。
/// @param asset 贷款货币。
/// @return 可以借用的 `asset` 数量。
function maxFlashLoan(
address asset
) external view returns (uint256);
/// @dev 特定贷款的费用。如果贷款不可能,则返回 type(uint256).max。
/// @param asset 贷款货币。
/// @param amount 外借的资产数量。
/// @return 除返还本金外,将收取的 `asset` 金额。
function flashFee(
IERC20 asset,
uint256 amount
) external view returns (uint256);
/// @dev 启动闪电贷。
/// @param loanReceiver 接收闪电贷的地址
/// @param asset 要贷款的资产
/// @param amount 要贷款的金额
/// @param data ABI 编码的用户数据
/// @param callback 回调函数的地址和签名
/// @return result 回调的 ABI 编码结果
function flash(
address loanReceiver,
ERC20 asset,
uint256 amount,
bytes calldata data,
/// @dev 回调。 它是回调接收方地址和回调函数的签名的组合。
/// @dev 它被打包编码为 20 字节 + 4 字节。
/// @dev 回调函数的返回值未在参数中编码,但必须是 `returns (bytes
/// memory)`,以符合标准。
/// @param initiator 调用此函数的地址
/// @param paymentReceiver 需要在回调结束时接收金额加费用的地址
/// @param asset 要贷款的资产
/// @param amount 要贷款的金额
/// @param fee 要支付的费用
/// @param data 要传递给回调的 ABI 编码数据
/// @return result 回调的 ABI 编码结果
function(address, address, IERC20, uint256, uint256, bytes memory) external returns (bytes memory) callback
)
external
returns (bytes memory);
}
maxFlashLoan
函数必须返回 asset
的最大可用贷款。 maxFlashLoan
函数不得回退。 如果指定 asset
不可能进行闪电贷,则返回的值必须为零。
flashFee
函数必须返回 amount
asset
贷款收取的费用。 flashFee
函数不得回退。 如果指定的 asset
和 amount
不可能进行闪电贷,则返回的值必须为 type(uint256).max
。
flash
函数必须执行作为参数传递的回调。
bytes memory result = callback(msg.sender, address(this), asset, amount, _fee, data);
flash
函数必须在执行回调之前将 amount
的 asset
转移到 loan receiver。
flash
函数必须在回调中包含 msg.sender
作为 initiator
。
flash
函数不得修改收到的 asset
、amount
和 data
参数,并且必须将它们传递给回调。
flash
函数必须在回调中包含一个 fee
参数,其中包含除本金外为贷款支付的费用,确保 fee == flashFee(asset, amount)
。
在回调结束之前,payment receiver
的 asset
余额必须从回调开始时的金额增加 amount + fee
,否则如果不是 true,则回退。
flash
函数的返回必须与回调的返回相同。
接收方规范
闪电贷的 callback receiver 必须至少实现一个具有以下参数和返回值的外部函数:
/// @dev 此函数可以有任何名称并被重载。
/// @param initiator 调用此函数的地址
/// @param paymentReceiver 需要在回调结束时接收金额加费用的地址
/// @param asset 要贷款的资产
/// @param amount 要贷款的金额
/// @param fee 要支付的费用
/// @param data 要传递给回调的 ABI 编码数据
/// @return result 回调的 ABI 编码结果
function(address, address, IERC20, uint256, uint256, bytes memory) external returns (bytes memory) callback;
理由
选择此 ERC 中描述的接口是为了涵盖已知的闪电贷用例,同时允许安全且节省 gas 的实现。
maxFlashLoan
和 flashFee
返回不可能贷款的数值,以允许对贷款方进行排序,而无需处理回退。
当 lender
无法提供贷款时,maxFlashLoan
返回的值与不可能的贷款一致。
当 lender
无法提供贷款时,flashFee
返回的值与不可能的贷款一致。
已选择 flash
作为函数名,因为该动词具有足够的描述性,不太可能与 lender
中的其他函数冲突,并且包括贷款方持有或铸造所贷款资产的用例。
现有的闪电贷方都从同一合约提供多种资产类型的闪电贷。 在 flash
和回调函数中提供 asset
参数与观察到的功能非常匹配。
包含 bytes calldata data
参数是为了让 initiator
将任意信息传递给 receiver
。 receiver
可以使用 bytes memory
返回值将任意信息传递回 initiator
。
回调函数通常需要一个 initiator
,lender
将其视为 msg.sender
。 另一种实现方式是通过调用者将 initiator
嵌入 data
参数中,这将需要额外的机制来使接收方验证其准确性,因此不建议这样做。
采用 loan receiver 作为参数,以允许在单独的贷款发起方、贷款接收方和回调接收方的实现方面具有灵活性。 此参数未传递给 callback receiver,原因是它通常与 callback receiver 相同,如果不是,则可以由 initiator
在 data
中对其进行编码。
payment receiver
允许在还款方面与借款相同的灵活性。 控制流和资产流是独立的。
回调函数中将需要 amount
,lender
将其作为参数。 另一种实现方式是通过调用者将 amount
嵌入 data
参数中,这将需要额外的机制来使接收方验证其准确性,因此不建议这样做。
通常会在回调函数中计算 fee
,回调接收方必须知道 fee
以进行还款。 将 fee
作为参数传递而不是附加到 data
简单有效。
回调接收方上的任意回调函数允许对回调接收方上的闪电贷实施不同的行为,而无需使用 data
参数对函数路由器进行编码。 函数调用类型为 24 个字节,其中前 20 个字节是目标地址,最后 4 个字节是函数签名。
amount + fee
被推送到 payment receiver
以允许资产和控制流的分离。 虽然“拉取”架构更为普遍,但“推送”架构也很常见。 对于 lender
无法实现“推送”架构的情况,一个简单的包装器合约可以提供本提案的外部接口,同时使用来自使用“拉取”架构的 lender
的流动性。
向后兼容性
此 EIP 是 ERC-3156 的后继产品。 虽然不是直接向后兼容,但使用从 ERC-3156 闪电 lender
获得的流动性提供本提案的外部接口的包装合约很容易实现。
安全考虑事项
验证回调参数
闪电贷回调的参数预计会反映闪电贷的条件,但不能无条件地信任它们。 它们可以分为两组,在可以信任它们是真实之前需要进行不同的检查。
- 在没有某种验证的情况下,不能假定任何参数是真实的。 如果回调的调用者决定撒谎,
initiator
、asset
和amount
指的是可能没有发生的过去交易。fee
可能是假的或计算不正确。data
可能已被调用者操纵。 - 为了相信
initiator
、asset
、amount
和fee
的值是真实的,一种合理的模式是验证回调调用者是否在经过验证的闪电贷方白名单中。 由于通常调用flash
的人也会收到回调,因此这很简单。 在所有其他情况下,如果要信任回调中的参数,则需要批准闪电贷方。 - 为了相信
data
的值是真实的,除了第 1 点中的检查之外,建议验证initiator
是否属于一组受信任的地址。 信任lender
和initiator
足以信任data
的内容是真实的。
闪电贷安全考虑事项
自动批准
任何偿还作为参数收到的 amount
和 fee
的 receiver
都需要在回调中加入一种机制来验证发起方和 lender
是否受信任。
或者,回调接收方可以实现权限函数,这些函数设置状态变量,指示已启动闪电贷以及期望的 amount
和 fee
。
或者,回调接收方可以验证 loanReceiver
是否收到了 amount
,并使用自己的启发式方法来确定 fee
是否公平且贷款已偿还,或者交易已回退。
闪电铸造外部安全考虑事项
闪电铸造交易中涉及的典型资产量将产生新的创新型攻击向量。
现货预言机操纵
可以轻松操纵闪电可铸造资产的供应,因此考虑闪电可铸造资产供应的预言机必须要么折算闪电铸造的金额,要么生成随时间平均的数据,要么找到其他解决不断变化的供应的方法。
算术溢出和下溢
如果闪电铸造提供商不对交易中闪电可铸造资产的数量设置任何限制,那么任何人都可以闪电铸造 $2^256-1$ 数量的资产。
闪电铸造接收端的协议需要确保它们的合约可以处理此问题,方法是使用将溢出保护嵌入到智能合约字节码中的编译器,或者设置显式检查。
闪电铸造内部安全考虑事项
在同一平台中将闪电铸造与业务特定功能相结合很容易导致意想不到的后果。
金库耗尽
假设一个智能合约闪电贷其原生资产。 当用户烧毁原生资产时,同一智能合约会向第三方借款。 此模式将用于将多个用户的抵押债务聚合到第三方中的单个帐户中。 闪电铸造可用于导致 lender
借款到其限额,然后在底层 lender
中推高利率,清算闪电 lender
:
- 从
lender
闪电铸造大量的 FOO。 - 将 FOO 兑换为 BAR,导致
lender
从underwriter
借款直至其借款限额。 - 触发
underwriter
中债务利率的增加,使lender
抵押不足。 - 清算
lender
以获取利润。
版权
通过 CC0 放弃版权和相关权利。
Citation
Please cite this document as:
Alberto Cuesta Cañada (@alcueca), Michael Amadi (@AmadiMichaels), Devtooligan (@devtooligan), Ultrasecr.eth (@ultrasecreth), Sam Bacha (@sambacha), "ERC-7399: ⚡ 闪电贷 ⚡ [DRAFT]," Ethereum Improvement Proposals, no. 7399, July 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7399.