Summary
During the liquidation process, the variables newOpenInterestX18 and newSkewX18 were not calculated, but the updateOpenInterest function was called to update market's open interest and sketch, which resulted in them being unexpectedly set to zero.
Vulnerability Details
During the liquidation process, the active markets of the liquidated account will be traversed, and the positions of the liquidated account will be cloesd.
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/leaves/PerpMarket.sol#L275
function checkOpenInterestLimits(
Data storage self,
SD59x18 sizeDelta,
SD59x18 oldPositionSize,
SD59x18 newPositionSize
)
internal
view
returns (UD60x18 newOpenInterest, SD59x18 newSkew)
{
...
}
Liquidation does not call the checkOpenInterestLimits function to verify the market's open interest and skew limits.
This is because we do not want the liquidation to be terminated by these limits.
However, it should be noted that not calling checkOpenInterestLimits also means that newOpenInterest and newSkew are not calculated.
returns (UD60x18 newOpenInterest, SD59x18 newSkew)
Ultimately, due to not calculating newOpenInterest and newSkew, the liquidation function calling updateOpenInterest function will unexpectedly set the market's open interest and skew to zero.
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/branches/LiquidationBranch.sol#L209C28-L209C46
function liquidateAccounts(uint128[] calldata accountsIds) external {
...
for (uint256 i; i < accountsIds.length; i++) {
...
for (uint256 j; j < ctx.activeMarketsIds.length; j++) {
...
position.clear();
tradingAccount.updateActiveMarkets(ctx.marketId, ctx.oldPositionSizeX18, SD59x18_ZERO);
perpMarket.updateOpenInterest(ctx.newOpenInterestX18, ctx.newSkewX18);
}
...
}
}
Impact
The market's open interest and skew will be incorrectly set to zero, causing checkOpenInterestLimits to fail and lose its restrictions.
Tools Used
Manual review
Recommendations
Set up a function in PerpMarket contract to calculate the new open interest and skew of the per market withOutChechLimit for liquidation
function getNewOpenInterestWithOutChechLimit(
Data storage self,
SD59x18 sizeDelta,
SD59x18 oldPositionSize,
SD59x18 newPositionSize
)
internal
view
returns (UD60x18 newOpenInterest, SD59x18 newSkew)
{
UD60x18 currentOpenInterest = ud60x18(self.openInterest);
newOpenInterest =
currentOpenInterest.sub(oldPositionSize.abs().intoUD60x18()).add(newPositionSize.abs().intoUD60x18());
SD59x18 currentSkew = sd59x18(self.skew);
newSkew = currentSkew.add(sizeDelta);
}
function liquidateAccounts(uint128[] calldata accountsIds) external {
...
for (uint256 i; i < accountsIds.length; i++) {
...
for (uint256 j; j < ctx.activeMarketsIds.length; j++) {
...
position.clear();
tradingAccount.updateActiveMarkets(ctx.marketId, ctx.oldPositionSizeX18, SD59x18_ZERO);
+ (ctx.newOpenInterestX18, ctx.newSkewX18) =
+ perpMarket.getNewOpenInterestWithOutChechLimit(ctx.oldPositionSizeX18, ctx.oldPositionSizeX18, SD59x18_ZERO);
perpMarket.updateOpenInterest(ctx.newOpenInterestX18, ctx.newSkewX18);
}
...
}
}