Relevant GitHub Links
https://github.com/Cyfrin/2024-07-zaros/blob/d687fe96bb7ace8652778797052a38763fbcbb1b/src/perpetuals/branches/LiquidationBranch.sol#L209
Summary
During the execution of LiquidationBranch.liquidateAccounts()
function, it interacts with perp market storage to update values depending on the liquidated user position. One of the updates is perpMarket.updateOpenInterest()
which should update current market open interest and skew. Values of newOpenInterestX18
and newSkewX18
are stored in ctx
variable which is LiquidationContext
struct. But these values are not set to ctx
, and because of that to perpMarket.updateOpenInterest()
as values of newOpenInterestX18
and newSkewX18
variables are passed default values which are 0. As a result open interest and skew for the current market are 0.
Impact
Open interest and skew are used in every market-changing function like filling orders or liquidating accounts. Incorrect values of their variables would result in incorrect calculation of funding rate (PerpMarket.getCurrentFundingRate()
, PerpMarket.getCurrentFundingVelocity
) and user fees (SettlementBranch._fillOrder()
).
Proof of Concept
Function LiquidationBranch.liquidateAccounts() in this line calls perpMarket.updateOpenInterest()
:
tradingAccount.updateActiveMarkets(ctx.marketId, ctx.oldPositionSizeX18, SD59x18_ZERO);
@> perpMarket.updateOpenInterest(ctx.newOpenInterestX18, ctx.newSkewX18);
}
But values ctx.newOpenInterestX18
and ctx.newSkewX18
are not set in the function.
Coded PoC
Modify test/integration/perpetuals/liquidation-branch/liquidateAccounts/liquidateAccounts.t.sol test with next code and execute this test with forge test --match-test testFuzz_OpenInterestAndSkewAreResettedAfterLiquidation
:
function testFuzz_OpenInterestAndSkewAreResettedAfterLiquidation(uint256 marketId, uint256 amountOfHealthyPositions) external {
amountOfHealthyPositions = bound(amountOfHealthyPositions, 1, 10);
MarketConfig memory fuzzMarketConfig = getFuzzMarketConfig(marketId);
uint256 marginValueUsd = amountOfHealthyPositions * 5_000e18;
uint256 initialMarginRate = fuzzMarketConfig.imr;
uint256 accountMarginValueUsd = marginValueUsd / 2;
deal({ token: address(usdz), to: users.naruto.account, give: marginValueUsd });
for (uint256 i = 0; i < amountOfHealthyPositions; i++) {
uint128 account = createAccountAndDeposit(accountMarginValueUsd, address(usdz));
openPosition(fuzzMarketConfig, account, initialMarginRate, accountMarginValueUsd, false);
}
uint128 liquidatableAccount = createAccountAndDeposit(accountMarginValueUsd, address(usdz));
openPosition(fuzzMarketConfig, liquidatableAccount, initialMarginRate, accountMarginValueUsd, true);
setAccountsAsLiquidatable(fuzzMarketConfig, true);
uint128[] memory accountsIds = new uint128[](1);
accountsIds[0] = liquidatableAccount;
changePrank({ msgSender: liquidationKeeper });
perpsEngine.liquidateAccounts(accountsIds);
SD59x18 skew = perpsEngine.getSkew(uint128(marketId));
(,, UD60x18 openInterestX18) = perpsEngine.getOpenInterest(fuzzMarketConfig.marketId);
assertEq(openInterestX18.intoUint128(), 0);
assertEq(skew.intoInt256(), 0);
}
Recommended Mitigation Steps
Not sure what update of open interest and skew was intended, but looking by SettlementBranch._fillOrder()
I can believe this mitigation is what it should be:
@@ -16,7 +16,7 @@ import { SafeCast } from "@openzeppelin/utils/math/SafeCast.sol";
// PRB Math dependencies
import { UD60x18, ud60x18, ZERO as UD60x18_ZERO } from "@prb-math/UD60x18.sol";
-import { SD59x18, sd59x18, ZERO as SD59x18_ZERO } from "@prb-math/SD59x18.sol";
+import { SD59x18, sd59x18, unary, ZERO as SD59x18_ZERO } from "@prb-math/SD59x18.sol";
@@ -206,6 +213,8 @@ contract LiquidationBranch {
// 1) open interest and skew are both decreased by liquidations
// 2) we don't want liquidation to be DoS'd in case somehow those cap
// checks would fail
+ (ctx.newOpenInterestX18, ctx.newSkewX18) =
+ perpMarket.checkOpenInterestLimits(unary(ctx.oldPositionSizeX18), ctx.oldPositionSizeX18, SD59x18_ZERO);
perpMarket.updateOpenInterest(ctx.newOpenInterestX18, ctx.newSkewX18);
}