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

Offchain target price `targetPrice` is wrongly checked in the function `fillOffchainOrders()`

Summary

targetPrice is checked incorrectly, allowing the order to be filled at a price the offchain signer did not agree to.

Vulnerability Details

The targetPrice in the offchain order is signed by traders to protect them from volatile pricing when the order is filled on-chain.

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. This allows traders to limit the maximum price when opening a LONG (buy) and the minimum price when closing a LONG (sell). This is clearly noted in the code comments.

// 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. // @audit should change <= to >= and vice versa?
ctx.isFillPriceValid = (ctx.isBuyOrder && ctx.offchainOrder.targetPrice <= ctx.fillPriceX18.intoUint256())
|| (!ctx.isBuyOrder && ctx.offchainOrder.targetPrice >= ctx.fillPriceX18.intoUint256());

However, the code does the exact opposite. For a buy order, it validates that the fill price is greater than or equal to the target price.

Impact

The order will be filled at a bad price, even though the traders have signed the order to prevent this.

Consider this scenario:

  1. Alice signed an offchain order to buy ETH with a maximum price of $3000. The order would have targetPrice = 3000. Alice would be happy if her buy order is filled with a fillPrice less than $3000 because she would have more profit.

  2. However, due to the incorrect check, the order will only be filled when fillPrice >= 3000. For example, it might be filled at fillPrice = 4000.

Tools Used

Manual review

Recommendations

Correcting the check by swapping >= and <=.

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

Lead Judging Commences

inallhonesty Lead Judge 10 months 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.