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

Offchain orders cannot be filled correctly

Offchain orders cannot be filled correctly

Summary

If multiple offchain orders are being filled in a single transaction and one of them fails in _fillOrder function, all preceding successful orders in the same transaction will also be reverted.

Vulnerability Details

The current implementation of fillOffchainOrders can revert the entire transaction if the _fillOrder function call fails. Multiple orders may need to be resubmitted, increasing gas costs and transaction times.

https://github.com/Cyfrin/2024-07-zaros/blob/main/src/perpetuals/branches/SettlementBranch.sol#L297-L314

Here we can see the function call inside a loop (checking each order):

function fillOffchainOrders(
uint128 marketId,
OffchainOrder.Data[] calldata offchainOrders,
bytes calldata priceData
)
external
onlyOffchainOrdersKeeper(marketId)
{
. . .
@> for (uint256 i; i < offchainOrders.length; i++) {
. . .
tradingAccount.hasOffchainOrderBeenFilled[ctx.structHash] = true;
// fill the offchain order.
@> _fillOrder(
ctx.offchainOrder.tradingAccountId,
marketId,
SettlementConfiguration.OFFCHAIN_ORDERS_CONFIGURATION_ID,
sd59x18(ctx.offchainOrder.sizeDelta),
ctx.fillPriceX18
);
}
}

Now let's check _fillOrder function. In some scenarios it is possible that the trader does not meet the margin requirement check here:

function _fillOrder(
uint128 tradingAccountId,
uint128 marketId,
uint128 settlementConfigurationId,
SD59x18 sizeDeltaX18,
UD60x18 fillPriceX18
)
internal
virtual
{
. . .
// initial margin requirement
ctx.shouldUseMaintenanceMargin = !ctx.isIncreasing && !ctx.oldPositionSizeX18.isZero();
ctx.requiredMarginUsdX18 =
ctx.shouldUseMaintenanceMargin ? requiredMaintenanceMarginUsdX18 : requiredInitialMarginUsdX18;
// reverts if the trader can't satisfy the appropriate margin requirement
@> tradingAccount.validateMarginRequirement(
ctx.requiredMarginUsdX18,
tradingAccount.getMarginBalanceUsd(accountTotalUnrealizedPnlUsdX18),
ctx.orderFeeUsdX18.add(ctx.settlementFeeUsdX18)
);
. . .
}

Furthermore the _fillOrder function includes a call to revert Errors.InsufficientMargin to handle insufficient margin scenarios. However, this call cannot emit the correct order details, as it only provides the trading account ID and margin-related values, without specific details about the failed order. This prevents the backend servce to retry the correct order of the account.

function validateMarginRequirement(
Data storage self,
UD60x18 requiredMarginUsdX18,
SD59x18 marginBalanceUsdX18,
UD60x18 totalFeesUsdX18
)
internal
view
{
if (requiredMarginUsdX18.add(totalFeesUsdX18).intoSD59x18().gt(marginBalanceUsdX18)) {
@> revert Errors.InsufficientMargin(
self.id,
marginBalanceUsdX18.intoInt256(),
requiredMarginUsdX18.intoUint256(),
totalFeesUsdX18.intoUint256()
);
}
}

Impact

  • A trader places an off-chain order.

  • The trader attempts to cancel all of his active orders and open a new position.

  • _fillOrder is called and reverts due to the trader not meeting the margin requirements.

  • The whole transaction reverts preventing further action on this order on the trader's end.

  • All of the prevoius trader's orders remain active.

This leads to an order management issue where legitimate orders cannot be fulfilled or cancelled correctly.

Traders may experience potential financial loss due to the inability to manage their orders accurately.

Tools Used

Manual review

Recommendations

Implement try-catch blocks to handle _fillOrder calls individually. This approach allows each order to be processed independently, ensuring that a failure in one order does not revert the entire transaction.

Updates

Lead Judging Commences

inallhonesty Lead Judge 10 months ago
Submission Judgement Published
Validated
Assigned finding tags:

fillOffchainOrders reverts everything if a single order fails one of the multiple checks

If you send 1 cancel and 1 create it should still run the cancel, not revert everything.

Support

FAQs

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