6

以太坊EIP-3156简明教程【FlashLoan】

 2 years ago
source link: http://blog.hubwiz.com/2021/05/25/eip-3156-intro/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

闪电贷(Flash Loan)是一种创新的DeFi模式,但是也很容易受到黑客的攻击。事实上各种 DeFi协议都有自己的闪电贷实现方式,这增加了安全防范的难度。在这个教程中,我们将介绍 以太坊最新定稿的闪电贷规范EIP-3156,希望该标准有助于提高DeFi协议中闪电贷的安全行。

1、什么是DeFi闪电贷

在普通贷款中,你需要先申请,然后等待批准或驳回。如果你成功贷款,那么需要在规定的期限内以规定的利率 偿还贷款。闪电贷很特殊,因为你无需申请、无需抵押,这意味着每个人都可以成功执行闪电贷。

要点是什么?

你必须在收到贷款的同一笔交易中偿还贷款!从贷方的角度来看,这是完全合理的。如果你立即偿还贷款, 则贷方没有风险。因此,他可以将这笔贷款提供给任何想要的人。

问题是,在同一笔交易中又借又还有什么好处?

这显然会极大地限制使用范围,但是由于以太坊的强大功能,我们仍然可以做很多事情。最常见的是, 你可以使用闪电贷进行套利,同时大幅降低自己的资金需求。无需在多个帐户中拥有数百万美元即可最大化利润, 你需要的只是获取一笔快速贷款,在一次交易中尽可能套利,然后在交易结束时偿还贷款。

但也如前所述,这使得闪电贷通常被用于黑客攻击。如果你在智能合约中发现了bug,可以通过闪电贷来利用 该漏洞,那有可能你可以最终获得数百万美元的利润,然后很轻松地偿还贷款。

Alberto提出的另一个很好的用例是将闪电贷用于债务再融资。想象一下,你已经将ETH锁仓到Aave并 借出Dai,为此你需要支付10%的贷款利息。然后你使用Dai买了房子,大概十年都不会还清。现在,Compound 支持你以ETH抵押品借出Dai,仅收取9%的利息。使用Dai的一笔闪电贷,你可以偿还Aave的债务,取回放置在 Compound中的ETH抵押品。你以9%的利率从Compound借出Dai并偿还了闪电带宽。简单地说,你将10%的Aave贷款 再融资为9%的复合贷款。

2、EIP-3156闪电贷标准

许多协议都提供了闪电贷实现,例如dYdX、Aave和 Uniswap。不幸的是,这些接口都不相同。这不仅对此类 闪电贷的用户不利,开发者还必须学习如何在每个生态系统中利用闪电贷。但是,当每个人都试图自己设计 一种安全的闪电贷机制时,这对于DeFi的安全性也是不利的。

这就是EIP-3156提出的意义,它旨在支持各种不同的还贷机制。

3、EIP-3156:IERC3156FlashLender接口

希望提供闪电贷的服务必须实现IERC3156FlashLender接口,定义如下:

interface IERC3156FlashLender {
function maxFlashLoan(
address token
) external view returns (uint256);

function flashFee(
address token,
uint256 amount
) external view returns (uint256);

function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external returns (bool);
}

maxFlashLoan和flashFee这两个方法的作用不言自明。

使用flashLoan方法,你就能够执行闪电贷。接收方地址必须指向一个实现了borrower接口的合约,可以 向该方法传入任意数据。

对实现的唯一要求是,你必须调用接收方的onFlashLoan回调:

require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data)
== keccak256("ERC3156FlashBorrower.onFlashLoan"),
"IERC3156: Callback failed"
);

回调之后,该flashLoan函数必须从贷款接收方提取回金额+手续费用,如果失败,则将交易回滚。

4、EIP-3156:IERC3156FlashBorrower接口

借款方需要实现IERC3156FlashBorrower接口,定义如下:

interface IERC3156FlashBorrower {
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external returns (bytes32);
}

该接口仅定义了一个方法即onFlashLoan回调。为了避免交易回滚,在onFlashLoan实现代码中必须授权金额+手续费 给msg.sender。

