xanoutas/solidity-security-findings huggingface dataset:
0xdeadbeef - Byte slicing corrupts data length - keepers funds will be drained by expensive memory expansion
Audit: 2023-02-gmx-judging
Severity: HIGH

0xdeadbeef

high

# Byte slicing corrupts data length - keepers funds will be drained by expensive memory expansion

## Summary

Users can define callbacks for Deposits/Withdrawals/Orders execution and cancellations.
GMX protocol attempts to manage errors during the execution of the callbacks 

A user controlled callback can return a specially crafted revert reason that will make the error handling waste the transaction gas.

During the handling of revert reason, the bytes length becomes corrupted. 
A hacker creates a revert reason in the callbacks of Deposits/Withdrawals/Orders that leverages the new high value bytes length.

The hacker can either:
1. Drains the transaction gas without causing the execution to fail
2. Prevent the Deposits/Withdrawals/Orders from being executed/cancelled and drain the transaction gas

## Vulnerability Detail

The bug resides in `ErrorUtils`s `getRevertMessage`  that is called on every callback attempt. Example of deposit callback:
```solidity
try IDepositCallbackReceiver(deposit.callbackContract()).afterDepositExecution{ gas: deposit.callbackGasLimit() }(key, deposit) {
        } catch (bytes memory reasonBytes) {
            (string memory reason, /* bool hasRevertMessage */) = ErrorUtils.getRevertMessage(reasonBytes);
            emit AfterDepositExecutionError(key, deposit, reason, reasonBytes);
        }
```
`ErrorUtils`s `getRevertMessage`:
```solidity
function getRevertMessage(bytes memory result) internal pure returns (string memory, bool) {
        // If the result length is less than 68, then the transaction either panicked or failed silently
        if (result.length < 68) {
            return ("", false);
        }

        bytes4 errorSelector = getErrorSelectorFromData(result);

        // 0x08c379a0 is the selector for Error(string)
        // referenced from https://blog.soliditylang.org/2021/04/21/custom-errors/
        if (errorSelector == bytes4(0x08c379a0)) {
            assembly {
                result := add(result, 0x04)
            }

            return (abi.decode(result, (string)), true);
        }

        // error may be a custom error, return an empty string for this case
        return ("", false);
    }
```

As can be seen in the above above snippets, the `reasonBytes` from the catch statement is passed to `getRevertMessage` which tries to extract the `Error(string)` message from the revert.

This piece of code slices the selector `0x08c379a0` from the bytes array  in order to be properly parsed in `abi.decode` by adding 4 to the results pointer:
https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/utils/ErrorUtils.sol#L19
```solidity
            assembly {
                result := add(result, 0x04)
            }
```

The problem is that once the selector is sliced, the length of the encoded data rises from 0x64 -> 0x6408c379a0

Since the attacker controls the byte array thats returned from the callback, he can craft the offset to data to be just under `
ShadowForce - Incorrect usage of block number
Audit: 2023-02-gmx-judging
Severity: HIGH

ShadowForce

high

# Incorrect usage of block number

## Summary
protocol does not use the correct block number. This leads to functions that rely on block number being incorrect, failing or not executing as they should.
## Vulnerability Detail
The protocol does not ensure that the block numbers ,used in various functions, are consistent throughout the code base. Below i will demonstrate how the protocol uses the L1 `block.number` when it truly intends to use the L2 `block.number`. This bug only affects deployment on the Arbitrum chain.

AdlUtils.sol
```solidity
    // @param maxOracleBlockNumbers the oracle block numbers for the prices stored in the oracle
    function updateAdlState(
        DataStore dataStore,
        EventEmitter eventEmitter,
        Oracle oracle,
        address market,
        bool isLong,
        uint256[] memory maxOracleBlockNumbers
    ) external {
        uint256 latestAdlBlock = getLatestAdlBlock(dataStore, market, isLong);

        if (!maxOracleBlockNumbers.areGreaterThanOrEqualTo(latestAdlBlock)) {
            OracleUtils.revertOracleBlockNumbersAreSmallerThanRequired(maxOracleBlockNumbers, latestAdlBlock);
        }

        Market.Props memory _market = MarketUtils.getEnabledMarket(dataStore, market);
        MarketUtils.MarketPrices memory prices = MarketUtils.getMarketPrices(oracle, _market);
        (bool shouldEnableAdl, int256 pnlToPoolFactor, uint256 maxPnlFactor) = MarketUtils.isPnlFactorExceeded(
            dataStore,
            _market,
            prices,
            isLong,
            Keys.MAX_PNL_FACTOR
        );

        setIsAdlEnabled(dataStore, market, isLong, shouldEnableAdl);
        setLatestAdlBlock(dataStore, market, isLong, block.number);

        emitAdlStateUpdated(eventEmitter, market, isLong, pnlToPoolFactor, maxPnlFactor, shouldEnableAdl);
    }
