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

Positions can be opened when markets and settlements have been disabled

Summary

When both markets and settlement are disabled, the protocol aims to disallow new positions from being opened and also disallow already opened positions from increasing their size. However, this invariant is susceptible to being broken as users can still open new positions even when the the markets and settlements are disabled.

Vulnerability Details

According to the protocol-

  • "both markets and settlement can be disabled, however when this happens we want to: 1) allow open positions not subject to liquidation to decrease their size or close 2) prevent new positions from being opened & existing positions being increased the idea is to prevent a state where traders have open positions but are unable to reduce size or close even though they can still be liquidated; such a state would severly disadvantage traders"

The above invariant can be bypassed in situations whereby a user opens a position that is currently decreasing thereby bypassing the explicit check meant to safeguard the invariant

if (ctx.isIncreasing) { //@audit
globalConfiguration.checkMarketIsEnabled(params.marketId);
settlementConfiguration.checkIsSettlementEnabled();
}

The above implementation only checks if the current context i.e the current position to be created is increasing before validating if the market and settlements are being enabled. And as such, can be bypassed by creating a position with a negative sizeDelta.

The logic that determines weather the position is increasing or not is as follows;

// determine whether position is being increased or not
ctx.isIncreasing = Position.isIncreasing(params.tradingAccountId, params.marketId, params.sizeDelta);

and the implementation is illustrated below:

function isIncreasing(
uint128 tradingAccountId,
uint128 marketId,
int128 sizeDelta
)
internal
view
returns (bool)
{
Data storage self = Position.load(tradingAccountId, marketId);
// If position is being opened (size is 0) or if position is being increased (sizeDelta direction is same as
// position size)
// For a positive position, sizeDelta should be positive to increase, and for a negative position, sizeDelta
// should be negative to increase.
// This also implicitly covers the case where if it's a new position (size is 0), it's considered an increase.
return self.size == 0 || (self.size > 0 && sizeDelta > 0) || (self.size < 0 && sizeDelta < 0);
}

From the above, it is made obvious the conditions required to be fulfilled in order to ascertain if a position is increasing or not. And this internal function only returns true if the sizeDelta is in the same direction as the current market position, otherwise, it is said to be decreasing.

Illustration

Looking closely at the above implementation, we can clearly deduce how this bypass could be achieved.
In situations whereby the current market position is in a positive direction, and the sizeDelta configured by a user is in the negative, the isIncreasing will return false, hence the checkMarketIsEnabled and checkIsSettlementEnabled check would be bypassed.

Walkthrough

  • Alice(a user) creates a market order with a negative int128 sizeDelta.

  • The isIncreasing returns false when the position size is positive.

  • checkMarketIsEnabled and checkIsSettlementEnabled would be bypassed

  • Alice successfully opens a position even when the market and settlement are disabled.

Moreover opening a position with a negative sizeDelta does not mean the position will result in a loss, market directions are not always stable, hence such positions will start off as a loss but might still move in profit(positive direction) later on.

Impact

This breaks an invariant and will lead to users still being able to open positions when market and settlements are disabled which could lead to protocol insolvency

POC

function createMarketOrder(CreateMarketOrderParams calldata params) external {
// working data
CreateMarketOrderContext memory ctx;
...
// determine whether position is being increased or not
ctx.isIncreasing = Position.isIncreasing(params.tradingAccountId, params.marketId, params.sizeDelta);
// both markets and settlement can be disabled, however when this happens we want to:
// 1) allow open positions not subject to liquidation to decrease their size or close
// 2) prevent new positions from being opened & existing positions being increased
//
// the idea is to prevent a state where traders have open positions but are unable
// to reduce size or close even though they can still be liquidated; such a state
// would severly disadvantage traders
if (ctx.isIncreasing) { //@audit
globalConfiguration.checkMarketIsEnabled(params.marketId);
settlementConfiguration.checkIsSettlementEnabled();
}
...
emit LogCreateMarketOrder(msg.sender, params.tradingAccountId, params.marketId, marketOrder);
}

Tools Used

Manual review

Recommendations

Restructure the logic or make an implementation in the liquidation function to be called by the keepers, to check for opened positions in markets that have been disabled and close such positions or technically liquidate them.

Updates

Lead Judging Commences

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

Disable market limitation can be bypassed

Support

FAQs

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