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

No Slippage or Minimum-Output Checks in _doDexSwap()

Summary:

Because _doDexSwap() accepts arbitrary aggregator call data and never checks the actual output beyond computing outputAmount, the protocol is fully exposed to slippage exploits or malicious aggregator calls that yield near-zero tokens for the vault. Implementing a minimum-output or slippage check on-chain is crucial to protect users from catastrophic losses.

Unexpected market event can effectively drain user deposits or cause severe losses during DEX swaps, all without triggering a revert.

  • FIX -> Accept a minOutput parameter in _doDexSwap() or metadatathen enforce:

    require(outputAmount >= minOutput, "Slippage too high");
  • This ensures that if the aggregator returns fewer tokens than minOutput, the vault reverts and does not accept the trade.

RootCause & Where It Happens

Take a close look at _doDexSwap():

function _doDexSwap(bytes memory data, bool isCollateralToIndex) internal returns (uint256 outputAmount) {
(address to, uint256 amount, bytes memory callData) = abi.decode(data, (address, uint256, bytes));
IERC20 inputToken;
IERC20 outputToken;
if (isCollateralToIndex) {
inputToken = collateralToken;
outputToken = IERC20(indexToken);
} else {
inputToken = IERC20(indexToken);
outputToken = collateralToken;
}
uint256 balBefore = outputToken.balanceOf(address(this));
ParaSwapUtils.swap(to, callData); // <--- Unchecked external call
outputAmount = IERC20(outputToken).balanceOf(address(this)) - balBefore;
// No check for how big outputAmount is, or if it meets any minimum threshold.
emit DexSwap(address(inputToken), amount, address(outputToken), outputAmount, isCollateralToIndex);
}

Notice that the function:

  1. Accepts a prepared callData and to address from an off-chain script (the “metadata”).

  2. Calls ParaSwapUtils.swap(to, callData); without any local checks on how many tokens actually came back.

  3. Computes outputAmount only by the difference in the contract’s token balance before and after the swap.

No code ensures outputAmount is at least X or “less than some slippage threshold.” Consequently:

A malicious or misconfigured off-chain aggregator call could yield close to 0 tokens in return, effectively draining the user’s or vault’s input tokens.

  • The vault never reverts or even checks that the swap was “fair.”

Attack / Exploit Scenario

  1. User or Keeper calls a vault action that triggers _runSwap(), which in turn calls _doDexSwap().

  2. The off-chain aggregator data is set up maliciously, or the aggregator front-runs the trade in some fashion to produce a near-zero output.

  3. _doDexSwap() just sees outputAmount = (finalBalance - initialBalance), which might be extremely low (or even 0).

  4. The vault then proceeds with the rest of the flow, treating that near-zero output as valid—no revert or slippage protection kicks in.

This can be particularly damaging when large amounts of collateral are swapped via Paraswap, and the vault receives little or nothing in return. Honest users who deposit expecting a normal DEX rate can be heavily impacted.

Severity

High if the vault expects normal market trades with only moderate slippage.

->This can cause direct, catastrophic user or vault losses if a malicious aggregator route or a significant front-running scenario occurs.

Issue summarized

  • The vault provides no fallback or revert condition if the output is grossly less than expected.

Implement Slippage Checks On-Chain

  • Accept a minOutput parameter in _doDexSwap() or metadata, then enforce:

    require(outputAmount >= minOutput, "Slippage too high");
  • This ensures that if the aggregator returns fewer tokens than minOutput, the vault reverts and does not accept the trade.

Updates

Lead Judging Commences

n0kto Lead Judge 3 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

Support

FAQs

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