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
Rank: 4/5
Findings: 0
Award: $0.00
🌟 Selected for report: 0
🚀 Solo Findings: 0
Data not available
Label | Description |
---|---|
L-01 | enterMarkets() error check 0 tranche borrow cap |
L-02 | enterMarkets() Lack of limitation on the length of the market, risk of OUT_GAS |
L-03 | OmniTokenNoBorrow supplyCap DOS risk |
L-04 | _evaluateAccountInternal() Markets with no deposits will also expire and be liquidated |
L-05 | OmniOracle.getPrice() when provider==Provider.Other It's very confusing |
enterMarkets()
error check 0 tranche
borrow capin 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); }
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); }
supplyCap
DOS riskOmniTokenNoBorrow.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
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; }
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