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

Reentrancy Risk in `PerpetualVault._runSwap` Due to Post-Interaction State Updates

Summary

The _runSwap function in the PerpetualVault contract updates critical state variables (swapProgressData) after making external calls to DEX/GMX swaps. This violates the Checks-Effects-Interactions pattern, creating a reentrancy risk if malicious tokens or callback functions reenter the contract before state consistency is restored.


Vulnerability Details

Affected Code

  • Function: _runSwap(bytes[], bool, MarketPrices) (PerpetualVault.sol#976-1019)

  • External Calls:

    • _doDexSwap(data, isCollateralToIndex) (L985)

    • _doGmxSwap(data, isCollateralToIndex) (L992)

Root Cause

  1. Unsafe State Management:

    • After external swaps (DEX/GMX), the contract updates swapProgressData.swapped (for DEX) and swapProgressData.remaining/isCollateralToIndex (for GMX).

    • Example from _doGmxSwap:

      function _doGmxSwap(...) internal {
      ...
      // External call to GMX
      gmxProxy.createOrder(...);
      // State updated AFTER external call
      swapProgressData.remaining = amountIn;
      swapProgressData.isCollateralToIndex = isCollateralToIndex;
      }
    • This allows reentrancy during the external call (e.g., GMX callback) while swapProgressData is stale.

  2. Cross-Function Reentrancy:

    • The afterOrderCancellation function (L592-623) uses swapProgressData to retry failed swaps.

    • An attacker could trigger this function mid-swap, exploiting outdated swapProgressData values.


Impact

  • Swap State Corruption: Reentrant calls could reset/modify swapProgressData, causing incorrect tracking of swap amounts.

  • Fund Loss/Theft: Malicious actors could exploit stale data to double-swap tokens or bypass swap limits.

  • Severity: High (exploitable via ERC777-like tokens or GMX callbacks).


Proof of Concept (PoC)

Attack Scenario

  1. Attacker Deposits ERC777 Token: Uses a token with a tokensReceived hook during transfers.

  2. Trigger _runSwap: Initiates a DEX swap for the malicious token.

  3. Reenter During Transfer:

    • The tokensReceived callback calls _runSwap again before swapProgressData is updated.

    • The reentrant _runSwap uses stale swapProgressData.remaining, allowing double swaps.

  4. Drain Funds: Attacker withdraws excess tokens from corrupted swap tracking.


Recommended Mitigation

1. Follow Checks-Effects-Interactions

Update swapProgressData before external calls:

function _doGmxSwap(...) internal {
// Update state FIRST
swapProgressData.remaining = amountIn;
swapProgressData.isCollateralToIndex = isCollateralToIndex;
// Execute external call AFTER
gmxProxy.createOrder(...);
}

2. Add Reentrancy Guards

Apply the nonReentrant modifier to _runSwap and related functions:

function _runSwap(...) internal nonReentrant {
...
}
Updates

Lead Judging Commences

n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
n0kto Lead Judge 7 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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