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

Fewer shares minted for positive price impact

Summary

In the afterOrderExecution function of PerpetualVault.sol during the processing of MarketIncrease orders in the deposit flow. Instead of adding a positive price impact—which represents a favorable execution price—the contract mistakenly subtracts it when calculating the net collateral increase. This error results in minting fewer shares for the depositor than they are entitled to, effectively undervaluing their contribution.

Vulnerability Details

Below is a snippet of the afterOrderExecution from PerpetualVault.sol :

/**
* @notice Callback function that is called when an order on GMX is executed successfully.
* This function handles the post-execution logic based on the type of order that was executed.
* @dev This callback function must never be reverted. It should wrap revertible external calls with `try-catch`.
* @param requestKey The request key of the executed order.
* @param positionKey The position key.
* @param orderResultData Data from the order execution.
*/
function afterOrderExecution(
bytes32 requestKey,
bytes32 positionKey,
IGmxProxy.OrderResultData memory orderResultData,
MarketPrices memory prices
) external nonReentrant {
if (msg.sender != address(gmxProxy)) {
revert Error.InvalidCall();
}
// MarketPrices memory marketPrices = gmxProxy.getMarketPrices(market);
_gmxLock = false;
// If the current action is `settle`
if (orderResultData.isSettle) {
nextAction.selector = NextActionSelector.WITHDRAW_ACTION;
emit GmxPositionCallbackCalled(requestKey, true);
return;
}
if (orderResultData.orderType == Order.OrderType.MarketIncrease) {
curPositionKey = positionKey;
if (flow == FLOW.DEPOSIT) {
uint256 amount = depositInfo[counter].amount;
uint256 feeAmount = vaultReader.getPositionFeeUsd(market, orderResultData.sizeDeltaUsd, false) / prices.shortTokenPrice.min;
uint256 prevSizeInTokens = flowData;
int256 priceImpact = vaultReader.getPriceImpactInCollateral(curPositionKey, orderResultData.sizeDeltaUsd, prevSizeInTokens, prices);
uint256 increased;
if (priceImpact > 0) {
increased = amount - feeAmount - uint256(priceImpact) - 1;
} else {
increased = amount - feeAmount + uint256(-priceImpact) - 1;
}
_mint(counter, increased, false, prices);
nextAction.selector = NextActionSelector.FINALIZE;
} else {
_updateState(false, orderResultData.isLong);
}
} else if (orderResultData.orderType == Order.OrderType.MarketDecrease) {
uint256 sizeInUsd = vaultReader.getPositionSizeInUsd(curPositionKey);
if (sizeInUsd == 0) {
delete curPositionKey;
}
if (flow == FLOW.WITHDRAW) {
nextAction.selector = NextActionSelector.FINALIZE;
uint256 prevCollateralBalance = collateralToken.balanceOf(address(this)) - orderResultData.outputAmount;
nextAction.data = abi.encode(prevCollateralBalance, sizeInUsd == 0, false);
} else {
_updateState(true, false);
}
} else if (orderResultData.orderType == Order.OrderType.MarketSwap) {
uint256 outputAmount = orderResultData.outputAmount;
if (swapProgressData.isCollateralToIndex) {
emit GmxSwap(
address(collateralToken),
swapProgressData.remaining,
indexToken,
outputAmount,
true
);
} else {
emit GmxSwap(
indexToken,
swapProgressData.remaining,
address(collateralToken),
outputAmount,
false
);
}
if (flow == FLOW.DEPOSIT) {
_mint(counter, outputAmount + swapProgressData.swapped, false, prices);
_finalize(hex'');
} else if (flow == FLOW.WITHDRAW) {
_handleReturn(outputAmount + swapProgressData.swapped, false, false);
} else { // Same as if (flow == FLOW.SIGNAL_CHANGE || FLOW.COMPOUND)
if (orderResultData.outputToken == indexToken) {
_updateState(false, true);
} else {
_updateState(true, false);
}
}
}
emit GmxPositionCallbackCalled(requestKey, true);
if (flow == FLOW.SIGNAL_CHANGE) {
emit GmxPositionUpdated(
positionKey,
market,
orderResultData.orderType == Order.OrderType.MarketIncrease,
orderResultData.isLong,
orderResultData.sizeDeltaUsd,
prices.indexTokenPrice.min
);
}
}

in the handling of MarketIncrease orders during the FLOW.DEPOSIT flow. When calculating the increased amount (the net collateral added to the position after fees and price impact), it subtracts the price impact when it is positive, instead of adding it.

This is incorrect because a positive price impact means the execution price is better, effectively increasing the value of the collateral. This benefit should be reflected in the shares minted, the user should get more value for their deposit, not less.

Impact

Depositors receive fewer shares than they should when the price impact is beneficial (positive), which is unfair and inconsistent with the intended vault mechanics.

Tools Used

Manual review

Recommendations

Change if (priceImpact > 0) block logic to :

if (priceImpact > 0) {
increased = amount - feeAmount + uint256(priceImpact) - 1;
}
Updates

Lead Judging Commences

n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Appeal created

danzero Submitter
6 months ago
n0kto Lead Judge
6 months ago
n0kto Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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