DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: low
Invalid

Incorrect Withdrawal Amount Calculation Due to Mismatched prevCollateralBalance After Decrease Order and Liquidation

Summary

The PerpetualVault contract’s withdrawal flow in _finalize incorrectly calculates the withdrawn amount using balanceOf - prevCollateralBalance, potentially overpaying or misallocating funds if a partial liquidation occurs via liquidation after a decrease order. Specifically, prevCollateralBalance is set to 0 after a decrease order of 50,000, but the vault’s balance includes 50,000 from the decrease order and an additional i.e 10,000 from liquidation, resulting in withdrawn = 60,000. This exceeds User A’s entitled 50,000, violating fair share distribution and risking overpayment or improper fund allocation.

Vulnerability Details

  • In the withdrawal process:

    • User A requests a withdrawal of 50,000 collateral from a position with 100,000 collateral, shared equally between 2 users.

    • A MarketDecrease order is created with collateralDeltaAmount = 50,000, reducing the position’s collateral to 0. In afterOrderExecution, prevCollateralBalance is set to 0 for the decrease order, assuming no collateral remains in the position.

    • The decrease order execution returns 50,000 collateral to the vault, and before _finalize is called, the position is partially liquidated by liquidation, transferring an additional 10,000 collateral to the perpVault.

    • Thus, collateralToken.balanceOf(address(this)) = 50,000 + 10,000 = 60,000.

    • In _finalize, the withdrawn amount is calculated as:

      uint256 withdrawn = collateralToken.balanceOf(address(this)) - prevCollateralBalance;
      • Here, collateralToken.balanceOf(address(this)) = 60,000.

      • prevCollateralBalance = 0 (from the decrease order).

      • Thus, withdrawn = 60,000 - 0 = 60,000, which is passed to _handleReturn.

    • In _handleReturn, the amount transferred is calculated as:

      uint256 amount = withdrawn + balanceBeforeWithdrawal * shares / totalShares;
      • If balanceBeforeWithdrawal is interpreted as the state after the decrease order (50,000) but before liquidation, and withdrawn = 60,000, this could lead to overpayment (e.g., User A receiving 60,000 instead of their entitled 50,000) or an incorrect distribution of funds.

  • Root Cause:

    • prevCollateralBalance is set to 0 after the decrease order, but it does not account for the subsequent return of 50,000 collateral from the decrease order execution or the additional 10,000 from liquidation.

    • The logic in _finalize and _handleReturn does not correctly reconcile the vault’s current balance with the user’s entitled share, leading to potential overpayment or misallocation.

Impact

  • Overpayment Risk: Users like User A may receive more collateral (e.g., 60,000 instead of 50,000) than their entitled share, risking depletion of the vault’s funds and inequitable distribution among users.

  • Unfair Share Distribution: The withdrawal process violates the README’s key invariant: "Depositor Share Value Preservation: The value of a depositor’s shares should never decrease due to the actions of other depositors. Any losses or gains should be distributed proportionally based on share ownership."

  • Vault Insolvency Risk: Overpaying users could drain the vault’s collateral, leaving insufficient funds for other users or operations, potentially leading to insolvency.

  • User Trust Erosion: Incorrect withdrawals could erode trust in the protocol, potentially causing loss of users and reputational damage.

Tools Used

  • Manual code review

Recommendations

Modify the _finalize function to correctly account for the total collateral returned from both the decrease order and external events (e.g., liquiation) before calculating the withdrawn amount

Updates

Lead Judging Commences

n0kto Lead Judge 9 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
Assigned finding tags:

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

Support

FAQs

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

Give us feedback!