请注意,通证的类型没关系。在下面的示例中,我们将使用ERC-20,但也可以使用其他标准。

5、EIP-3156闪电贷合约示例

在下面的示例中,我们将在ERC-20合约中实现闪电贷功能。我们将根据需要铸造新的通证,然后在结束时销毁。

5.1 借款方合约代码

下面是借款方合约示例代码:

function flashBorrow(
address token,
uint256 amount,
bytes memory data
) public {
uint256 allowance = IERC20(token).allowance(
address(this),
address(lender)
);
uint256 fee = lender.flashFee(token, amount);
uint256 repayment = amount + fee;
IERC20(token).approve(
address(lender),
allowance + repayment
);
lender.flashLoan(this, token, amount, data);
}

在上面的合约代码中,我们实现了flashBorrow功能。lender指向在部署时定义的IERC3156FlashLender实现合约。

我们首先授权通证给贷方以偿还总金额。还款额计算为贷款额+费用。我们可以通过调用flashFee方法来获取 手续费的具体数额。

最后,我们执行flashLoan方法。

当然,现在我们还需要调用借款方的onFlashLoan回调函数。在示例中,我们执行如下操作:

  • 验证交易发起方确实是贷方
  • 验证闪电贷的发起人确实是我们的合约
  • 返回预定义的哈希值以验证成功的闪电贷

如果需要,我们可以根据传入的额外数据在此处实现其他逻辑。

代码如下所示:

function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external override returns(bool) {
require(
msg.sender == address(lender),
"FlashBorrower: Untrusted lender"
);
require(
initiator == address(this),
"FlashBorrower: Untrusted loan initiator"
);

// optionally check data here if wanted

return keccak256("ERC3156FlashBorrower.onFlashLoan");
}

现在,我们可以进入贷方实现,在其中执行实际的闪电贷逻辑。

5.2 贷款方合约代码

让我们从maxFlashLoan函数开始:

function maxFlashLoan(
address token
) external view override returns (uint256) {
return type(uint256).max - totalSupply();
}

该函数通常可以返回账户余额,但是由于我们新铸造了要求的通证,因此可以允许闪电贷 直至totalSupply溢出。

接下来我们让flashFee方法返回总贷款额的0.1%,即手续费为总额的千分之一:

function flashFee(
address token,
uint256 amount
) public view override returns (uint256) {
require(
token == address(this),
"FlashMinter: Unsupported currency"
);

uint256 fee = 1000; // 0.1 %.
return amount * fee / 10000;
}

借款方和贷款方都需要调用此方法来决定闪电贷的手续费具体数额。

现在我们终于可以实现flashLoan功能了。在下面的代码中,我们将:

  • 向借款人铸造所要求的金额
  • 执行onFlashLoan回调并验证其返回值
  • 检查还款是否被批准
  • 减少还款额的配额并将其销毁

代码如下:

function flashLoan(
IERC3156FlashBorrower receiver,
address token,
uint256 amount,
bytes calldata data
) external override returns (bool) {
require(token == address(this), "FlashMinter: Unsupported currency");

uint256 fee = flashFee(token, amount);
_mint(address(receiver), amount);

bytes32 CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
require(
receiver.onFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS,
"FlashMinter: Callback failed"
);

uint256 _allowance = allowance(address(receiver), address(this));
require(_allowance >= (amount + fee), "FlashMinter: Repay not approved");

_approve(address(receiver), address(this), _allowance - (amount + fee));
_burn(address(receiver), amount + fee);

return true;
}

你可以在这里查看完整的EIP3156合约示例代码。

6、让现有的闪电贷协议兼容EIP3156

有一个有趣的项目是EIP-3156包装器, 它可以将现存的闪电贷协议封装为EIP3156兼容接口,目前已经支持如下协议:

  • Uniswap
  • Yield

其中针对dYdX的包装器已经部署到以太坊主网。


原文链接:EIP-3156: Creating a standard for Flash Loans

汇智网翻译整理,转载请标明出处


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK