FNCY Chain

CertiKFiles named in the report

CertiK reviewed FNCY (FNCY Chain) on 22 Mar 2024: 43 findings, 0 critical; 30 marked fixed, 6 acknowledged, 7 open.

No open critical findings
Open the reportOpen FNCY’s security record

Report hosted on skynet.certik.com.

Trustblock does not review code.

Findings43

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

SeverityOpenAcknowledgedFixed
Critical000
High032
Medium034
Low7024
HighCentralization Related RisksAcknowledged

### ExchangeFeeInterceptor #### Owner In the contract `ExchangeFeeInterceptor`, the role `_owner` has authority over the following functions: - updateExchangeContract() - Update exchange contract address - changeCollectionConfig() - Update an existing NFT collection - recoverAsset() - Transfer funds to the owner address Any compromise to the `_owner` account may allow a hacker to take advantage of this authority to update important contract details and settings and to transfer funds out of the contract. #### Exchange In the contract `ExchangeFeeInterceptor`, the role `exchangeAddress` has authority over the following functions: - executeFundsTransfer() - Transfer funds to seller and fee recipients Any compromise to the `exchangeAddress` account may allow a hacker to take advantage of this authority to transfer funds. #### Server Signer In the contract `ExchangeFeeInterceptor`, the role `serverSigner` has authority over the following functions: - initCollection() - Create a new NFT collection Any compromise to the `serverSigner` account may allow a hacker to take advantage of this authority to create new NFT collections. ### LaunchpadFeeInterceptor In the contract `LaunchpadFeeInterceptor`, the role `_owner` has authority over the following functions: - updateLaunchpadContract() - Update launchpad contract address - changeLaunchpadConfig() - Update an existing launchpad - recoverAsset() - Transfer funds to the owner address Any compromise to the `_owner` account may allow a hacker to take advantage of this authority to update important contract details and settings and to transfer funds out of the contract. #### Server Signer In the contract `ExchangeFeeInterceptor`, the role `serverSigner` has authority over the following functions: - initLaunchpad() - Create a new launchpad configuration Any compromise to the `serverSigner` account may allow a hacker to take advantage of this authority to create new launchpads. #### Launchpad In the contract `ExchangeFeeInterceptor`, the role `launchpadAddress` has authority over the following functions: - executeFundsTransfer() - Transfer funds to seller and fee recipients Any compromise to the `launchpadAddress` account may allow a hacker to take advantage of this authority to transfer funds. ### NFTExchange #### Owner In the contract `NFTExchange`, the role `_owner` has authority over the following functions: - pause() - Pause the contract and disable certain functions such as buying and selling - unpause() - Unpause contract and return it to full functionality - setupAdmin() - Set admin role Any compromise to the `_owner` account may allow a hacker to take advantage of this authority to set admins and pause/unpause the contract. #### Admin In the contract `NFTExchange`, the role `ADMIN_ROLE` has authority over the following functions: - setTradingConfig() - Set receiver addresses for corresponding NFT collection - setTradingFee() - Set trading fee percentages Any compromise to the `ADMIN_ROLE` account may allow a hacker to take advantage of this authority to update important setting such as receiver addresses and fee percentages. ### NFTLaunchpad #### Admin In the contract `NFTLaunchpad`, the role `ADMIN_ROLE` has authority over the following functions: - addProject() - Add a new NFT project collection - updateProject() - Update NFT project collection - setupAdmin() - Grant or revoke `ADMIN_ROLE` rights - setupSpecial() - Set up `SPECIAL_ROLE` addresses Any compromise to the `ADMIN_ROLE` account may allow a hacker to take advantage of this authority to add and update NFT projects. Due to the handling of how NFT projects are created an exploiter can also potentially cause damage by updating values such as the NFT price as well as the NFT uri value. #### Special Role In the contract `NFTLaunchpad`, the role `SPECIAL_ROLE` has authority over the following functions: - buyFromAnotherChain() - Purchase a cross-chain NFT - entryWithSpecial() - Purchase a special NFT Any compromise to the `SPECIAL_ROLE` account may allow a hacker to take advantage of this authority to mint special NFTs. #### Owner In the contract `NFTExchange`, the role `_owner` has authority over the following functions: - setLaunchpadFeeInterceptor() - Set the `launchpadFeeInterceptor` contract address Any compromise to the `_owner` account may allow a hacker to take advantage of this authority to set the `launchpadFeeInterceptor` contract address. ### TransferManagement #### Owner In the contract `TransferManagerment`, the role `Owner` has authority over the following functions: - setupRole() - Setup role for `MERCHANT_ROLE`, `SPECIAL_ROLE`, `SERVER_ROLE` - updateCollectionConfig () - Edit the configuration for the collection Any compromise to the `owner` account may allow a hacker to take advantage of this authority to set admins/roles. Also, the hacker can change the `merchant` of any existing collections to mint any amount of collection NFT for thems

HighCentralized Control of Contract UpgradeAcknowledged

