xanoutas/solidity-security-findings huggingface dataset:
NFTFloorOracle trust feeder without any fallback
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#L191-L216


# Vulnerability details

## Impact

In the current design, NFTFloorOracle take the median of valid feeder price as the oracle price. This design can be dangerous if more than half (as few as 2) of the feeders decided to collude and publish malicious prices. If they set the price higher, they will be able to drain the pool by borrowing everything; If they set the price lower, they will be able to liquidate users. 

## Proof of Concept

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

```solidity
    /// @notice Allows updater to set new price on PriceInformation and updates the
    /// internal Median cumulativePrice.
    /// @param _asset The nft contract to set a floor price for
    /// @param _twap The last floor twap
    function setPrice(address _asset, uint256 _twap)
        public
        onlyRole(UPDATER_ROLE)
        onlyWhenAssetExisted(_asset)
        whenNotPaused(_asset)
    {
        bool dataValidity = false;
        if (hasRole(DEFAULT_ADMIN_ROLE, msg.sender)) {
            _finalizePrice(_asset, _twap);
            return;
        }
        dataValidity = _checkValidity(_asset, _twap);
        require(dataValidity, "NFTOracle: invalid price data");
        // add price to raw feeder storage
        _addRawValue(_asset, _twap);
        uint256 medianPrice;
        // set twap price only when median value is valid
        (dataValidity, medianPrice) = _combine(_asset, _twap);
        if (dataValidity) {
            _finalizePrice(_asset, medianPrice);
        }
    }
```

## Recommended Mitigation Steps

Add another source of price as fallback to make sure the returned price is within a reasonable range. Preferably something that is based on actual bid / ask of the NFT collection.
Decimals of the oracle is not checked
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Decimals of the Chainlink oracle is not checked, it is assumed to have the same unit as BASE_CURRENCY_UNIT but it might not always be the case. If the oracle use a different number of decimals all the price will be wrong by magnitudes. 

## Proof of Concept

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

```solidity
    function getAssetPrice(address asset)
        public
        view
        override
        returns (uint256)
    {
        if (asset == BASE_CURRENCY) {
            return BASE_CURRENCY_UNIT;
        }

        uint256 price = 0;
        IEACAggregatorProxy source = IEACAggregatorProxy(assetsSources[asset]);
        if (address(source) != address(0)) {
            price = uint256(source.latestAnswer());
        }
        if (price == 0 && address(_fallbackOracle) != address(0)) {
            price = _fallbackOracle.getAssetPrice(asset);
        }

        require(price != 0, Errors.ORACLE_PRICE_NOT_READY);
        return price;
    }
```

## Recommended Mitigation Steps

Check the decimal when setting oracle
Dangerous casting from i256 to u256 of the price returned by the Chainlink oracle
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/ParaSpaceOracle.sol#L128
https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/ParaSpaceOracle.sol#L130-L132


# Vulnerability details

