# GMX Hack Analysis & Attack Scenarios with Wake

> This analysis examines the 42M attack on the GMX protocol. We provide details of the vulnerability with a working reproduction of the attack scenario.

- Canonical: https://ack3.ai/research/gmx-hack-analysis-attack-scenarios-with-wake/
- Published: July 22, 2025
- Updated: August 4, 2025
- Authors: Naoki Yoshida
- Category: Incident analysis
- Topics: education, ethereum, evm, exploit, hack, security, smart-contract, solidity, tutorial, wake

This analysis examines the [42M attack on the GMX protocol](https://x.com/GMX_IO/status/1943336664102756471). We provide a detailed technical breakdown of the vulnerability and include a working reproduction of the attack scenario for educational purposes in a forked environment.

The attack exploited a **cross-contract reentrancy vulnerability** that bypassed access controls during position increases. This resulted in GLP token price manipulation with a higher price, allowing attackers to redeem tokens at the manipulated price and extract profits from the protocol.

![wake test output](/research/media/2025/07/cmd_result.png)

## Reproducing with Wake

  1. Clone the [repository](https://github.com/Ackee-Blockchain/gmx-exploit-scenario)
  2. Import GMX project dependencies:
`$ npm i`
  3. Initialise Wake:
`$ wake up`
  4. Get an Arbitrum fork URL from Alchemy or other providers and set it in `.env` similar to `.env.example`.
  5. Run the test:
`$ wake test tests/test_attack_simple.py`
  6. Uncomment `print(tx.call_trace)` to see the call trace.



## Root cause

The vulnerability stems from a reentrancy issue. While the reentrancy itself is straightforward, its impact is significant.

The core issue: GLP token price calculation relies on the `globalShortAveragePrices` variable from `ShortsTracker`. This dependency creates an exploitable attack vector.

The vulnerability is cross-contract reentrancy. Multiple contracts were involved during the transaction. Each contract has a reentrancy guard; however, reentrancy occurred after it had already exited for one specific contract.

For a detailed explanation of cross-contract reentrancy, see [this comprehensive guide](/research/cross-contract-reentrancy-attack/).

## Entry point

The attack begins when a user increases their position:

  1. User calls `createIncreaseOrder` to register the order
  2. The orderkeeper bot calls `PositionManager.executeIncreaseOrder` to execute it
  3. Within `executeIncreaseOrder`, `ShortsTracker.updateGlobalShortData` is invoked



`ShortsTracker.updateGlobalShortData` stores the `globalShortAveragePrice` for the token — the average entry price of all short positions. This value directly influences GLP token price calculations.

```solidity
contract PositionManager {
    function executeIncreaseOrder(
        address _account,
        uint256 _orderIndex,
        address payable _feeReceiver
    ) external onlyOrderKeeper {
        //...
        IShortsTracker(shortsTracker).updateGlobalShortData(_account, collateralToken, indexToken, isLong, sizeDelta, markPrice, true);
        ITimelock(timelock).enableLeverage(_vault); // isLeverageEnabled <- True
        IOrderBook(orderBook).executeIncreaseOrder(_account, _orderIndex, _feeReceiver);
        ITimelock(timelock).disableLeverage(_vault); // isLeverageEnabled <- False
        _emitDecreasePositionReferral(_account, sizeDelta);
    }
}
```

External calls follow this path to reach the `Vault`:

  * `OrderBook.executeIncreaseOrder`
    * `Router.pluginIncreasePosition`
      * `Vault.increasePosition`



The `decreasePosition` flow follows a similar pattern.

The `Vault.increasePosition` function checks that `isLeverageEnabled` equals `True` to verify that the call occurs between `Timelock.enableLeverage` and `Timelock.disableLeverage`. This check proved insufficient.

```solidity
contract Vault {
    // function has no msg.sender check.
    // Assumes caller transfers tokens or at least the caller is trusted.
    function increasePosition(
        address _account,
        address _collateralToken,
        address _indexToken,
        uint256 _sizeDelta,
        bool _isLong
    ) external override nonReentrant {
        _validate(isLeverageEnabled, 28); // this will be bypassed
        _validateGasPrice();
        _validateRouter(_account);
        ...
        ...
    }
}
```

During `Vault.decreasePosition`, the contract transfers collateral tokens for closed positions. When the collateral token is WETH, the system withdraws ETH and transfers it to the user’s account. Notably, these WETH operations occur outside the `Vault` contract.

The call flow proceeds as follows:

  * `OrderBook.executeDecreaseOrder`
    * `Router.pluginDecreasePosition`
      * `Vault.decreasePosition`
        1. ReentrancyGuard set to `ENTERED`
        2. `Vault` closes position
        3. Sends WETH to OrderBook
        4. ReentrancyGuard set to `NOT_ENTERED`
    * `OrderBook` withdraws ETH
    * Transfers ETH to user
      * `User.receive` is triggered
        * `Vault.increasePosition` (exploit)
          1. ReentrancyGuard confirms `NOT_ENTERED`
          2. ReentrancyGuard is set to `ENTERED`
          3. Attack continues…



The reentrancy guard in `Vault` starts as `NOT_ENTERED`, but the reentrant call occurs after this status has been reset, bypassing the protection.

## Attack escalation

The direct `Vault.increasePosition` call bypasses `ShortsTracker.updateGlobalShortData`, causing `GlpManager.getAum` to return inflated values and artificially increase the GLP token price.

Attack sequence:

  1. Re-enter through the open entry point
  2. Add liquidity to obtain GLP tokens
  3. Call `increasePosition` to manipulate the GLP token price upward
  4. Remove liquidity at the inflated GLP token price



## Operational details

The attacker uses `RewardRouterV2.mintAndStakeGlp` because `GLPManager.inPrivateMode` is enabled, preventing direct calls to `GLPManager.addLiquidity`.

The attacker used a flash loan with USDC in fallback function to create a large WBTC short position.

## Summary

The attack succeeded due to fragmented data responsibility across contracts. Critical state information was split between `ShortsTracker` and `Vault`, rendering the reentrancy guard ineffective. This architectural vulnerability allowed attackers to manipulate GLP token prices through carefully orchestrated re-entrant calls, enabling the multimillion exploit.