```
In the Snippet above we should focus on the `setLatestAdlBlock()` function. As we can see the function here uses block.number.

Chain.sol
```solidity
  // @dev return the current block's number
    // @return the current block's number
    function currentBlockNumber() internal view returns (uint256) {
        if (block.chainid == ARBITRUM_CHAIN_ID || block.chainid == ARBITRUM_RINKEBY_CHAIN_ID) {
            return arbSys.arbBlockNumber();
        }

        return block.number;
    }
```
However in this snippet of code we can see that if the chain is arbitrum, we will not have the same block numbers as the previous snippet.

let me explain why this is a problem.

The protocol uses something called 'ADL' or auto-deleverage. Essentially what this is for is to lower the leverage when it has passed a certain threshold. This is explained in comments in the snippet below.
```solidity
 // @dev Multiple positions may need to be reduced to ensure that the pending
    // profits does not exceed the allowed thresholds
    //
    // This automatic reduction of positions can only be done if the pool is in a state
    // where auto-deleveraging is
float-audits - A component in fees is incorrectly double-charged and hence one additional portion remains in the market pool
Audit: 2023-02-gmx-judging
Severity: HIGH

float-audits

high

# A component in fees is incorrectly double-charged and hence one additional portion remains in the market pool

## Summary

`borrowingFeeAmountForFeeReceiver` is double-counted in `totalNetCostAmount` and the additional amount remains in the market pool.

## Vulnerability Detail

Fee calculation is initiated in `IncreasePositionUtils.processCollateral`. 
`PositionPricingUtils.getPositionFees` is called. 
The important variable in the output is `totalNetCostAmount`, which is subtracted from the output tokens of the swap chain to get `collateralDeltaAmount` which is used to increase position collateral amount and total collateral amount.

Problem is that the leftover values in `totalNetCostAmount` do not match up with the values in `feeAmountForPool` which is to be left in the pool - there is the an `borrowingFeeAmountForFeeReceiver`.

`feeAmountForPool` is defined as:
```solidity
fees.feeAmountForPool = fees.positionFeeAmountForPool + fees.borrowingFeeAmount - borrowingFeeAmountForFeeReceiver;
```
which can be rearranged as:
```solidity
fees.feeAmountForPool + borrowingFeeAmountForFeeReceiver = fees.positionFeeAmountForPool + fees.borrowingFeeAmount;
```

`totalNetCostAmount` is defined as:
```solidity
fees.totalNetCostAmount = fees.referral.affiliateRewardAmount + fees.feeReceiverAmount + fees.positionFeeAmountForPool + fees.funding.fundingFeeAmount + fees.borrowingFeeAmount;
```
substituting `fees.positionFeeAmountForPool + fees.funding.fundingFeeAmount`
```solidity
fees.totalNetCostAmount = fees.referral.affiliateRewardAmount + fees.feeReceiverAmount + fees.funding.fundingFeeAmount + fees.feeAmountForPool + borrowingFeeAmountForFeeReceiver;
```

However `fees.feeReceiverAmount` is defined as follows:
```solidity
fees.feeReceiverAmount = previous.fees.feeReceiverAmount + borrowingFeeAmountForFeeReceiver;
```

subbing this into `totalNetCostAmount`, the result is:
```solidity
fees.totalNetCostAmount = fees.referral.affiliateRewardAmount + previous.fees.feeReceiverAmount + fees.funding.fundingFeeAmount + fees.feeAmountForPool + 2*borrowingFeeAmountForFeeReceiver;
```

## Impact