## Impact
Referring to the [docs](https://docs.chain.link/data-feeds/price-feeds/api-reference), Chainlink oracles are returning the price as an `int256`, which means that the answer can be a negative price.
Later, this price is [casted as an `uint256`](https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/misc/ParaSpaceOracle.sol#L128) in the case of an oracle that is set, which overflows when `price < 0`.
Any price returned that would be negative, would return a over-inflated price and a massive lent asset of any user which has deposited this specific token in the protocol, allowing to borrow any asset almost for free.

## Proof of Concept
See this `solidity-shell` instance showing that the answer, when negative, and casted to a `uint256`, will overflow.

```sh
🚀 Entering interactive Solidity ^0.8.17 shell (🧁:Ganache built-in). '.help' and '.exit' are your friends.
 »  int256 bad_answer = -1000;
 »  uint256 casted = uint256(bad_answer);
 »  casted
115792089237316195423570985008687907853269984665640564039457584007913129638936
```

## Tools Used
Manual inspection

## Recommended Mitigation Steps

A solution could be to instead use:

```solidity
if (price <= 0) {
  if (address(_fallbackOracle) != address(0)) {
    return _fallbackOracle.getAssetPrice(asset);
  } else {
      revert(Errors.ORACLE_PRICE_NOT_READY);
  }
} else {
  return price;
}
```
`MarketplaceLogic.sol` doesn't support ERC20 Tokens with fee on transfer
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/8b56ab655e8edf8f6d2bdc922e11eb2f33d66b6/paraspacecore/contracts/protocol/libraries/logic/SupplyLogic.sol#L104-L115
https://github.com/code-423n4/2022-11-paraspace/blob/c01a980e5d6e15b2993b912c3569ed8b5236ff33/paraspace-core/contracts/protocol/libraries/logic/BorrowLogic.sol#L190-L198
https://github.com/code-423n4/2022-11-paraspace/blob/c01a980e5d6e15b2993b912c3569ed8b5236ff33/paraspace-core/contracts/protocol/libraries/logic/LiquidationLogic.sol#L531-L540
https://github.com/code-423n4/2022-11-paraspace/blob/154c657d927c66fc8a45a9a6df881ec937323be8/paraspace-core/contracts/protocol/tokenization/NToken.sol#L158-L165
https://github.com/code-423n4/2022-11-paraspace/blob/154c657d927c66fc8a45a9a6df881ec937323be8/paraspace-core/contracts/protocol/tokenization/NToken.sol#L133


# Vulnerability details

## `MarketplaceLogic.sol` doesn't support ERC20 Tokens with fee on transfer
### Summary 
There are ERC20 tokens with transfer at fees. For checking if the transferred amount is the same as expected, code already compares balanceOf before and balanceOf after transfer. People can get confused in cases where real value doesn't match, also applications like subgraphs that uses this value won't work as expected.

### Vulnerability details
There are ERC20 tokens that charge fee for every `transfer()` / `transferFrom()`.

`MarketplaceLogic.sol.sol#executeSupply()` assumes that the received amount is the same as the transfer amount, and uses it to calculate attributions, balance amounts, etc. While the actual transferred amount can be lower for those tokens.

### Impact
Assets lost by using wrong values. Some reverts if not enough assets on the later use of the amount transferred value

### PoC
For example:
https://github.com/code-423n4/2022-11-paraspace/blob/8b56ab655e8edf8f6d2bdc922e11eb2f33d66b6/paraspacecore/contracts/protocol/libraries/logic/SupplyLogic.sol#L104-L115

- Sends in `params.amount` `100 ether`
- The token `params.asset` takes 1/100 of the value as fee
- `xTokenAddress` receives `99 ether`
- `IPToken` is called with `100 ether`
```
        IERC20(params.asset).safeTransferFrom(
            params.payer,
            reserveCache.xTokenAddress,
            params.amount
        );

        bool isFirstSupply = IPToken(reserveCache.xTokenAddress).mint(
            msg.sender,
            params.onBehalfOf,
            params.amount, //@audit minting more amount that the actual value transferred
            reserveCache.nextLiquidityIndex
        );
```
### Instances
- Affecting assets ()
https://github.com/code-423n4/2022-11-paraspace/blob/8b56ab655e8edf8f6d2bdc922e11ecb2f33d66b6/paraspace-core/contracts/protocol/libraries/logic/SupplyLogic.sol#L104-L115
https://github.com/code-423n4/2022-11-paraspace/blob/c01a980e5d6e15b2993b912c3569ed8b5236ff33/paraspace-core/contracts/protocol/libraries/logic/BorrowLogic.sol#L190-L198
https://github.com/code-423n4/2022-11-paraspace/blob/c01a980e
User can repeatedly claim airdrop tokens.
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
User can claim the airdrop tokens more than once thus the protocol will lose funds.

## Proof of Concept
`AirdropFlashClaimReceiver.sol::executeOperation` function is designed to transfer airdrop tokens to owner only once for each flash claim params, but the owner can repeatedly claim the airdrop tokens by calling `PoolCore::flashClaim` because there's no code to check if the owner has claimed the airdrop tokens or not. It only set the `airdropClaimRecords[vars.airdropKeyHash] = true`  instead of check if the `airdropKeyHash` has been used.

```solidity
 vars.airdropKeyHash = getClaimKeyHash(
            initiator,
            nftAsset,
            nftTokenIds,
            params
        );
        // @audit-issue should check the `airdropKeyHash` has been used or not.
        airdropClaimRecords[vars.airdropKeyHash] = true;

        // transfer airdrop tokens to owner
        for (
            uint256 typeIndex = 0;
            typeIndex < vars.airdropTokenTypes.length;
            typeIndex++
        ) {
        ...
       }
```

## Tools Used
manual review

## Recommended Mitigation Steps
check the `airdropKeyHash` has been used or not before airdrop tokens
```
require(!airdropClaimRecords[vars.airdropKeyHash], "airdropKeyHash has been used");
airdropClaimRecords[vars.airdropKeyHash] = true;
```
Unauthorised access to external function supplyERC721FromNToken, making it possible to transfer assets of NToken contract maliciously 
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/PoolCore.sol#L135


# Vulnerability details

## Impact
There is an external function named ```supplyERC721FromNToken``` in the ```PoolCore``` contract that has no appropriate access control for unauthorised accesses. This function is called from NTokenMoonBirds contract. Then it calls ```executeSupplyERC721FromNToken``` function in SupplyLogic contract. ```executeSupplyERC721FromNToken``` function first validates that the supply action is from NToken contract, then it calls ```executeSupplyERC721Base``` function which mints NTokens to the an address provided by the caller. 

The problem is that since there is no appropriate access control for unauthorised accesses to the ```supplyERC721FromNToken``` function, an attacker can mint any NTokens from other users’ assets (that are already transferred to the NToken contract) and transfer them to his own account. Indeed, it is possible for everybody to transfer their ERC721 assets to the NToken contract.


## Proof of Concept
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/pool/PoolCore.sol#L135

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/libraries/logic/SupplyLogic.sol#L228

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/libraries/logic/ValidationLogic.sol#L118

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/libraries/logic/SupplyLogic.sol#L134


## Recommended Mitigation Steps
For mitigating this vulnerability, it is needed to have an appropriate access control for calling ```supplyERC721FromNToken``` function. For example the access control can be based on ```msg.sender```  and checks whether it is called from an authorised location.
Frontrunning attack vector for cryptopunks
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/ui/WPunkGateway.sol#L137
https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/ui/WPunkGateway.sol#L175


# Vulnerability details

## Impact
Theft of CryptoPunks

## Proof of Concept
The following functions (`WPunkGateway.sol#acceptBidWithCredit()` and `WPunkGateway.sol#batchAcceptBidWithCredit()`) for cryptopunks are frontrunnable:
```
paraspace-core/contracts/ui/WPunkGateway.sol:
  129      function acceptBidWithCredit(
  130          bytes32 marketplaceId,
  131          bytes calldata payload,
  132          DataTypes.Credit calldata credit,
  133          uint256[] calldata punkIndexes,
  134          uint16 referralCode
  135      ) external nonReentrant {
  136          for (uint256 i = 0; i < punkIndexes.length; i++) {
  137:             Punk.buyPunk(punkIndexes[i]); //@audit front-runnable
  138              Punk.transferPunk(proxy, punkIndexes[i]);
  139              // gatewayProxy is the sender of this function, not the original gateway
  140              WPunk.mint(punkIndexes[i]);
  141  
  142              IERC721(wpunk).safeTransferFrom(
  143                  address(this),
  144                  msg.sender,
  145                  punkIndexes[i]
  146              );
  147          }
...
  167      function batchAcceptBidWithCredit(
  168          bytes32[] calldata marketplaceIds,
  169          bytes[] calldata payloads,
  170          DataTypes.Credit[] calldata credits,
  171          uint256[] calldata punkIndexes,
  172          uint16 referralCode
  173      ) external nonReentrant {
  174          for (uint256 i = 0; i < punkIndexes.length; i++) {
  175:             Punk.buyPunk(punkIndexes[i]); //@audit front-runnable
  176              Punk.transferPunk(proxy, punkIndexes[i]);
  177              // gatewayProxy is the sender of this function, not the original gateway
  178              WPunk.mint(punkIndexes[i]);
  179  
  180              IERC721(wpunk).safeTransferFrom(
  181                  address(this),
  182                  msg.sender,
  183                  punkIndexes[i]
  184              );
  185          }
...
```

Extract from: [NFTX v2 PUNK Incident Post Mortem](https://blog.nftx.io/nftx-v2-punk-incident-post-mortem/)
> CryptoPunks, being rather early code before the ERC721 NFT standard, does not support normal approvals. They are performed by “listing” a CryptoPunk for sale to a specific address at a specific price (0 ETH). An approval for a contract to use a CryptoPunk is similar to “listing Punk #2550 for sale at 0 ETH to address nftx.eth”.
> However, even though the sale is approved for sale to a specific address, we did not verify that the transaction caller was the owner of the CryptoPunk, allowing anyone to execute that sale. So the moment Alex approved his CryptoPunks for “sale” and tried to mint, he
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/package.json#L31-L32
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/PoolConfigurator.sol#L76
https://github.com/code-423n4/2022-11-paraspace/blob/main/paraspace-core/contracts/ui/WPunkGateway.sol#L57


# Vulnerability details

## Impact
Can cause REENTRANCY or some other bugs due to old libraries.

## Proof of Concept

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

The solution uses:

    "@openzeppelin/contracts": "4.2.0",
    "@openzeppelin/contracts-upgradeable": "4.2.0",

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:

       contracts/protocol/pool/PoolCore.sol
       75.  function initialize(IPoolAddressesProvider provider)
        external
        virtual
        initializer
    {

       contracts/misc/NFTFloorOracle.sol

      101.   function initialize(
        address _admin,
        address[] memory _feeders,
        address[] memory _assets
    ) public initializer {

          contracts/protocol/pool/PoolConfigurator.sol
    76. function initialize(IPoolAddressesProvider provider) external initializer {


        contracts/ui/WPunkGateway.sol
       57. function initialize() external initializer {



## Tools Used

  manual review 

## Recommended Mitigation Steps

Upgrade @openzeppelin/contracts and @openzeppelin/contracts-upgradeable to version 4.4.1 or higher.
NFTFloorOracle Might Return Stale Price
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#L351


# Vulnerability details

## Proof of Concept

During initialization, the `MAX_DEVIATION_RATE` is set to `150`. This means that price is rejected if it increases or decreases 1.5 times more than the original value

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

```solidity
File: NFTFloorOracle.sol
08: //we need to deploy 3 oracles at least
09: uint8 constant MIN_ORACLES_NUM = 3;
10: //expirationPeriod at least the interval of client to feed data(currently 6h=21600s/12=1800 in mainnet)
11: //we do not accept price lags behind to much
12: uint128 constant EXPIRATION_PERIOD = 1800;
13: //reject when price increase/decrease 1.5 times more than original value
14: uint128 constant MAX_DEVIATION_RATE = 150;
```

Within the `NFTFloorOracle._checkValidity`, it will verify that the new price is within the acceptable deviation in Line 365-372 below.

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

```solidity
File: NFTFloorOracle.sol
351:     function _checkValidity(address _asset, uint256 _twap)
352:         internal
353:         view
354:         returns (bool)
355:     {
356:         require(_twap > 0, "NFTOracle: price should be more than 0");
357:         PriceInformation memory assetPriceMapEntry = assetPriceMap[_asset];
358:         uint256 _priorTwap = assetPriceMapEntry.twap;
359:         uint256 _updatedAt = assetPriceMapEntry.updatedAt;
360:         uint256 priceDeviation;
361:         //first price is always valid
362:         if (_priorTwap == 0 || _updatedAt == 0) {
363:             return true;
364:         }
365:         priceDeviation = _twap > _priorTwap
366:             ? (_twap * 100) / _priorTwap
367:             : (_priorTwap * 100) / _twap;
368: 
369:         // config maxPriceDeviation as multiple directly(not percent) for simplicity
370:         if (priceDeviation >= config.maxPriceDeviation) {
371:             return false;
372:         }
373:         return true;
374:     }
```

However, in the event of a sudden fall in price, the latest price submitted by all feeders might get rejected if all feeders provide the actual market price too quickly. At this point, the price within the NFTFloorOracle will be stale and cannot be updated by the feeders.

Assume there are three feeders within NFTFloorOracle, and LUNANFT is a supported asset/NFT within ParaSpace. The current price for LUNANFT is `$1` within NFTFloorOracle.

Due to a black swan event, LUNANFT crashes to `$0.01` within a short period. Thus, the three feeders submit `$0.05`, `$0.1`, `$0.3`, respectively. 

However, the NFTFloorOracle will reject all the new prices submitted by feeders because it deviates from the o
Adversary can use UniswapV3 NFTs to dramatically inflate liquidation gas cost to take risk free loans
Audit: 2022-11-paraspace-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-paraspace/blob/c6820a279c64a299a783955749fdc977de8f0449/paraspace-core/contracts/protocol/libraries/logic/LiquidationLogic.sol#L286-L426


# Vulnerability details

## Impact

Liquidation costs so much gas that liquidation is never profitable

## Proof of Concept

Valuing UniswapV3 NFTs are extremely gas intensive due to the extensive number of external calls and calculations. Before a position can be liquidate the contract must perform a health check to confirm that the position is allowed to be liquidated. During this check every UniswapV3 NFT must individually be valued. Each user is currently allowed to have up to 30 UniswapV3 NFTs. With this number of UniswapV3 NFTs the gas cost to liquidate their position would be extremely high causing a liquidation to potentially cost the liquidator hundreds of dollars in gas fees. A malicious user could use this to their advantage to take out risk free loans.

According to the [Docs](https://docs.para.space/para-space/risk-framework/asset-risk) ETH has a liquidation bonus of 5%, that is the liquidator is paid 5% of the liquidation value (i.e. liquidating 100 USD would result in a 5 USD bonus).

Example:
Imagine a user deposits $1000 in ETH and immediately takes a loan for 825 USDC (max 82.5% TVL). With the liquidation bonus, if the entire loan was liquidated it would result in the liquidator receiving $41.25. They then add 30 low value UniswapV3 NFTs as collateral. Due to the large amount of gas required to value the UniswapV3 NFTs it would like cost $100+ in gas fees to liquidate. Since it is never profitable to liquidate the user now has a risk free loan.

Another way to exploit this would be to use 30 NFTs each valued at $500. This would make the max liquidation fee ~$20 for each NFT. Now as long as it cost more than $20 in gas fees the position would never be profitable to liquidate.

## Tools Used

Manual Review

## Recommended Mitigation Steps

I recommend two changes:

1. Require a minimum value for the UniswapV3 NFT when depositing (i.e. $500)
2. Reduce the number of UniswapV3 NFTs allowed (i.e. 30 => 5)