ParaSpace Lending V1 security record

BNB Chain, Ethereumpara.spaceXGitHub

As of 3 Nov 2026, ParaSpace Lending V1 has 2 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 23 Dec 2022. Those reports list no open critical findings.

Critical findings
No open critical findings
Across the 2 reports, 2 critical findings were reported. Each is marked fixed by the firm that reported it. See the findings
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.

Audits2

Newest first

AuditSecurity firmDateScopeCritical findingsReport
ParaSpace (Audit #3)Security firm: CertiKDate: Scope: Files named in the reportNo open critical findingsOpen · skynet.certik.com
ParaSpace - NFT Money MarketSecurity firm: CertiKDate: Scope: Files named in the report2 critical findings, fixedOpen · skynet.certik.com

Findings38

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

SeverityOpenAcknowledgedFixed
Critical002
High022
Medium104
Low17010
MediumCheck-Effects-Interact Pattern Not ImplementedParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

A reentrancy attack can occur when the contract creates a function that makes an external call to another untrusted contract before resolving any effects. If the attacker can control the untrusted contract, they can make a recursive call back to the original function, repeating interactions that would have otherwise not run after the external call resolved the effects. *** ## PoolAddressesProvider.sol ### External call(s) ```solidity=85 address oldImplementationAddress = _getProxyImplementation(id); ``` * This function call executes the following external call(s). * In `PoolAddressesProvider._getProxyImplementation`, * `InitializableImmutableAdminUpgradeabilityProxy(payableProxyAddress).implementation()` ```solidity=86 _updateImpl(id, newImplementationAddress); ``` * This function call executes the following external call(s). * In `PoolAddressesProvider._updateImpl`, * `proxy.initialize(newAddress,params)` * In `PoolAddressesProvider._updateImpl`, * `proxy.upgradeToAndCall(newAddress,params)` ### State variables written after the call(s) ```solidity=86 _updateImpl(id, newImplementationAddress); ``` * This function call executes the following assignment(s). * In `PoolAddressesProvider._updateImpl`, * `_addresses[id] = proxyAddress = address(proxy)` --- ## PoolAddressesProvider.sol ### External call(s) ```solidity=102 address oldPoolImpl = _getProxyImplementation(POOL); ``` * This function call executes the following external call(s). * In `PoolAddressesProvider._getProxyImplementation`, * `InitializableImmutableAdminUpgradeabilityProxy(payableProxyAddress).implementation()` ```solidity=103 _updateImpl(POOL, newPoolImpl); ``` * This function call executes the following external call(s). * In `PoolAddressesProvider._updateImpl`, * `proxy.initialize(newAddress,params)` * In `PoolAddressesProvider._updateImpl`, * `proxy.upgradeToAndCall(newAddress,params)` ### State variables written after the call(s) ```solidity=103 _updateImpl(POOL, newPoolImpl); ``` * This function call executes the following assignment(s). * In `PoolAddressesProvider._updateImpl`, * `_addresses[id] = proxyAddress = address(proxy)` --- ## PoolAddressesProvider.sol ### External call(s) ```solidity=118 address oldPoolConfiguratorImpl = _getProxyImplementation( POOL_CONFIGURATOR ); ``` * This function call executes the following external call(s). * In `PoolAddressesProvider._getProxyImplementation`, * `InitializableImmutableAdminUpgradeabilityProxy(payableProxyAddress).implementation()` ```solidity=121 _updateImpl(POOL_CONFIGURATOR, newPoolConfiguratorImpl); ``` * This function call executes the following external call(s). * In `PoolAddressesProvider._updateImpl`, * `proxy.initialize(newAddress,params)` * In `PoolAddressesProvider._updateImpl`, * `proxy.upgradeToAndCall(newAddress,params)` ### State variables written after the call(s) ```solidity=121 _updateImpl(POOL_CONFIGURATOR, newPoolConfiguratorImpl); ``` * This function call executes the following assignment(s). * In `PoolAddressesProvider._updateImpl`, * `_addresses[id] = proxyAddress = address(proxy)` --- ## BorrowLogic.sol ### External call(s) ```solidity=99 ( isFirstBorrowing, reserveCache.nextTotalStableDebt, reserveCache.nextAvgStableBorrowRate ) = IStableDebtToken(reserveCache.stableDebtTokenAddress).mint( params.user, params.onBehalfOf, params.amount, currentStableRate ); ``` ```solidity=110 ( isFirstBorrowing, reserveCache.nextScaledVariableDebt ) = IVariableDebtToken(reserveCache.variableDebtTokenAddress).mint( params.user, params.onBehalfOf, params.amount, reserveCache.nextVariableBorrowIndex ); ``` ### State variables written after the call(s) ```solidity=125 reserve.updateInterestRates( reserveCache, params.asset, 0, params.releaseUnderlying ? params.amount : 0 ); ``` * This function call executes the following assignment(s). * In `ReserveLogic.updateInterestRates`, * `reserve.currentLiquidityRate = vars.nextLiquidityRate.toUint128()` * In `ReserveLogic.updateInterestRates`, * `reserve.currentStableBorrowRate = vars.nextStableRate.toUint128()` * In `ReserveLogic.updateInterestRates`, * `reserve.currentVariableBorrowRate = vars.nextVariableRate.toUint128()` ```solidity=125 reserve.updateInterestRates( reserveCache, params.asset, 0, params.releaseUnderlying ? params.amount : 0 ); ``` * This function call executes the following assignment(s). * In `ReserveLogic.updateInterestRates`, * `reserve.currentLiquidityRate = vars.nextLiquidityRate.toUint128()` * In `ReserveLogic.updateInterestRates`, * `reserve.currentStableBorrowRate = vars.nextStableRate.toUint128()` * In `ReserveLogic.updateInterestRates`, * `reserve.currentVariableBorrowRate = va

Open the report
Low`auctionStrategyAddress` is not explicitly checkeParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

`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()`.

Open the report
LowNo access restriction on function `initialize()`ParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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); ```

Open the report
LowNo Price Validation Between UniswapV3 And ChainLiParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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.

Open the report
LowNot Distinguish Between ERC20 or ERC721 TokenParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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 ); } ```

