Part 2

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

Funding Rate Direction Inversion in Liquidation and Settlement Logic Due to Incorrect Unary Operation

Summary

The getPendingFundingFeePerUnit function in PerpMarket.sol incorrectly calculates funding rates by applying the unary operation to rate averages. This issue impacts critical in-scope functions in LiquidationBranch and SettlementBranch by causing incorrect margin calculations and potentially preventing necessary liquidations.

Vulnerability Details

Both LiquidationBranch::liquidateAccounts and SettlementBranch::_fillOrder rely on tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(), which uses the vulnerable funding rate calculation:

// In PerpMarket.sol
SD59x18 avgFundingRate = unary(sd59x18(self.lastFundingRate).add(fundingRate)).div(sd59x18Convert(2));

The issue manifests in margin calculations through this chain:

  1. LiquidationBranch::liquidateAccounts or SettlementBranch::_fillOrder

  2. -> tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd()

  3. -> position.getAccruedFunding(fundingFeePerUnit)

  4. -> PerpMarket::getPendingFundingFeePerUnit (where the bug occurs)

For example, during a market transition:

lastRate = -0.0002 (shorts paying longs)
currentRate = 0.0001 (longs should pay shorts)
Current: unary(-0.0002 + 0.0001)/2 = 0.00005
Correct: (-0.0002 + 0.0001)/2 = -0.00005

This causes incorrect accrued funding calculation in:

SD59x18 positionUnrealizedPnl = position.getUnrealizedPnl(markPrice)
.add(position.getAccruedFunding(fundingFeePerUnit));

Impact

This impacts the liquidation and settlement logic:

  1. Incorrect Liquidation Decisions

    • When LiquidationBranch::liquidateAccounts checks account health via getAccountMarginRequirementUsdAndUnrealizedPnlUsd, the inflated PnL from incorrect funding calculations may prevent necessary liquidations

    • For example, if a position should pay -5 in funding but receives +5 instead, their PnL is inflated by 10 units

  2. Settlement Issues

    • SettlementBranch::_fillOrder uses the same margin calculations for validation

    • Incorrect funding calculations could allow trades that should be rejected based on true margin requirements

Proof of Concept

contract LiquidationTest is Test {
function testIncorrectLiquidation() public {
// Setup account with position near liquidation threshold
// Account should be liquidatable with -5 funding payment
// But stays alive due to incorrect +5 funding calculation
// Position setup omitted for brevity
SD59x18 lastRate = sd59x18(-200_000_000_000_000); // -0.0002
SD59x18 currentRate = sd59x18(100_000_000_000_000); // +0.0001
// Funding calculation inverts sign, inflating account margin
bool isLiquidatable = perpEngine.checkAccountLiquidatable(accountId);
assertTrue(!isLiquidatable); // Should be true
}
}

Tools Used

Manual Analysis, Foundry

Recommendations

Remove the unary operation and calculate average directly:

SD59x18 avgFundingRate = (lastRate.add(currentRate)).div(convert(2));

This ensures correct funding rate signs flow through to liquidation and settlement margin checks.

Additional Notes

  • Direct impact on in-scope functions: LiquidationBranch::liquidateAccounts and SettlementBranch::_fillOrder

  • Affects core account health calculations through margin requirement checks

Updates

Lead Judging Commences

inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Appeal created

smol_korok Submitter
4 months ago
inallhonesty Lead Judge
4 months ago
inallhonesty Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Out of scope

Support

FAQs

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