Incident Summary
On 9 July 2025, GMX V1 vault was exploited by a white-hat for ~$42M due to a reentrancy issue. The funds were later returned to GMX who awarded the white-hat a 10% bounty.
The whitehat had minted and then staked GLP before creating a short position directly from the vault contract through reentrancy. Executing in this order bypassed the ShortsTracker, and prevented the average short position price from being updated. This occurs when the market price exceeds the tracked average price, resulting in the protocol overestimating unrealized losses. As a result, the Assets Under Management (AUM) calculation was manipulated to inflate the apparent value of GLP.
Two days after the incident, the white-hat sent an on-chain message to say funds will be returned, then later transferred ~$37M to GMX.

Background
GMX is a decentralized spot and perpetual exchange that supports low swap fees and low price impact trades. The liquidity for trades comes from LPs who deposit these and other tokens into a single Vault contract. In return, they are given the GLP token, which can be redeemed at any time.
In 2022 GMX, who had a TVL (Total Value Locked) exceeding $500 million, awarded a million-dollar bug bounty to Collider's research arm (https://www.collider.vc/post/gmx-granted-million-dollar-bug-bounty-to-collider-the-bug-aftermath).
Prior to the bounty award, users placed orders for their positions, and GMX off-chain keepers executed the order as follows:

Updates of short sized and average global short price of user-declared indexToken used to both be in the Vault contract at the lower level of execution.
After the major vulnerability was found in the bug bounty, changes were made so that the updates were handled by a newly introduced ShortTracker called by the higher-level PositionManager contract:

This update, or what’s left behind, introduced an unexpected vulnerability that led to ~$42M loss.
Vulnerability
During the AUM (asset under management) calculation by GlpManager, which is critical in determining the value of a user’s holding, the size is still fetched from the Vault.
As shown in the “legacy” code in Vault.IncreasePosition() (but not in DecreasePosition() somehow), it is updated there as well.
However, globalShortAveragePrices is fetched and maintained by the newly introduced _shortsTracker.


As these occur at different levels of execution, the white-hat could use reentrancy to bypass the short avgp update at the PositionManager level and still trigger the short size update at the Vault level. The asynchronous updates were exploited to create discrepancies that manipulate the AUM calculation to the white-hat’s advantage.
Specifically, the attacker exploited the interaction between minting and staking GLP and opened a short position directly through the Vault contract. By bypassing the ShortsTracker, the attacker prevented the average short position price from being updated. This is done strategically when the market price is higher than the tracked average price, causing the protocol to overestimate unrealized losses. As a result, the Assets Under Management (AUM) calculation was inflated, increasing the apparent value of GLP.
The attacker could then redeem their GLP at an artificially elevated value, effectively withdrawing more funds than they initially deposited.

Key Transactions
https://arbiscan.io/tx/0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef
Attack Flow
Addresses
Exploiter wallets:
- 0xDF3340A436c27655bA62F8281565C9925C3a5221
- 0x414A734F7D9d204b3C30CBd66572685B8E675287
- 0x6aCC60B11217A1fd0e68B0EcaEE7122d34A784c1
- 0xa33Fcbe3b84Fb8393690D1E994B6A6aDC256D8A3
- 0x639cd2fc24EC06bE64aaf94eB89392Bea98A6605
- 0x99CdeB84064c2BC63de0CEa7C6978e272d0f2DAe
- 0xe9Ad5a0F2697A3cF75FfA7328BdA93dBAeF7F7e7
- 0x69c965e164fa60e37a851aA5CD82B13Ae39C1d95
- 0x1DA7653D18B59bc19424d0017B21a045aD8DB038
Exploiter contract:
- 0x7D3BD50336f64b7A473C51f54e7f0Bd6771cc355
GMX Vault:
- 0x489ee077994B6658eAfA855C308275EAd8097C4A
GMX Order Book:
- 0x09f77E8A13De9a35a7231028187e9fD5DB8a2ACB
GMX ShortsTracker:
- 0xf58eEc83Ba28ddd79390B9e90C4d3EbfF1d434da
GMX GlpManager:
- 0x3963FfC9dff443c2A94f21b129D429891E32ec18
Step by Step
Step 1:
The attacker opened a new position using the attack contract as the account, then placed a small decreasing order in the GMX Order Book.

Step 2:
The GMX off-chain keeper executed this order via PositionManager.executeDecreaseOrder().
GMX Keeper → PositionManager.executeDecreaseOrder() → OrderBook.executeDecreaseOrder() → Router.pluginDecreasePosition() → Vault.decreasePosition()
Note that during the execution of this order, the PositionManager temporarily set the _isLeverageEnabled flag of the Vault contract to true, which enabled the OrderBook contract to execute a decreasing position operation via OrderBook.executeDecreaseOrder.
The decreasing position operation returned the released collateral (ETH) to the user, which was the attack contract.
The executeDecreaseOrder()->_transferOutETH()->_receiver.sendValue() function for ETH enabled the attacker to execute operation before the _isLeverageEnabled flag was reset to false.