Open the report
LowPotential Price ManipulationParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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.

Open the report
LowRedundant CodesParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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

Open the report
LowSafe Transfer Not Invoke `_checkOnERC721Received(ParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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); } ```

Open the report
LowThird Party DependenciesParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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.

Open the report
LowUnchecked Value of ERC-20 `transfer()`/`transferFParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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.

Open the report
Show 8 more
LowUnused Return ValueParaSpace (Audit #3) · CertiK · 23 Dec 2022Open

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

Open the report
Low`initialize()` Is UnprotectedParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

The function `initialize()` is `public` and can be called by anyone as long as the contract is deployed.

Open the report
LowIncorrect CommentsParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

The title comment for `NToken.sol` is for `PToken`, not `NToken`. --- The comments for `executeBorrow()` and `executeRepay()` isolated positions are mentioned, however, there are no isolated positions in the code.

Open the report
LowLack of reasonable boundaryParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

The variables `auctionRecoveryHealthFactor`, `newReserveFactor`, `newBorrowCap`, and `newFee` do not have reasonable boundaries, so they can be given arbitrary values after deploying.

Open the report
LowPotential risks of pool establishmentParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

If multiple pools are deployed, there may be a risk of code reentrancy. The hacker can call the `withdrawERC721()` method of one of the Pools and use the hook `_checkOnERC721Received` method of the ERC721 receiver to reenter the methods(such as the method `liquidationERC721()`) of other Pools. Please check if multiple Pools are allowed to be deployed at the same time.

Open the report
LowThird Party DependenciesParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

The contract is serving as the underlying entity to interact with third-party `UniswapV3`, `OpenSea`, `X2Y2`, `MoonBird`,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.

Open the report
LowUnused Return ValueParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

The return value of an external call is not stored in a local or state variable. ```solidity=290 stableDebtToken.burn(user, stableDebt); ``` --- ```solidity=651 INToken(vars.collateralXToken).burn(params.user, msg.sender, tokenIds); ``` --- ```solidity=80 Address.functionDelegateCall( params.marketplace.adapter, abi.encodeWithSelector( IMarketplace.matchAskWithTakerBid.selector, params.marketplace.marketplace, params.payload, priceEth ) ); ``` --- ```solidity=134 Address.functionDelegateCall( params.marketplace.adapter, abi.encodeWithSelector( IMarketplace.matchBidWithTakerAsk.selector, params.marketplace.marketplace, params.payload ) ); ``` --- ```solidity=200 IERC20(token).approve(params.marketplace.operator, 0); ``` --- ```solidity=201 IERC20(token).approve(params.marketplace.operator, price); ``` --- ```solidity=353 MarketplaceLogic.executeBuyWithCredit( _reserves, _reservesList, _usersConfig[onBehalfOf], DataTypes.ExecuteMarketplaceParams({ marketplaceId: marketplaceId, payload: payload, credit: credit, ethLeft: msg.value, marketplace: marketplace, orderInfo: orderInfo, WETH: WETH, referralCode: referralCode, maxStableRateBorrowSizePercent: _maxStableRateBorrowSizePercent, reservesCount: _reservesCount, oracle: ADDRESSES_PROVIDER.getPriceOracle(), priceOracleSentinel: ADDRESSES_PROVIDER.getPriceOracleSentinel() }) ); ``` --- ```solidity=205 Address.functionCall( airdropContract, airdropParams, Errors.CALL_AIRDROP_METHOD_FAILED ); ``` --- ```solidity=107 INonfungiblePositionManager(_underlyingAsset).decreaseLiquidity( params ); ``` --- ```solidity=614 try IERC721Receiver(to).onERC721Received( _msgSender(), from, tokenId, _data ) returns (bytes4 retval) { return retval == IERC721Receiver.onERC721Received.selector; } catch (bytes memory reason) { if (reason.length == 0) { revert( "ERC721: transfer to non ERC721Receiver implementer" ); } else { assembly { revert(add(32, reason), mload(reason)) } } } ```

Open the report
LowUnused State VariableParaSpace - NFT Money Market · CertiK · 25 Oct 2022Open

One or more state variables are never used in the codebase. Variable `BORROWABLE_IN_ISOLATION_MASK` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=22 uint256 internal constant BORROWABLE_IN_ISOLATION_MASK = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFDFFFFFFFFFFFFFFF; // prettier-ignore ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `UNBACKED_MINT_CAP_MASK` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=28 uint256 internal constant UNBACKED_MINT_CAP_MASK = 0xFFFFFFFFFFF000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `DEBT_CEILING_MASK` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=29 uint256 internal constant DEBT_CEILING_MASK = 0xF0000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF; // prettier-ignore ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `BORROWABLE_IN_ISOLATION_START_BIT_POSITION` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=40 uint256 internal constant BORROWABLE_IN_ISOLATION_START_BIT_POSITION = 61; ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `IS_DYNAMIC_CONFIGS_START_BIT_POSITION` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=48 uint256 internal constant IS_DYNAMIC_CONFIGS_START_BIT_POSITION = 168; ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `UNBACKED_MINT_CAP_START_BIT_POSITION` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=49 uint256 internal constant UNBACKED_MINT_CAP_START_BIT_POSITION = 176; ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `DEBT_CEILING_START_BIT_POSITION` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=50 uint256 internal constant DEBT_CEILING_START_BIT_POSITION = 212; ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `MAX_VALID_EMODE_CATEGORY` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=60 uint256 internal constant MAX_VALID_EMODE_CATEGORY = 255; ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `MAX_VALID_UNBACKED_MINT_CAP` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=61 uint256 internal constant MAX_VALID_UNBACKED_MINT_CAP = 68719476735; ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `MAX_VALID_DEBT_CEILING` in `ReserveConfiguration` is never used in `ReserveConfiguration`. ```solidity=62 uint256 internal constant MAX_VALID_DEBT_CEILING = 1099511627775; ``` ```solidity=12 library ReserveConfiguration { ``` --- Variable `BASE_CURRENCY_DECIMALS` in `LiquidationLogic` is never used in `LiquidationLogic`. ```solidity=82 uint256 private constant BASE_CURRENCY_DECIMALS = 18; ``` ```solidity=34 library LiquidationLogic { ``` --- Variable `______gap` in `VersionedInitializable` is never used in `ATokenDebtToken`. ```solidity=78 uint256[50] private ______gap; ``` ```solidity=14 contract ATokenDebtToken is RebasingDebtToken { ``` --- Variable `_allowances` in `MintableIncentivizedERC721` is never used in `NTokenMoonBirds`. ```solidity=95 mapping(address => mapping(address => uint256)) private _allowances; ``` ```solidity=27 contract NTokenMoonBirds is NToken, IMoonBirdBase { ```

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

The repositories and files the reports name

Show this on your site

Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.

Content

Facts

Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="paraspace"
></div>
</body>

Paste the code where the label should appear. It uses no API credit and updates by itself.

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