Overview

This document details our collaborative engineering effort with BendDAO regarding their contracts.

Disclaimer

The audit does not ensure that it has identified every security issue in the smart contracts, and it should not be seen as a confirmation that there are no more vulnerabilities. While we have conducted an analysis to the best of our ability, it is our recommendation for high-value contracts to commission several independent audits, a public bug bounty program, as well as continuous onchain security auditing and monitoring through Fuzzland Blaz+ Analysis and Alert. Additionally, this report should not be interpreted as personal financial advice or recommendations.

Auditing Process

Findings

[HIGH] Self-liquidation leads to miscalculation in totalCrossSupply

In the smart contract system, the executeCrossLiquidateERC20 function is a key component designed to handle liquidation logic. By analyzing the test case test_Should_LiquidateUSDTself, we found that when the liquidator and the borrower are the same participant, triggering this function will lead to a discrepancy between the total cross supply totalCrossSupply after liquidation and the expected value.

POC:

  // test\\integration\\TestIntCrossLiquidateERC20.t.sol
  
  function test_Should_LiquidateUSDTself() public {
    prepareUSDT(tsDepositor1);

    prepareWETH(tsBorrower1);

    TestUserAccountData memory accountDataBeforeBorrow = getUserAccountData(address(tsBorrower1), tsCommonPoolId);

    // borrow some eth
    uint8[] memory borrowGroups = new uint8[](1);
    borrowGroups[0] = tsLowRateGroupId;

    uint256[] memory borrowAmounts = new uint256[](1);
    uint256 usdtCurPrice = tsPriceOracle.getAssetPrice(address(tsUSDT));
    borrowAmounts[0] = (accountDataBeforeBorrow.availableBorrowInBase * (10 ** tsUSDT.decimals())) / usdtCurPrice;

    actionCrossBorrowERC20(
      address(tsBorrower1),
      tsCommonPoolId,
      address(tsUSDT),
      borrowGroups,
      borrowAmounts,
      new bytes(0)
    );

    // make some interest
    advanceTimes(365 days);

    // drop down eth price to lower heath factor
    uint256 wethCurPrice = tsPriceOracle.getAssetPrice(address(tsWETH));
    uint256 wethNewPrice = (wethCurPrice * 80) / 100;
    tsCLAggregatorWETH.updateAnswer(int256(wethNewPrice));

    TestUserAccountData memory accountDataAfterBorrow = getUserAccountData(address(tsBorrower1), tsCommonPoolId);
    assertLt(accountDataAfterBorrow.healthFactor, 1e18, 'ACC:healthFactor');

    // liquidate some eth
    tsBorrower1.approveERC20(address(tsUSDT), type(uint256).max);

    actionCrossLiquidateERC20(
      address(tsBorrower1),
      tsCommonPoolId,
      address(tsBorrower1),
      address(tsWETH),
      address(tsUSDT),
      borrowAmounts[0],
      false,
      new bytes(0)
    );
  }
  
  // log
  checkAssetData begin
  checkAssetData group 0
  checkAssetData group 1
  checkAssetData group 2
  checkAssetData group 3
  checkAssetData end
  checkUserAssetData begin
  Error: UAD:totalCrossSupply
  Error: a ~= b not satisfied [uint]
        Left: 0
       Right: 10000000000000000000
   Max Delta: 1
       Delta: 10000000000000000000
  checkUserAssetData group 0
  checkUserAssetData group 1
  checkUserAssetData group 2
  checkUserAssetData group 3
  checkUserAssetData end
  actionCrossLiquidateERC20 check: liquidator & debt
  checkAssetData begin
  checkAssetData group 0
  checkAssetData group 1
  checkAssetData group 2
  checkAssetData group 3
  checkAssetData end
  checkUserAssetData begin
  checkUserAssetData group 0
  checkUserAssetData group 1
  Error: UAD:totalCrossBorrow
  Error: a ~= b not satisfied [uint]
        Left: 2866274286
       Right: 18558267614
   Max Delta: 1
       Delta: 15691993328
  checkUserAssetData group 2
  checkUserAssetData group 3
  checkUserAssetData end
  actionCrossLiquidateERC20 check: borrower & collateral
  checkUserAssetData begin
  Error: UAD:walletBalance
  Error: a ~= b not satisfied [uint]
        Left: 1000000000000000000000000
       Right: 999990000000000000000000
   Max Delta: 1
       Delta: 10000000000000000000
  checkUserAssetData group 0
  checkUserAssetData group 1
  checkUserAssetData group 2
  checkUserAssetData group 3
  checkUserAssetData end
  actionCrossLiquidateERC20 check: borrower & debt
  checkUserAssetData begin
  Error: UAD:walletBalance
  Error: a ~= b not satisfied [uint]
        Left: 1000887578372
       Right: 1016579571700
   Max Delta: 1
       Delta: 15691993328
  checkUserAssetData group 0
  checkUserAssetData group 1
  checkUserAssetData group 2
  checkUserAssetData group 3
  checkUserAssetData end
  >>>>actionCrossLiquidateERC20 end