Step 3:
During reentrancy, the attack created a WBTC short position with 30 times leverage, 3,001 USDC * 30, via Vault.increasePosition() adding a substantial _sizeDelta of 90030000000000000000000000000000000 (90030e30), compared to the existing globalShortSize of 105403061114092959107000000000000000(105403e30)
without triggering the globalShortAveragePrices update.
The white-hat then placed a reverse normal (not through reentrancy) order to decrease the short position and retrieve most of the 3001 USDC.
The tactic was repeated five times.
As the WBTC global short average price updated after the decrease order was skipped by reentrancy, the white-hat was able to deflate the price, 108757787000274036210359376021024492 (~1.087e35), to 1913705482286167437447414747675542 (~1.913e33) ~57 times in just five iterations.
As a consequence of the last manipulation, a larger priceDelta contributed to a larger PnL at the denominator and resulted in a lower price and even larger priceDelta.
First iteration:
priceDelta = ~109505e30-~108757e30 = ~747e30
size.mul(priceDelta).div(averagePrice) = 78840119444662794775583864191648649089204261998551356000000000000000 / 108757787000274036210359376021024492
GlobalShortAveragePrice = 104766755156748843189540879601516878
Second iteration:
priceDelta = ~109529e30 - ~104934e30 = ~4595e30
size.mul(priceDelta).div(averagePrice) = 1683806156985823019383613620000000000000000000000000000000000000000000
GlobalShortAveragePrice = 85421390228731999769933799789356123
Such a dynamic allows the exploiter to manipulate price in an accelerating pattern.

Step 4:
In the final exploit, again, placing a decrease order on WETH and through executeDecreaseOrder()->_transferOutETH()->_receiver.sendValue() , the white-hat flashloaned 7.538M USD Coin, then minted 4,129,578 GLP tokens with 6M USD Coin via RewardRouter.mintAndStakeGlp.
According to GlpManager._addLiquidity(), mintedGLP = usdgAmount * glpSupply / aumInUSDG


The first three terms in the AUM calculation by GlpManager are relatively stable, and volatility comes from the last term of shorts loss, of WBTC in this exploit.
usdgAmount = ~ 5,997,000 * 10^18
glpSupply = ~ 32324775445383446445240290 * 10^18
poolAmounts = 11,439,302,230
globalShortSizes = 15373061114092959107000000000000000
globalShortAveragePrices = 1913705482286167437447414747675542
aum = 12160892925083681998303470188395218961
globalShortSizes = 15373061114092959107000000000000000
The total AUM Value to start 46942248263037264990037614165759809197 (~4.694e37).
Step 5:
The white-hat opened a large short position for WBTC via Vault.increasePosition() with the remaining 1.538M USD Coin from flashloan.
Again, through reentrancy, the white-hat could directly call Vault.increasePosition() and increase the position without a globalShortAveragePrices update.
As the average price (for WBTC) was not updated in the shorts tracker, when getAUM() was called, it thinks that the increased short size is at the (NOT UPDATED) average price, which was less than the market price, so this inflated value was counted as a loss and added to the AUM value.
Initially, the global short size for WBTC was 15373061114092959107000000000000000 (~1.537e34),
Now the short size was set to
15373061114092959107000000000000000 + 15385676195700000000000000000000000000 = 15401049256814092959107000000000000000 (~1.54e37), 1000 times larger than previous.
The AUM was in turn inflated to 917911611253245890552483378294110666923(9.179e38), ~20 times larger than previous (~4.694e37).
Step 6:
The white-hat performed multiple GLP burning operations (removing liquidity from GLP pools) via RewardRouter.unstakeAndRedeemGlp().
The AUM was used to determine the amount of assets that the user's GLP can be redeemed for, effectively allowing the user to withdraw over 19x more value than what they initially deposited.
More than enough to drain the victim Vault and take ~$42M assets.
Step 7: The white-hat closed the large short position, retrieved collateral assets, and then repaid the flash loan.
Fund Flow
The ~$42M of assets were split into 5 wallets as follows:
- 0xDF33 = $10,497,825.69
- 0x639c = $7,586,724.5
- 0xe9Ad = $8,429,850
- 0xa33F = $8,429,850
- 0x69c9 = $8,429,850

The funds were then swapped for ETH before being returned to the GMX protocol two days later, with the white-hat keeping 1,700 ETH after returning 10k ETH.

To keep up to date on the latest incident alerts and statistics follow @certikalert on X, or read our latest analysis on certik.com.



