xanoutas/solidity-security-findings huggingface dataset:
Discrepency in the Uniswap V3 position price calculation because of decimals
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/UniswapV3OracleWrapper.sol#L241-L277


# Vulnerability details

## Impact
When the squared root of the Uniswap V3 position is calculated from the [`_getOracleData()` function](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/UniswapV3OracleWrapper.sol#L221-L280), the price may return a very high number (in the case that the token1 decimals are strictly superior to the token0 decimals). See: https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/UniswapV3OracleWrapper.sol#L249-L260

The reason is that at the denominator, the `1E9` (10**9) value is hard-coded, but should take into account the delta between both decimals.
As a result, in the case of `token1Decimal > token0Decimal`, the [`getAmountsForLiquidity()`](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/dependencies/uniswap/LiquidityAmounts.sol#L172-L205) is going to return a huge value for the amount of token0 and token1 as the user position liquidity.

The [`getTokenPrice()`](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/UniswapV3OracleWrapper.sol#L156), using this amount of liquidity to [calculate the token price](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/UniswapV3OracleWrapper.sol#L176-L180) is as its turn going to return a huge value.

## Proof of Concept

This POC demonstrates in which case the returned squared root price of the position is over inflated

```solidity
// SPDX-License-Identifier: UNLISENCED
pragma solidity 0.8.10;

import {SqrtLib} from "../contracts/dependencies/math/SqrtLib.sol";
import "forge-std/Test.sol";

contract Audit is Test {
    function testSqrtPriceX96() public {
        // ok
        uint160 price1 = getSqrtPriceX96(1e18, 5 * 1e18, 18, 18);

        // ok
        uint160 price2 = getSqrtPriceX96(1e18, 5 * 1e18, 18, 9);

        // Has an over-inflated squared root price by 9 magnitudes as token0Decimal < token1Decimal
        uint160 price3 = getSqrtPriceX96(1e18, 5 * 1e18, 9, 18);
    }

    function getSqrtPriceX96(
        uint256 token0Price,
        uint256 token1Price,
        uint256 token0Decimal,
        uint256 token1Decimal
    ) private view returns (uint160 sqrtPriceX96) {
        if (oracleData.token1Decimal == oracleData.token0Decimal) {
            // multiply by 10^18 then divide by 10^9 to preserve price in wei
            sqrtPriceX96 = uint160(
                (SqrtLib.sqrt(((token0Price * (10**18)) / (token1Price))) *
                    2**96) / 1E9
            );
        } else if (token1Decimal > token0Decimal) {
            // multiple by 10^(decimalB - decimalA) to preserve price in wei
            sqrtPriceX96 = uint160(
                (SqrtLib.sqrt(
                    (token0Price * (10**(18 + token1Decimal - token0Decimal))) /
PoolApeStaking#withdrawApeCoin compares health factor to incorrect value and allows user to take out large loans that expected
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/pool/PoolApeStaking.sol#L60-L90


# Vulnerability details

## Impact

BAYC, MAYC and BAKC can be leveraged past max LTV and up to the liquidation factor

## Proof of Concept

    function withdrawApeCoin(
        address nftAsset,
        ApeCoinStaking.SingleNft[] calldata _nfts
    ) external nonReentrant {
        DataTypes.PoolStorage storage ps = poolStorage();
        checkSApeIsNotPaused(ps);

        DataTypes.ReserveData storage nftReserve = ps._reserves[nftAsset];
        address xTokenAddress = nftReserve.xTokenAddress;
        INToken nToken = INToken(xTokenAddress);

        uint256 amountToWithdraw = 0;
        for (uint256 index = 0; index < _nfts.length; index++) {
            require(
                nToken.ownerOf(_nfts[index].tokenId) == msg.sender,
                Errors.NOT_THE_OWNER
            );
            amountToWithdraw += _nfts[index].amount;
        }

        INTokenApeStaking(nftReserve.xTokenAddress).withdrawApeCoin(
            _nfts,
            msg.sender
        );

        //@audit compares health factor to liquidation rather than max LTV
        require(
            getUserHf(msg.sender) >
                DataTypes.HEALTH_FACTOR_LIQUIDATION_THRESHOLD,
            Errors.HEALTH_FACTOR_LOWER_THAN_LIQUIDATION_THRESHOLD
        );
    }

withdrawApeCoin allows a user to withdraw sAPE from any NFT even APE that has been borrowed. The only thing it checks at the end is that the health factor of the user is still adequate. The problem with this is that it compares it to the wrong value. Here it compares it to the liquidation threshold instead of the max LTV. This allows the user to borrow and withdraw APE until they hit a usage of 80% (for BAYC) rather than the intended 40%.

## Tools Used

Manual Review

## Recommended Mitigation Steps

Confirm that the user's health factor is above the the max LTV rather than the liquidation threshold
Missing access control in `NFTFloorOracle::removeFeeder`
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/6542d6e946762fc7a914a3f6f2e08fcbbf6c8a13/paraspace-core/contracts/misc/NFTFloorOracle.sol#L167


# Vulnerability details

### Proof of Concept

THe `removeFeeder` method is lacking `onlyRole(*DEFAULT_ADMIN_ROLE*)` modifier, even though the NatSpec states “Allows owner to remove feeder”. Due to this, now everyone can remove a feeder anytime. Different types of attack can be executed, one of which is the following:

1. Some price changes and now a feeder tries to call `setPrice`
2. A malicious user does not like the price change reported, so he front-runs that transaction with `removeFeeder` transaction
3. The price update fails, so now price is stale
4. Repeat as many times as needed 

### Severity

The likelihood of this happening is high because attacks on missing access control are very easy to execute. The impact is also high because an attacker can force the protocol to operate with a stale price or even to not have any price updaters apart from the owner of the protocol. Since both likelihood and impact are high this should be a High severity vulnerability.

### Recommendation

Add `onlyRole(DEFAULT_ADMIN_ROLE)` modifier to the `removeFeeder` method
Anyone can remove feeders from `NFTFloorOracle`.
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L167-L172


# Vulnerability details

## Impact
There is no `onlyRole` modifier in `removeFeeder`, so anyone can remove feeders from `NFTFloorOracle`, and it will cause a DOS attack.

## Proof of Concept

```solidity
    function removeFeeder(address _feeder)
        external
        onlyWhenFeederExisted(_feeder) //@audit no modifier
    {
        _removeFeeder(_feeder);
    }
```
There is `onlyRole` modifier in `addFeeders`, so only default admin can add feeders.
But there is no `onlyRole` modifier in `removeFeeder`, so anyone can remove feeders from `NFTFloorOracle`.
If the number of feeders are less than `MIN_ORACLES_NUM`, `_combine` will return `false` for `dataValidity`, and we can't set price using `_finalizePrice` in `setPrice`. So it can induce a DOS attack.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Add `onlyRole` modifier in `removeFeeder`.
Update initializer library to prevent reentrancy during initialization
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/package.json#L31-L32


# Vulnerability details

Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer function so it can only be called once.

The solution uses:
   "@openzeppelin/contracts": "^4.2.0",
    "@openzeppelin/contracts-upgradeable": "^4.2.0",

## Proof of Concept
These dependencies have a known high-severity vulnerability:

https://security.snyk.io/vuln/SNYK-JS-OPENZEPPELINCONTRACTSUPGRADEABLE-2320177

https://snyk.io/test/npm/@openzeppelin/contracts-upgradeable/4.3.2#SNYK-JS-OPENZEPPELINCONTRACTSUPGRADEABLE-2320177

https://snyk.io/test/npm/@openzeppelin/contracts/4.0.0#SNYK-JS-OPENZEPPELINCONTRACTS-2320176

Which makes these contracts vulnerable:

[PoolConfigurator.sol#L76](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/protocol/pool/PoolConfigurator.sol#L76).

  76: 	function initialize(IPoolAddressesProvider provider) external initializer {


[MockInitializableImplementation.sol#L25](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/mocks/upgradeability/MockInitializableImplementation.sol#L25).

25: 	) external initializer {

[WETHGateway.sol#L39](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/ui/WETHGateway.sol#L39).

39: 	function initialize() external initializer {   


[WPunkGateway.sol#L57](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/ui/WPunkGateway.sol#L57).

57: 	function initialize() external initializer 

## Recommended Mitigation Steps
Upgrade @openzeppelin/contracts and @openzeppelin/contracts-upgradeable to version 4.4.1 or higher.
Unsafe casting from uint256 to uint8 cause wrong index of asset and feeder in NFTFloorOracle
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L284
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L312


# Vulnerability details

## Impact
In NFTFloorPrice, it has a set of feeders and a set of assets. Feeders will set price for assets. Of coursers, with the growing NFT space, number of assets collection could be much more than 256 and because Paraspace uses median price from keepers so number of keepers must be even larger.

However, in both functions `_addAsset()` and `_addFeeder()`, it casted the index of newly added asset and feeder to `uint8`. If there is more than 256 feeders for example, its index will be overflow and return incorrect value.
```solidity
function _addAsset(address _asset)
    internal
    onlyWhenAssetNotExisted(_asset)
{
    assetFeederMap[_asset].registered = true;
    assets.push(_asset);
    assetFeederMap[_asset].index = uint8(assets.length - 1); // @audit unsafe cast
    emit AssetAdded(_asset);
}

function _addFeeder(address _feeder)
    internal
    onlyWhenFeederNotExisted(_feeder)
{
    feeders.push(_feeder);
    feederPositionMap[_feeder].index = uint8(feeders.length - 1); // @audit unsafe cast
    feederPositionMap[_feeder].registered = true;
    _setupRole(UPDATER_ROLE, _feeder);
    emit FeederAdded(_feeder);
}
```

## Proof of Concept

Consider the scenario
1. NFTPriceOracle already had 256 feeders so last feeder index now is `255`
2. Admin add new feeder, its index will be overflow and become `0`.
3. Now if admin remove last feeder, it will actually remove the first feeder, because first feeder is actually the feeder with index `0`.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider using SafeCast library from OpenZeppelin instead https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol
Wrong logic in function `_removeFeeder()` cause feeders unable to be removed.
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L332


# Vulnerability details

## Impact
In NFTFloorPrice, there are multiple components to allow easy adding/removing keepers
- address[] public feeders - List of all keepers' address
- mapping(address => FeederPosition{index, register}) private feederPositionMap

When adding new keeper, new keeper address is pushed to `feeders` and its index is stored in `feederPositionMap`. Its index is used to find position of keeper in the list to easily delete.
```solidity
function _addFeeder(address _feeder)
    internal
    onlyWhenFeederNotExisted(_feeder)
{
    feeders.push(_feeder);
    feederPositionMap[_feeder].index = uint8(feeders.length - 1);
    feederPositionMap[_feeder].registered = true;
    _setupRole(UPDATER_ROLE, _feeder);
    emit FeederAdded(_feeder);
}
```

When removing a keeper, its will use `feederPositionMap` to find its position, swap it with the last element in the `feeders` list, and pop the last element.
```solidity
function _removeFeeder(address _feeder)
    internal
    onlyWhenFeederExisted(_feeder)
{
    uint8 feederIndex = feederPositionMap[_feeder].index;
    if (feederIndex >= 0 && feeders[feederIndex] == _feeder) {
        feeders[feederIndex] = feeders[feeders.length - 1]; 
        // @audit did not update index for last feeder.
        feeders.pop();
    }
    delete feederPositionMap[_feeder];
    revokeRole(UPDATER_ROLE, _feeder);
    emit FeederRemoved(_feeder);
}
```

However, function `_removeFeeder()` did not update the index for the last feeder - the one replaced position of removed keeper.

The result is even last keeper had been moved to different position in the list, it still kept the same index. 

## Proof of Concept

There is 2 cases after admin `_removeFeeder()` and feeders' index is in wrong state:

1. Admin add new keeper, new keeper index will be the same as the wrong-index keeper. Now removing keeper A can result in keeper B removed.
2. Admin remove the wrong-index keeper by using `_removeFeeder()`, it will point to wrong index and result in TX reverted.

So the more admin trying to fix this onchain, the more the keepers be indexed wrongly. 

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider updating correct index for the last keeper in `_removeKeeper()` function.
Attacker can manipulate low TVL Uniswap V3 pool to borrow and swap to make Lending Pool in loss.
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/UniswapV3OracleWrapper.sol#L176


# Vulnerability details

## Impact

In Paraspace protocol, any Uniswap V3 position that are consist of ERC20 tokens that Paraspace support can be used as collateral to borrow funds from Paraspace pool. The value of the Uniswap V3 position will be sum of value of ERC20 tokens in it.

```solidity
function getTokenPrice(uint256 tokenId) public view returns (uint256) {
    UinswapV3PositionData memory positionData = getOnchainPositionData(
        tokenId
    );

    PairOracleData memory oracleData = _getOracleData(positionData);

    (uint256 liquidityAmount0, uint256 liquidityAmount1) = LiquidityAmounts
        .getAmountsForLiquidity(
            oracleData.sqrtPriceX96,
            TickMath.getSqrtRatioAtTick(positionData.tickLower),
            TickMath.getSqrtRatioAtTick(positionData.tickUpper),
            positionData.liquidity
        );

    (
        uint256 feeAmount0,
        uint256 feeAmount1
    ) = getLpFeeAmountFromPositionData(positionData);

    return // @audit can be easily manipulated with low TVL pool
        (((liquidityAmount0 + feeAmount0) * oracleData.token0Price) /
            10**oracleData.token0Decimal) +
        (((liquidityAmount1 + feeAmount1) * oracleData.token1Price) /
            10**oracleData.token1Decimal);
}
```


However, Uniswap V3 can have multiple pools for the **same pairs** of ERC20 tokens with **different fee** params. A fews has most the liquidity, while other pools have extremely little TVL or even not created yet. Attackers can abuse it, create low TVL pool where liquidity in this pool mostly (or fully) belong to attacker’s position, deposit this position as collateral and borrow token in Paraspace pool, swap to make the original position reduce the original value and cause Paraspace pool in loss.

## Proof of Concept

Consider the scenario where WETH and DAI are supported as collateral in Paraspace protocol. 
1. Alice (attacker) create a new WETH/DAI pool in Uniswap V3 and add liquidity with the following amount
`1e18 wei WETH - 1e6 wei DAI = 1 WETH - 1e-12 DAI ~= 1 ETH`
Let's just assume Alice position has price range from [MIN_TICK, MAX_TICK] so the math can be approximately like Uniswap V2 - constant product. 
Note that this pool only has liquidity from Alice. 
2. Alice deposit this position into Paraspace, value of this position is approximately `1 WETH` and Alice borrow maximum possible amount of USDC.
3. Alice make swap in her WETH/DAI pool in Uniswap V3 to make the position become
`1e6 wei WETH - 1e18 wei DAI = 1e-12 WETH - 1 DAI ~= 1 DAI`

Please note that the math I've done above is approximation based on Uniswap V2 formula `x * y = k` because Alice provided liquidity from MIN_TICK to MAX_TICK. 
For more information about Uniswap V3 formula, please check their whitepaper here: https://uniswap.org/whitepaper-v3.pdf
[NAZ-H2] Update Initializer Modifier To Prevent Reentrancy During Initialization
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/NFTFloorOracle.sol#L101
https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/protocol/pool/PoolCore.sol#L78
https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/protocol/tokenization/NToken.sol#L59
https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/protocol/tokenization/NTokenApeStaking.sol#L43
https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/ui/WPunkGateway.sol#L57


# Vulnerability details

## Impact
Currently the project uses both : `"@openzeppelin/contracts": "4.2.0", "@openzeppelin/contracts-upgradeable": "4.2.0"`.

This dependency has a known high severity vulnerability [Deserialization of Untrusted Data](https://security.snyk.io/vuln/SNYK-JS-OPENZEPPELINCONTRACTS-2320176)

## Proof of Concept
Because of the Deserialization of Untrusted Data issue and the current use of  `"@openzeppelin/contracts": "4.2.0", "@openzeppelin/contracts-upgradeable": "4.2.0"` these contracts are vulnerable to Reentrancy.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Upgrade `@openzeppelin/contracts && @openzeppelin/contracts-upgradeable` to version 4.4.1 or higher.
Anyone can prevent themselves from being liquidated as long as they hold one of the supported NFTs
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L167-L169


# Vulnerability details

Contrary to what the function comments say, `removeFeeder()` is able to be called by anyone, not just the owner. By removing all feeders (i.e. floor twap price oracle keepers), a malicious user can cause all queries for the price of NFTs reliant on the `NFTFloorOracle` (all NFTs except for the UniswapV3 ones), to revert, which will cause all calls to `liquidateERC721()` to revert.

## Impact
If NFTs can't be liquidated, positions will remain open for longer than they should, and the protocol may become insolvent by the time the issue is resolved.

## Proof of Concept
The `onlyRole(DEFAULT_ADMIN_ROLE)` should have been used instead of `onlyWhenFeederExisted`...
```solidity
File: /paraspace-core/contracts/misc/NFTFloorOracle.sol   #1

165      /// @notice Allows owner to remove feeder.
166      /// @param _feeder feeder to remove
167      function removeFeeder(address _feeder)
168          external
169          onlyWhenFeederExisted(_feeder)
170      {
171          _removeFeeder(_feeder);
172:     }
```
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L165-L172

... since `onlyWhenFeederExisted` is already on the internal call to `_removeFeeder()` (`onlyWhenFeederExisted` doesn't do any authentication of the caller):
```solidity
File: /paraspace-core/contracts/misc/NFTFloorOracle.sol   #2

326      function _removeFeeder(address _feeder)
327          internal
328          onlyWhenFeederExisted(_feeder)
329      {
330          uint8 feederIndex = feederPositionMap[_feeder].index;
331          if (feederIndex >= 0 && feeders[feederIndex] == _feeder) {
332              feeders[feederIndex] = feeders[feeders.length - 1];
333              feeders.pop();
334          }
335          delete feederPositionMap[_feeder];
336          revokeRole(UPDATER_ROLE, _feeder);
337          emit FeederRemoved(_feeder);
338:     }
```
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/misc/NFTFloorOracle.sol#L326-L338

Note that `feeders` must have the `UPDATER_ROLE` (revoked above) in order to update the price.

The fetching of the price will revert if the price is stale:
```solidity
File: /paraspace-core/contracts/misc/NFTFloorOracle.sol   #3

234      /// @param _asset The nft contract
235      /// @return price The most recent price on chain
236      function getPrice(address _asset)
237          external
238          view
239          override
240          returns (uint256 price)
241      {
242          uint256 updatedAt = assetPriceMap[_asset].updatedAt;
243          require(
244 @>           (block.number - updatedAt) <= config.expirationPeriod,
245              "NFTOracle: asset price expired"
246          );
[NAZ-H1] Anyone Can Call `removeFeeder()`
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/NFTFloorOracle.sol#L167


# Vulnerability details

## Impact
Currently anyone can call `removeFeeder()` which can cause an ample amount of issues across the protocol. It is also stated in the NatSpec:
> Allows owner to remove feeder.

## Proof of Concept
Anyone can remove a feeder and only the admin should be able to remove a feeder.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider adding the modifier `onlyRole()` checking if the user calling is the `DEFAULT_ADMIN_ROLE`.