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

Arithmetic Underflow on Large Negative Price Impact

Summary

When GMX triggers a callback (afterOrderExecution) with a large negative priceImpact, code in PerpetualVault subtracts priceImpact from the user deposit. If priceImpact > deposit net, a raw uint256 underflow reverts repeatedly—locking the vault flow.

Technical Details

  • GmxProxy finalizes an order with a priceImpact value.

  • PerpetualVault.afterOrderExecution() does something like amount - feeAmount - uint256(priceImpact).

  • If priceImpact > amount - feeAmount, the subtraction underflows.

Proof of Revert Loop

  1. A user deposit net is 100, but the callback sets priceImpact = 150.

  2. The code 100 - 150 underflows → revert.

  3. GMX tries again; it reverts again. The vault remains in partial deposit flow.

Impact

  • Infinite Flow Lock: The vault can’t finalize or cancel; each callback reverts.

  • User Funds Stuck: The user’s deposit is effectively locked as the flow can’t finish.

PoC / Exploit Scenario

High volatility or malicious off-chain signals produce an extreme negative price impact. The deposit underflow reverts. The keeper re-tries; no progress is made.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
import "../src/PerpetualVault.sol";
import "../src/libraries/StructData.sol";
// A minimal harness contract to expose the internal _mint() via a simulated callback.
contract UnderflowHarnessVault is PerpetualVault {
// Expose a function to simulate the MarketIncrease branch where underflow occurs.
function simulateMarketIncrease(
uint256 depositAmount,
uint256 feeAmount,
int256 priceImpact,
uint256 prevSizeInTokens
) external {
// Set up a deposit.
counter = 1;
depositInfo[1] = DepositInfo({
amount: depositAmount,
shares: 0,
owner: msg.sender,
executionFee: 0,
timestamp: block.timestamp,
recipient: msg.sender
});
flow = FLOW.DEPOSIT;
flowData = prevSizeInTokens; // previous position size in tokens
curPositionKey = keccak256("fakePosition");
// Simulate the arithmetic branch:
// If priceImpact > 0 then: increased = amount - feeAmount - uint256(priceImpact) - 1.
// For depositAmount = 100, feeAmount = 10, priceImpact = 200,
// increased = 100 - 10 - 200 - 1 = -111, which underflows.
_simulateMint(depositAmount, feeAmount, priceImpact);
}
function _simulateMint(
uint256 amount,
uint256 feeAmount,
int256 priceImpact
) internal {
uint256 increased;
if (priceImpact > 0) {
// This should underflow when priceImpact is too high.
increased = amount - feeAmount - uint256(priceImpact) - 1;
} else {
increased = amount - feeAmount + uint256(-priceImpact) - 1;
}
// Using dummy prices with all values set to 1.
MarketPrices memory prices = MarketPrices({
indexTokenPrice: PriceRange({min: 1, max: 1}),
longTokenPrice: PriceRange({min: 1, max: 1}),
shortTokenPrice: PriceRange({min: 1, max: 1})
});
_mint(counter, increased, false, prices);
}
}
contract UnderflowTest is Test {
UnderflowHarnessVault harness;
address alice = address(0xAA);
function setUp() public {
harness = new UnderflowHarnessVault();
harness.setVaultState(
PerpetualVault.FLOW.NONE,
0,
true,
bytes32(0),
false,
false,
PerpetualVault.Action({selector: PerpetualVault.NextActionSelector.NO_ACTION, data: hex""})
);
}
function testUnderflowReverts() public {
vm.prank(alice);
// depositAmount = 100, feeAmount = 10, priceImpact = 200, prevSizeInTokens = 0 should cause underflow.
vm.expectRevert();
harness.simulateMarketIncrease(100, 10, 200, 0);
}
}

Recommendations

  1. Clamp or Check: Before subtracting, do:

    require(priceImpact <= amount - feeAmount, "Excessive price impact");

    or clamp to zero if it exceeds.

  2. Graceful Fallback: If impact is too large, revert with a clear “priceImpact too high” so the vault can be canceled or re-attempted.

Updates

Lead Judging Commences

n0kto Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Informational or Gas

Please read the CodeHawks documentation to know which submissions are valid. If you disagree, provide a coded PoC and explain the real likelihood and the detailed impact on the mainnet without any supposition (if, it could, etc) to prove your point.

Suppositions

There is no real proof, concrete root cause, specific impact, or enough details in those submissions. Examples include: "It could happen" without specifying when, "If this impossible case happens," "Unexpected behavior," etc. Make a Proof of Concept (PoC) using external functions and realistic parameters. Do not test only the internal function where you think you found something.

Support

FAQs

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