User collateral position is always charged 2 times the `borrowingFeeAmountForFeeReceiver`, 1 of which is claimed by the receiver via incrementing `feeReceiverAmount` and another 1 which remains in the market pool.

This initial loss of user funds would lead to disruption in internal accounting of the system.

## Code Snippet

LoC: https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/pricing/PositionPricingUtils.sol#L351-L400

## Tool used

Manual Review

## Recommendation

Change `totalNetCostAmount` equation to:

```solidity
 totalNetCostAmount = F.referral.affiliateRewardAmount + F.feeReceiverAmount + F.funding.fundingFeeAmount + F.feeAmountForPool
```

where F is output of `getPositionFees`


Duplicate of #135
float-audits - Incorrect `nextOpenInterest` values set for `priceImpact`
Audit: 2023-02-gmx-judging
Severity: HIGH

float-audits

high

# Incorrect `nextOpenInterest` values set for `priceImpact`

## Summary

Logic for setting `nextOpenInterest` values is incorrect for long and short, depending on which side the action was in.
These incorrect values for `nextOpenInterest` values lead to incorrect calculation of `priceImpact` for the user.

## Vulnerability Detail
Inside PositionPricingUtils.sol the following chain of function calls can be observed:
`getPriceImpactUsd()`->`getNextOpenInterest()`->`getNextOpenInterestParams()`

`getNextOpenInterestParams()` is intended to return the next open interest values for long and short after taking into account the action of the user.
The default values for the `nextLongOpenInterest` and `nextShortOpenInterest` are 0.
However it only updates the next open interest of the side on which the action is on, therefore the next open interest of the other side is **incorrectly** returned as 0.

Should the open interest values be really large, then the implication of having 0 value as next open interest value for one side is quite significant.

This causes the consequent price impact calculations based on current and next open interest values to be materially incorrect.

This causes boolean value `isSameSideRebalance` in `_getPriceImpactUsd()` function to have an incorrect value.

For a user who could actually be improving the balance in the market and should be given a positive price impact, this calculation would lead to negative price impact that is very large in magnitude and hence loss of user funds.

## Impact

Example below:

`isLong` = true;

`sizeDeltaUsd` = 2;

`longOpenInterest` = 10; // next should be 12
`shortOpenInterest` = 13; // next should be 13

`nextLongOpenInterest` = 12;
`nextShortOpenInterest` = 0;

`longOpenInterest` <= `shortOpenInterest` = true;
`nextLongOpenInterest` <= `nextShortOpenInterest` = false;

`isSameSideRebalance` = false; // should be true

`priceImpact` calculated based on the `nextDiffUsd` value of 12 (instead of 1)  

## Code Snippet

LoC: https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/pricing/PositionPricingUtils.sol#L310-L340

