DeFiFoundry
60,000 USDC
View results
Submission Details
Severity: high
Invalid

Attackers can trade around chainlink oracles updates to make risk free trades

Vulnerability Details

The following extract from getAccountMarginRequirementUsdAndUnrealizedPnlUsd shows how the protocol calculates a user's profit

function getAccountMarginRequirementUsdAndUnrealizedPnlUsd(
...
...
// calculate price impact of the change in position
UD60x18 markPrice = perpMarket.getMarkPrice(sizeDeltaX18, perpMarket.getIndexPrice());
...
...
// get unrealized pnl + accrued funding fees
SD59x18 positionUnrealizedPnl =
position.getUnrealizedPnl(markPrice).add(position.getAccruedFunding(fundingFeePerUnit));
...
...

markPrice is proportional to the indexPrice

indexPrice gets the price of the asset from a chainlink price feed, as follows

function getIndexPrice(Data storage self) internal view returns (UD60x18 indexPrice) {
address priceAdapter = self.configuration.priceAdapter;
uint32 priceFeedHeartbeatSeconds = self.configuration.priceFeedHeartbeatSeconds;
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
address sequencerUptimeFeed = globalConfiguration.sequencerUptimeFeedByChainId[block.chainid];
if (priceAdapter == address(0)) {
revert Errors.PriceAdapterNotDefined(self.id);
}
indexPrice = ChainlinkUtil.getPrice(
IAggregatorV3(priceAdapter), priceFeedHeartbeatSeconds, IAggregatorV3(sequencerUptimeFeed)
);
}

position.getUnrealizedPnl uses the calculated markPrice of the asset and compares it to the lastInteractionPrice, If the markPrice > lastInteractionPrice then there will be a profit recorded. As seen in the following code.

function getUnrealizedPnl(Data storage self, UD60x18 price) internal view returns (SD59x18 unrealizedPnlUsdX18) {
SD59x18 priceShift = price.intoSD59x18().sub(ud60x18(self.lastInteractionPrice).intoSD59x18());
unrealizedPnlUsdX18 = sd59x18(self.size).mul(priceShift);
}

Chainlink priceFeeds will only update if the price deviates > threshold OR the heartbeat is exceeded

The above fact gives rise to the following scenario:
If the price of the asset has increased by 0.99% and the deviation threshold = 1%, the priceFeed will NOT update till the heartbeat is exceeded OR the price increases more to surpass the threshold

Attackers can take advantage of this by the following steps:

  1. Price of asset reported by the chainlink oracle is $1 BUT it is actually 1.0099 (0.99% higher) AND the heartbeat is about to be surpassed in the next few minutes or so

  2. Attacker opens a long order, which will get executed by keepers before the heartbeat update (if not they can cancel the order)

  3. Oracle updates the price to be 0.99% higher, therefore indexPrice is 0.99% higher, therefore getUnrealizedPnl will be positive

  4. attacker closes the order making a profit

  5. Repeat

This attack can also be done if the price goes down by shorting

Impact

Attackers are performing risk free trades in an unfair way

Attackers extract value from the protocol over time

Tools Used

Manual Review

Recommendations

This is not easy to mitigate without removing the use of price feeds and sticking to the low latency price streams only

Updates

Lead Judging Commences

inallhonesty Lead Judge
12 months ago
inallhonesty Lead Judge 11 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.