DeFiFoundry
50,000 USDC
View results
Submission Details
Severity: medium
Valid

Market Swap order is underestimated and could cause order failure

Summary

PerpVault does not estimate market swap properly as is prescribed in GMX, which could lead to failed transactiojn on GMX.

Vulnerability Details

When estimating the market swap order it simply does this.

function getExecutionGasLimit(
Order.OrderType orderType,
uint256 _callbackGasLimit
) public view returns (uint256 executionGasLimit) {
uint256 baseGasLimit = dataStore.getUint(
ESTIMATED_GAS_FEE_BASE_AMOUNT_V2_1
);
uint256 oraclePriceCount = 5;
baseGasLimit +=
dataStore.getUint(ESTIMATED_GAS_FEE_PER_ORACLE_PRICE) *
oraclePriceCount;
uint256 multiplierFactor = dataStore.getUint(
ESTIMATED_GAS_FEE_MULTIPLIER_FACTOR
);
uint256 gasPerSwap = dataStore.getUint(SINGLE_SWAP_GAS_LIMIT);
uint256 estimatedGasLimit;
/// Edited for clarity.
if (orderType == Order.OrderType.MarketSwap) {
estimatedGasLimit =
dataStore.getUint(SWAP_ORDER_GAS_LIMIT) +
gasPerSwap;
}
// multiply 1.2 (add some buffer) to ensure that the creation transaction does not revert.
executionGasLimit =
baseGasLimit +
((estimatedGasLimit + _callbackGasLimit) * multiplierFactor) /
PRECISION; // 1e30/
}

Estimated Gas is calculated as so: estimatedGasLimit = dataStore.getUint(SWAP_ORDER_GAS_LIMIT) + gasPerSwap; on gamma, while in GMX estimated gas for market swap is multiplied by swap paths, as supplied by the keeper on run.

function estimateExecuteSwapOrderGasLimit(DataStore dataStore, Order.Props memory order) internal view returns (uint256) {
uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
return dataStore.getUint(Keys.swapOrderGasLimitKey()) + gasPerSwap * order.swapPath().length + order.callbackGasLimit();
}

So the actual swapPath is only known at execution as supplied by metadata when the keeper executes run.

function run(
bool isOpen,
bool isLong,
MarketPrices memory prices,
bytes[] memory metadata
) external nonReentrant {
_noneFlow();
_onlyKeeper();
if (gmxProxy.lowerThanMinEth()) {
revert Error.LowerThanMinEth();
}
flow = FLOW.SIGNAL_CHANGE;
if (isOpen) {
if (positionIsClosed) {
if (_isFundIdle() == false) {
revert Error.InsufficientAmount();
}
if (_isLongOneLeverage(isLong)) {
_runSwap(metadata, true, prices);
} else {
(uint256 acceptablePrice) = abi.decode(metadata[0], (uint256));
_createIncreasePosition(isLong, acceptablePrice, prices);
}
} else {
if (beenLong == isLong) {
revert Error.NoAction();
} else {
// Close current position first and then open the requested position in the next action
nextAction.selector = NextActionSelector.INCREASE_ACTION;
nextAction.data = abi.encode(isLong);
if (_isLongOneLeverage(beenLong)) {
_runSwap(metadata, false, prices);
} else {
(uint256 acceptablePrice) = abi.decode(metadata[0], (uint256));
_createDecreasePosition(0, 0, beenLong, acceptablePrice, prices);
}
}
}
} else {
if (positionIsClosed == false) {
if (_isLongOneLeverage(beenLong)) {
_runSwap(metadata, false, prices);
} else {
(uint256 acceptablePrice) = abi.decode(metadata[0], (uint256));
_createDecreasePosition(0, 0, beenLong, acceptablePrice, prices);
}
} else {
revert Error.NoAction();
}
}
}

Impact

Under estimation of gas could lead to failure of transactions on GMX.

Tools Used

manual

Recommendations

Market should update the estimation of market swaps, to include swap parts, so as not to underestimate market swaps which could possibly lead to failure of the order type on gmx.

Updates

Lead Judging Commences

n0kto Lead Judge 8 months ago
Submission Judgement Published
Validated
Assigned finding tags:

finding_swapPath_does_not_increase_the_executionFee

Likelihood: Low/Medium, when swapPath has more than 1 item. Impact: Medium/High, could lead to not enough fee collected to execute the transaction in GMX

Support

FAQs

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