Beta Finance Invitational - bin2chen's results

Personalized lending and borrowing with zero liquidity fragmentation and maximal capital efficiency on the EVM.

General Information

Platform: Code4rena

Start Date: 01/11/2023

Pot Size: $24,150 USDC

Total HM: 5

Participants: 5

Period: 5 days

Judge: cccz

Total Solo HM: 4

Id: 303

League: ETH

Beta Finance

Findings Distribution

Researcher Performance

Rank: 4/5

Findings: 0

Award: $0.00

QA:
grade-a

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: T1MOH

Also found by: 0xStalin, bin2chen, dirk_y, ladboy233

Labels

bug
grade-a
QA (Quality Assurance)
sponsor confirmed
edited-by-warden
Q-03

Awards

Data not available

External Links

Findings Summary

LabelDescription
L-01enterMarkets()error check 0 tranche borrow cap
L-02enterMarkets()Lack of limitation on the length of the market, risk of OUT_GAS
L-03OmniTokenNoBorrow supplyCap DOS risk
L-04_evaluateAccountInternal() Markets with no deposits will also expire and be liquidated
L-05OmniOracle.getPrice() when provider==Provider.Other It's very confusing

[L-01] enterMarkets()error check 0 tranche borrow cap

in enterMarkets() will check IOmniToken(market).getBorrowCap(0) > 0 But the user's BorrowTier is not always 0, it could be IsolatedMarket.riskTranche suggest check getAccountBorrowTier(account) borrow cap

    function enterMarkets(uint96 _subId, address[] calldata _markets) external {
...
-           require(IOmniToken(market).getBorrowCap(0) > 0, "OmniPool::enterMarkets: Market has no borrow cap for 0 tranche.");
+           require(IOmniToken(market).getBorrowCap(getAccountBorrowTier(_account)) > 0, "OmniPool::enterMarkets: Market has no borrow cap for 0 tranche.");
            newMarkets[i + existingMarkets.length] = market;
        }
        accountMarkets[accountId] = newMarkets;
        emit EnteredMarkets(accountId, _markets);
    }

[L-02] enterMarkets()Lack of limitation on the length of the market, risk of OUT_GAS.

Currently there is no limit to the maximum number of markets that can enter In _evaluateAccountInternal() we loop through all markets and calculate accordingly. Since there is no limit on the length, there is a risk of OUT_GAS. Suggest adding a limit

    function enterMarkets(uint96 _subId, address[] calldata _markets) external {
...
+      require(newMarkets.length <=25);
        accountMarkets[accountId] = newMarkets;
        emit EnteredMarkets(accountId, _markets);
    }

L-03: OmniTokenNoBorrow supplyCap DOS risk

OmniTokenNoBorrow.sol will currently limit the global maximum supply supplyCap Since both deposit() and withdraw() currently charge no fees

This maliciously deposits a large amount, resulting in close to supplyCap thus preventing users from depositing more collateral to avoid liquidation It is recommended to limit the maximum supply per user instead of a global supply limit

L-04: _evaluateAccountInternal() Markets with no deposits will also expire and be liquidated

