Assessment reports>GTE -- Perp>Critical findings>Incorrect position update order relative to ,isLiquidatable, check in ,processMakerFill, permits fund theft
Category: Coding Mistakes

Incorrect position update order relative to isLiquidatable check in processMakerFill permits fund theft

Critical Severity
Critical Impact
High Likelihood

Description

The processMakerFill function performs an isLiquidatable check to ensure that settling a maker's limit order does not immediately make the maker's account liquidatable.

function processMakerFill(ClearingHouse storage self, bytes32 asset, MakerFillParams memory params)
    internal
    returns (bool unfillable)
{
    // [...]

    if (self.isLiquidatable(params.maker, params.subaccount, margin)) {
        market.setPosition(params.maker, params.subaccount, position); // revert to old position
        return true;
    }


    // [...]
    if (result.fullClose) self.positions[params.maker][params.subaccount].remove(asset);
    else self.positions[params.maker][params.subaccount].add(asset);

    // [...]
}

However, the asset data is added to the account's positions EnumerableSet only after this isLiquidatable check. Consequently, if the maker does not have an existing position in the specified asset market, the isLiquidatable check does not account for the newly settled position in that market.

If the isLiquidatable check does not consider this new position, the maker's account can incur significant bad debt if the trade execution price deviates substantially from the index price. An attacker filling the limit order can then capture this bad debt as profit.

Impact

An attacker can profit from this vulnerability by performing the following steps:

  1. Clear the order book by filling existing orders to allow subsequent orders to be settled at arbitrary prices.

  2. Use a sacrificial account (account A) to place a limit order at a price designed to create immediate debt for account A upon settlement.

  3. Use another account (account B) to fill this order, generating unrealized profit and loss (PNL) for account B from account A's debt.

  4. Use a third sacrificial account (account C) to place a limit order that, when matched, closes account B's position.

  5. Use account B to fill account C's order, thereby converting account B's unrealized PNL into realized profit.

  6. Abandon accounts A and C, retaining the extracted funds in account B.

Because orders can be settled at arbitrary prices once the order book is cleared and there is no cap on trading volume, an attacker's potential profit can significantly outweigh the cost of performing this attack. Such an attack could potentially result in the draining of all funds from the contract.

Recommendations

We recommend performing the isLiquidatable check only after the account's positions EnumerableSet is updated to include the new asset market.

Remediation

This issue has been acknowledged by Liquid Labs, Inc., and fixes were implemented in the following commits:

Zellic © 2025Back to top ↑