Part 2

Zaros
PerpetualsDEXFoundrySolidity
70,000 USDC
View results
Submission Details
Severity: medium
Invalid

Incorrect Position Exposure Detection in Core Settlement Logic Allows Bypass of Market Safety Checks and Risk Limits

Summary

A critical logic error has been identified in the position notional value checking logic. The system incorrectly determines whether a trade increases a position's notional value by only considering absolute position sizes rather than actual exposure changes.

The issue manifests in the core settlement logic of the system. The _fillOrder function in SettlementBranch.sol serves as the primary entry point for trade execution and relies on isNotionalValueIncreasing for critical safety checks:

function _fillOrder(
uint128 tradingAccountId,
uint128 marketId,
uint128 settlementConfigurationId,
SD59x18 sizeDeltaX18,
UD60x18 fillPriceX18
) internal virtual {
FillOrderContext memory ctx;
ctx.isNotionalValueIncreasing =
Position.isNotionalValueIncreasing(tradingAccountId, marketId, sizeDeltaX18.intoInt256().toInt128());
if (ctx.isNotionalValueIncreasing) {
perpsEngineConfiguration.checkMarketIsEnabled(marketId);
settlementConfiguration.checkIsSettlementEnabled();
}
// ...
}

The Position library's implementation of this critical check contains a fundamental logic error:

return sizeX18.isZero() || (sizeDeltaX18.add(sizeX18).abs() > sizeX18.abs());

This implementation makes a critical assumption that comparing absolute position sizes is sufficient to determine if notional value is increasing. However, this assumption breaks down in several important trading scenarios. Consider a trader with a short position of -100 contracts who places a buy order for +60 contracts through the settlement branch. The resulting position would be -40 contracts. The current implementation would return false because |40| < |100|, incorrectly indicating a decrease in notional value when the trade might actually be increasing the trader's exposure.

The function fails to account for the directional nature of positions and the complexity of exposure changes. When reducing short positions, reducing long positions that cross zero, or switching position direction, the absolute size comparison becomes misleading. A decrease in absolute position size does not necessarily correspond to a decrease in notional value or exposure.

These edge cases have serious implications for the system's risk management. When _fillOrder processes a trade, it uses this determination to decide whether to enforce market disablement and settlement checks. The current implementation could allow trades during market disablement that should be blocked, as it fails to recognize certain types of exposure increases. Additionally, the margin requirement calculations are influenced by this determination:

ctx.shouldUseMaintenanceMargin = !ctx.isNotionalValueIncreasing && !ctx.oldPositionSizeX18.isZero();

Impact

The issue creates a systemic risk in the trading engine. When _fillOrder receives incorrect signals about position exposure changes, the system may bypass critical market disablement restrictions that should be enforced. The faulty position change detection leads to incorrect margin requirement applications, potentially allowing trades that increase system risk during periods when such increases should be prevented. Furthermore, the miscategorization of position changes can lead to accumulated risk in the system. This vulnerability is particularly concerning because it sits in the critical path of trade execution, affecting every trade that modifies an existing position.

Proposed Solution

A corrected implementation should account for position directionality, zero-crossing scenarios, and true exposure changes:

function isNotionalValueIncreasing(
uint128 tradingAccountId,
uint128 marketId,
SD59x18 sizeDeltaX18
)
internal
view
returns (bool)
{
Data storage self = load(tradingAccountId, marketId);
SD59x18 sizeX18 = sd59x18(self.size);
SD59x18 newSizeX18 = sizeX18.add(sizeDeltaX18);
if (sizeX18.isZero()) {
return true; // Opening a new position always increases notional value
}
if (sizeX18.mul(newSizeX18).lt(SD59x18_ZERO)) {
return true; // Position direction change always increases notional value
}
return newSizeX18.abs().gt(sizeX18.abs()); // Check size increase in same direction
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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