The current method _evaluateAccountInternal() will liquidate the market as soon as it expires, regardless of whether the user has made a deposit in the market or not. It doesn't make sense. It should only affect the calculation of the liquidation result if there is a deposit. Suggest to calculate expiration only if there is a deposit

    function _evaluateAccountInternal(bytes32 _accountId, address[] memory _poolMarkets, AccountInfo memory _account)
        internal
        returns (Evaluation memory eval)
    {
...
            if (i < _poolMarkets.length) {
                market = _poolMarkets[i];
            } else {
                market = _account.isolatedCollateralMarket;
            }
            MarketConfiguration memory marketConfiguration_ = marketConfigurations[market];
-           if (marketConfiguration_.expirationTimestamp <= block.timestamp) {
-               eval.isExpired = true; // Must repay all debts and exit market to get rid of unhealthy account status if expired
-           }
            address underlying = IWithUnderlying(market).underlying();
            uint256 price = IOmniOracle(oracle).getPrice(underlying); // Returns price in 1e18
            uint256 depositAmount = IOmniTokenBase(market).getAccountDepositInUnderlying(_accountId);
            if (depositAmount != 0) {
+              if (marketConfiguration_.expirationTimestamp <= block.timestamp) {
+                   eval.isExpired = true; // Must repay all debts and exit market to get rid of unhealthy account status if expired
+              }

                ++eval.numDeposit;
                uint256 depositValue = (depositAmount * price) / PRICE_SCALE; // Rounds down
                eval.depositTrueValue += depositValue;
                uint256 collateralFactor = marketCount == 1
                    ? SELF_COLLATERALIZATION_FACTOR
                    : _account.modeId == 0 ? uint256(marketConfiguration_.collateralFactor) : uint256(mode.collateralFactor);
                eval.depositAdjValue += (depositValue * collateralFactor) / FACTOR_PRECISION_SCALE; // Rounds down
            }
            if (i >= _poolMarkets.length) {
                // Isolated collateral market. No borrow.
                continue;
            }

[L-05] OmniOracle.getPrice() when provider==Provider.Other It's very confusing.

in OmniOracle.getPrice() if config.provider == Provider.Other will return

return IOmniOracle(config.oracleAddress).getPrice(_underlying) * (PRICE_SCALE / 1e18) / (10 ** IERC20Metadata(_underlying).decimals());

It is recommended not to use this interface IOmniOracle.getPrice(). Because the current getPrice() of OmniOracle.sol is already a converted quantity based on _underlying.decimals(). Example: chainlink USD decimals = 6 when _underlying.decimals() == 4, price = ORACLE_USD * (10 ** (18-6)) * (10** (18-4))

Suggestion: add new interfaceICustomOracle.getOraclePrice()

    function getPrice(address _underlying) external view returns (uint256) {
...
        } else if (config.provider == Provider.Other) {
-           return IOmniOracle(config.oracleAddress).getPrice(_underlying) * (PRICE_SCALE / 1e18) / (10 ** IERC20Metadata(_underlying).decimals());
+           return ICustomOracle(config.oracleAddress).getOraclePrice(_underlying) * (PRICE_SCALE / 1e18) / (10 ** IERC20Metadata(_underlying).decimals());
        } else {
            revert("OmniOracle::getPrice: Invalid provider.");
        }
    }

or use new define of getPrice()

    function getPrice(address _underlying) external view returns (uint256) {
...
        } else if (config.provider == Provider.Other) {
-           return IOmniOracle(config.oracleAddress).getPrice(_underlying) * (PRICE_SCALE / 1e18) / (10 ** IERC20Metadata(_underlying).decimals());
+           return IOmniOracle(config.oracleAddress).getPrice(_underlying); // with _underlying.decimals() to 18
        } else {
            revert("OmniOracle::getPrice: Invalid provider.");
        }
    }

#0 - JeffCX

2023-11-06T21:26:06Z

L-02 related to #19

but does not mention the OOS can block and interfere with liquidation and the wording risk of OOS is too generic to be considered as duplicate

#1 - c4-judge

2023-11-07T17:18:41Z

thereksfour marked the issue as grade-a

#2 - c4-judge

2023-11-07T17:18:45Z

thereksfour marked the issue as selected for report

#3 - allenjlee

2023-11-12T19:19:05Z

Solid QA, incorporated everything besides the supplyCap DoS. Feel doing it per user introduces other issues, where you can create multiple accounts to over borrow using that token as collateral. I think it is ok if someone fills the cap for the deposits and doesn't borrow. Worst case, we assess the likelihood of them suddenly borrowing and can work on a case-by-case basis.

#4 - c4-sponsor

2023-11-12T19:19:12Z

allenjlee (sponsor) confirmed

#5 - c4-judge

2023-11-15T04:51:31Z

thereksfour marked the issue as not selected for report

#6 - c4-judge

2023-11-15T05:14:49Z

thereksfour marked the issue as selected for report

#7 - c4-judge

2023-11-15T05:24:34Z

thereksfour marked the issue as not selected for report

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter