Harvest.finance闪电贷攻击事件的全盘梳理
2021-04-16 # 智能合约

前言

2020年10月16日,DeFi项目Harvest.finance遭受黑客攻击,黑客利用闪电贷,套利2400万美元,涉及金额巨大,轰动一时

本文旨在通过全盘梳理攻击流程和代码细节,一窥闪电贷套利的秘密

全盘梳理

基础信息

攻击者地址:0xF224ab004461540778a914ea397c589b677E27bb

攻击合约地址:0xc6028a9Fa486F52efd2B95B949AC630d287CE0aF

首次攻击tx:0x35f8d2f572fceaac9288e5d462117850ef2694786992a8c3f6d02612277b0877

VaultProxy(fUSDC):0xf0358e8c3CD5Fa238a29301d0bEa3D63A17bEdBE

CRVStrategyStableMainnet:0xD55aDA00494D96CE1029C201425249F9dFD216cc

VaultYCRV:0xF2B223Eb3d2B382Ead8D85f3c1b7eF87c1D35f3A

CRVStrategyYCRVMainnet:0x2427DA81376A0C0a0c654089a951887242D67C92

convertor:0xfCA4416d9dEF20aC5b6Da8b8b322b6559770eFbF

*为方便起见,后面提到的地址均只用地址前4位代表

交易始末

从tx0x35f8中的代币转移记录中可以大致看出事件经过

详细的合约调用过程可通过以太坊交易分析平台载入交易hash进行分析

流程分析大致如上,事件概括起来即是攻击者0xf224部署了攻击合约0xc602,然后一系列闪电贷攻击均在攻击合约的0xfdb57542方法中进行,其中核心流程就是通过Uniswap的Flash Swap进行闪电贷,先获得大量USDT和USDC为后续攻击做准备,然后重复执行如下动作:

  1. Curve ySwap中进行USDT=>USDC的巨额兑换(巨额兑换造成y池中USDC价格上涨)
  2. USDC质押存入VaultProxy fUSDC池(USDC价格上涨,铸造出较平常更多的fUSDC)
  3. Curve ySwap进行USDC=>USDT回兑(1步骤的逆操作,USDC价格恢复)
  4. VaultProxy fUSDC池中赎回USDC(USDC价格回落,赎回出较平常更多的USDC)

最后归还闪电贷并将获利的USDC兑换为ETH提取

代码细节

攻击合约未开源,暂时不作分析。可先从关键的VaultProxy fUSDC池合约0xf035deposit函数入手,分析fUSDC的铸造量是如何计算的

从质押函数中可以看出fUSDC的铸造量是根据fUSDC总量和USDC策略的总投资量的比例来决定的

underlyingBalanceWithInvestment函数实现如下:

fUSDC池代理合约0xf035会进一步调用CRVStrategyStableMainnet策略合约0xD55a去进一步查询已投资的底层资产 USDC 的量

来到稳定币策略合约0xD55ainvestedUnderlyingBalance函数实现如下:

这里的调用就稍微复杂一点了,从ycrvVault合约0xF2B2获取sharesprice,将乘积传入underlyingValueFromYCrv函数,结果与该合约 USDC 的量的和作为最后的函数返回值

我们先来看 ycrvVault 合约0xF2B2

该金库合约0xf2b2本身继承了ERC20,具有代币属性,从构造函数中可以看出代币代表 fyToken

也就是说上面获取的shares即是策略合约拥有的 fyToken 量

然后是price,来看getPricePerFullShare函数:

可以明显看出price即是yToken对fyToken的占比,那么sharesprice的乘积即代表策略合约所占有的 yToken 量,最后传入underlyingValueFromYCrv函数,在该函数中会调用convertor.yCrvToUnderlying

这里就到了整个过程中最关键的地方了,也是问题的根本所在

convertor 合约0xfCA4并未开源

我们再次回到以太坊交易分析平台,查看整个deposit调用过程

可以看到前面的调用流程分析如实,并且 convetor 的调用最终会调用 Curve 的Zap.calc_withdraw_one_coin,而该函数用于查询 lpToken 的赎回价

问题就在这里了,这里相当于就是向 Curve 问价,而调用传入的是 yToken 的量,那么返回的就是 yUSDC 兑换 USDC 的价格,即USDC/yUSDC

而当前面巨额兑换USDC后,y池中 USDC 价格上涨,那么相对价格 USDC/yUSDC 就会下跌。Harvest.finance的 USDC 策略中 yUSDC 资产所具有的 USDC 净值经calc_withdraw_one_coin计算而来就损耗减少,最终反映到deposit函数的 fUSDC 铸造算法中,将导致 fUSDC 铸造量增加

总结与思考

归根到底,Harvest.finance被攻击的本质原因在于对策略稳定币价值的估价出现了问题,直接调用易被操纵价格的 Curve 的calc_withdraw_one_coin函数来估价,从而使攻击者有机可乘

这就是一次典型的喂价机制不完善导致的价格操纵的经济攻击事件