```solidity
function getNextOpenInterestParams(
    GetPriceImpactUsdParams memory params,
    uint256 longOpenInterest,
    uint256 shortOpenInterest
) internal pure returns (OpenInterestParams memory) {
    uint256 nextLongOpenInterest;
    uint256 nextShortOpenInterest;

    if (params.isLong) {
        if (params.usdDelta < 0 && (-params.usdDelta).toUint256() > longOpenInterest) {
            revert UsdDeltaExceedsLongOpenInterest(params.usdDelta, longOpenInterest);
        }

        nextLongOpenInterest = Calc.sumReturnUint256(longOpenInterest, params.usdDelta);
    } else {
        if (params.usdDelta < 0 && (-params.usdDelta).toUint256() > shortOpenInterest) {
            revert UsdDeltaExceedsShortOpenInterest(params.usdDelta, shortOpenInterest);
        }

        nextShortOpenInterest = Calc.sumReturnUint256(shortOpenInt
float-audits - Incorrect inequality applied
Audit: 2023-02-gmx-judging
Severity: HIGH

float-audits

high

# Incorrect inequality applied

## Summary

The check in `MarketUtils.sol` for `claimCollateral()` for `adjustedClaimableAmount` and `claimedAmount` has an incorrect inequality.

## Vulnerability Detail

The check for whether the user has already claimed the claimable collateral amount is incorrect.
The contract will error out if the condition `if (adjustedClaimableAmount >= claimedAmount)` is not met, because of the following assignment of `uint256 remainingClaimableAmount = adjustedClaimableAmount - claimedAmount;`

## Impact

This means that a user cannot claim their collateral from the protocol and hence loss of user funds

## Code Snippet

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

```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
    );
}
```

## Tool used

Manual Review

## Recommendation
Change line 632 to `if (adjustedClaimableAmount <= claimedAmount) {`

Duplicate of #136
stopthecap - Wrong if statement logic breaks the claimable process
Audit: 2023-02-gmx-judging
Severity: HIGH

stopthecap

high

# Wrong if statement logic breaks the claimable process

## Summary

On the following 2 lines:

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

there is a signing mistake that makes the collateral unclaimable to users.

## Vulnerability Detail

The problem with those 2 lines that try to check that no more collateral is claimed than the available to claim is that, as you can see,

        if (adjustedClaimableAmount >= claimedAmount) {
            revert CollateralAlreadyClaimed(adjustedClaimableAmount, claimedAmount);
        } 


the following uses wrong signing: adjustedClaimableAmount >= claimedAmount

With this logic the execution will revert always that the claimable amount is higher or equal than the claimed amount, which does not male sense.

Imagine the case where:

You have to claim 200 USDC
You claimed 0 USDC

200 >= 0    <--- TRUE

As you can see, the sign is incorrectly used, leading to users not being able to claim their collateral
## Impact
Critical, users will not be able to claim their collateral

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

## Tool used

Manual Review

## Recommendation

The is statement has to change signing:

              if(adjustedClaimableAmount <= claimedAmount) revert  CollateralAlreadyClaimed();

Duplicate of #136
float-audits - Incorrect parameter ordering in function call
Audit: 2023-02-gmx-judging
Severity: HIGH

float-audits

high

# Incorrect parameter ordering in function call

## Summary

The ordering of parameters in function call made in `updateTotalBorrowing()` in `PositionUtils.sol` is incorrect.

## Vulnerability Detail

The function call of `updateTotalBorrowing()` in `PositionUtils.sol` has a different parameter ordering to the one defined in the actual function being called in `MarketUtils.sol`.

More specifically, `params.position.borrowingFactor()` and `params.position.sizeInUsd()` are swapped around.

## Impact

Updating the total borrowing function with incorrect parameter values would upset the internal accounting of the system and would result in loss of user funds.

## Code Snippet

Loc:
- `PositionUtils.sol`: https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/position/PositionUtils.sol#L460-L474
- `MarketUtils.sol`: https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/market/MarketUtils.sol#L1773-L1793


In `PositionUtils.sol`
```solidity
function updateTotalBorrowing(
    PositionUtils.UpdatePositionParams memory params,
    uint256 nextPositionSizeInUsd,
    uint256 nextPositionBorrowingFactor
) internal {
    MarketUtils.updateTotalBorrowing(
        params.contracts.dataStore,
        params.market.marketToken,
        params.position.isLong(),
        params.position.borrowingFactor(),
        params.position.sizeInUsd(),
        nextPositionSizeInUsd,
        nextPositionBorrowingFactor
    );
}
```

In `MarketUtils.sol`
```solidity
function updateTotalBorrowing(
    DataStore dataStore,
    address market,
    bool isLong,
    uint256 prevPositionSizeInUsd,
    uint256 prevPositionBorrowingFactor,
    uint256 nextPositionSizeInUsd,
    uint256 nextPositionBorrowingFactor
) external {
    uint256 totalBorrowing = getNextTotalBorrowing(
        dataStore,
        market,
        isLong,
        prevPositionSizeInUsd,
        prevPositionBorrowingFactor,
        nextPositionSizeInUsd,
        nextPositionBorrowingFactor
    );

    setTotalBorrowing(dataStore, market, isLong, totalBorrowing);
}
```

## Tool used

Manual Review

## Recommendation

Correct the ordering of parameters in function call made in `PositionUtils.sol` so that it aligns to that defined in the function signature in `MarketUtils.sol`
rvierdiiev - DecreaseOrderUtils.processOrder doesn't have slippage protection, when `order.swapPath().length == 0`
Audit: 2023-02-gmx-judging
Severity: HIGH

rvierdiiev

high

# DecreaseOrderUtils.processOrder doesn't have slippage protection, when `order.swapPath().length == 0`

## Summary
DecreaseOrderUtils.processOrder doesn't have slippage protection, when `order.swapPath().length == 0`.
## Vulnerability Detail
When `DecreaseOrderUtils.processOrder` is called, then later in case if there is no `secondaryOutputAmount`, `result.outputToken` which is collateral token of position will be swapped according to `order.swapPath()` that was provided by user. In this case user can provide `order.minOutputAmount()` param which will take care of slippage.

In case if `order.swapPath().length == 0`, then `outputAmount` will be [sent directly](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/order/DecreaseOrderUtils.sol#L76-L81) to user and user can't use slippage in this case.

Why this slippage is still needed for him? Because there are 2 places later in the code, where the swap is executed. First one [inside DecreasePositionCollateralUtils.processCollateral function](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/position/DecreasePositionCollateralUtils.sol#L144-L148), which [set 0 as  `minOutputAmount`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/position/DecreasePositionCollateralUtils.sol#L434).

Second one is [inside `DecreasePositionUtils.decreasePosition`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/position/DecreasePositionUtils.sol#L281). And this function also [set 0 as  `minOutputAmount`](https://github.com/sherlock-audit/2023-02-gmx/blob/main/gmx-synthetics/contracts/position/DecreasePositionCollateralUtils.sol#L392).

Because the `minOutputAmount` is 0 in both cases, user who provides `order.swapPath().length == 0` doesn't have ability to use slippage protection later and can be frontrunned.
## Impact
When `order.swapPath().length == 0`, user doesn't have slippage protection
## Code Snippet

## Tool used

Manual Review

## Recommendation
Slippage should be checked even when `order.swapPath().length == 0`

Duplicate of #138
KingNFT - Attack on ````ErrorUtils```` library
Audit: 2023-02-gmx-judging
Severity: HIGH

KingNFT

high

# Attack on ````ErrorUtils```` library

## Summary
The ````ErrorUtils```` library is designed to parse ABI encoded ````bytes```` to ````string````, the ````bytes```` parameters come from both trusted GMX contracts and untrusted third-party contracts. The ````getRevertMessage()```` of ````ErrorUtils```` library is not implemented properly. The untrusted third-party contracts can provide carefully crafted ````bytes```` to trigger unexpected revert on GMX contracts.

## Vulnerability Detail
The issue arises on L22, attackers can construct  abnormal ````bytes```` parameter to make ````abi.decode()```` reverting.
```solidity
File: contracts\utils\ErrorUtils.sol
07:     function getRevertMessage(bytes memory result) internal pure returns (string memory, bool) {
08:         // If the result length is less than 68, then the transaction either panicked or failed silently
09:         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.soliditylang.org/2021/04/21/custom-errors/
17:         if (errorSelector == bytes4(0x08c379a0)) {
18:             assembly {
19:                 result := add(result, 0x04)
20:             }
21: 
22:             return (abi.decode(result, (string)), true); // @audit trigger revert
23:         }
24: 
25:         // error may be a custom error, return an empty string for this case
26:         return ("", false);
27:     }
```
To verify the issue, here is a minor modification from ````library```` to ````contract````
```solidity
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

contract ErrorUtils {
    function getRevertMessage(bytes memory result) public pure returns (string memory, bool) {
        // If the result length is less than 68, then the transaction either panicked or failed silently
        if (result.length < 68) {
            return ("", false);
        }

        bytes4 errorSelector = getErrorSelectorFromData(result);

        // 0x08c379a0 is the selector for Error(string)
        // referenced from https://blog.soliditylang.org/2021/04/21/custom-errors/
        if (errorSelector == bytes4(0x08c379a0)) {
            assembly {
                result := add(result, 0x04)
            }

            return (abi.decode(result, (string)), true);
        }

        // error may be a custom error, return an empty string for this case
        return ("", false);
    }

    function getErrorSelectorFromData(bytes memory data) internal pure returns (bytes4) {
        bytes4 errorSelector;

        assembly {
            errorSelector := mload(add(data, 0x20))
        }

        return errorSelector;
    }
}
```
Deploy it to https://remix.ethereum.org/
When we call ````getRevertMessage()```` with a normal ````bytes```` such as
```solidity
0x08c379a0000000000000000000000000000000000000000000000