Belt Finance事件分析

事件总结与影响

  1. 本次一共通过8次交易,每次交易循环同样的流程7次
  2. 导致beltBUSD的价格从1.018262跌到0.800687,beltBUSD金库损失了21.36%的存款
  3. 导致4Belt LP 的价格从1.017904跌到0.961767,4Belt LP 的持有者损失了5.51%

事件信息

Belt Finance的审计由SOOHOHAECHI AUDIT提供

涉及到的合约

TAG 解释
BF-Attacker Belt Finance事件交易发起人地址
BF-A-Contract Belt Finance事件交易发起人使用的合约地址
PCS-USDC/BUSD Pancake Swap的USDC/BUSD交易对
PCS-BUSDT/BUSD Pancake Swap的BUSDT/BUSD交易对
PCS-DAI/BUSD Pancake Swap的DAI/BUSD交易对
PCS-UST/BUSD Pancake Swap的UST/BUSD交易对
PCS-VAI/BUSD Pancake Swap的VAI/BUSD交易对
PCS-ALPACA/BUSD Pancake Swap的ALPACA/BUSD交易对
BF-Attacker-2 Belt Finance事件最终收款地址
beltBUSD
bVenusBUSD
vBUSD Token Venus BUSD

交易:

本次事件Attacker使用相同的方式进行了8次交易:

https://bscscan.com/tx/0x50b0c05dd326022cae774623e5db17d8edbc41b4f064a3bcae105f69492ceadc

https://bscscan.com/tx/0xc4d4156aab1fca85c99e85352b836274d3c53bafe98a2c9867b68950e1eafde9

https://bscscan.com/tx/0xb57acfeab13d52664416aa2ada9b490e340292731fced049fc8c4a730b7af700

https://bscscan.com/tx/0xcca1ebf01e694bb4c447f6018eebb34a3b829cff9ea1ec5fce236eb3cc2ef99c

https://bscscan.com/tx/0x7719e1bae25dbe80539edea37c962e941ec4141145e6eabe63540b7178ffd0d0

https://bscscan.com/tx/0xd790026feda9a16646647e9df0779dc4a7b173053369847691b8f3f678da1f66

https://bscscan.com/tx/0xf598e092ab82ce08798f9dab7ea6ade64f152aa91db897f3449b23ab591baa1d

https://bscscan.com/tx/0x7b3b727a56d1649ee325c42416a1199f4a9b4f4eb024a60b5848a7b1485953b1

事件分析

与AutoShark和BurgerSwap一样,本次攻击也是始于从PancakeSwap的闪电贷。0xf598…91baa1d交易的攻击过程如下:

  1. 从PancakeSwap的多个流动池借出大量(390,379,325)BNB
    • 闪电贷
  2. 将200,379,325个BUSD铸造成235,391,847个beltBUSD
  3. 使用190,000,000个BUSD兑换168,535,055个BUSDT
  4. burn掉235,391,847个beltBUSD,然后获得了201,383,385个BUSD
    • 注意此步骤使用的beltBUSD是第二部铸造的,铸造时使用了200,379,325个BUSD,此时burn之后却获得了201,383,385个BUSD,多了1,004,060 BUSD
  5. 使用第三步兑换出的168,535,055个BUSDT兑换出189,339,377个BUSD
    • 这一步兑换出了189,339,377个BUSD比第三步使用的190,000,000个BUSD少了660,623个BUSD
    • 但是第四步额外获得的1,004,060个BUSD比660,623个BUSD多了343,437个BUSD(即通过2-5步获利了343,437个BUSD)
  6. 继续重复2-5步骤的交易(数字不完全一样,但是逻辑一样),最终额外获得7,132,461个BUSD
  7. 还闪电贷(闪电贷因为还有手续费还的必须比借的多)
  8. 获利离场,最终转移了1,320,605个BUSD

涉及到的代码

第二步是调用beltBUSD合约的deposit方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

function deposit(uint256 _amount)
external
nonReentrant
{
require(_amount > 0, "deposit must be greater than 0");
pool = calcPoolValueInToken();
// 把token(即BUSD)转给当前的合约
IERC20(token).safeTransferFrom(msg.sender, address(this), _amount);
// 计算所占的份数
uint256 shares = 0;
if (pool == 0) {
shares = _amount;
pool = _amount;
} else {
//0.1%(999/1000) enterance fee
shares = (_amount.mul(totalSupply())).div(pool).mul(999).div(1000);
}
pool = calcPoolValueInToken();
// 铸造bBUSD给发送者
_mint(msg.sender, shares);
rebalance();
}

第四步是调用beltBUSD合约的withdraw方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

function withdraw(uint256 _shares)
external
nonReentrant
{
require(_shares > 0, "withdraw must be greater than 0");
// 用户的余额
uint256 ibalance = balanceOf(msg.sender);
// 取出的份额不能超过余额
require(_shares <= ibalance, "insufficient balance");
// 计算当前合约和venus合约的BUSD之和
pool = calcPoolValueInToken();
// 根据份额和总发行量计算本次应该取出的BUSD金额
uint256 r = (pool.mul(_shares)).div(totalSupply());
// 销毁当前的token,即beltBUSD
_burn(msg.sender, _shares);
// 获取当前地址的BUSD余额
uint256 b = IERC20(token).balanceOf(address(this));
// 如果当前合约的BUSD余额不够,则先从venus中提现出不够的那一部分
if (b < r) {
_withdrawSome(r.sub(b));
}
// 把所有的需要提现的BUSD转移给用户
IERC20(token).safeTransfer(msg.sender, r);
pool = calcPoolValueInToken();
}

提现时计算Eps3pool的稳定币数量的方法

此方法是导致攻击成功的关键因素,在计算各个稳定币的数量时,直接使用了稳定币合约的balanceOf方法会使计算的值变大,因此再burn掉bBUSD时才能获得较多的BUSD。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function eps3ToWant() public view returns (uint256) {
// 获取ellipsisSwap的busd、usdc和usdt余额;
// 在第三步时因为进行了一次兑换(BUSD->BUSDT)所以ellipsisSwapAddress地址的BUSD便多了,BUSDT变少了
// 但是因为增加的BUSD比减少的BUSDT要更多,因此整体来说这三个稳定币只和变大了
uint256 busdBal = IERC20(busdAddress).balanceOf(ellipsisSwapAddress);
uint256 usdcBal = IERC20(usdcAddress).balanceOf(ellipsisSwapAddress);
uint256 usdtBal = IERC20(usdtAddress).balanceOf(ellipsisSwapAddress);
// 当前地址在ellipsisStake的有多少的Eps3
(uint256 curEps3Bal, )= LpTokenStaker(ellipsisStakeAddress).userInfo(poolId, address(this));
// Eps3一共有少个
uint256 totEps3Bal = IERC20(eps3Address).totalSupply();
// 根据当前地址的占比和busd、usdc和usdt余额,计算当前地址的busd、usdc、usdt之和
return busdBal.mul(curEps3Bal).div(totEps3Bal)
.add(
usdcBal.mul(curEps3Bal).div(totEps3Bal)
)
.add(
usdtBal.mul(curEps3Bal).div(totEps3Bal)
);
}

事件处理结果

  1. 修改获取Eps3Pool中稳定币的方法,使用池中的方法获取而不是使用外部的ERC20代币的balanceOf方法

2、 在计算Eps3Pool对应的稳定币数量的方法中增加校验,校验的具体实现如上图,主要是校验“三个稳定币的最大的数量和最小的数量相差不能太多”。校验不成功就不允许提现