本文深入探讨了Yield Aggregators(收益聚合器)的安全问题,以Beefy Finance为案例,强调了DeFi项目中的多种安全风险和审计要求。作者讨论了收益聚合器如何通过策略实现收益最大化,并且指出了在计算收益、资产管理以及代币特性方面的潜在攻击向量。读者能够通过案例分析,更清楚地了解Yield Aggregators的复杂性与安全性。
作者:Daniil Ogurtsov - MixBytes的安全研究员
在本文中,我们将概述在收益聚合器中遇到的安全问题,这是DeFi中非常常见的一类项目。自行业诞生以来,它们已经走过了一段漫长的道路,并积累了丰富的安全技术和最佳实践经验。
本文旨在描述特定于收益聚合器的bug。由于它们具有独特的背景,因此安全审计可更详细和深入地考虑这些特点。额外的分析框架可以帮助发现更复杂的bug。
来源:https://arxiv.org/pdf/2210.04194.pdf
DeFi项目通常需要大量的流动性来运作。流动性在行业中是有限的,项目竞争吸引流动性。因此,DeFi项目设计用以激励流动性提供者——以费用或代币铸造的形式。这些奖励取决于用户在这些DeFi项目中的活动,因此流动提供者的回报是波动的。
收益聚合器项目“聚合”了一些流动性提供者的选项,并提供一些额外的特性和工具:
他们以增长为导向运作。收益聚合器通常选择不承担不可永久性损失风险,并避免出现负回报的情况。因此,投资通常以LP代币的形式接受。在某些情况下,收益聚合器可能会提供单边存款的额外选项——有时可用于大流动性稳定币池(例如Curve上的稳定交换池)。
他们进行复利——奖励被每小时/每日再投资以提升进一步回报。这是大多数用户使用收益聚合器的主要原因。对所有流动性提供者同时在一次交易中再投资更为有效——在大多数项目中,这被称为“收割”。然而,这不仅仅是再投资,也是某些代理在积极回报时收取费用的时刻(策略构建者、收益聚合器本身、收割功能调用者)。
上述特征是任何收益聚合器的基础,但推广的产品可能有所不同。其中一些以收益聚合作为核心工具(如Yearn Finance、Beefy Finance),其他可能将其他产品推向用户,而将收益生成本身视为次要(如OUSD提供稳定币或Idle以分期的形式提供固定或杠杆利率)。这些特殊产品的转变将从我们的分析中排除。
我们看到收益聚合器的两个基本组成部分使得安全分析变得特殊。
1) 通常它们有许多金库/策略——它们的设计和风险可能显著不同。
收益聚合器更愿意为其金库/策略的安全负责。因此,收益聚合器设计了内部审计、测试、监控的流程,以便在上线前对策略进行充分分析。此外,他们邀请外部策略构建者并激励他们扩展金库/策略的市场。
这与大多数DEX的情况不同,DEX中的对之间共享同一个智能合约,但可以有不可预知的代币被交易。DEX团队仅构建智能合约标准,但通常不对交易对中的代币负责。
相比之下,收益聚合器的金库/策略很少标准化——它们有不同的交易流、连接的DeFi项目和转移的代币。因此,更广泛的坏场景可能会出现。让外部审计人员分析一些金库(尤其是核心的)是一种正常的做法。
2) 独特的信任假设集。
金库有策略构建者和管理者。他们具有某些特权功能旨在有利于流动性提供者。但这些所有者可能是潜在的恶意行为者。因此,审计人员必须明确将所有者视为等待机会利用其特权的攻击者。
操作会被操控的股份与基础代币之间的转换率
股份与接受的代币之间的转换是许多DeFi项目的痛点。错误的计算很容易被利用。
收益聚合器在这里并没有太大区别——它们通常铸造代币,Beefy Finance对此的描述非常准确“具有利息的代币化存款证明”。用户的余额并不会随着赚取的更多奖励而上升,但一个股份应该随着计入的奖励而代表越来越多的基础代币。这个转换率是动态的,可以被利用。
费率计算往往使用预言机或DEX。过度使用DEX是脆弱性的核心所在。这正是攻击者在黑客攻击最大的收益聚合器时所利用的地方。攻击者的目标是降低股份的转换率并提高后者的转换率。
对于分析的一般建议与许多使用DEX进行记账的项目相同——假设DEX可以在同一交易中设置为任何价格。如果能够在同一交易中使用不同的转换率进行存款和取款,则表明存在红旗。
依赖DEX可能有不同的形式。考虑PancakeBunny黑客(漏洞分析链接),该黑客向存款人收取每1 BNB收取3个BUNNY代币作为费用。然而,回报是以LP代币计算并需要转换为以BNB计价,而PancakeBunny在这一转换中犯了错误,这导致攻击者进行递归操控BUNNY铸造。
未能防护三明治攻击的交换
策略广泛使用交换。它们通常不只是想赚取协定费用;它们尝试最大化以额外代币获取的额外激励,如PancakeSwap为相关池的流动性提供额外CAKE交换。这些额外奖励从来不会长时间累积——策略在每次收割时会立即在DEX上进行交换。
攻击者可以利用这些以可预测的方式卖出代币的交易。他们进行三明治攻击,以增加金库的滑点并获得无风险的代币回报。
同样的问题出现在接受不同代币投资的金库中。金库在一开始会交换这些代币以获得用于生成收益的LP。
这里的建议很直接——控制交换中的风险容忍度,包括交换后最低接收代币的不变条件。
来自所有者的攻击
如前所述,最佳实践是将金库所有者/管理者视为恶意行为者。他们通常与收益聚合器团队没有关系。
这里有一些所有者/管理者通常可用的攻击向量:
代币特定风险
对于许多收益聚合器,覆盖的活跃策略数量可以达到十个或数十个——受影响的项目集和传输的代币集独特。
偏离可预测ERC-20标准的多样化代币可能引入额外的漏洞向量。这在许多DeFi项目中都是这样的,但在这里,情况控制得更少,因为收益聚合器自身的性质暗示着场景的多样性,这使得出现不寻常的代币被认为是可能的。
所有类型的不寻常代币对于策略都是风险——通货膨胀/通货紧缩、重置、带息、可钩、具有奇怪的小数位、在函数调用时不返回任何值。这就是为啥在分析策略时特别关注代币。
这也是审计人员建议在所有关键功能上添加nonReentrant修饰符的原因——以消除意外钩子的风险。这是非常简单但极其有效的。
不良的余额管理
有时奖励的计算是错误的。有些奖励代币可能会被忽视,或者被阻塞在某些智能合约中。奖励值得进行强有力的测试,因为有许多容易出错的地方。
糟糕的金库生命周期设计
糟糕的金库设计可能低估金库出现的坏场景。有些只意味着存款和取款展示完整的生命周期。这显然不是真的。金库必须与更广泛的风险特征一起运作,必须设计风险缓解程序。产生收益的池可以被黑客攻击,借贷协议上的头寸可以被清算等。
团队必须检查是否嵌入特殊功能供所有者/管理者处理此类情况(暂停、紧急提款、管理流动性头寸和杠杆/折叠)。
值得注意的是,这是最难分析的主题之一,因为这不仅需要理解代码,还需要了解什么时候出错可能发生的各种场景。
在接下来的案例研究中,我们将以一个简单的金库为例,尝试从这些攻击向量的角度进行分析。
在这里,我们将描述Beefy Finance金库以及它们如何处理攻击向量。
我们的例子是CAKE-BNB LP金库。
https://app.beefy.finance/vault/cakev2-cake-bnb
下面是该金库的智能合约系统示意图,包括资金流动。
这个Beefy金库关系到五个合约,其中三个是Beefy编写的:Vault、Strategy、BoostStaker。每个合约都有各自的角色。
Vault - 负责与用户的交互(接收LP和发送增长的LP)。每次存款都会铸造股份,每次提款都会销毁股份。这个合约是ERC20的,因此股份像普通代币一样可转让。金库是股份与LP之间转换逻辑的关键点。
https://bscscan.com/address/0xb26642B6690E4c4c9A6dAd6115ac149c700C7dfE
Strategy - 管理代币转移的整体逻辑。收割在这里进行——它应该1) 再投资增长的LP和2) 将以其他代币形式获得的奖励进行销售,在DEX上出售以获得token0和token1,将它们存入DEX交易对以将其转换为更多的LP。
https://bscscan.com/address/0xDE238C509bcCBCd91B90dE40dF3e25B43A131311
BoostStaker - 是一个中间合约,旨在通过将其一部分发送到veContract(如果该代币存在并且包括在策略中)来提升获得的代币奖励。这个Beefy策略使用了BoostStaker但没有将代币发送到veContract - 因此附加奖励实际上并没有进行耕作。
https://bscscan.com/address/0xd7c0c12c91750a6ef37580d44b0fd6af1068e615#code
MasterChef - 一个来自PancakeSwap的合约,接收LP,LP必须来自白名单,通常来自于一侧为CAKE代币的对。MasterChef持有约100万美元的CAKE余额并将其分配给LP的存款者。这是项目激励流动性提供的知名工具。这是策略中CAKE的来源。
https://bscscan.com/address/0xa5f8c5dbd5f286960b9d90548680ae5ebff07652#code
DEX - 策略并不评估CAKE代币的回报。为了不与其打交道,现金流中每个CAKE都会在DEX出售以获得更多的LP。
https://bscscan.com/address/0x10ed43c718714eb63d5aa57b78b54704e256024e
有时赚取的铸造代币(如我们例子中的CAKE)可以通过添加时间锁抵押到veContract以获得额外的铸造分发(BoostStaker模块)。但对于这个CAKE-BNB LP金库来说并不适用。
让我们逐个检查一些常见的攻击向量,并寻找一些论据来证明该金库在攻击中是安全的。
向量一:被操控的股份与基础之间的转换率
我们将展示一个简单的框架来分析转换率。我们将检查攻击者是否无法提取超过存入的金额。
步骤如下:
处理这个问题的主要合约是Vault。
这是存入/提取函数的代码。
让我们将其分解为更简单的公式。
这些公式来自代码,旨在单独说明所有组件。某些行涉及到通货紧缩代币的风险(代币在传输过程中可能会丢失)。我们在公式中保留这一部分。
此外,你可能从代码中注意到,函数withdraw()还额外检查了Vault合约上的b - LP余额。目前我们将其忽略,但稍后在分析其他向量时将重新提及。我们找到了有趣的数学错误。
我们将集中在每个公式元素上,估计攻击者操控某个元素数字的可能性。同时,我们假设攻击者拥有无限资金并可以随时闪贷。攻击者的目标是提升[shares per LP](每个LP的股份)在存入时增加,并降低[share per LP](每个LP的股份)在提款时减少——这有可能让他们提取超过存入的资金(但不一定)。
现在让我们分析所有的元素:
该参数在任何代币移动之前计算,攻击者无法通过其他函数铸造/销毁股份。此外,该参数作为合约变量存储,无需调用即可计算。
因此,这个元素是被保护的。
这些参数容易被提升——攻击者可以向这些合约发送代币,但这并不意味着会有什么危险。首先,攻击者无法撤回这些发送的代币。其次,在我们的情况中,攻击者提升了[shares per LP],但这些操控的收益少于平衡提升的成本。想象一下,提供100个股份,余额中有200个LP代币,转换率为0.5。首先,攻击者以当前费率存入100个LP代币。现在我们有150个股份和300个LP代币。然后,攻击者直接向余额发送150个代币。转换率变成150 /450 = 0.25。是的,攻击者成功地降低了[shares per LP],但这样的攻击不会带来利润。攻击者提取50个股份,但获得了450 *50 /150 = 150个LP代币,尽管花费是100个LP代币(存款)和150个代币(直接提升余额)。
因此,这个元素没有保护,但也不脆弱。
在这里,金库试图处理通货紧缩代币。
这个元素是被保护的,攻击者通常无法在一笔交易中影响此参数,但对于重置代币某些操控是可能的。攻击者可以进行余额更新交易的抢跑交易,进而使同一序列的交易具有不同的转换率。然而,必须指出的是,收益聚合器优先不与重置代币打交道(像许多DeFi项目一样)。
因此,公式是防止攻击的。我们可以从中得出什么教训?
最后的话,在这里,你可能注意到这些公式并不完全相同。问题就在于传输过程中丢失的LP百分比。在存款中是乘法,而在提款中是除法。但在通货紧缩代币的背景下这没有问题。用户的损失是永远存在的:在存款时收到更少的股份,在提款时需要更多的股份。因此,这里的公式偏差是必要的。
向量二:未防止三明治攻击的交换
对于收益聚合器,有两个常见的地方存在不受保护的交换:
让我们来观察我们选择的CAKE-BNB金库的这两个区域。
存款和提款时的代币转换
从界面页面可以看到,这个金库允许存入和提取多个代币。
https://app.beefy.finance/vault/cakev2-cake-bnb
但是,金库合约仅接受一种代币类型——LP。
因此,转换在某些外部合约中发生。
这是Beefy描述的交易流。
这是这样的交易的示例。
从交易中,我们导出了处理交换的合约。
https://bscscan.com/address/0x10ab57c5889a00c497ce6be7ce142bb9a613e362#code
此处的交换通过DEX路由器经过每个地方实现AmountOutMin——该参数在交易初始化时以数据输入,因此是离线计算的。交易示例如证实该金额不是默认设置为零。这足以减轻三明治攻击的风险。
收割或其他操作时的代币转换
策略合约是处理这些交换的合约。在我们的策略中,交换用于转换从MasterChef获得的CAKE代币作为奖励。
如你所见,此处AmountOutMin被设置为零。因此,在发生重大滑点交换的情况下,它在前置运行中是脆弱的。但上下文是,在这里发生重大滑点交易的可能性不大——池的规模远大于平均CAKE收割的数量。此外,收割可以频繁发生,因为金库在Binance智能链上运行——这降低了在一笔交易中发生大量CAKE收割的可能性。
向量三:来自所有者的攻击
我们的CAKE-BNB金库使用Beefy标准来设置角色特权——所有金库是相同且相当安全的。
向量四:代币特定风险
在我们的金库中,代币相当常见且没有额外的风险。
向量五:不良的余额管理
金库处理简单代币,但保留了某些代码来减轻通货紧缩代币传输可能出现的意外情况。在我们的金库中并不存在这样的情况,但作为标准,它的代码会从策略迁移。代码应用余额检查,因此只有实际收到的代币才被计算。
我们发现了一个微小的错误,这可能导致对股东的错误提款计算。此情况看起来不严重,但有趣的是这是如何通过设计而可能的。
问题出现在这个金库函数中,以及它的用法。
此函数计算总的金库代币,并用来计算[share per LP]。它总结了Vault、Strategy上的LP余额和MasterChef上的累积LP代币。
必须满足两个条件:
在这种情况下,股东的提款将不正常。
回想一下这个函数。
让我们设想一个简单的场景。
我们有铸造的100个股份,两位股东各50。
我们从balance()函数返回200个LP代币,合约之间的分配为:
让我们应用传输时1%的燃烧率以处理通货紧缩代币。
想象一下第一个股东提取50个股份。
根据withdraw()函数,该股东将提取98.5个代币。
当第二个股东提取50个股份时,他们将获得96.05个代币。
结果是,他们持有相同数量的股份,但提取的LP代币不同。
这是如何发生的?
每个合约都从链中的下一个合约提款。
如果Vault合约拥有一些LP代币的余额,则它不会从Strategy中提款。Strategy也是如此——它试图优先以自身的资金支付,然后再撤回BoostStaker。依此类推。
“更深”合约余额中的LP代币在通货紧缩代币的情况中变得更昂贵。
应用1%的烧毁率以传输,MasterChef上的100个LP代币将在到用户钱包时需要进行4次传输,最终仅剩下96.05。
因此,第一位股东提取了“更便宜”的代币,这种转移的最小成本。
而第二位股东留下了“更贵”的代币,将在提取时经历4次传输。
在withdraw()函数中,关键参数r依赖于balance()函数。该函数累计三个余额组件,每个组件有效值不同。
对于通货紧缩代币,将不同合约上的代币余额相加是不正确的,因为它们需要不同数量的转移。
向量六:不良的金库生命周期设计
这个向量更适用于更复杂的策略。在我们的CAKE-BNB金库中,一切都是直接的。尽管如此,金库遵循内部标准并具有紧急功能。
一些收益聚合器采用内部程序来评估金库和策略。
Yearn通过许多步骤和高风险评分引入策略:
https://docs.yearn.finance/resources/risks/risk-score#strategy-risk-score
同样,Beefy Finance也适用:
https://docs.beefy.finance/safu-protocol/beefy-safety-score
收益聚合器处理DeFi中可能发生的整个情况集。因此,审计人员必须特别关注金库的安全分析及其特殊向量。
我们只描述了最著名的安全问题,这些都是由收益聚合器特有的。显然,这里的所有智能合约漏洞都可能存在,并且具有破坏性。
MixBytes 是一支由专家区块链审计员和安全研究员组成的团队,专注于为兼容EVM和基于子战略的项目提供全面的智能合约审计和技术咨询服务。请在X上关注我们,以保持对最新行业趋势和见解的关注。
- 原文链接: mixbytes.io/blog/yield-a...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!