Boost _ IDEX 34437 - [Smart Contract - Insight] User positions could be unfairly liquidated due to s
Submitted on Mon Aug 12 2024 15:50:10 GMT-0400 (Atlantic Standard Time) by @marchev for Boost | IDEX
Report ID: #34437
Report type: Smart Contract
Report severity: Insight
Target: https://github.com/idexio/idex-contracts-ikon/blob/main/contracts/oracle-price-adapters/PythOraclePriceAdapter.sol
Impacts:
Permanent freezing of funds
Description
Brief/Intro
A vulnerability exists in the protocol where positions could be erroneously liquidated using outdated price data from the Pyth oracle. This issue arises because the protocol accepts any valid Pyth price data, regardless of its age, when updating the last index price of a market as long as it is fresher than the last known market index price. This issue could lead to unfair liquidations, potentially causing users to lose funds.
Vulnerability Details
The vulnerability is rooted in the way the protocol handles price data from the Pyth oracle. Specifically, positions below maintenance margin are liquidated using the Exchange#liquidateWalletInMaintenance()
function, which calls WalletInMaintenanceLiquidation.liquidate_delegatecall()
. This function further invokes _validateQuoteQuantitiesAndLiquidatePositions()
, which is responsible for ensuring a wallet has fallen below the maintenance threshold.
The protocol calculates the total account value and margin requirements using the IndexPriceMargin.loadTotalAccountValueInDoublePipsAndMaintenanceMarginRequirementInTriplePips()
function. The total account value is computed as the sum of the USD balance and the value of any open positions. The value of open positions is determined by multiplying the asset price by the last index price, as shown in the following code snippet:
Prices for any given market are updated via Exchange#publishIndexPrices()
, which calls MarketAdmin.publishIndexPrices_delegatecall()
. This function validates the price against using IIndexPriceAdapter#validateIndexPricePayload()
. As per the project documentation, under normal conditions prices are sourced from the Pyth oracle and thus the PythIndexPriceAdapter
in this context. PythIndexPriceAdapter#validateIndexPricePayload()
validates price data against the Pyth oracle through the Pyth#parsePriceFeedUpdates()
function. Notably, this function allows developers to specify a time range within which the price data should be considered valid. However, the minPublishTime
argument is hardcoded to 0
, meaning any price published since the inception of the Pyth oracle is considered valid, regardless of its age. This flaw could lead to positions being liquidated based on outdated or stale price data, resulting in unfair liquidations.
Although there is a check in publishIndexPrices_delegatecall()
that ensures the price is fresher than the last one:
this check is insufficient under certain conditions, such as outages of the off-chain component or other force majeure circumstances. In these cases, the check only ensures the new price is more recent than the last one, but it does not guarantee the price is sufficiently fresh to avoid using stale data.
Impact Details
The potential impact of this vulnerability includes the erroneous liquidation of user positions due to possible reliance on outdated price data. This could lead to substantial financial losses for users whose positions are unfairly liquidated. While the likelihood of such an event is low due to the generally reliable nature of the Pyth oracle and the protocol’s off-chain component, the consequences of the issue ocuring could be severe.
According to Immunefi’s Vulnerability Severity Classification System, such an issue involving user loss of funds typically warrants a Critical severity rating. However, given the specific circumstances and low likelihood of occurrence, the assessed severity of this vulnerability is Medium.
Recommendation
It is recommended to introduce a sensible default value for the minPublishTime
parameter in the Pyth#parsePriceFeedUpdates()
function. This default value should be set to a timeframe that ensures only fresh and recent price data is accepted when publishing index price data by the protocol. By enforcing a reasonable minPublishTime
, the protocol would mitigate the risk of using stale or outdated price data, thereby reducing the likelihood of erroneous liquidations and ensuring more accurate and reliable market operations.
References
https://github.com/idexio/idex-contracts-ikon/blob/a4bfee2cb80daec8ba22ee926a13884807d0a94a/contracts/index-price-adapters/PythIndexPriceAdapter.sol#L146-L151
https://docs.pyth.network/price-feeds/api-reference/evm/parse-price-feed-updates
Proof of concept
Proof of Concept
The following coded PoC demonstrates how Pyth#parsePriceFeedUpdates()
would validate a 1-month old price when minPublishTime = 0
:
Run the PoC via:
Last updated