xanoutas/solidity-security-findings huggingface dataset:
`PerpMarketLib::validateStopPrice` calculates the ratio of `oraclePrice` and `stopPrice` incorrectly
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketV1.sol#L213-L215
https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketLib.sol#L97-L115
https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketLib.sol#L147-L153
https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketLib.sol#L178-L186


# Vulnerability details

## Impact
After trades are executed in the `Perp Market`, the trade order is validated to ensure that the trade conforms to the parameters set by the user, such as market price, limit price, and stop price, etc.

When the `stop price` is validated, the ratio of the `oracle price` and `stop price` is calculated.

The problem is that the ratio incorrectly returns 0 when `oracle price == stop price`, when it should actually return 1.

This leads to a problem where a valid stop price may incorrectly be invalidated, causing DoS of trades.

## Proof of Concept
After trades are completed, they are validated

[PerpMarketV1.sol#L213-L215](https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketV1.sol#L213-L215)
```javascript
    PerpMarketLib.validateTrade(
        tradeResult, tradeAmount, perpOrder.limitPrice, perpOrder.stopPrice, perpOrder.auctionData
    );
```

The `stop price` is validated under certain conditions

[PerpMarketLib.sol#L97-L115](https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketLib.sol#L97-L115)
```javascript
    } else if (limitPrice > 0 && stopPrice > 0) {
        // limit & stop order
@>      if (
            !validateLimitPrice(tradePrice, tradeAmount, limitPrice)
                && !validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData)
        ) {
            revert LimitStopOrderDoesNotMatch();
        }
    } else if (limitPrice > 0) {
        // limit order
        if (!validateLimitPrice(tradePrice, tradeAmount, limitPrice)) {
            revert LimitPriceDoesNotMatch();
        }
    } else if (stopPrice > 0) {
        // stop order
@>      if (!validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData)) {
            revert StopPriceDoesNotMatch();
        }
    }
```

`validateStopPrice` calls `decay2` with `ratio(oraclePrice, stopPrice)` as the `value` parameter

[PerpMarketLib.sol#L147-L153](https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketLib.sol#L147-L153)
```javascript
    uint256 decayedSlippageTorelance = DecayLib.decay2(
        auctionParams.startPrice,
        auctionParams.endPrice,
        auctionParams.startTime,
        auctionParams.endTime,
@>      ratio(oraclePrice, stopPrice)
    );
```

[PerpMarketLib.sol#L178-L186](https://github.com/code-423n4/2024-05-predy/blob/main/src/markets/perp/PerpMarketLib.sol#L178-L186)
```javascript
    function ratio(uint256 price1, uint256 price2) internal pure returns (uint256) {
        if (price1 == price2) {
No `deadline` parameter on `swapExactIn` and `swapExactOut` can lead to less faourable swaps.
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/settlements/UniswapSettlement.sol#L22
https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/settlements/UniswapSettlement.sol#L38


# Vulnerability details

## Impact
Without a deadline,`transaction` might execute at an unexpected time when the price is significantly different, leading to `higher slippage` and unfavorable execution. this will lead to swap results which are very different from the `quote value`.

## Proof of Concept
Lets say that a `user` intends to swap base for underlying using the `swapExactIn`. since there is no deadline check that ensures that the swap occurs within a set `timeframe` in which the swap is favorable to them, the transaction may occur at much less favorable conditions maybe due to price change, or as slippage tends to its maximum value.
```solidity
function swapExactOut(
        address quoteToken,
        address,
        bytes memory data,
        uint256 amountOut,
        uint256 amountInMaximum,
        address recipient
    ) external override returns (uint256 amountIn) {
        ERC20(quoteToken).safeTransferFrom(msg.sender, address(this), amountInMaximum);
        ERC20(quoteToken).approve(address(_swapRouter), amountInMaximum);

        //more code...
```
## Tools Used
Manual review
## Recommended Mitigation Steps
Implement deadline parameter and deadline checks.


## Assessed type

DoS
Improper Validation in PerpMarketV1: Reverting Mechanism Fails to revert an Invalid Trades
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketV1.sol#L213-L215
https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketLib.sol#L80
https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketLib.sol#L100-L101


# Vulnerability details

## Impact
The discovered issue in the `PerpMarketV1` contract's `_executeOrderV3` function creates a critical vulnerability where invalid trades are not correctly reverted. This occurs due to an invalid validation in the `PerpMarketLib.validateTrade` function. Specifically, the validation logic allows a trade to proceed if either `validateLimitPrice` or `validateStopPrice` returns true. As a result, trades with invalid parameters might still be executed.

## Proof of Concept
To illustrate this issue, consider the following scenarios and corresponding proof:

#### Scenario: Invalid Trade Execution

**Steps:**

1. A trade is initiated with specific `limitPrice` and `stopPrice` parameters.
2. The `PerpMarketLib.validateTrade` function is called to validate the trade at line 213 in `PerpMarketV1.sol`.

```solidity
PerpMarketLib.validateTrade(
      tradeResult, tradeAmount, perpOrder.limitPrice, perpOrder.stopPrice, perpOrder.auctionData
    );
```

3. The current logic checks:

```solidity
if (limitPrice > 0 && stopPrice > 0) {
      // limit & stop order
      if (
        !validateLimitPrice(tradePrice, tradeAmount, limitPrice)
          && !validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData)
      ) {
        revert LimitStopOrderDoesNotMatch();
      }
    }
```

4. This code only reverts if both `validateLimitPrice` and `validateStopPrice` return false. If either one is true, the trade does not revert and proceeds.

Consider a trade where `validateLimitPrice` returns false (invalid limit price) and `validateStopPrice` returns true (valid stop price):

```solidity
// Example function calls
bool limitPriceValid = validateLimitPrice(tradePrice, tradeAmount, limitPrice); // returns false
bool stopPriceValid = validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData); // returns true
```

- The trade is incorrectly validated and proceeds despite the invalid limit price.


## Tools Used

Manual code analysis

## Recommended Mitigation Steps

To address this vulnerability, modify the validation logic in the `PerpMarketLib.validateTrade` function to ensure that the trade reverts if either validation fails. The corrected code should look like this:

```solidity
if (limitPrice > 0 && stopPrice > 0) {
      // limit & stop order
      if (
        !validateLimitPrice(tradePrice, tradeAmount, limitPrice)
          || !validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData)
      ) {
        revert LimitStopOrderDoesNotMatch();
      }
    }
```
Chainlink's `latestRoundData` may return a stale or incorrect result
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/main/src/PriceFeed.sol#L46


# Vulnerability details

## Impact
Chainlink's latestRoundData is used here to retrieve price feed data; however, there is insufficient protection against price staleness.

Return arguments other than int256 quoteAnswer are necessary to determine the validity of the returned price, as it is possible for an outdated price to be received.  

The return value updatedAt contains the timestamp at which the received price was last updated, and can be used to ensure that the price is not outdated. See more information about latestRoundID in the Chainlink docs. Inaccurate price data can lead to functions not working as expected and/or loss of funds.

## Proof of Concept
https://github.com/code-423n4/2024-05-predy/blob/main/src/PriceFeed.sol#L46

```solidity
    function getSqrtPrice() external view returns (uint256 sqrtPrice) {
        (, int256 quoteAnswer,,,) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Add a check for the updatedAt returned value from latestRoundData.

## References

https://ethereum.stackexchange.com/questions/133242/how-future-resilient-is-a-chainlink-price-feed/133843#133843

https://docs.chain.link/data-feeds/api-reference#latestrounddata


## Assessed type

Oracle
Use of Potentially Stale Price Data from Pyth Oracle
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/PriceFeed.sol#L45


# Vulnerability details

## Impact
The `PriceFeed` contract's reliance on the `getPriceNoOlderThan` function from the Pyth oracle introduces a risk where not necessarily the most current price data is used, but instead, the most recent within a permissible oldness (`VALID_TIME_PERIOD`). This setup could lead to significant financial impact due to potential price manipulation. An attacker could exploit this by executing transactions using stale prices, benefiting from anticipated price movements within the same block, particularly in volatile markets. This might allow entering and exiting liquidity positions at non-current prices, leading to unfair gains or losses.


## Proof of Concept
```solidity
function getSqrtPrice() external view returns (uint256 sqrtPrice) {
    (, int256 quoteAnswer,,,) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();
    IPyth.Price memory basePrice = IPyth(_pyth).getPriceNoOlderThan(_priceId, VALID_TIME_PERIOD);
    require(basePrice.expo == -8, "INVALID_EXP");
    require(quoteAnswer > 0 && basePrice.price > 0);
    uint256 price = uint256(int256(basePrice.price)) * Constants.Q96 / uint256(quoteAnswer);
    price = price * Constants.Q96 / _decimalsDiff;
    sqrtPrice = FixedPointMathLib.sqrt(price);
}
```
**Demonstration of Issue:**
- No real-time price validation: The contract does not verify if the fetched price is the freshest available.
- Potential manipulation scenario: An attacker could anticipate the transaction based on the stale price and execute a profitable trade before newer price data becomes available.

## Tools Used
Manual

## Recommended Mitigation Steps
Dynamically adjust the `VALID_TIME_PERIOD` based on observed market volatility. In more volatile periods, reduce the time period to ensure fresher data.



## Assessed type

Oracle
Lack of Chainlink Price Range Validation in PriceFeed Contract
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/PriceFeed.sol#L45


# Vulnerability details

## Impact
The PriceFeed contract leverages Chainlink and Pyth oracles to determine the price of assets. The `getSqrtPrice` function fetches data from Chainlink without validating if the price is within a predefined acceptable range. This oversight can lead to using distorted prices in financial calculations, especially during extreme market conditions or oracle malfunctions.

## Proof of Concept
The `PriceFeed` contract employs `AggregatorV3Interface` for fetching the price of quote tokens. The function does not incorporate checks for these prices against any minPrice or maxPrice limits, unlike the setup in some other oracle integrations which have built-in circuit breakers to handle such scenarios.

```solidity
function getSqrtPrice() external view returns (uint256 sqrtPrice) {
    (, int256 quoteAnswer,,,) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();
    IPyth.Price memory basePrice = IPyth(_pyth).getPriceNoOlderThan(_priceId, VALID_TIME_PERIOD);

    require(basePrice.expo == -8, "INVALID_EXP");
    require(quoteAnswer > 0 && basePrice.price > 0);

    uint256 price = uint256(int256(basePrice.price)) * Constants.Q96 / uint256(quoteAnswer);
    price = price * Constants.Q96 / _decimalsDiff;

    sqrtPrice = FixedPointMathLib.sqrt(price);
}
```

## Tools Used
Manual

## Recommended Mitigation Steps
Implement minPrice/maxPrice validation within the `getSqrtPrice` function to ensure price integrity. Here is a recommended code snippet to add:

```solidity
(, int256 price, , , ) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();

if (price < minPrice || price > maxPrice) {
    revert("Price out of bounds");
}
```


## Assessed type

Oracle
`validateStopPrice()` is potentially flawed by wrong `value` parameter
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketLib.sol#L147-L153
https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/orders/DecayLib.sol#L16-L37


# Vulnerability details

## Summary
In `validateStopPrice()`, the `value` parameter is set to `ratio(oraclePrice, stopPrice)`, which is a `price ratio`, not a `time value`. This leads to errors in the decay calculation.

## Proof of Concept
When [decay()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/orders/DecayLib.sol#L13) invokes `decay2()` to calculate `decayedPrice `, it passes `block.timestamp` as the  last parameter, `value`.
```solidity
        decayedPrice = decay2(startPrice, endPrice, decayStartTime, decayEndTime, block.timestamp);
```
This is because the `value` parameter in `decay2()` is set to accept a `time value` therefore allowing logical comparisons between `decayEndTime` and this provided `time value`.

However, [validateStopPrice()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketLib.sol#L147-L153) passes `ratio(oraclePrice, stopPrice)`, which is a `price ratio` as the `value` parameter to the `DecayLib.decay2()` to calculate the `decayedSlippageTorelance`:

```solidity
uint256 decayedSlippageTorelance = DecayLib.decay2(
    auctionParams.startPrice,
    auctionParams.endPrice,
    auctionParams.startTime,
    auctionParams.endTime,
    ratio(oraclePrice, stopPrice)  // @ audit-issue Incorrect parameter
);
```
##### Problem:
The `value` parameter in [DecayLib.decay2()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/orders/DecayLib.sol#L16-L37) is intended to represent a `time point` within the decay period not `price ratio`.

```solidity
function decay2(uint256 startPrice, uint256 endPrice, uint256 decayStartTime, uint256 decayEndTime, uint256 value)
        internal
        pure
        returns (uint256 decayedPrice)
    {
     //...
    }
```

`DecayLib.decay2()` expects the `value` parameter to be a `time value`, but it receives a `price ratio`. The `decayEndTime` therefore ends up being [compared against a price ratio instead of time stamp](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/orders/DecayLib.sol#L23-L27).

```solidity
} else if (decayEndTime <= value) {
            decayedPrice = endPrice; // @audit-issue decayedPrice set wrt the comparison which may be wrong
        } else if (decayStartTime >= value) {
            decayedPrice = startPrice; // @audit-issue decayedPrice set wrt the comparison which may be wrong
        }
```
This leads to incorrect calculations within the function.

## Impact
This `decayedSlippageTorelance ` returned is then used to [validate price](https://github.com/code-423n
Incorrect  data is saved as `userPosition`  during trade execution
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/gamma/GammaTradeMarket.sol#L185-L187


# Vulnerability details

## Impact
`_getOrInitPosition()` is used to fetch a user's posistion details or create a new position if `isCreatedNew` is set to `true`. 

However, when invoked in `_executeTrade()`, it passes `tradeResult.vaultId` in place of `positionId`. This may result in the function saving incorrect information  under `userPosition` via `_saveUserPosition()`.

## Vulnerability Details
[_getOrInitPosition()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/gamma/GammaTradeMarket.sol#L408-L411) takes the following parameters:
```solidity
function _getOrInitPosition(uint256 positionId, bool isCreatedNew, address trader, uint64 pairId)
     internal
     returns (GammaTradeMarketLib.UserPosition storage userPosition)
{}
```
As seen above, the first parameter is `positionId`.

However, in place of `positionId`, `tradeResult.vaultId` is passed instead of `gammaOrder.positionId` here within [_executeTrade()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/gamma/GammaTradeMarket.sol#L185-L187).
```solidity
        GammaTradeMarketLib.UserPosition storage userPosition = _getOrInitPosition(
            tradeResult.vaultId, gammaOrder.positionId == 0, gammaOrder.info.trader, gammaOrder.pairId
        ); // @audit-issue Incorrect value used
```
As such, the `_getOrInitPosition()` will end up retrieving incorrect information as `userPosition` which finally will get saved for this particular user via [_saveUserPosition()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/gamma/GammaTradeMarket.sol#L189):
```solidity
 _saveUserPosition(userPosition, gammaOrder.modifyInfo);
```

### Note:
Reffer [_modifyAutoHedgeAndClose()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/gamma/GammaTradeMarket.sol#L220-L222) where the correct value is passed:
```solidity
    // save user position
    GammaTradeMarketLib.UserPosition storage userPosition =
        _getOrInitPosition(gammaOrder.positionId, false, gammaOrder.info.trader, gammaOrder.pairId); // @audt Correct value used here

```
As seen here, the correct value to pass is `gammaOrder.positionId` not a `vaultId`.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Pass `gammaOrder.positionId` instead of `tradeResult.vaultId` in the `_getOrInitPosition()` call.

```diff
        GammaTradeMarketLib.UserPosition storage userPosition = _getOrInitPosition(
-           tradeResult.vaultId, gammaOrder.positionId == 0, gammaOrder.info.trader, gammaOrder.pairId
+           gammaOrder.positionId, gammaOrder.positionId == 0, gammaOrder.info.trader, gammaOrder.pairId
        );
```

















## Assessed type

Error
UniV3 Oracle Manipulation on L2s
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/UniHelper.sol#L35


# Vulnerability details

## Impact
The cost of manipulating the TWAP in L2 network is too low so TWAP should not be used in L2

## Proof of Concept
Predy uses TWAP prices in places such as when liquidating a user.
According to the information provided by the Uniswap team, as documented in the Uniswap Oracle Integration on Layer 2 Rollups guide, primarily addresses the integration of Uniswap oracle on L2 Optimism. However, it is relevant to note that the same concerns apply to Arbitrum as well. Arbitrum's average block time is approximately 0.25 seconds, making it vulnerable to potential oracle price manipulation.

Oracles Integrations on Layer 2 Rollups

 > On Optimism, every transaction is confirmed as an individual block. The block.timestamp of these blocks, however, reflect the block.timestamp of the last L1 block ingested by the Sequencer. For this reason, Uniswap pools on Optimism are not suitable for providing oracle prices, as this high-latency block.timestamp update process makes the oracle much less costly to manipulate. In the future, it's possible that the Optimism block.timestamp will have much higher granularity (with a small trust assumption in the Sequencer), or that forced inclusion transactions will improve oracle security. For more information on these potential upcoming changes, please see the Optimistic Specs repo. For the time being, usage of the oracle feature on Optimism should be avoided.

In predy The Oracle period is even [hardcoded to 30 mins](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/UniHelper.sol#L42) which makes the manipulation much easy.
And After getting the twap period it calculates the price of the already manipulated pool in [here](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/UniHelper.sol#L68)
```
/.......
....
...
        int56[] memory tickCumulatives = abi.decode(data, (int56[]));

        int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(int256(ago)));

        uint160 sqrtPriceX96 = TickMath.getSqrtRatioAtTick(tick);

        return (sqrtPriceX96, ago);
```


## Tools Used
Uniswap 

## Recommended Mitigation Steps
Fetch the oracle source from chainlink in place of twap 





## Assessed type

Uniswap
Chainlink price staleness is not validated from `latestRoundData()` function call
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/e96f378007e3dc56b184079f0c0e4fe48a72efaa/src/PriceFeed.sol#L46


# Vulnerability details

## Impact
Stale price can be used which will lead to loss of funds.

## Proof of Concept
In the  `getSqrtPrice()` of `PriceFeed.sol` the Chainlink `AggregatorV3Interface(_quotePriceFeed).latestRoundData()` was called but there was not enough price staleness check on the returned price.

```solidity
/// @notice This function returns the square root of the baseToken price quoted in quoteToken.
    function getSqrtPrice() external view returns (uint256 sqrtPrice) {
@>        (, int256 quoteAnswer,,,) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();//FINDING: No price staleness check with updateAt timestamp

        IPyth.Price memory basePrice = IPyth(_pyth).getPriceNoOlderThan(_priceId, VALID_TIME_PERIOD);

        require(basePrice.expo == -8, "INVALID_EXP");

        require(quoteAnswer > 0 && basePrice.price > 0);

        uint256 price = uint256(int256(basePrice.price)) * Constants.Q96 / uint256(quoteAnswer);
        price = price * Constants.Q96 / _decimalsDiff;

        sqrtPrice = FixedPointMathLib.sqrt(price);
    }
```

## Tools Used
Manual Review


## Recommended Mitigation Steps
Consider validating price staleness with the returned `updatedAt` timestamp.

```diff
 function getSqrtPrice() external view returns (uint256 sqrtPrice) {
--        (, int256 quoteAnswer,,,) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();
++        (, int256 quoteAnswer,,uint256 updateAt,) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();
++ if (block.timestamp > updateAt + heartbeat) {
++    revert("Stale price")
++  }
```


## Assessed type

Oracle