xanoutas/solidity-security-findings huggingface dataset:
rvierdiiev - PositionPricingUtils.getPositionFees takes more fees than should
Audit: 2023-02-gmx-judging
Severity: HIGH

rvierdiiev

high

# PositionPricingUtils.getPositionFees takes more fees than should

## Summary
PositionPricingUtils.getPositionFees takes more fees than should. User loses funds every order.
## Vulnerability Detail
`PositionPricingUtils.getPositionFees` function is called to calculate fees for the position.
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/pricing/PositionPricingUtils.sol#L351-L400
```solidity
    function getPositionFees(
        DataStore dataStore,
        IReferralStorage referralStorage,
        Position.Props memory position,
        Price.Props memory collateralTokenPrice,
        address longToken,
        address shortToken,
        uint256 sizeDeltaUsd
    ) internal view returns (PositionFees memory) {
        PositionFees memory fees;


        (
            fees.referral.affiliate,
            fees.referral.traderDiscountAmount,
            fees.referral.affiliateRewardAmount,
            fees.feeReceiverAmount,
            fees.positionFeeAmountForPool
        ) = getPositionFeesAfterReferral(
            dataStore,
            referralStorage,
            collateralTokenPrice,
            position.account(),
            position.market(),
            sizeDeltaUsd
        );


        fees.borrowingFeeAmount = MarketUtils.getBorrowingFees(dataStore, position) / collateralTokenPrice.min;


        uint256 borrowingFeeReceiverFactor = dataStore.getUint(Keys.BORROWING_FEE_RECEIVER_FACTOR);
        uint256 borrowingFeeAmountForFeeReceiver = Precision.applyFactor(fees.borrowingFeeAmount, borrowingFeeReceiverFactor);


        fees.feeAmountForPool = fees.positionFeeAmountForPool + fees.borrowingFeeAmount - borrowingFeeAmountForFeeReceiver;
        fees.feeReceiverAmount += borrowingFeeAmountForFeeReceiver;


        int256 latestLongTokenFundingAmountPerSize = MarketUtils.getFundingAmountPerSize(dataStore, position.market(), longToken, position.isLong());
        int256 latestShortTokenFundingAmountPerSize = MarketUtils.getFundingAmountPerSize(dataStore, position.market(), shortToken, position.isLong());


        fees.funding = getFundingFees(
            position,
            longToken,
            shortToken,
            latestLongTokenFundingAmountPerSize,
            latestShortTokenFundingAmountPerSize
        );


        fees.totalNetCostAmount = fees.referral.affiliateRewardAmount + fees.feeReceiverAmount + fees.positionFeeAmountForPool + fees.funding.fundingFeeAmount + fees.borrowingFeeAmount;
        fees.totalNetCostUsd = fees.totalNetCostAmount * collateralTokenPrice.max;


        return fees;
    }
```

Let's check how `fees.totalNetCostAmount` amount is calculated for user.
`fees.totalNetCostAmount = fees.referral.affiliateRewardAmount + fees.feeReceiverAmount + fees.positionFeeAmountForPool + fees.funding.fundingFeeAmount + fees.borrowingFeeAmount;`

Here `fees.referral.affiliateRewardAmount` is amount that user should pay for refferal. `fees.feeReceiverAmount` is everyth
rvierdiiev - MarketUtils.claimCollateral implemented incorrectly
Audit: 2023-02-gmx-judging
Severity: HIGH

rvierdiiev

high

# MarketUtils.claimCollateral implemented incorrectly

