xanoutas/solidity-security-findings huggingface dataset:
Unable to perform liquidation if vault `recipient` is  blacklisted by `quotePool.token`
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/logic/LiquidationLogic.sol#L93-L100


# Vulnerability details

## Impact
`USDC` is supported by the protocol. Note, it has `blacklist` functionality. Thus `USDC` tokens can't be transferred to/from blacklisted address.

During the `liquidation` process, any remaining `margin` will be  transferred to the `vault recipient`. This could be distributed using any tokens supported by the protocol, including `USDC`. 

However, if the `USDC` contract blacklists the `vault recipient`, the liquidation transaction will revert. As a result, the  debt will be unable to be liquidated if they have been blacklisted by the `USDC` token contract.

## Proof of Concept
The [liquidate()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/logic/LiquidationLogic.sol#L93-L100) executes a liquidation call to close an unsafe `vault` and sends the `remaining margin` to the recipient.
```solidity
                if (vault.recipient != address(0)) {
                    // Send the remaining margin to the recipient.
                    vault.margin = 0;


                    sentMarginAmount = uint256(remainingMargin);


                    ERC20(pairStatus.quotePool.token).safeTransfer(vault.recipient, sentMarginAmount);
                }
```
If `pairStatus.quotePool.token` is `USDC`, the liquidation process might DoS due to its reliance on paying back remaining `margin`. This will error where transferring `USDC` tokens to blacklisted users can cause the transaction to be reverted, disrupting the liquidation flow. This will result in a `bad debt` for the platform.

**There is also a possibility that this can be done intentionally by a malicious vault owner.**
---> During vault creation, the creator is both the `owner` and `recipient`. But later, the vault owner can change the `recipient`.
 
- A malicious vault owner calls the [updateRecepient()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/PredyPool.sol#L286-L292)
```solidity
    function updateRecepient(uint256 vaultId, address recipient) external onlyVaultOwner(vaultId) {
        DataType.Vault storage vault = globalData.vaults[vaultId];


        vault.recipient = recipient;


        emit RecepientUpdated(vaultId, recipient);
    }
```
This function enables him to set the `recipient` to a USDC blacklisted address by intention.

As such, during liquidation, the whole flow reverts and his vault remains a debt bomb. 

## Tools Used
Manual Review

## Recommended Mitigation Steps
Remove the transfer of remaining `margin` to the `recipient` during the liquidation flow. Instead, allow the user to withdraw their remaining `margin` on their own after the liquidation process is complete.























## Assessed type

Token-Transfer
Inflexible Twap  `_ORACLE_PERIOD` leads to inadequate pricing of assets
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Update periods in `TWAP` oracles reflect `risk` of an asset. Updating more frequently accurately prices an `asset` but increases capabilities of manipulation (which should be harder with more stable assets) whereas longer update periods prevent manipulation but do not accurately price assets (due to the time difference between updates).

Volatility of an asset should be considered when calculating updating periods. 

However, in `UniHelper.sol` no such considerations are made.
The `_ORACLE_PERIOD` cannot be changed after deployment of the contract. Additionally, each asset uses the same `_ORACLE_PERIOD` which does not adequately account for the difference in risk for each asset.

This could lead to inadequate pricing of assets.

## Vulnerability Details
 [_ORACLE_PERIOD](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/UniHelper.sol#L11) in `UniHelper.sol` is fixed at `30 minutes`
 ```solidity
 uint256 internal constant _ORACLE_PERIOD = 30 minutes;
 ```
To get the square root of time weighted average price, this same `_ORACLE_PERIOD` is used everytime in [getSqrtTWAP](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/UniHelper.sol#L20-L22).
 ```solidity
    function getSqrtTWAP(address uniswapPoolAddress) internal view returns (uint160 sqrtTwapX96) {
        (sqrtTwapX96,) = callUniswapObserve(IUniswapV3Pool(uniswapPoolAddress), _ORACLE_PERIOD); // @audit-issue Same update period used
    }
 ```
This means that regardless of which asset whose price is being fetched, the same update period is used with no account whatsoever for the risks.

This is evident when oracle is set to `address(0)` in `PositionCalculator.sol`.
Here, [getSqrtIndexPrice()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/PositionCalculator.sol#L141-L149) uses two methods to fetch asset price.
1. If oracle is set, it uses the oracle price
2. If this is not set, it fetches TWAP

 ```solidity
    function getSqrtIndexPrice(DataType.PairStatus memory pairStatus) internal view returns (uint256 sqrtPriceX96) {
        if (pairStatus.priceFeed != address(0)) {
            return PriceFeed(pairStatus.priceFeed).getSqrtPrice();
        } else {
            return UniHelper.convertSqrtPrice(
                UniHelper.getSqrtTWAP(pairStatus.sqrtAssetStatus.uniswapPool), pairStatus.isQuoteZero // @audit-issue Twap fetched here
            );
        }
    }
 ```
Evidently, if `oracle` is not set, it fetches `TWAP` by invoking `UniHelper.getSqrtTWAP()` which as seen above, uses the same `_ORACLE_PERIOD`.

This will lead to inadequate pricing for different assets.

 ## Tools Used
Manual Review

## Recommended Mitigation Steps
Implement a method do upd
Incorrect Limit Stop Order Validation.
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#L97-L104
https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketLib.sol#L100-L101


# Vulnerability details

## Impact
`PerpMarketLib#validateTrade` in the following condition [#L97-L104](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketLib.sol#L97-L104)
```solidity
} else if (limitPrice > 0 && stopPrice > 0) {
    // limit & stop order
    if (
        !validateLimitPrice(tradePrice, tradeAmount, limitPrice)
            && !validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData)
    ) {
        revert LimitStopOrderDoesNotMatch();
    }
}
```
The condition: [#L100-L101](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/markets/perp/PerpMarketLib.sol#L100-L101)
```solidity
!validateLimitPrice(tradePrice, tradeAmount, limitPrice) && !validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData)
```
is using the logical AND (`&&`) operator, which means that the `LimitStopOrderDoesNotMatch` error will only be reverted if both `validateLimitPrice` and `validateStopPrice` return false.

However the intended behavior seems to be that the error should be reverted if either the limit price condition or the stop price condition is not met. In other words, the error should be reverted if at least one of the two conditions is not satisfied.

An example to illustrate the impact of this issue:

Suppose we have the following trade parameters:

`tradePrice` = 100
`tradeAmount` = 10
`limitPrice` = 90
`stopPrice` = 110
`oraclePrice` = 105

In this case, the `validateLimitPrice` function should return false because the `tradePrice` (100) is greater than the `limitPrice` (90) for a buy order (`tradeAmount` is positive).

However, the `validateStopPrice` function should return true because the `tradePrice` (100) is less than the `oraclePrice` (105) plus the slippage tolerance (calculated based on `auctionData`), which is a valid condition for a buy order.

With the current implementation, the condition:
```solidity
!validateLimitPrice(tradePrice, tradeAmount, limitPrice) && !validateStopPrice(oraclePrice, tradePrice, tradeAmount, stopPrice, auctionData)
```
will evaluate to:
```solidity
true && false
```
which is `false`.

As a result, the `LimitStopOrderDoesNotMatch` error will not be reverted, and the invalid trade (violating the limit price condition) will be allowed to execute.

## Proof of Concept
A test that demonstrates the issue and the intended behavior.
```solidity
pragma solidity ^0.8.17;

import "forge-std/Test.sol";
import "../src/markets/perp/PerpMarketLib.sol";

contract PerpMarketLibTest is Test {
    function testValidateTrade() public {
        // Mock trade result
        IPredyPool.TradeResult m
Potential Use of Stale Prices in SqrtPrice Calculation in PriceFeed.sol
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/PriceFeed.sol#L46-L52
https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/PositionCalculator.sol#L141-L147


# Vulnerability details


## Impact

This report identifies a potential vulnerability in the `getSqrtPrice` function of the protocol. The function retrieves price data from two separate oracles: a Chainlink oracle and an IPyth oracle. While the function verifies the validity of the IPyth price data by ensuring it's not older than a specified time period (`VALID_TIME_PERIOD`), it **does not perform a similar check for the Chainlink oracle data**. This omission could lead to the use of stale prices in the calculation of the square root price (`sqrtPrice`), potentially impacting various functionalities within the protocol.


## Proof of Concept

 The `sqrtPrice` calculated using potentially stale Chainlink data might not reflect the true market price. this is actively used in position calculation 


```solidity
  function calculateMinMargin(
        DataType.PairStatus memory pairStatus,
        DataType.Vault memory vault,
        DataType.FeeAmount memory feeAmount
    ) internal view returns (int256 minMargin, int256 vaultValue, bool hasPosition, uint256 sqrtOraclePrice) {
        int256 minValue;
        uint256 debtValue;

        sqrtOraclePrice = getSqrtIndexPrice(pairStatus);
```

```solidity

 function getSqrtIndexPrice(DataType.PairStatus memory pairStatus) internal view returns (uint256 sqrtPriceX96) {
        if (pairStatus.priceFeed != address(0)) {
            return PriceFeed(pairStatus.priceFeed).getSqrtPrice();
```
The value obtained from the feed are used to Calculate minmargin which are also critical to other sensitive functions used to liquidate and check the vault's health. Using a stale price can prevent the liquidation of an account that should be liquidated as the vault will still be safe.

```solidity


    function calculateMinMargin(
        DataType.PairStatus memory pairStatus,
        DataType.Vault memory vault,
        DataType.FeeAmount memory feeAmount
    ) internal view returns (int256 minMargin, int256 vaultValue, bool hasPosition, uint256 sqrtOraclePrice) {
        int256 minValue;
        uint256 debtValue;

        sqrtOraclePrice = getSqrtIndexPrice(pairStatus);

```
```solidity
    function getIsSafe(
        DataType.PairStatus memory pairStatus,
        DataType.Vault memory _vault,
        DataType.FeeAmount memory feeAmount
    ) internal view returns (int256 minMargin, bool isSafe, bool hasPosition) {
        int256 vaultValue;

        (minMargin, vaultValue, hasPosition,) = calculateMinMargin(pairStatus, _vault, feeAmount);
 isSafe = vaultValue >= minMargin && _vault.margin >= 0;
```

## Tools Used

* Manual code analysis

## Recommended Mitigation Steps

To mitigate this vulnerability, it's recommended to implement a similar timeline
Incorrect `prices` would be returned for `negative ticks`
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
If `tickCumulatives[1] - tickCumulatives[0]` is negative and `((tickCumulatives[1] - tickCumulatives[0]) % int56(int256(ago)) != 0)`, then returned `tick` will be bigger than it should be  which places operations requiring prices to be right not be able to achieve this goal.

As such, in the case where  `TWAP` is required, then protocol would work with a `flawed prices` since the returned price would be different than it really is, potentially leading to say for example some positions that should be `liquidatable` not being liquidated because before liquidation there is a check to see if the loan is `healthy`. 

Now this check queries the value of this asset via `getSqrtTWAP()` and if returned price is wrong then `unhealthy` loans could be pronounced as `healthy` and vice versa.

## Proof of Concept
In `UniHelper` contract, [getSqrtTWAP()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/UniHelper.sol#L20-L22) function is used by protocol to get `average price`.
```solidity
    function getSqrtTWAP(address uniswapPoolAddress) internal view returns (uint160 sqrtTwapX96) {
        (sqrtTwapX96,) = callUniswapObserve(IUniswapV3Pool(uniswapPoolAddress), _ORACLE_PERIOD);
    }
```

This calls [callUniswapObserve()](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/UniHelper.sol#L68-L74) which actually performs the price calculation:
```solidity
        //...
        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);
```
It is required that if `tickCumulatives[1] - tickCumulatives[0]` is negative, then `tick` should be rounded down like in [Uniswap library](https://github.com/Uniswap/v3-periphery/blob/697c2474757ea89fec12a4e6db16a574fe259610/contracts/libraries/OracleLibrary.sol#L36).

However, this is not done. As result, if `tickCumulatives[1] - tickCumulatives[0]` is negative and `(tickCumulatives[1] - tickCumulatives[0]) % int56(int256(ago)) != 0)`, `tick` will be bigger then it should be. Which opens possibility for arbitrage opportunities.

## Tools Used
Manual Review

## Recommended Mitigation Steps
`tick` should be rounded down in this case:
```diff
    int24 tick = int24((tickCumulatives[1] - tickCumulatives[0]) / int56(int256(ago)));
+  if ((tickCumulatives[1] - tickCumulatives[0]) < 0 && ((tickCumulatives[1] - tickCumulatives[0]) % int56(int256(ago)) != 0)) tick--;
```

















## Assessed type

Uniswap
Liquidation may revert when the account is blacklisted
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/logic/LiquidationLogic.sol#L99


# Vulnerability details

## Impact
Users might not get liquidated, and can make their position healthy by depositing more collateral

## Proof of Concept
In Predy A particular user can liquidate themselves, and if they are eligible for liquidation can be confirmed by [checkVaultIsDanger](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/logic/LiquidationLogic.sol#L57) which ultimately checks `bool isSafe = vaultValue >= minMargin && _vault.margin >= 0;`
And they can be liquidated if they above check is true.
After Liquidation if they have remainning amount still left they are transfered the [remainingmargin back by this check](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/logic/LiquidationLogic.sol#L92) 
```
if (!hasPosition) {
            int256 remainingMargin = vault.margin;

            if (remainingMargin > 0) {
                if (vault.recipient != address(0)) {
                    // Send the remaining margin to the recipient.
                    vault.margin = 0;

                    sentMarginAmount = uint256(remainingMargin);

                    ERC20(pairStatus.quotePool.token).safeTransfer(vault.recipient, sentMarginAmount);//@audit if the user get's blacklisted after opening the position then the liquidation will fail
                }
...
....

}
```
The protocol is compatible with tokens such as USDC/USDT,So if the user get's blacklisted after depositing in the protocol or before liquidation their liquidation will revert . So anyone also trying to liquidate them or themselves will fail in this process

## Tools Used
Manual Review 

## Recommended Mitigation Steps
Have a logic to transfer the remaining margin to the predy pool and users withdrawing it from there.


## Assessed type

ERC20
Chainlink stale price
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Since the token prices are used in many contracts from executing a order to liquidation, stale data could be catastrophic for the project.

## Proof of Concept
The PriceFeed.getSqrtPrice() function is used to get the price of base tokens, the problem is that the function does not check for necesaary round or timestamp checks and directly delegates them for price calculation with the pyth oracle returned price. So if the pyth oracle returned price is ok but chainlink has stale results the overall price display would be wrong The protocol uses chainlink for fetching the prices of quote tokens.

This omission can lead to issues if Chainlink starts a new round and struggles to establish consensus on the new value for the oracle. Without proper checks, consumers of this contract may continue using outdated, stale, or incorrect data if oracles are unable to submit and start a new round. Possible reasons for this could include Chainlink nodes abandoning the oracle, chain congestion, or vulnerabilities/attacks on the Chainlink system.


Also When using Chainlink with L2 chains like Arbitrum, smart contracts must check whether the L2 Sequencer is down to avoid stale pricing data that appears fresh - Chainlink’s official documentation provides an example implementation. 

## Tools Used
Chainlink

## Recommended Mitigation Steps
Read the updatedAt return value from the Chainlink.latestRoundData() function and verify that is not older than than specific time tolerance.
```
require(block.timestamp - udpatedData < toleranceTime, "stale price");
```
Additionaly also add the check for roundId 
```
require(answeredInRound >= roundId, "Stale price");
```

And for L2 sequencer check the official chainlink docs [here](https://docs.chain.link/data-feeds/l2-sequencer-feeds#example-code)






## Assessed type

Oracle
Chainlink FlashCrash Price
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Chainlink aggregators have a built-in circuit breaker to prevent the price of an asset from deviating outside a predefined price range. This circuit breaker may cause the oracle to persistently return the minPrice instead of the actual asset price in the event of a significant price drop, as witnessed during the LUNA crash.

## Proof of Concept
PriceFeed::latestRoundData extracts the linked aggregator and requests round data from it. If an asset's price falls below the minPrice, the protocol continues to value the token at the minPrice rather than its real value. This discrepancy could result in user getting liquidated early or executing the order at a uneven price.

For instance, if TokenA's minPrice is $1 and its price falls to $0.10, the aggregator continues to report $1, rendering the related function calls to entail a value that is ten times the actual value.

It's important to note that while Chainlink oracles form part of the Price Feed system  since we use different oracles for different base/quote token price fetching. but the prices can be disrupted if one of the prices is wrong from a particular oracle.

## Tools Used
Chainlink

## Recommended Mitigation Steps
```diff
 (, int256 quoteAnswer,,,) = AggregatorV3Interface(_quotePriceFeed).latestRoundData();
+ if (quoteAnswer >= maxPrice or quoteAnswer <= minPrice) revert();
```
This ensures that a false price will not be returned if the underlying asset's value hits the minPrice.


## Assessed type

Oracle
Use of slot 0 is easy to manipulate
Audit: 2024-05-predy-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
the getSqrtPrice is pulled from Uniswap.slot0, which is the most recent data point and can be manipulated easily via MEV bots and Flashloans with sandwich attacks; which can cause the loss of funds when interacting with the Uniswap.swap function.

## Proof of Concept
slot0 is the most recent data point and is therefore extremely easy to manipulate. The protocol uses slot0 to fetch the prices while performing a [swap](https://github.com/code-423n4/2024-05-predy/blob/a9246db5f874a91fb71c296aac6a66902289306a/src/libraries/Trade.sol#L49) during executing a order.

An attacker can simply manipulate the getSqrtPrice and the token will be bought at a higher price and the attacker would run the transaction to sell; thereby earning gains but causing a loss of funds to whoever called those functions.

## Tools Used
Uniswap

## Recommended Mitigation Steps
Use the TWAP instead of slot0.


## Assessed type

Uniswap