Since the mentioned contracts are all implementation contracts for a proxy contract, there is the risk that the admin role of the proxy contract can update the implementation contracts without the consensus of the community. For example: **The users (sellers or buyers) of the `NFTExchange` contract can transfer and store ether or NFTs to this contract. They also will approve the contract to use their NFTs. If the admin of the proxy is compromised, the malicious admin can upgrade the contract with a malicious function that can steal the users' ether and NFTs.** **The users of the `NFTLaunchpad` contract will approve the contract to use some amounts of their `paymentToken`. If the admin of the proxy is compromised, the malicious admin can upgrade the contract with a malicious function that can steal the users' `paymentToken`.** **The merchants in the `TransferManagerment` contract will approve the contract to use some of their NFTs. If the admin of the proxy is compromised, the malicious admin can upgrade the contract with a malicious function that can steal the merchants' NFTs.** Any compromise to this admin role of proxy may allow a hacker to take advantage of this authority and change the implementation contract, which is referenced by the proxy, and add or execute potentially malicious functionality in the updated implementation contract.

HighPotential Hardcoded Token ID for NFT Minting CausAcknowledged

The functions `entryNFT()`, `buyFromAnotherChain()`, `entryWithSpecial()` contain calls to the function `project.nft.safeMint()`. As the example shows, the function will always mint the `tokenId` of `0`. ```solidity project.nft.safeMint(user, 0, ""); ``` This code will throw an error because the ID is already used in the first mint and cannot be minted again. A unique `tokenId` must be generated for every new NFT minted. However, since the `nft` contract code was not provided, we need to discuss with the team whether the contract has the necessary logic to generate unique tokens (incremental or random).

MediumPotential Inconsistency Between `_price` And `totAcknowledged

In function `_transferFee()` there is a rounding issue that could cause issues in the execution of the function. The `totalPayout` is calculated by adding up a series of calculations of the `payout` with the assumption that the sum of all `fee.feeRates` used should equal the `FEE_BASIS_POINT`. ```solidity uint256 payout = _price.mul(fee.feeRate).div(FEE_BASIS_POINT); ... totalPayout += payout; ``` In the end, due to rounding issues, the value of `totalPayout` may not be equal to the value of `_price` as originally intended. The end of the function contains a `require` statement that will fail if `_price` does not equal the amount `totalPayout`. ```solidity require(_price == totalPayout, "insufficient payouts"); ``` Suppose `_price = 10` (just to make calculations simpler) and you have 3 fees with fee rates: - `feeRate = 3333` (33.33% of the FEE_BASIS_POINT which is typically 10,000 for 100%) - `feeRate = 3333` - `feeRate = 3334` Now, when calculating the payouts: - The payout for the first fee would be: `(10 * 3333) / 10000 = 33.33` which when rounded down would be 33 - The payout for the second fee would be the same: 33 - The payout for the third fee would be: `(10 * 3334) / 10000 = 33.34` which when rounded down would be 33 Adding them together, it would get 99 as the `totalPayout`. Due to the rounding down in each individual calculation, the total `payout` is not equal to the original `_price`, even though the sum of the fee rates equals `FEE_BASIS_POINT`.

MediumPotential Inconsistent Decimal and Value of `PaymAcknowledged

In `entryNFT` function, the `paymentAmount` is checked against `project.price * count`. If `project.price` is set for a specific token, and a user pays with another token, there's no conversion or equivalence check based on the current market value or token decimals. ```solidity require((project.price * count == paymentAmount),"Invalid payment amount"); ``` ERC20 tokens can have different decimals. For example, Ether (ETH) has 18 decimals, but another token could have 6, 8, or any other number. Also, different tokens have different market values, which can fluctuate over time. A fixed project.price in one token might not equate to the same value in another token at any given time.

MediumPotential Inconsistent URI Due to Unsigned URIAcknowledged

Within the `TransferManagement` contract, the role designated as `onlySpecialMembers` has the ability to invoke the `mint` and `bulkMint` functions, which allow for the minting or transferring of NFTs, provided that the transaction is signed by the merchant. Here's an example of how the `mint` function is structured: ```solidity function mint( address collection, address to, uint256 nftId, uint256 tokenId, string memory uri, bytes calldata signature ) external onlySpecialMembers nonReentrant whenNotPaused returns (bool success) { CollectionConfig memory collectionConfig = _checkEnabledCollection(collection); bytes32 hash = _hash(collectionConfig, to, nftId, tokenId); require(_verify(collectionConfig, hash, signature), "Mint Failed Authorization"); uint256 rTokenId = 0; _checkHasMinted(collection, nftId); (success, rTokenId) = _mint(collectionConfig, to, tokenId, uri); emit Mint(collection, to, nftId, rTokenId); return success; } ``` However, it's important to note that the signature does not encompass the NFT's URI. As a result, a "special member" can enter any URI without needing the merchant's consent. This could lead to potential problems, such as the unauthorized reuse of URIs from previously minted, more valuable NFTs. We would like to engage in a discussion with the team to understand the intended functionality of the "special member" role and to assess whether it could be exploited by a malicious entity.

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 (6)

  • contracts/market/NFTLaunchpad.sol
  • contracts/market/LaunchpadFeeInterceptor.sol
  • contracts/market/TransferManagement.sol
  • contracts/base/TransferHelper.sol
  • contracts/market/NFTExchange.sol
  • contracts/market/ExchangeFeeInterceptor.sol

Part of FNCY’s security record

FNCY
Audits1 audit by 1 firmLast audit Mar 2024, by CertiK.
Critical findingsNo open critical findingsIn 1 report.
Open FNCY’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