## Summary
MarketUtils.claimCollateral implemented incorrectly. Because of that it will be impossible to claim collateral.
## Vulnerability Detail
When user wants to claim collateral, then `ExchangeRouter.claimCollateral` calls `MarketUtils.claimCollateral` function.
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/market/MarketUtils.sol#L618-L658
```solidity
    function claimCollateral(
        DataStore dataStore,
        EventEmitter eventEmitter,
        address market,
        address token,
        uint256 timeKey,
        address account,
        address receiver
    ) internal {
        uint256 claimableAmount = dataStore.getUint(Keys.claimableCollateralAmountKey(market, token, timeKey, account));
        uint256 claimableFactor = dataStore.getUint(Keys.claimableCollateralFactorKey(market, token, timeKey, account));
        uint256 claimedAmount = dataStore.getUint(Keys.claimedCollateralAmountKey(market, token, timeKey, account));


        uint256 adjustedClaimableAmount = Precision.applyFactor(claimableAmount, claimableFactor);
        if (adjustedClaimableAmount >= claimedAmount) {
            revert CollateralAlreadyClaimed(adjustedClaimableAmount, claimedAmount);
        }


        uint256 remainingClaimableAmount = adjustedClaimableAmount - claimedAmount;


        dataStore.setUint(
            Keys.claimedCollateralAmountKey(market, token, timeKey, account),
            adjustedClaimableAmount
        );


        MarketToken(payable(market)).transferOut(
            token,
            receiver,
            remainingClaimableAmount
        );


        MarketEventUtils.emitCollateralClaimed(
            eventEmitter,
            market,
            token,
            timeKey,
            account,
            receiver,
            remainingClaimableAmount
        );
    }
```

The problem in this function is following condition: 
```solidity
        if (adjustedClaimableAmount >= claimedAmount) {
            revert CollateralAlreadyClaimed(adjustedClaimableAmount, claimedAmount);
        }
```
It's implemented incorrectly, as it actually doesn't allow user to claim. In case if `adjustedClaimableAmount` is bigger than `claimedAmount`, that means that user now should be able to claim `adjustedClaimableAmount - claimedAmount` amount. But function doesn't allow that.
## Impact
MarketUtils.claimCollateral is not working.
## Code Snippet
Provided above
## Tool used

Manual Review

## Recommendation
Change condition to:
```solidity
        if (adjustedClaimableAmount <= claimedAmount) {
            revert CollateralAlreadyClaimed(adjustedClaimableAmount, claimedAmount);
        }
```

Duplicate of #136
KingNFT - The ````callbacks```` are exposed to ````returnbomb```` attack
Audit: 2023-02-gmx-judging
Severity: HIGH

KingNFT

high

# The ````callbacks```` are exposed to ````returnbomb```` attack

## Summary
````callbacks```` such as ````afterDepositExecution()````, ````afterDepositCancellation()```` and ````afterOrderExecution()```` are not implemented properly. The ````revert```` reason size is not checked, they are all exposed to ````returnbomb```` attack.

## Vulnerability Detail
Take ````afterOrderExecution()```` for example, the ````order.callbackGasLimit()```` is freely set by users, attackers can set it large enough to make ````afterOrderExecution()```` optionally revert after call on L117.
Let's say the transaction's normal gas cost for remaining process after L118 is 50,000.  Attackers can set ````order.callbackGasLimit()```` to 100,000, and revert with long ````reasonBytes```` which costs 90,000 gas on memory usage. It will trigger out of gas revert on L118, as a standard solidity call will copy any amount of bytes from return of subcall to local memory.
```solidity
File: gmx-synthetics\contracts\callback\CallbackUtils.sol
114:     function afterOrderExecution(bytes32 key, Order.Props memory order) internal {
115:         if (!isValidCallbackContract(order.callbackContract())) { return; }
116: 
117:         try IOrderCallbackReceiver(order.callbackContract()).afterOrderExecution{ gas: order.callbackGasLimit() }(key, order) {
118:         } catch (bytes memory reasonBytes) {
119:             (string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
120:             emit AfterOrderExecutionError(key, order, reason, reasonBytes);
121:         }
122:     }
```

## Impact
The issue will introduce many attack vectors such as risk free trade.
Let's say it's the ETH/USDC market, the confirmation time is 5 minutes and the current ETH price is $1000.
The attacker opens a long position, 5 minutes later, the price goes down to $950, the order keeper tries to execute the order.
Now the attacker can stop order execution to avoid loss. On the other hand, if price goes up, the attacker can submit a position close request by back running the order keeper's transaction to lock profit.

## Code Snippet
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/callback/CallbackUtils.sol#L114
## Tool used

Manual Review

## Recommendation
Limit the the length of ````reasonBytes````
for reference https://github.com/nomad-xyz/ExcessivelySafeCall


