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

Exploitation via Front-Run Position Changes (Order Mirroring Vulnerability)

Summary

Keeper transactions are vulnerable to MEV bots front/back-running due to lack of order uniqueness protections.

Vulnerability Details

The PerpetualVault contract allows keepers to execute orders without unique identifiers, making it possible for attackers to detect pending transactions in the mempool and front-run them with identical orders. This results in the attacker's order being executed first, causing the legitimate order to execute at a worse price, leading to potential economic losses for the protocol and its users.

Root Cause:
The root cause of this issue is the lack of order uniqueness protections and slippage control in the PerpetualVault contract. The contract does not use unique identifiers for transactions, allowing attackers to duplicate and front-run orders.

Proof of Concept:

Copy and paste in PerpetualVault.t.sol and run

function test_FrontRunPositionChange() public {
address keeper = PerpetualVault(vault).keeper();
address attacker = makeAddr("attacker");
// 1. Legitimate large deposit
depositFixture(keeper, 1e18); // 1M USDC
MarketPrices memory prices = mockData.getMarketPrices();
bytes[] memory data = new bytes[](1);
data[0] = abi.encode(3380000000000000); // Long order
// 2. Attacker detects pending tx in mempool
vm.prank(attacker);
// Front-run with identical order
PerpetualVault(vault).run(true, true, prices, data);
// 3. Execute attacker's order first
GmxOrderExecuted(true);
// 4. Legitimate order executes at worse price
uint256 initialPrice = prices.indexTokenPrice.min;
prices.indexTokenPrice.min = initialPrice * 101 / 100; // 1% price impact
vm.prank(keeper);
PerpetualVault(vault).run(true, true, prices, data);
GmxOrderExecuted(true);
// Verify economic impact
uint256 vaultPositionSize = reader.getPositionInfo(
PerpetualVault(vault).curPositionKey(),
prices
).sizeInUsd;
// Attacker's profit = (Execution Price - Original Price) * Size
uint256 attackerProfit = (prices.indexTokenPrice.min - initialPrice) *
vaultPositionSize / prices.indexTokenPrice.min;
assertGt(attackerProfit, 0, "No MEV profit extracted");
}

Impact

  • Economic Losses: The protocol and its users may suffer economic losses due to front-running attacks.

  • Price Impact Exploitation: Front-run orders amplify price movement for back-run profit.

  • Lack of Slippage Control: No per-order max slippage checks allow unlimited price impact.

Tools Used

Manual

Recommendations

  1. Commit-Reveal Scheme:
    Implement a commit-reveal scheme to ensure order uniqueness and prevent front-running.

    // KeeperProxy.sol
    mapping(address => bytes32) public commitments;
    function commitRun(bytes32 commitment) external onlyKeeper {
    commitments[msg.sender] = commitment;
    }
    function revealRun(
    bytes32 nonce,
    bytes calldata params,
    bytes calldata signature
    ) external onlyKeeper {
    require(commitments[msg.sender] == keccak256(abi.encode(nonce, params, signature)));
    // Execute logic
    }
  2. MEV-Resistant Order Types:
    Implement MEV-resistant order types with unique identifiers and slippage control.

    // PerpetualVault.sol
    function run(
    uint256 maxPriceImpact,
    bytes32 salt // Unique per order
    ) external {
    require(!usedSalts[salt], "Duplicate order");
    usedSalts[salt] = true;
    // Revert if price impact exceeds allowed
    require(priceImpact <= maxPriceImpact);
    }
Updates

Lead Judging Commences

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

invalid_swap_slippage_and_deadline

Slippage and deadline are handled externally. Paraswap implementation used by the current code (behind the proxy): https://etherscan.io/address/0xdffd706ee98953d3d25a3b8440e34e3a2c9beb2c GMX code: https://github.com/gmx-io/gmx-synthetics/blob/caf3dd8b51ad9ad27b0a399f668e3016fd2c14df/contracts/order/OrderUtils.sol#L150C15-L150C33

invalid_gmx_increase/decrease_no_slippage

acceptablePrice does that job for increase/decrease positions. https://github.com/gmx-io/gmx-synthetics/blob/caf3dd8b51ad9ad27b0a399f668e3016fd2c14df/contracts/order/BaseOrderUtils.sol#L276C49-L276C66

Support

FAQs

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