主页 > imtoken国际版和国内版 > 黄金硬核 | 一篇文章看懂DeFi上的闪电贷(以及如何使用闪电贷进行套利)
黄金硬核 | 一篇文章看懂DeFi上的闪电贷(以及如何使用闪电贷进行套利)
金色财经近期推出硬核专栏,为读者提供热门项目的介绍或深度解读。
2月16日,bZx被曝遭遇“攻击”。 “攻击者”操纵了多个DeFi项目的代币价格,从而在15秒内实现了36万美元的套利。 涉及Compound、DyDx、Uniswap、kyber、bZx等多个DeFi明星项目。 这种“攻击”利用了 DeFi 上最新的闪贷功能。 正如 bZx 所说,这种“攻击”是有史以来最复杂的攻击之一,只有对每个 DeFi 协议及其各种工具有非常深刻的理解才有可能发生,在传统金融系统中没有比这更好的了。
什么是闪电贷? 如何利用闪电贷(flashloans)进行套利? 本期Hardcore综合整理了AbitrageDAO和PeckShield最早和最新的闪电贷套利研究。
AbitrageDAO是Stake资本团队成立的DeFi套利基金,结合链上流动性和链下机器人寻求套利机会。 PeckShield 是一家区块链安全公司。
最新进展:2月18日,bZx再次发推宣布停牌,疑似再次“被攻击”,“攻击者”可能获得2388 ETH。
在交易世界中,套利是一种利用市场之间的价格差异来获利的策略。 金融市场中存在各种形式的套利机会。 加密货币也不例外,许多交易所之间存在套利机会。 套利有助于减少资产在不同市场的价格差异,也有助于增加流动性。
我们 (AbitrageDAO) 专注于在以太坊上运行合约可填充流动性 (CFL) 的去中心化交易所。 与 CFL 的去中心化交易所包括 Oasis、0x 中继器、Uniswap、Bancor 和 Kyber。 与 CFL 的交易允许交易者利用以太坊区块链的一个区块内的套利机会。
什么是闪电贷
闪电贷旨在让开发商无需提供任何抵押品即可立即获得贷款。 所有这些都是在一次交易(一个以太坊区块)中完成的。 开发者可以从 Aave 储备池借钱(注:最早关于闪电贷的讨论来自 Aave 协议,第一笔闪电贷也来自 Aave),前提是在交易结束前将流动性返还到池中。 如果这笔流动性未能及时回流到储备金中,交易将被逆转,从而保证储备池的安全。
闪电贷有许多有趣的用例,包括:
这允许更多的玩家在套利和清算之间进行游戏,因为不需要资金就可以开始。 套利机会通常不需要太多资金(在 100 美元到 10,000 美元之间)。 另一方面,清算需要大量资金来清算借款人的头寸。 许多对 Compound 或单一抵押品 DAI (SAI) 的清算需要超过 100 万美元的 ETH 或 DAI。
ArbitrageDAO 是低/无抵押快速贷款的最佳用例。 但我们相信,在接下来的几个月里,我们将看到许多使用闪电贷的激动人心的项目。
第一笔闪电贷
ArbitrageDAO 于 2020 年 1 月 18 日完成了第一笔无担保闪电贷。请参阅下面 Camilla Russo 的报告。
为了展示快速贷款的巨大潜力,我们决定专注于一种特定的套利策略,但它也可以应用于许多其他方面。 您可以从此处的钱包查看由 ArbitrageDAO 发起的交易:
在上述套利交易中,AbitrageDAO 在 Aave 上借出超过 3,100 DAI。
更多优惠:
截至 2020 年 1 月 23 日,AbitrageDAO 共从 Aave 借入约 9,400 DAI,赚取 33 DAI。
最新套利案例:bZx 2月15日闪贷套利流程全披露
2月15日的bZx闪贷套利流程来自证券公司拍盾的分析。
这不是预言机攻击。 相反,这是一个非常聪明的套利,它确实利用了 bZx 智能合约中的一个漏洞,让本应锁定的 bZx 资金流向 Uniswap,并将泄漏的资金进一步吸收到 Compound 头寸中。
五步套利
交易在这里 0xb5c8bd9430b6cc87a0e2fe110ece6bf527fa4f170a4bc8cd032f768fc5219838,在 2020-02-15 01:38:57+UTC 的区块高度 9484688。 如上图所示,这种“攻击”可以分为五个不同的步骤:Flashloan Borrow、Hoard、Margin Pump、Dump、Flashloan Repay。
1:闪贷借款。 这一步利用 dYdX 的闪电贷功能借入 10,000 ETH。
经过这一步,“攻击者”资产负债情况如下,没有收入。
2:吸金。 使用借来的贷款,“攻击者”将 5,500 ETH 作为抵押品存入 Compound 并借入 112 WBTC。 这是一个正常的Compound操作以太坊套利,累积的WBTC会在第4步中出货。
经过这一步,“攻击者”的资产负债表如下以太坊套利,仍然无利可图。
3:杠杆拉板。 积累资金后,这一步使用bZx杠杆交易功能做空ETH换取WBTC(即sETHwBTCx5)。 具体来说,“攻击者”存入了1300 ETH,并调用了bZx的保证金交易函数,即mintWithEther(不断调用marginTradeFromDeposit)。 保证金交易功能使用 KyberSwap 将借入的 5637.623762 ETH 兑换成 51.345576 WBTC。 请注意,5 倍杠杆用于借入 ETH。 本质上是将 1 WBTC 的转换率提高到 109.8 WETH,大约是正常转换率(~38.5 WETH/WBTC)的三倍。
具体来说,为了完成这笔交易,bZx 将订单转发给 KyberSwap,然后 KyberSwap 查询其储备并找到最佳汇率,实际上是 KyberUniswap 储备。 这一步实质上是 Uniswap 中 WBTC 价格的三倍。
应该通过内置的健全性检查来防止此步骤,该检查会验证该头寸在交换后不会违约。 然而,当这次攻击发生时,这个检查并没有做,我们稍后会在智能合约漏洞部分检查细节。
经过这一步,“攻击者”的资产负债表如下,仍然无利可图。
4:航运。 随着Uniswap中WBTC价格的飙升,“攻击者”将Uniswap中从Compound借来的112个WBTC卖掉,换取了WETH。
此发货步骤净收益为6871.4127388702245 ETH,平均汇率为1 WBTC = 61.4 WETH。 执行此步骤后,攻击者通过以下资产负债表获得可观的利润:
5:偿还闪电贷。 “攻击者”用112个WBTC出货获得的6871.4127388702245个ETH偿还了借给dYdX的10000个ETH,从而完成闪贷。
完成此步骤后,您可以重新计算资产和负债。 最终,“攻击者”套利了 71 个 ETH,外加两个仓位,一个在 Compound(+5500WETH/-112WBTC),一个在 bZx(-4337WETH/+51WBTC)。 当 bZx 仓位处于默认状态时,复合仓位非常有利可图。 在利用该漏洞后,“攻击者”立即开始安排偿还 Compound 债务(112BTC)以赎回抵押品(5500WETH)。 对于 bZx 位置,“攻击者”不再表现出任何兴趣。
考虑到 1 WBTC = 38.5WETH(或 1 WETH = 0.025BTC)的平均市场价格,“攻击者”可以获得 4300 ETH 等同于 112 WBTC。 结果,“攻击者”得到了 71 WETH + 5500 WETH -4300 ETH = 1271 ETH,约合 355,900 美元(假设 ETH 价格为 280 美元)。
bZx 智能合约错误
这种“攻击”背后的魔力在于 Uniswap WBTC/ETH 是如何被操纵获得高达 61.4 的利润的。 如步骤3所述,WBTC/ETH价格在正常市场价格仅38左右时被拉升至109.8,换句话说,这个巨大的价差是被人为操纵的。 然而,如此大的价差会使 bZx 头寸抵押不足。 但是为什么允许抵押不足的状态存在,这自然会导致在 bZx 智能合约实现中发现隐藏的错误。
保证金提取函数从 marginTradeFromDeposit() 开始。
如上所示,marginTradeFromDeposit() 在第 840 行调用 _borrowTokenAndUse() 并将第四个参数设置为 true
在 _borrowTokenAndUse() 内部,当 amountIsADeposit 为真时,在第 1348 行调用 _getBorrowAmountAndRate()。返回的借款金额将存储在 sendAmounts[1] 中。
同样在 _borrowTokenAndUse() 中,如果 amountIsADeposit == true,在第 1355 行中用 sendAmounts[6] 填充 sendAmounts[1] 的值(稍后会看到)。 随后,在第 1370 行调用了 _borrowTokenAndUseFinal()。
在1414行,_borrowTokenAndUseFinal()通过IBZx接口调用takeOrderFromiToken()获取交易流进入bZxContract。
有趣的来了。 在第 145–153 行中,有一个 require() 调用来检查位置是否健康。 不幸的是,在 loadDataBytes.length == 0 && sentAmounts[6] == sentAmounts[1] 的情况下,完整性检查(sanity check)bZxOracle::shoudLiquidate() 被跳过了。 这正是触发漏洞以避免完整性检查的条件。
如果我们查看 bZxOracle::shouldLiquidate(),检查第 514 行中的 getCurrentMarginAmount()