Balancer v2 通过精度损失进行价格操纵,做空BPT

11月3日,BalancerV2的池子在多条链上被攻击,造成了1.2亿美元的损失。我来分析下攻击原理

11月3日,Balancer V2的池子在多条链上被攻击,造成了1.2亿美元的损失。我来分析下攻击原理 攻击交易: https://app.blocksec.com/explorer/tx/eth/0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742 被害合约地址: https://etherscan.io/address/0xba12222222228d8ba445958a75a0704d566bf2c8

什么是精度损失

区块链的世界没有小数点。所以如果除法结果带有小数点,要么被向上取整,要么被向下取整。 例如A用户向商家B购买一个币,如果最终成交价是3.5 U,A要么给B 3U,要么给B 4U。这个取整的方向由合约代码设定。一般都是往利好商家(项目方)的方向取整。 另外,如果结算是10000.13,向下取整为10000,也还能接受,因为损失仅为 0.13/10000 但是,如果结算是10.9,向下取整为10,这个损失就很大:0.9/10 这次攻击就利用了上述特征。

项目背景介绍

本次攻击覆盖了balancer上面多个链,多个池子,我们以ETH链的 osETH-wETH-BPT 池子为例子看一下具体发生了什么 osETH/wETH-BPT 的相关信息如下:

  • PoolId: 0xdacf5fa19b1f720111609043ac67a9818262850c000000000000000000000635
  • token: [WETH, BPT, osETH]
  • balance:
    • WETH: 4922356564867078856521
    • osETH/wETH-BPT: 2596148429267421974637745197985291
    • osETH: 6851581236039298760900
  • ScalingFactors:
    • 1000000000000000000
    • 1000000000000000000
    • 1058109553424427048 一些概念,便于理解攻击思路:
  • 用户为池子提供流动性可以获取BPT---》joinPool
  • 用户取消流动性可以销毁BPT赎回代币--〉exitPool
  • BPT可以用于swap出osETH或者swap出wETH。
  • wETH和osETH也可以互相swap。
  • BPT价格=(池子里wETH总价值+池子里osETH总价值)/BPT的TotalSupply
  • 池子提供batchSwap功能,也就是用户可以一次性把多笔swap放到这个函数里一次性完成所有swap。在batchSwap结束前所有的费用只是记账而没有真正划转。等batch结束后,再统一算嘎差进行划转,这样设计的初衷大概是为了省Gas fee。但是同样这中间的只记账不划转会造成空手套白狼(类似闪电贷)

    攻击思路

    所有的攻击都是在一次batchSwap里搞定的,在这次调用里,黑客构造了多笔swaps

    1. 首先把大量BPT投入池子,swap出osETH和wETH。注意在这些swaps中没有真正的BPT划转到池子里,只是记账用户应该付多少BPT。此时的BPT价格还是在正常价格内波动。
    2. 用户通过osETH和wETH互相swap触发精度损失压低了池子里的(池子里wETH总价值+池子里osETH总价值),导致BPT价格降低(分子降低,分母不变)。
    3. BPT价格大幅降低,黑客把之前在第一步欠的BPT还回池子,账面上黑客不再欠池子钱,整个batchSwap完成。 整体流程就是一次精准的做空BPT,方法是利用精度损失漏洞操纵BPT价格暴跌。

      攻击步骤

      第一步:

      攻击者通过 batchSwap 用 osETH/wETH-BPT 多次换出 WETH 和 osETH 代币,大幅降低 Pool 中的流动性。 image.png 攻击者只能够每次兑换大约 99% 的代币余额,就是为了留有空间给 fee 的计算。最终只能通过多次兑换来不断减少 balance 的值,直到 6700。

      第二步:

      此时 Pool 中的流动性已经被大量移除,黑客在这个基础上利用精度丢失问题操控了 WETH 和 osETH 兑换 osETH/wETH-BPT 的比例。 在 Phase2 中反复进行以下操作:

    4. Swap WETH to osETH,使得 osETH 的余额为 18
    5. Swap WETH to osETH,amountOut 为 17 osETH,剩余 1 osETH 在 Pool 中
    6. Swap osETH to WETH,将部分 osETH 兑换成 WETH 其中一组 onSwap 操作 image.png 为什么兑换出的值是17: 进入到 _swapGivenOut() 函数中时,会通过 _upscale 对 osETH 的值进行缩放(因为他是 rebasing token),计算其对应的 ETH 数量。 image.png image.png 由于 osETH 的 ScalingFactor 为 1058109553424427048,当 amountOut 为 17 时能够计算得到最大的精度丢失值。 也就是兑换 amountOut 为价值 17.98 ETH的 osETH,只需要按照 17 的数量来提供 WETH。每次进行这个操作都会多获取到池子中的 0.98 个 osETH,使得 invariant 的值不断缩小。

      第三步:

      在经过 Phase2 对 Pool 代币余额的操纵后,osETH/wETH-BPT 代币的数量不变, WETH 和 osETH 的数量大幅减少。这导致了同样的 WETH 和 osETH 数量能够兑换出更多的 osETH/wETH-BPT 代币。 黑客在 Phase3 中用高价的 WETH 和 osETH 逐步兑换出 osETH/wETH-BPT,恢复在 Phase1 中被移除流动性。同时保留了剩余的 WETH 和 osETH 代币,完成获利。 最终获利:

  • 4623.601508853283067843 WETH
  • 44.154666355785411629 osETH/wETH-BPT
  • 6851.122954235076557965 osETH

    reference

    本文很多内容借鉴这篇文章,写的很好:https://www.cnblogs.com/ACaiGarden/p/19235024

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
黑梨888
黑梨888
web3安全,合约审计。biu~