Arbitrage opportunities with older price feeds
Description
The protocol uses Pyth oracles to obtain the prices of the tokens. When opening, modifying, or closing a trade, the user must supply a priceUpdateData
blob, which is obtained from and signed by the off-chain Pyth oracle.
In PriceAggregator.fulfill
, this data is then passed to the on-chain Pyth contract using Pyth.updatePriceFeeds
to update the price before the updated price is fetched using Pyth.getPrice
, and then that price is used for the order.
Since the priceUpdateData
blob is taken from user input, it could be old price data or the price data for a different price feed. In this case, the update will be rejected by Pyth, which means getPrice
will return the latest price the on-chain Pyth contract has seen, which is not necessarily the most recent price that exists.
If we assume that this price is not too stale (it is <2 minutes old) but is also not equal to the most recent price, an arbitrage opportunity exists because the user can place orders under the old price while already knowing what the next price is going to be.
Impact
At times of high volatility (high when leverage is considered), this arbitrage opportunity is worth the fees and can be done inside a single transaction, allowing for risk-free arbitrage.
For example, let's say an asset is relatively stable, so its maximum leverage is set to 100x, and then volatility causes its price to increase by 0.1% in one tick. If a user notices that the Pyth oracle has not yet seen the new price, they can take out a flash loan and then call openTrade
to open a long position at maximum leverage using a stale or incorrect priceUpdateData
. This causes the old price to be used. Then, they can provide the new priceUpdateData
and immediately close the trade in the same transaction, letting them book an instant and risk-free profit of 10% of the value of the flash loan.
Recommendations
The issue at hand is challenging to rectify within the constraints of the current contract-trader interface due to the following reasons:
Taking
priceUpdateData
from traders is not safe as traders are always able to refuse to give newer data that they nonetheless already have, until a later call in the same block or transaction.If the responsibility for providing
priceUpdateData
is shifted to governance, it would have to call this function at the update frequency of the underlying price feed, likely many times per block for every pair.
Remediation
This issue has been acknowledged by Avantis Labs, Inc., and fixes were implemented in the following commits: Avantis acknowledges the risk of using user-supplied price update data in margin update and limit execution orders, since feasible exploitation of these arbitrage opportunities will require the payment of margin fees on the leveraged position until the price jumps sufficiently, so it is not risk-free.