xanoutas/solidity-security-findings huggingface dataset:
berndartmueller - Inability to claim collateral
Audit: 2023-02-gmx-judging
Severity: HIGH

berndartmueller

high

# Inability to claim collateral

## Summary

Due to using the incorrect comparison operator in the `MarketUtils.claimCollateral` function, a user is unable to claim available collateral from a market.

## Vulnerability Detail

In the `MarketUtils.claimCollateral()` function, the `if` statement in line 632 uses an inappropriate comparison operator. The used `>=` comparison operator asserts that the claimable amount (`adjustedClaimableAmount`) is greater than or equal to the claimed amount (`claimedAmount`) and reverts with the `CollateralAlreadyClaimed` custom error if this is the case.

For a user claiming collateral for the first time, the `claimedAmount` is zero, and any available claimable amount (`adjustedClaimableAmount`) is non-zero. As a result, the `if` statement in line 632 will always evaluate to `true`, reverting with the `CollateralAlreadyClaimed` error.

## Impact

A user is unable to claim collateral from a market.

## Code Snippet

[contracts/market/MarketUtils.sol#L632](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/market/MarketUtils.sol#L632)

```solidity
618: function claimCollateral(
619:     DataStore dataStore,
620:     EventEmitter eventEmitter,
621:     address market,
622:     address token,
623:     uint256 timeKey,
624:     address account,
625:     address receiver
626: ) internal {
627:     uint256 claimableAmount = dataStore.getUint(Keys.claimableCollateralAmountKey(market, token, timeKey, account));
628:     uint256 claimableFactor = dataStore.getUint(Keys.claimableCollateralFactorKey(market, token, timeKey, account));
629:     uint256 claimedAmount = dataStore.getUint(Keys.claimedCollateralAmountKey(market, token, timeKey, account));
630:
631:     uint256 adjustedClaimableAmount = Precision.applyFactor(claimableAmount, claimableFactor);
632:     if (adjustedClaimableAmount >= claimedAmount) { // @audit-info invalid comparison operator
633:         revert CollateralAlreadyClaimed(adjustedClaimableAmount, claimedAmount);
634:     }
635:
...      // [...]
658: }
```

## Tool used

Manual Review

## Recommendation

Consider adjusting the comparison operator in the `if` statement in line 632 to prevent claiming already claimed collateral:

```solidity
if (adjustedClaimableAmount <= claimedAmount) {
    // [...]
}
```


Duplicate of #136
0xAmanda - Uncalculated gas while swapping tokens will make the keeper lose funds on withdrawals
Audit: 2023-02-gmx-judging
Severity: HIGH

0xAmanda

high

# Uncalculated gas while swapping tokens will make the keeper lose funds on withdrawals

## Summary

While calculating the gas that is estimated for withdrawals, it does not calculate also the gas of the swapPath for none of both tokens, `long and short`. Those paths are addresses from the struct  `CreateWithdrawalParams ` used for creating a withdrawal.

## Vulnerability Detail

The following struct is used with creating a withdrawal order:

https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L46

       struct CreateWithdrawalParams {
        address receiver;
        address callbackContract;
        address market;
        address[] longTokenSwapPath;
        address[] shortTokenSwapPath;
        uint256 marketTokenAmount;
        uint256 minLongTokenAmount;
        uint256 minShortTokenAmount;
        bool shouldUnwrapNativeToken;
        uint256 executionFee;
        uint256 callbackGasLimit;
       }

The following 2 params:

        address[] longTokenSwapPath;
        address[] shortTokenSwapPath;

are not accounted in the calculation of the gas to execute the tx.

https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L163

Calculation using the withdrawal request: 

        uint256 estimatedGasLimit = GasUtils.estimateExecuteWithdrawalGasLimit(dataStore, withdrawal);

As you can see in the previous snippets, there is no calculation or whatsoever of the gas that will cost swapping the tokens through the previously inputted paths. Specially if the path is long, the gas used for swapping will compound charging the keeper because they were unexpected costs. These gas will be deducted directly from the contract, draining it slowly due to the missed calculation

## Impact
High. The contract (the keeper)will be charged the gas that is not accounted for those swapPaths, draining the contracts native currency slowly.

## Code Snippet

https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/gas/GasUtils.sol#L149

## Tool used

Manual Review

## Recommendation

Add a gas calculation for the swapPaths of both, the long and short token. Consider calculating that amount taking into account the lenght of each path, because that will increase/decrease the gas costs. 

Duplicate of #195
0xAmanda - Incorrect calculation of the value in the pool
Audit: 2023-02-gmx-judging
Severity: HIGH

0xAmanda

medium

# Incorrect calculation of the value in the pool

## Summary
On the way to calculate the entire pool value, GMX is including the fees that the user that borrows funds has to pay to the address in `feeReceiver` in the pool value 

## Vulnerability Detail
The specific line where this happens is: 

          cache.value += Precision.applyFactor(cache.totalBorrowingFees, cache.borrowingFeeReceiverFactor);

As you can see, they add the borrowing fees to the cache.value (pool value basically). To specify, those borrowing fees are distributed between the `feeReceiver` and the pool, therefore here shows better that the fees that will go to `feeReceiver`, shouldn't be accounted inside pool value.

## Impact
Incorrectly handles the real balance from the pool because those funds do not really belong to the pool. 
Therefore it affects the accounting while depositing and withdrawing:

https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L446

## Code Snippet

https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/market/MarketUtils.sol#L317

https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/market/MarketUtils.sol#L340

## Tool used

Manual Review

## Recommendation

Make a distinction  between the borrowing fees that go to the pool and the ones that go to `feeReceiver`, therefore only accounting the fees that will go to the pool as pool value

Duplicate of #131
IllIllI - Tracking of the latest ADL block use the wrong block number on Arbitrum
Audit: 2023-02-gmx-judging
Severity: HIGH

IllIllI

medium

# Tracking of the latest ADL block use the wrong block number on Arbitrum

## Summary

Tracking of the latest ADL block use the wrong block number on Arbitrum


## Vulnerability Detail

The call to `setLatestAdlBlock()` passes in `block.timestamp`, which on Arbitrum, is the L1 block timestamp, not the L2 timestamp on which order timestamps are based.


## Impact

Tracking of whether ADL is currently required or not will be based on block numbers that are very far in the past (since Arbitrum block numbers are incremented much more quickly than Ethereum ones), so checks of whether ADL is enabled will pass, and the ADL keeper will be able to execute ADL orders whenever it wants to.


## Code Snippet

Uses `block.number` rather than [`Chain.currentBlockNumber()`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/chain/Chain.sol#L24-L30
):
```solidity
// File: gmx-synthetics/contracts/adl/AdlUtils.sol : AdlUtils.updateAdlState()   #1

104            MarketUtils.MarketPrices memory prices = MarketUtils.getMarketPrices(oracle, _market);
105            (bool shouldEnableAdl, int256 pnlToPoolFactor, uint256 maxPnlFactor) = MarketUtils.isPnlFactorExceeded(
106                dataStore,
107                _market,
108                prices,
109                isLong,
110                Keys.MAX_PNL_FACTOR
111            );
112    
113            setIsAdlEnabled(dataStore, market, isLong, shouldEnableAdl);
114 @>         setLatestAdlBlock(dataStore, market, isLong, block.number);
115    
116            emitAdlStateUpdated(eventEmitter, market, isLong, pnlToPoolFactor, maxPnlFactor, shouldEnableAdl);
117:       }
```
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/adl/AdlUtils.sol#L104-L117

The block number (which is an L1 block number) is checked against the L2 oracle [block numbers](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/adl/AdlUtils.sol#L192-L195).


## Tool used

Manual Review


## Recommendation

Use `Chain.currentBlockNumber()` as is done everywhere else in the code base
IllIllI - ADL operations do not have any slippage protection
Audit: 2023-02-gmx-judging
Severity: HIGH

IllIllI

medium

# ADL operations do not have any slippage protection

## Summary

ADL operations do not have any slippage protection


## Vulnerability Detail

ADL orders, generated by the ADL keeper, are forced to allow unlimited slippage, which means the user may get a lot less than they deserve.


## Impact

There may be a large price impact associated with the ADL order, causing the user's large gain to become a loss, if the price impact factor is large enough, which may be the case if the price suddenly spikes up a lot, and there are many ADL operations after a lot of users exit their positions.


## Code Snippet

Any price is allowed, and the minimum output amount is set to zero:
```solidity
// File: gmx-synthetics/contracts/adl/AdlUtils.sol : AdlUtils.createAdlOrder()   #1

141    
142            Order.Numbers memory numbers = Order.Numbers(
143                Order.OrderType.MarketDecrease, // orderType
144                Order.DecreasePositionSwapType.NoSwap, // decreasePositionSwapType
145                params.sizeDeltaUsd, // sizeDeltaUsd
146                0, // initialCollateralDeltaAmount
147                0, // triggerPrice
148 @>             position.isLong() ? 0 : type(uint256).max, // acceptablePrice
149                0, // executionFee
150                0, // callbackGasLimit
151 @>             0, // minOutputAmount
152                params.updatedAtBlock // updatedAtBlock
153:           );
```
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/adl/AdlUtils.sol#L141-L153

The only checks after the ADL order completes are related to [global](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/exchange/AdlHandler.sol#L150-L160) metrics, not position-specific ones.


## Tool used

Manual Review


## Recommendation

Introduce a `MAX_POSITION_IMPACT_FACTOR_FOR_ADL`, similar to `MAX_POSITION_IMPACT_FACTOR_FOR_LIQUIDATIONS`
IllIllI - Keepers can be forced to waste gas with long revert messages
Audit: 2023-02-gmx-judging
Severity: HIGH

IllIllI

high

# Keepers can be forced to waste gas with long revert messages

## Summary

Most actions done on behalf of users have a callback triggered on an address provided by the position's account holder. In order to ensure that these callbacks don't affect order execution, they're wrapped in a try-catch block, and the actual external call has a limited amount of gas provided to it. If the callback fails, the revert message is fetched and emitted, before processing continues. Keepers are given a limited gas budget based on estimated amounts of gas expected to be used, including a maximum amount allotted for callbacks via the [`callbackGasLimit()`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/gas/GasUtils.sol#L175-L194).


## Vulnerability Detail

A malicious user can use the full `callbackGasLimit()` to create an extremely long revert string. This would use three times the expected amount of gas, since the callback will spend that amount, the fetching of the message during the revert will fetch that much, and the emitting of the same string will fetch that much.


## Impact

Keepers can be forced to spend more gas than they expect to for all operations. If they don't have a reliable way of calculating how much gas is used, they'll have spent more gas than they get in return. 

If all keepers are expected to be able to calculate gas fees, then an attacker can submit an order whose happy path execution doesn't revert, but whose failure path reverts with a long string, which would essentially prevent the order from being executed until a more favorable price is reached. The user could, for example, submit an order with a long revert string in the frozen order callback, which would prevent the order from being frozen, and would allow them to [game the price impact](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/exchange/OrderHandler.sol#L256-L261)

## Code Snippet

A single revert message uses gas x3:
```solidity
// File: gmx-synthetics/contracts/callback/CallbackUtils.sol : CallbackUtils.afterOrderCancellation()   #1

127        function afterOrderCancellation(bytes32 key, Order.Props memory order) internal {
128            if (!isValidCallbackContract(order.callbackContract())) { return; }
129    
130 @>         try IOrderCallbackReceiver(order.callbackContract()).afterOrderCancellation{ gas: order.callbackGasLimit() }(key, order) {
131            } catch (bytes memory reasonBytes) {
132 @>             (string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
133 @>             emit AfterOrderCancellationError(key, order, reason, reasonBytes);
134            }
135:       }
```
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/callback/CallbackUtils.sol#L127-L135

## Tool used

Manual Review


## Recommendation

Count the gas three times during gas estimation
IllIllI - Limit orders are broken when there are price gaps
Audit: 2023-02-gmx-judging
Severity: HIGH

IllIllI

high

# Limit orders are broken when there are price gaps

## Summary

Limit orders fail to execute when the price becomes worse for the order in the block after the order is submitted.


## Vulnerability Detail

Limit orders require that all oracle-provided prices come from blocks [_after_](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/order/DecreaseOrderUtils.sol#L134-L156) the order has been submitted. In addition, limit orders's primary and secondary prices are required to straddle the trigger price.

## Impact

If the primary/secondary prices go well past the trigger price (e.g. due to a price gap), then the order execution will revert, leaving the user exposed to their position, even if they had a valid stoploss set.


## Code Snippet

Limit orders require prices to straddle the trigger price, and revert if they don't:
```solidity
// File: gmx-synthetics/contracts/order/BaseOrderUtils.sol : BaseOrderUtils.setExactOrderPrice()   #1

238            if (orderType == Order.OrderType.LimitIncrease ||
239                orderType == Order.OrderType.LimitDecrease ||
240                orderType == Order.OrderType.StopLossDecrease
241            ) {
242                uint256 primaryPrice = oracle.getPrimaryPrice(indexToken).pickPrice(shouldUseMaxPrice);
243                uint256 secondaryPrice = oracle.getSecondaryPrice(indexToken).pickPrice(shouldUseMaxPrice);
244    
...
258                if (shouldValidateAscendingPrice) {
259                    // check that the earlier price (primaryPrice) is smaller than the triggerPrice
260                    // and that the later price (secondaryPrice) is larger than the triggerPrice
261 @>                 bool ok = primaryPrice <= triggerPrice && triggerPrice <= secondaryPrice;
262                    if (!ok) {
263                        revert InvalidOrderPrices(primaryPrice, secondaryPrice, triggerPrice, shouldValidateAscendingPrice);
264                    }
...
270                } else {
271                    // check that the earlier price (primaryPrice) is larger than the triggerPrice
272                    // and that the later price (secondaryPrice) is smaller than the triggerPrice
273 @>                 bool ok = primaryPrice >= triggerPrice && triggerPrice >= secondaryPrice;
274                    if (!ok) {
275                        revert InvalidOrderPrices(primaryPrice, secondaryPrice, triggerPrice, shouldValidateAscendingPrice);
276                    }
...
282:               }
```
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/order/BaseOrderUtils.sol#L228-L288


## Tool used

Manual Review


## Recommendation

Don't [revert](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/exchange/OrderHandler.sol#L253) if both the primary and secondary prices are worse than the trigger price. Use the trigger price as the execution price.
IllIllI - Malicious revert reasons with faked lengths can disrupt order execution
Audit: 2023-02-gmx-judging
Severity: HIGH

IllIllI

high

# Malicious revert reasons with faked lengths can disrupt order execution

## Summary

Malicious revert reasons with faked lengths can be used in the various order callbacks to disrupt order execution


## Vulnerability Detail

For most order-related operations, the user is allowed to provide the address of a contract on which a callback is triggered, whenever anything related to an order changes. These callbacks are executed with a limited amount of gas, under a try-catch block, in order to ensure that the user-supplied callback cannot affect subsequent processing. Whenever the callback reverts, the revert reason is fetched so that it can be emitted.

The code that parses the return bytes uses `abi.decode(result, (string))` to parse the "string", which relies on the first word of the data, to figure out how many bytes long the string is. Because the `results` variable is a memory variable, rather than calldata, any read past the end of the bytes is allowed, and is considered as value zero byte slots. If a malicious callback provides a reason "string" that is only a few bytes long, but sets the length to a very large number, when the decode call is made, it will try to read a string of that provided length, and will eventually run out of gas.

Note that this is not the same as providing an actual long string, because in that case, the callback will revert with an out of gas error, and there won't be a string to parse.


## Impact

A malicious user can use this attack in many different places, but they all stem from the bug in `ErrorUtils.getRevertMessage()`. One such attack would be that a user can prevent themselves from being liquidated or ADLed, by providing a malicious string in the revert reason in their [CallbackUtils.afterOrderExecution()](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/order/OrderUtils.sol#L168) callback.

Othere places include the freezing of orders, bypassing pauses by having resting orders that can never execute until allowed, and preventing orders from being canceled when their normal execution reverts.


## Code Snippet

The input `result` is a memory variable, and it uses `abi.decode()` without checking the string's length against the calldata length, or any sort of maximum:
```solidity
// File: gmx-synthetics/contracts/utils/ErrorUtils.sol : ErrorUtils.getRevertMessage()   #1

6         // To get the revert reason, referenced from https://ethereum.stackexchange.com/a/83577
7  @>     function getRevertMessage(bytes memory result) internal pure returns (string memory, bool) {
8             // If the result length is less than 68, then the transaction either panicked or failed silently
9             if (result.length < 68) {
10                return ("", false);
11            }
12    
13            bytes4 errorSelector = getErrorSelectorFromData(result);
14    
15            // 0x08c379a0 is the selector for Error(string)
16            // referenced from https://blog.sol
IllIllI - User-supplied slippage for decrease orders is ignored
Audit: 2023-02-gmx-judging
Severity: HIGH

IllIllI

high

# User-supplied slippage for decrease orders is ignored

## Summary

The user-supplied `minOutputAmount` order parameter for controlling slippage is ignored when a user decreases their position


## Vulnerability Detail

There are no checks that the amount out matches the user-supplied value during order creation, and in fact infinite slippage is allowed during the swap of PNL and collateral, ensuring there are opportunities for sandwiching.


## Impact

User's orders will have swap impacts applied to them during swaps, resulting in the user getting less than they asked for.


## Code Snippet

Infinite slippage is allowed during the swap from collateral tokens to the PNL token:
```solidity
// File: gmx-synthetics/contracts/position/DecreasePositionCollateralUtils.sol : DecreasePositionCollateralUtils.swapProfitToCollateralToken()   #1

425                try params.contracts.swapHandler.swap(
426                    SwapUtils.SwapParams(
427                        params.contracts.dataStore,
428                        params.contracts.eventEmitter,
429                        params.contracts.oracle,
430                        Bank(payable(params.market.marketToken)),
431                        pnlToken, // tokenIn
432                        profitAmount, // amountIn
433                        swapPathMarkets, // markets
434 @>                     0, // minOutputAmount
435                        params.market.marketToken, // receiver
436                        false // shouldUnwrapNativeToken
437                    )
438:               ) returns (address /* tokenOut */, uint256 swapOutputAmount) {
```
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/position/DecreasePositionCollateralUtils.sol#L425-L438

and during the swap of collateral tokens to PNL tokens:
```solidity
// File: gmx-synthetics/contracts/position/DecreasePositionCollateralUtils.sol : DecreasePositionCollateralUtils.swapWithdrawnCollateralToPnlToken()   #2

383                try params.contracts.swapHandler.swap(
384                    SwapUtils.SwapParams(
385                        params.contracts.dataStore,
386                        params.contracts.eventEmitter,
387                        params.contracts.oracle,
388                        Bank(payable(params.market.marketToken)),
389                        params.position.collateralToken(), // tokenIn
390                        values.output.outputAmount, // amountIn
391                        swapPathMarkets, // markets
392 @>                     0, // minOutputAmount
393                        params.market.marketToken, // receiver
394                        false // shouldUnwrapNativeToken
395                    )
396:               ) returns (address tokenOut, uint256 swapOutputAmount) {
```
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/position/DecreasePositionCollateralUtils.sol#L383-L396

And there are no checks in the calling layers tha