Summary
SettlementBranch.fillOffchainOrders
intends to fill TP orders, but the code is for SL orders.
Vulnerability Details
If the order is a buy order, the comment says "the fill price must be less than or equal to the target price".
However, the code checks isBuyOrder && targetPrice <= fillPrice
, which is the opposite of the comment.
It is the same with a sell order.
ctx.isFillPriceValid = (ctx.isBuyOrder && ctx.offchainOrder.targetPrice <= ctx.fillPriceX18.intoUint256())
|| (!ctx.isBuyOrder && ctx.offchainOrder.targetPrice >= ctx.fillPriceX18.intoUint256());
https://github.com/Cyfrin/2024-07-zaros/blob/7439d79e627286ade431d4ea02805715e46ccf42/src/perpetuals/branches/SettlementBranch.sol#L283-L287
The comment assumes TP(Take Profit) order, but the code is for SL(Stop Loss) order.
Impact
Users place TP orders like in other order book exchanges, but the system fills them as SL orders.
Tools Used
Manual.
Recommendations
There are two solutions to choose.
Change the condition of ctx.isFillPriceValid
to support TP orders.
- ctx.isFillPriceValid = (ctx.isBuyOrder && ctx.offchainOrder.targetPrice <= ctx.fillPriceX18.intoUint256())
- || (!ctx.isBuyOrder && ctx.offchainOrder.targetPrice >= ctx.fillPriceX18.intoUint256());
+ ctx.isFillPriceValid = (ctx.isBuyOrder && ctx.offchainOrder.targetPrice >= ctx.fillPriceX18.intoUint256())
+ || (!ctx.isBuyOrder && ctx.offchainOrder.targetPrice <= ctx.fillPriceX18.intoUint256());
https://github.com/Cyfrin/2024-07-zaros/blob/7439d79e627286ade431d4ea02805715e46ccf42/src/perpetuals/branches/SettlementBranch.sol#L283-L287
Add bool triggerAboveThreshold
in OffchainOrder.Data
struct to support both TP/SL orders.
- ctx.isFillPriceValid = (ctx.isBuyOrder && ctx.offchainOrder.targetPrice <= ctx.fillPriceX18.intoUint256())
- || (!ctx.isBuyOrder && ctx.offchainOrder.targetPrice >= ctx.fillPriceX18.intoUint256());
+ ctx.isFillPriceValid = (ctx.offchainOrder.triggerAboveThreshold && ctx.offchainOrder.targetPrice <= ctx.
fillPriceX18.intoUint256())
+ || (!ctx.offchainOrder.triggerAboveThreshold && ctx.offchainOrder.targetPrice >= ctx.fillPriceX18.intoUint256());