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

Incorrect implementation of limit orders

Summary

The fillOffchainOrders function is using the mark price as the fill price and then comparing it to the target price, which doesn't correctly implement the intended logic for limit orders.

Vulnerability Details

// verify the provided price data against the verifier and ensure it's valid, then get the mark price
// based on the returned index price.
ctx.fillPriceX18 = perpMarket.getMarkPrice(sd59x18(ctx.offchainOrder.sizeDelta), ctx.indexPriceX18);
// if the order increases the trading account's position (buy order), the fill price must be less than or
// equal to the target price, if it decreases the trading account's position (sell order), the fill price
// must be greater than or equal to the target price.
ctx.isFillPriceValid = (ctx.isBuyOrder && ctx.offchainOrder.targetPrice <= ctx.fillPriceX18.intoUint256())
|| (!ctx.isBuyOrder && ctx.offchainOrder.targetPrice >= ctx.fillPriceX18.intoUint256());
// we don't revert here because we want to continue filling other orders.
if (!ctx.isFillPriceValid) {
continue;
}

https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/branches/SettlementBranch.sol#L279C13-L292C14

The problem is that the fillOffchainOrders function is using the mark price as the fill price and then comparing it to the target price, which doesn't correctly implement the intended logic for limit orders. This can lead to several issues:

  1. Incorrect price comparison: For a buy order, we want to fill if the market price is less than or equal to the target price. For a sell order, we want to fill if the market price is greater than or equal to the target price. The current implementation does the opposite.

  2. Using mark price instead of market price: The mark price might not accurately reflect the current market price, especially in volatile markets.

Impact

This implementation might fill orders at prices worse than what the user specified in their limit order.

Tools Used

Manual Review

Recommendations

Use the actual market price (bid for sell orders, ask for buy orders) instead of the mark price for comparison.

  • Correct the comparison logic to properly implement limit order behavior.

`ctx.marketPriceX18 = ctx.isBuyOrder ? ctx.askX18 : ctx.bidX18;

ctx.isFillPriceValid = (ctx.isBuyOrder && ctx.marketPriceX18.intoUint256() <= ctx.offchainOrder.targetPrice)
|| (!ctx.isBuyOrder && ctx.marketPriceX18.intoUint256() >= ctx.offchainOrder.targetPrice);

if (ctx.isFillPriceValid) {
// Use the market price for the fill, not the mark price
ctx.fillPriceX18 = ctx.marketPriceX18;

// fill the offchain order
_fillOrder(
ctx.offchainOrder.tradingAccountId,
marketId,
SettlementConfiguration.OFFCHAIN_ORDERS_CONFIGURATION_ID,
sd59x18(ctx.offchainOrder.sizeDelta),
ctx.fillPriceX18
);

}`

Updates

Lead Judging Commences

inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

fillOffchainOrders inverses the comparison between `fillPrice` and `targetPrice`

Support

FAQs

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