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

Incorrect Handling of `swapProgressData.swapped` in Swap Operations

Summary

The contract uses the swapProgressData struct to track the progress of swaps, particularly in scenarios where swaps are performed as part of deposit, withdrawal, or position adjustment flows. However, the contract does not consistently update the swapProgressData.swapped variable after a swap is completed. This oversight can lead to incorrect tracking of swap progress, incorrect calculations of output amounts and unexpected behavior in subsequent operations.

Vulnerability Details

The swapProgressData struct is used to track the progress of swaps, including:

  • isCollateralToIndex: Indicates the direction of the swap (e.g., collateral to index token or vice versa).

  • swapped: The amount of output tokens that have already been swapped.

  • remaining: The amount of input tokens that remain to be swapped.

The swapProgressData.swapped variable is not consistently updated after a swap is completed. For example:
In the _runSwap function when metadata.length is 1 and the protocol is PROTOCOL.DEX, the function calls _doDexSwap(data, isCollateralToIndex) to perform a DEX swap. However, the output amount from this swap is not used to update swapProgressData.swapped. Instead, the output amount is only utilized in the context of the flow (either FLOW.DEPOSIT or FLOW.WITHDRAW) to determine the total amount processed.

function _runSwap(bytes[] memory metadata, bool isCollateralToIndex, MarketPrices memory prices) internal returns (bool completed) {
if (metadata.length == 0) {
revert Error.InvalidData();
}
if (metadata.length == 2) {
(PROTOCOL _protocol, bytes memory data) = abi.decode(metadata[0], (PROTOCOL, bytes));
if (_protocol != PROTOCOL.DEX) {
revert Error.InvalidData();
}
swapProgressData.swapped = swapProgressData.swapped + _doDexSwap(data, isCollateralToIndex);
(_protocol, data) = abi.decode(metadata[1], (PROTOCOL, bytes));
if (_protocol != PROTOCOL.GMX) {
revert Error.InvalidData();
}
_doGmxSwap(data, isCollateralToIndex);
return false;
} else {
if (metadata.length != 1) {
revert Error.InvalidData();
}
(PROTOCOL _protocol, bytes memory data) = abi.decode(metadata[0], (PROTOCOL, bytes));
if (_protocol == PROTOCOL.DEX) {
uint256 outputAmount = _doDexSwap(data, isCollateralToIndex);
// update global state
if (flow == FLOW.DEPOSIT) {
// last `depositId` equals with `counter` because another deposit is not allowed before previous deposit is completely processed
_mint(counter, outputAmount + swapProgressData.swapped, true, prices);
} else if (flow == FLOW.WITHDRAW) {
_handleReturn(outputAmount + swapProgressData.swapped, false, true);
} else {
// in the flow of SIGNAL_CHANGE, if `isCollateralToIndex` is true, it is opening position, or closing position
_updateState(!isCollateralToIndex, isCollateralToIndex);
}
return true;
} else {
_doGmxSwap(data, isCollateralToIndex);
return false;
}
}
}

In the afterOrderExecution function, the swapProgressData.swapped variable is not updated at all.

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;
//...SNIP...
} 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);
//...SNIP...

Impact

The swapProgressData.swapped variable is used to calculate the total output amount in functions like _mint and _handleReturn. If this variable is not updated, the calculations will be incorrect leading to overswapping or underswapping

Tools

Manual Review

Recommendations

Ensure that the swapProgressData.swapped variable is updated consistently after every swap operation, regardless of the swap type (DEX or GMX).

function _runSwap(bytes[] memory metadata, bool isCollateralToIndex, MarketPrices memory prices) internal returns (bool completed) {
//...SNIP...
if (_protocol == PROTOCOL.DEX) {
uint256 outputAmount = _doDexSwap(data, isCollateralToIndex);
+ swapProgressData.swapped = swapProgressData.swapped + outputAmount;
// update global state
if (flow == FLOW.DEPOSIT) {
// last `depositId` equals with `counter` because another deposit is not allowed before previous deposit is completely processed
- _mint(counter, outputAmount + swapProgressData.swapped, true, prices);
+ _mint(counter, swapProgressData.swapped, true, prices)
} else if (flow == FLOW.WITHDRAW) {
- _handleReturn(outputAmount + swapProgressData.swapped, false, true);
+ _handleReturn(swapProgressData.swaped, false, true)
//...SNIP...

Update the swapProgressData.swapped variable in the afterOrderExecution function after a GMX swap is completed.

function afterOrderExecution(...) external nonReentrant {
if (msg.sender != address(gmxProxy)) {
revert Error.InvalidCall();
}
_gmxLock = false;
//...SNIP...
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
);
}
+ swapProgressData.swapped += outputAmount; // Update swapped amount for GMX swap
}
// Handle other order types (e.g., increase, decrease)
}
Updates

Lead Judging Commences

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

Support

FAQs

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

Give us feedback!