Duplicate of #141
rvierdiiev - No slippage for withdrawal without swapping path
Audit: 2023-02-gmx-judging
Severity: HIGH

rvierdiiev

high

# No slippage for withdrawal without swapping path

## Summary
No slippage for withdrawal without swapping path
## Vulnerability Detail
When user withdraws, he should provide amount of LP tokens, that he wants to burn. According to that amount, output amount of long and short tokens of the market [will be calculated](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L274) for him.

In case if user wants to swap this short/long tokens to another tokens, then he can provide longTokenSwapPath/shortTokenSwapPath array. If he doesn't want to swap them, but just receive without swapping, he doesn't provide that array.

No matter if he provided swap path or no, swapping [will be called](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L354-L374).

Then inside `SwapUtils.swap` in case if no swap path is provided, then function will just [send tokens to receiver](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/swap/SwapUtils.sol#L99-L109).
One of parameters of `SwapUtils.swap` function is [`minOutputAmount`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L414). This parameter is important as it stands as slippage protection. In case if swap path is not provided, then amount will not be checked for that slippage. If swap path present, then slippage check [will be done](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/swap/SwapUtils.sol#L144-L146). 
So in case if no swap path is provided, then slippage provided by user will not be checked, as inside `WithdrawalUtils._executeWithdrawal` there is no any check that user received [`minShortTokenAmount`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L371) and [`minLongTokenAmount`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L360).
Because of that user can be frontrunned and lose some funds.
## Impact
There is no slippage check in case if user doesn't provide swap path.
## Code Snippet
Provided above
## Tool used

Manual Review

## Recommendation
You need to check that `minLongTokenAmount`, `minShortTokenAmount` is satisfied after the swap.
stopthecap - Unpaid funding fees from wrong calculation are going to be substracted from the pool
Audit: 2023-02-gmx-judging
Severity: HIGH

stopthecap

high

# Unpaid funding fees from wrong calculation are going to be substracted from the pool

## Summary

The vulnerability start at the function getNextFundingAmountPerSize:

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

Basically, GMX is handling incorrectly the way on how they calculate the FundingFees to be paid because they calculate the amount using the `getOpenInterest` from both the short and long tokens.


## Vulnerability Detail

The detailed issue here is that GMX is incorrectly handling the calculation with the `getOpenInterest`  (the open Interest) of the two tokens, short and long.  They are dividing the `cache.fundingUsd`  for the interest of the two tokens:

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

After dividing, you get the `cache.fundingUsdForLongCollateral` which is basically the amount that has to be paid or paidTo for any of both collaterals and positions.

That is a big problem in one case, when users need to pay those feed. When the user does have to pay the fundingFees. Because customers can only pay  those fundingFees for their collateral token, not for both.

As said, this is not an issue when users do receive the fundingFees because you can claim both tokens, but it is an issue when paying the fees.

This will result in less fundingFees being paid by users.

## Impact
2 Impacts:

1:
A missmatch in the pools accounting will occur over time which might have unexpected results in terms of insolvency's.

2:
High because users are not paying the fees that they should pay, so there is an economic damage. Specifically because the bug just described, does reduce the variable `fundingAmountPerSizePortion` : 

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

which is what user pays for the collateral token.   As said before, the amounts are being divided by the entire interest, therefore, just a small part only a portion of the FundingFees will be paid, meanwhile the entire amount can be claimed.
## Code Snippet

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

## Tool used

Manual Review

## Recommendation

The way on how the funding fees are calculate for users that have to pay them has to change in a way that it is not calculated accounting all the open interest. The calculation of the funding fees for users that have to claim fundingFees, not necessarily has to change.
rvierdiiev - GasUtils.estimateExecuteWithdrawalGasLimit calculates gas amount incorrectly
Audit: 2023-02-gmx-judging
Severity: HIGH

rvierdiiev

medium

# GasUtils.estimateExecuteWithdrawalGasLimit calculates gas amount incorrectly

## Summary
GasUtils.estimateExecuteWithdrawalGasLimit calculates gas amount incorrectly, so user can pay less amount for gas
## Vulnerability Detail
`GasUtils.estimateExecuteWithdrawalGasLimit` function is used to calculate amount of gas that is needed to execute withdrawal order.
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/gas/GasUtils.sol#L149-L151
```solidity
    function estimateExecuteWithdrawalGasLimit(DataStore dataStore, Withdrawal.Props memory withdrawal) internal view returns (uint256) {
        return dataStore.getUint(Keys.withdrawalGasLimitKey(false)) + withdrawal.callbackGasLimit();
    }
```
As you can see function only calculates withdrawal gas limit and callback.
But when user wants to make withdraw, he can also swap his short/long tokens [for another tokens](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/withdrawal/WithdrawalUtils.sol#L354-L374).
So i believe that `estimateExecuteWithdrawalGasLimit` function should calculate similar to `estimateExecuteDepositGasLimit` function, which [handles situations](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/gas/GasUtils.sol#L135-L137) when long/short token should be swapped.
## Impact
Order creator underpays for withdrawal order, so keepers will not be interested in such orders.
## Code Snippet
Provided above
## Tool used

Manual Review

## Recommendation
Use similar approach as in `estimateExecuteDepositGasLimit` function.

Duplicate of #195
stopthecap - Creating an order of type MarketIncrease opens an attack vector where attacker can execute txs with stale prices by inputting a very extense swapPath
Audit: 2023-02-gmx-judging
Severity: HIGH

stopthecap

high

# Creating an order of type MarketIncrease opens an attack vector where attacker can execute txs with stale prices by inputting a very extense swapPath

## Summary

The vulnerability relies on the create order function: 

        function createOrder(
        DataStore dataStore,
        EventEmitter eventEmitter,
        OrderVault orderVault,
        IReferralStorage referralStorage,
        address account,
        BaseOrderUtils.CreateOrderParams memory params
        ) external returns (bytes32) {
        ReferralUtils.setTraderReferralCode(referralStorage, account, params.referralCode);

        uint256 initialCollateralDeltaAmount;

        address wnt = TokenUtils.wnt(dataStore);

        bool shouldRecordSeparateExecutionFeeTransfer = true;

        if (
            params.orderType == Order.OrderType.MarketSwap ||
            params.orderType == Order.OrderType.LimitSwap ||
            params.orderType == Order.OrderType.MarketIncrease ||
            params.orderType == Order.OrderType.LimitIncrease
        ) {
            initialCollateralDeltaAmount = orderVault.recordTransferIn(params.addresses.initialCollateralToken);
            if (params.addresses.initialCollateralToken == wnt) {
                if (initialCollateralDeltaAmount < params.numbers.executionFee) {
                    revert InsufficientWntAmountForExecutionFee(initialCollateralDeltaAmount, params.numbers.executionFee);
                }
                initialCollateralDeltaAmount -= params.numbers.executionFee;
                shouldRecordSeparateExecutionFeeTransfer = false;
            }
        } else if (
            params.orderType == Order.OrderType.MarketDecrease ||
            params.orderType == Order.OrderType.LimitDecrease ||
            params.orderType == Order.OrderType.StopLossDecrease
        ) {
            initialCollateralDeltaAmount = params.numbers.initialCollateralDeltaAmount;
        } else {
            revert OrderTypeCannotBeCreated(params.orderType);
        }

        if (shouldRecordSeparateExecutionFeeTransfer) {
            uint256 wntAmount = orderVault.recordTransferIn(wnt);
            if (wntAmount < params.numbers.executionFee) {
                revert InsufficientWntAmountForExecutionFee(wntAmount, params.numbers.executionFee);
            }

            GasUtils.handleExcessExecutionFee(
                dataStore,
                orderVault,
                wntAmount,
                params.numbers.executionFee
            );
        }

        // validate swap path markets
        MarketUtils.getEnabledMarkets(
            dataStore,
            params.addresses.swapPath
        );

        Order.Props memory order;

        order.setAccount(account);
        order.setReceiver(params.addresses.receiver);
        order.setCallbackContract(params.addresses.callbackContract);
        order.setMarket(params.addresses.market);
        order.setInitialCollateralToken(params.addresses.initialCollateralToken);
GalloDaSballo - `setLatestAdlBlock` may set the wrong block number
Audit: 2023-02-gmx-judging
Severity: HIGH

GalloDaSballo

medium

# `setLatestAdlBlock` may set the wrong block number

## Summary

The whole codebase is written to use `currentBlockNumber` to ensure that the blocks on Arbitrum are properly accounted for.
However `UpdateAdlState` doesn't use `currentBlockNumber` and instead uses `block.number` which may not match with the block number on arbitrum

## Vulnerability Detail

The discrepancy may cause the Adl functionality to not work as intended

## Impact

Because `UpdateAdlState` uses `block.number` instead of `currentBlockNumber()` the adl functionality may work in unintended ways

## Code Snippet

https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/adl/AdlUtils.sol#L114-L115

## Tool used

Manual Review

## Recommendation

Replace

```solidity
        setLatestAdlBlock(dataStore, market, isLong, block.number);
```

With

```solidity
        setLatestAdlBlock(dataStore, market, isLong, currentBlockNumber());
```


Duplicate of #150
drdr - Unclaimable collateral
Audit: 2023-02-gmx-judging
Severity: HIGH

drdr

high

# Unclaimable collateral

## Summary

After decreasing the position, the protocol increases claimable collateral. Later, user is meant to clam this collateral within a separate transaction calling `ExchangeRouter.claimCollateral` function. However, the function always reverts and does not allow to claim.  

## Vulnerability Detail

The `claimCollateral` function in `MarketUtils` library compares the claimable amount and already claimed amount (https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/market/MarketUtils.sol#L632). However, the function reverts if the claimable amount is greater than already claimed amount which is true by default (any claimable amount is greater than 0). The expression is never false and thus, does not allow to claim collateral.

## Impact

The user who has decreased position is not allowed to claim collateral.

## Code Snippet

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

## Tool used

Manual Review

## Recommendation

Change expression in `if` statement to:
`if (adjustedClaimableAmount <= claimedAmount) {`


Duplicate of #136
bin2chen - estimateExecuteWithdrawalGasLimit() Missing included gasForSwaps
Audit: 2023-02-gmx-judging
Severity: HIGH

bin2chen

medium

# estimateExecuteWithdrawalGasLimit() Missing included gasForSwaps

## Summary

withdraw does not contain gasForSwaps malicious users can construct a very large SwapPath, malicious consumption orderKeeper gas costs
 
## Vulnerability Detail

The longTokenSwapPath/minLongTokenAmount path can be specified when used for withdrawals.
However, the estimated withdrawal fee does not include swap gas
The estimated codes are as follows
```solidity
    function estimateExecuteWithdrawalGasLimit(DataStore dataStore, Withdrawal.Props memory withdrawal) internal view returns (uint256) {
        return dataStore.getUint(Keys.withdrawalGasLimitKey(false)) + withdrawal.callbackGasLimit();//@audit <-------don't include gasForSwaps 
    }
```

We can find estimateExecuteDepositGasLimit/estimateExecuteOrderGasLimit etc.'s are included.
I think this one also needs to be included
Because if not included, a malicious attacker, can set a very long SwapPath, consume the gas cost but not take the loss



## Impact

Consuming large amounts of gas from the OrderKeeper

## Code Snippet

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

## Tool used

Manual Review

## Recommendation
```solidity
    function estimateExecuteWithdrawalGasLimit(DataStore dataStore, Withdrawal.Props memory withdrawal) internal view returns (uint256) {
-       return dataStore.getUint(Keys.withdrawalGasLimitKey(false)) + withdrawal.callbackGasLimit();
+       uint256 gasPerSwap = dataStore.getUint(Keys.singleSwapGasLimitKey());
+       uint256 swapCount = withdrawal.longTokenSwapPath().length + withdrawal.shortTokenSwapPath().length;
+       uint256 gasForSwaps = swapCount * gasPerSwap;
+       return dataStore.getUint(Keys.withdrawalGasLimitKey(false)) + withdrawal.callbackGasLimit() + gasForSwaps;
    }
```

Duplicate of #195