ParaSpace (Audit #3)

CertiKFiles named in the report

CertiK reviewed ParaSpace Lending V1 (ParaSpace (Audit #3)) on 23 Dec 2022: 15 findings, 0 critical; 4 marked fixed, 1 acknowledged, 10 open.

No open critical findings
Open the reportOpen ParaSpace Lending V1’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings15

Statuses are the security firm’s own, from its report.

SeverityOpenAcknowledgedFixed
Critical000
High011
Medium001
Low1002
Low`auctionStrategyAddress` is not explicitly checkeOpen

`validateEndAuction()` requires `isAuctioned(tokenId)`, however, doesn't require ```solidity require(collateralReserve.auctionStrategyAddress != address(0), Errors.AUCTION_NOT_ENABLED); ``` Theoretically, it can be turned off via a call to `PoolConfigurator.setReserveAuctionStrategyAddress()`.

LowNo access restriction on function `initialize()`Open

The below `require` statement is invalid because the function caller can input the same address as the contract **POOL** to bypass the validation. Hence anyone can call the function `initialize()` to initialize the contract **NToken**. ```solidity=60 require(initializingPool == POOL, Errors.POOL_ADDRESSES_DO_NOT_MATCH); ```

LowNo Price Validation Between UniswapV3 And ChainLiOpen

In the function `getTokenPrice()`, the price from the ChainLink is used to calculate the amount0 and amount1 of Token0 and Token1. If the current price of ChainLink is out of the range of the position's lower and upper price, the amount0 or amount1 may be zero, which affects the price of the LP token finally. ```solidity=137 function getTokenPrice(uint256 tokenId) public view returns (uint256) { UinswapV3PositionData memory positionData = getOnchainPositionData( tokenId ); PairOracleData memory oracleData = _getOracleData(positionData);//ChainLink (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 (((liquidityAmount0 + feeAmount0) * oracleData.token0Price) / 10**oracleData.token0Decimal) + (((liquidityAmount1 + feeAmount1) * oracleData.token1Price) / 10**oracleData.token1Decimal); } ``` **Proof of concept** - Case 1: Liquidate Asset 1. A liquidator can call the function `PoolCore.liquidateERC721()` to liquidate a borrower's non-healthy asset with Health Factor below 1. 2. Before the liquidation, the function `calculateUserAccountData()` calculates the total `xToken` balance of the user in the based currency used by the price oracle function `IPriceOracleGetter(oracle).getTokenPrice()`. 3. If the price obtained from ChainLink is at the left of `positionData.tickLower`, the number of token1 obtained will be 0. The price of the LP obtained according to the L158-L161 calculation logic may be lower than the price of the LP itself, which will eventually reduce the health factor of the asset. 4. In this case, the liquidator may successfully liquidate the asset that should be healthy. - Case 2: Borrow Asset 1. A user who provided collateral can call the function `PoolCore.borrow()` to borrow the ERC20 asset. 2. Before the borrowing, the function `calculateUserAccountData()` calculates the total `xToken` balance of the user in the based currency used by the price oracle function `IPriceOracleGetter(oracle).getTokenPrice()`. 3. If the price obtained from ChainLink is within the range of `positionData.tickLower` and `positionData.tickUpper`, but `currentTick` is exactly equal to `positionData.tickLower`(the number of token1 is 0), according to the calculation logic of L158-L161 the LP price may be higher than the actual price of LP, which will eventually increase the health factor of the asset. 4. In this case, the user may successfully borrow more ERC20 assets than the collateral.

LowNot Distinguish Between ERC20 or ERC721 TokenOpen

The function `getReserveData()` is used to get the reserve data, however, there is no logical distinction between ERC20 and ERC721 assets. ```solidity=189 { DataTypes.ReserveData memory reserve = IPool( ADDRESSES_PROVIDER.getPool() ).getReserveData(asset); return ( reserve.accruedToTreasury, IERC20Detailed(reserve.xTokenAddress).totalSupply(), IERC20Detailed(reserve.variableDebtTokenAddress).totalSupply(), reserve.currentLiquidityRate, reserve.currentVariableBorrowRate, reserve.liquidityIndex, reserve.variableBorrowIndex, reserve.lastUpdateTimestamp ); } ```

LowPotential Price ManipulationOpen

Flash loans are a way to borrow large amounts of money for a certain fee. The requirement is that the loans need to be returned within the same transaction in a block. If not, the transaction will be reverted. An attacker can use the borrowed money as the initial funds for an exploit to enlarge the profit and/or manipulate the token price in the decentralized exchanges. We find that the function `getOnchainPositionData()` relies on price calculations based on the current sqrt price, meaning that they would be susceptible to flash-loan attacks by manipulating the price of given pairs to the attacker's benefit. ```solidity=71 (uint160 currentPrice, int24 currentTick, , , , , ) = pool.slot0(); ``` The result of this function could impact the results of the below functions: - getLiquidityAmount() - getLiquidityAmountFromPositionData() - getLpFeeAmount() - getLpFeeAmountFromPositionData() Two functions among them are used in the `UiPoolDataProvider.sol`. - getLiquidityAmountFromPositionData() - getLpFeeAmountFromPositionData() Although the current usage of the abovementioned functions is only on UI, we would like to remind the client to be cautious about the usage of these functions.

LowRedundant CodesOpen

The linked codes do not affect the functionality of the codebase and appear to be either remnant of test code or older functionality.

LowSafe Transfer Not Invoke `_checkOnERC721Received(Open

The function `_safeTransfer()` does not invoke {IERC721Receiver-onERC721Received} on a target address, which is against the EIP-721 standard. ```solidity=320 function _safeTransfer( address from, address to, uint256 tokenId, bytes memory ) internal virtual { _transfer(from, to, tokenId); } ```

LowThird Party DependenciesOpen

The contract is serving as the underlying entity to interact with third-party `UniswapV3`, `OpenSea`, `LooksRare`, `X2Y2`, `MoonBird`, `ChainLink`, `ApeCoinStaking`, `IEACAggregatorProxy`, `IAtomicPriceAggregator` and NFT Oracle protocols. The scope of the audit treats 3rd party entities as black boxes and assumes their functional correctness. However, in the real world, 3rd parties can be compromised and this may lead to lost or stolen assets. In addition, upgrades of 3rd parties can possibly create severe impacts, such as increasing fees of 3rd parties, migrating to new LP pools, etc. The `ParaSpace` protocol allows users to borrow assets using NFT as collateral. If NFT prices fluctuate significantly in the third-party markets, the Supplier's health factory may fluctuate as well. This is a potential risk to this protocol and to the Supplier.

LowUnchecked Value of ERC-20 `transfer()`/`transferFOpen

The linked `transfer()`/`transferFrom()` invocations do not check the return value of the function call, which should yield `true` in the case of proper ERC-20 implementation.

LowUnused Return ValueOpen

The return value of an external call is not stored in a local or state variable.

Contracts in scope

No deployed address is recorded for this audit.

The report names a repository or files. Open the report to see its scope.

Repository and files the report names (45)

  • contracts/protocol/pool/PoolMarketplace.sol
  • contracts/protocol/tokenization/NTokenApeStaking.sol
  • contracts/protocol/libraries/logic/SupplyLogic.sol
  • contracts/protocol/tokenization/NTokenBAYC.sol
  • contracts/protocol/tokenization/NToken.sol
  • contracts/protocol/libraries/logic/PoolLogic.sol
  • contracts/protocol/libraries/configuration/UserConfiguration.sol
  • contracts/protocol/tokenization/NTokenUniswapV3.sol
  • contracts/misc/NFTFloorOracle.sol
  • contracts/misc/ProtocolDataProvider.sol
  • contracts/protocol/libraries/logic/ValidationLogic.sol
  • contracts/protocol/pool/PoolApeStaking.sol
  • contracts/protocol/tokenization/NTokenMoonBirds.sol
  • contracts/protocol/pool/PoolConfigurator.sol
  • contracts/misc/marketplaces/LooksRareAdapter.sol
  • contracts/protocol/pool/PoolCore.sol
  • contracts/protocol/pool/PoolParameters.sol
  • contracts/protocol/configuration/PoolAddressesProvider.sol
  • contracts/ui/WETHGateway.sol
  • contracts/ui/WPunkGateway.sol
  • contracts/protocol/pool/DefaultReserveInterestRateStrategy.sol
  • contracts/misc/flashclaim/AirdropFlashClaimReceiver.sol
  • contracts/protocol/tokenization/libraries/MintableERC721Logic.sol
  • contracts/misc/marketplaces/SeaportAdapter.sol
  • contracts/protocol/configuration/ACLManager.sol
  • contracts/protocol/libraries/logic/AuctionLogic.sol
  • contracts/protocol/libraries/logic/ConfiguratorLogic.sol
  • contracts/protocol/pool/PoolStorage.sol
  • contracts/protocol/tokenization/libraries/ApeStakingLogic.sol
  • contracts/protocol/libraries/logic/LiquidationLogic.sol
  • contracts/misc/flashclaim/UserFlashclaimRegistry.sol
  • contracts/protocol/libraries/configuration/ReserveConfiguration.sol
  • contracts/misc/marketplaces/X2Y2Adapter.sol
  • contracts/protocol/libraries/logic/ReserveLogic.sol
  • contracts/protocol/libraries/logic/BorrowLogic.sol
  • contracts/misc/ERC721OracleWrapper.sol
  • contracts/misc/UniswapV3OracleWrapper.sol
  • contracts/protocol/configuration/PoolAddressesProviderRegistry.sol
  • contracts/protocol/tokenization/base/MintableIncentivizedERC721.sol
  • contracts/protocol/libraries/logic/FlashClaimLogic.sol
  • contracts/protocol/libraries/logic/GenericLogic.sol
  • contracts/protocol/libraries/logic/MarketplaceLogic.sol
  • contracts/protocol/pool/DefaultReserveAuctionStrategy.sol
  • contracts/misc/ParaSpaceOracle.sol
  • contracts/protocol/tokenization/NTokenMAYC.sol

Part of ParaSpace Lending V1’s security record

ParaSpace Lending V1
Audits2 audits by 1 firmLast audit Dec 2022, by CertiK.
Critical findingsNo open critical findingsAcross the 2 reports, 2 critical findings were reported. Each is marked fixed by the firm that reported it.
Open ParaSpace Lending V1’s security record

Security firm

CertiK

1,044 projects reviewed · 1,375 reports · latest May 2025

See CertiK’s audits

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us