GMX GLP AUM Roundtrip
Exploit Transactions
0x0b8cd648fb585bc3d421fc02150013eab79e211ef8d1c68100f2820ce90a47120x20abfeff0206030986b05422080dc9e81dbb53a662fbc82461a47418decc49af0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626efVictim Addresses
0x489ee077994b6658eafa855c308275ead8097c4aArbitrumLoss Breakdown
Similar Incidents
Lodestar cplvGLP Inflation
31%Sharwa Ephemeral Collateral Exploit
30%Paribus Redeem Reentrancy
29%Sentiment Balancer Oracle Overborrow
28%The Standard Self-Swap Bad Debt
28%dForce Oracle Reentrancy Liquidation
28%Root Cause Analysis
GMX GLP AUM Roundtrip
1. Incident Overview TL;DR
An adversary-controlled helper contract exploited GMX on Arbitrum during keeper transaction 0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef. Inside that single transaction, the helper borrowed USDC, minted GLP while GMX AUM was low, opened WBTC shorts to raise the redemption-side AUM inputs, redeemed GLP across multiple vault assets, repaid the flash loan, and kept a large residual basket.
The root cause was GMX's use of mutable intra-transaction AUM snapshots for both sides of a same-transaction GLP roundtrip. GlpManager._addLiquidity priced minting from an early AUM snapshot, GlpManager._removeLiquidity priced redemption from a later snapshot, and Vault.increasePosition let the attacker mutate the short-accounting inputs between those two pricing points while cooldownDuration was zero.
2. Key Background
GMX GLP is a share over vault assets. The number of GLP minted or burned depends on GlpManager.getAumInUsdg(...), which aggregates vault balances and short-accounting inputs. For non-stable assets, the AUM calculation includes globalShortSizes, globalShortAveragePrices, guaranteedUsd, reservedAmounts, and the unreserved portion of poolAmounts.
The relevant source behavior is:
// GMX GlpManager._addLiquidity
uint256 aumInUsdg = getAumInUsdg(true);
uint256 glpSupply = IERC20(glp).totalSupply();
...
uint256 mintAmount = aumInUsdg == 0 ? usdgAmount : usdgAmount.mul(glpSupply).div(aumInUsdg);
// GMX GlpManager._removeLiquidity
uint256 aumInUsdg = getAumInUsdg(false);
uint256 glpSupply = IERC20(glp).totalSupply();
uint256 usdgAmount = _glpAmount.mul(aumInUsdg).div(glpSupply);
For shorts, Vault.increasePosition immediately updates the short-accounting state:
if (globalShortSizes[_indexToken] == 0) {
globalShortAveragePrices[_indexToken] = price;
} else {
globalShortAveragePrices[_indexToken] = getNextGlobalShortAveragePrice(_indexToken, price, _sizeDelta);
}
_increaseGlobalShortSize(_indexToken, _sizeDelta);
At the incident block, GlpManager.cooldownDuration() was zero, so newly minted GLP could be redeemed without a waiting period.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is a share-pricing manipulation in which the same actor can obtain two inconsistent prices for the same GLP roundtrip inside one transaction. GMX computed mint-side pricing from a pre-deposit AUM snapshot and redeem-side pricing from a later AUM snapshot. The attacker was able to mutate that later AUM by opening large WBTC shorts between mint and redeem. Because getAum() incorporates global short state, the later redemption used a much larger AUM than the mint leg. With cooldownDuration = 0, there was no temporal barrier preventing immediate redemption of newly minted GLP. This broke the expected invariant that one actor's mint-and-burn roundtrip should not let that actor extract more vault value merely by altering protocol pricing inputs mid-transaction. The exploit is ACT because it required only public protocol transactions, public GMX contracts, and temporary on-chain capital.
4. Detailed Root Cause Analysis
The keeper transaction began from GMX PositionManager and eventually paid ETH into helper contract 0x7d3bd50336f64b7a473c51f54e7f0bd6771cc355, which triggered the exploit body. The filtered call flow shows the helper calling GMX RewardRouter/GlpManager, then GMX Vault, inside the same keeper-executed transaction.
The receipt proves the critical ordering:
AddLiquidity token=USDC tokenAmount=6000000000000 usdg=5997000000000000000000000 glpMinted=4129578056417997084610025
IncreasePosition collateral=USDC index=WBTC collateralDelta=1538567619570000000000000000000000000 sizeDelta=15385676195700000000000000000000000000 isLong=False
RemoveLiquidity token=WBTC ...
RemoveLiquidity token=WETH ...
...
Flash ... amount1=7538567619570 paid1=3769283810
The incident root-cause numbers are consistent with those logs. The mint leg recorded aumInUsdg = 46,942,248.263037264990037614 and minted 4,129,578.056417997084610025 GLP from 6,000,000 USDC. After the helper transferred additional USDC collateral into GMX Vault and opened the WBTC short, the first redemption-side RemoveLiquidity event recorded aumInUsdg = 917,911,611.253245890552483378. That is the core pricing break: the same GLP position was minted against a far lower AUM than the AUM used moments later to redeem it.
The balance diff confirms this manipulation produced real vault outflows. The helper finished the transaction with positive deltas in USDC, USDC.e, USDT, FRAX, DAI, WETH, WBTC, LINK, UNI, and residual fsGLP, while the GMX Vault lost matching balances across those assets. The same balance diff also shows the Uniswap pool ended with the flash-loan fee increase in USDC, confirming the attack remained solvent after repayment.
This is the invariant violation in code terms: a GLP minter must not be able to improve its own redemption rate by mutating getAum() inputs after minting but before burning. GMX violated that invariant because _addLiquidity and _removeLiquidity each sampled attacker-mutable AUM at different points within a single transaction.
5. Adversary Flow Analysis
The adversary lifecycle had three stages.
First, controller EOA 0xdf3340a436c27655ba62f8281565c9925c3a5221 deployed and funded helper contract 0x7d3bd50336f64b7a473c51f54e7f0bd6771cc355. Collector artifacts tie the helper deployment directly to that EOA and show two setup transactions, 0x0b8cd648fb585bc3d421fc02150013eab79e211ef8d1c68100f2820ce90a4712 and 0x20abfeff0206030986b05422080dc9e81dbb53a662fbc82461a47418decc49af.
Second, GMX keeper 0xd4266f8f82f7405429ee18559e548979d49160f3 executed the helper's queued order in 0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef. That public keeper action transferred ETH into the helper and entered the exploit body.
Third, the helper performed the profit-taking loop: it borrowed 7,538,567.61957 USDC from the Uniswap V3 pool, minted GLP at the low AUM snapshot, opened WBTC shorts to change GMX short-accounting, redeemed GLP into WBTC, WETH, USDC.e, LINK, UNI, USDT, FRAX, DAI, repeated FRAX-based mint/short/redeem subloops, then repaid 7,542,336.90338 USDC to the pool. The helper retained a large multi-asset basket and residual staked GLP at transaction end.
6. Impact & Losses
The collector balance diff shows the following direct helper gains at the end of the incident transaction:
[
{"token_symbol":"USDC","amount":"9749629327098","decimal":6},
{"token_symbol":"USDC.e","amount":"187343427631","decimal":6},
{"token_symbol":"USDT","amount":"1343601991557","decimal":6},
{"token_symbol":"FRAX","amount":"10548626304231692487914531","decimal":18},
{"token_symbol":"DAI","amount":"1338385137937366936342579","decimal":18},
{"token_symbol":"WETH","amount":"3205499178565922195198","decimal":18},
{"token_symbol":"WBTC","amount":"8817336478","decimal":8},
{"token_symbol":"LINK","amount":"23800209308406681099727","decimal":18},
{"token_symbol":"UNI","amount":"65479241421767977905495","decimal":18}
]
The stablecoin-only lower bound reported in the root cause is 23,167,586.188455059 USD after the flash-loan fee. That is a lower bound because it excludes the helper's positive WETH, WBTC, LINK, UNI, native ETH, and residual fsGLP balances. The direct victim was GMX Vault 0x489ee077994b6658eafa855c308275ead8097c4a, with GLP holders bearing the extraction through manipulated share pricing.
7. References
- Incident transaction:
0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef - Adversary setup transactions:
0x0b8cd648fb585bc3d421fc02150013eab79e211ef8d1c68100f2820ce90a4712,0x20abfeff0206030986b05422080dc9e81dbb53a662fbc82461a47418decc49af - GMX Vault:
0x489ee077994b6658eafa855c308275ead8097c4a - GMX GlpManager:
0x3963ffc9dff443c2a94f21b129d429891e32ec18 - GMX RewardRouterV2:
0xb95db5b167d75e6d04227cfffa61069348d271f5 - Collector evidence used: decoded event summary, focused state diff, filtered call flow, helper selector summary, tx metadata, and balance diff under
/workspace/session/artifacts/collector/ - Upstream source references used for validation: GMX
GlpManager.solandVault.solfromhttps://raw.githubusercontent.com/gmx-io/gmx-contracts/master/contracts/core/