Halborn Logo

icon

NFTfi - GenArt - NFTfi


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/29/2024

Date of Engagement by: March 6th, 2024 - March 15th, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

9

Critical

0

High

0

Medium

1

Low

3

Informational

5


Introduction

NFTfi engaged Halborn to conduct a security assessment on their smart contracts beginning on 02/06/2024 and ending on 02/20/2024. The security assessment was scoped to the smart contracts provided in the NFTfi-Genesis/eth.gen-art GitHub repository. Commit hashes and further details can be found in the Scope section of this report.

Assessment Summary

Halborn was provided 1.5 weeks for the engagement and assigned 1 full-time security engineer to review the security of the smart contracts in scope. The engineer is a blockchain and smart contract security expert with advanced penetration testing and smart contract hacking skills, and deep knowledge of multiple blockchain protocols.

The purpose of the assessment is to:

  • Identify potential security issues within the smart contracts.

  • Ensure that smart contract functionality operates as intended.

In summary, Halborn identified some issues that were mostly addressed by the NFTfi Team.

Test Approach and Methodology

Halborn performed a combination of manual and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of this assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of the code and can quickly identify items that do not follow the security best practices. The following phases and associated tools were used during the assessment:

  • Research into architecture and purpose.

  • Smart contract manual code review and walkthrough.

  • Graphing out functionality and contract logic/connectivity/functions (solgraph).

  • Manual assessment of use and safety for the critical Solidity variables and functions in scope to identify any arithmetic related vulnerability classes.

  • Manual testing by custom scripts.

  • Static Analysis of security for scoped contract, and imported functions (slither).

  • Testnet deployment (Foundry).

Out-of-scope

  • External libraries and financial-related attacks.

  • New features/implementations after/with the remediation commit IDs.

1. RISK METHODOLOGY

Every vulnerability and issue observed by Halborn is ranked based on two sets of Metrics and a Severity Coefficient. This system is inspired by the industry standard Common Vulnerability Scoring System.
The two Metric sets are: Exploitability and Impact. Exploitability captures the ease and technical means by which vulnerabilities can be exploited and Impact describes the consequences of a successful exploit.
The Severity Coefficients is designed to further refine the accuracy of the ranking with two factors: Reversibility and Scope. These capture the impact of the vulnerability on the environment as well as the number of users and smart contracts affected.
The final score is a value between 0-10 rounded up to 1 decimal place and 10 corresponding to the highest security risk. This provides an objective and accurate rating of the severity of security vulnerabilities in smart contracts.
The system is designed to assist in identifying and prioritizing vulnerabilities based on their level of risk to address the most critical issues in a timely manner.

1.1 EXPLOITABILITY

Attack Origin (AO):
Captures whether the attack requires compromising a specific account.
Attack Cost (AC):
Captures the cost of exploiting the vulnerability incurred by the attacker relative to sending a single transaction on the relevant blockchain. Includes but is not limited to financial and computational cost.
Attack Complexity (AX):
Describes the conditions beyond the attacker’s control that must exist in order to exploit the vulnerability. Includes but is not limited to macro situation, available third-party liquidity and regulatory challenges.
Metrics:
EXPLOITABILIY METRIC (mem_e)METRIC VALUENUMERICAL VALUE
Attack Origin (AO)Arbitrary (AO:A)
Specific (AO:S)
1
0.2
Attack Cost (AC)Low (AC:L)
Medium (AC:M)
High (AC:H)
1
0.67
0.33
Attack Complexity (AX)Low (AX:L)
Medium (AX:M)
High (AX:H)
1
0.67
0.33
Exploitability EE is calculated using the following formula:

E=meE = \prod m_e

1.2 IMPACT

Confidentiality (C):
Measures the impact to the confidentiality of the information resources managed by the contract due to a successfully exploited vulnerability. Confidentiality refers to limiting access to authorized users only.
Integrity (I):
Measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of data stored and/or processed on-chain. Integrity impact directly affecting Deposit or Yield records is excluded.
Availability (A):
Measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability. This metric refers to smart contract features and functionality, not state. Availability impact directly affecting Deposit or Yield is excluded.
Deposit (D):
Measures the impact to the deposits made to the contract by either users or owners.
Yield (Y):
Measures the impact to the yield generated by the contract for either users or owners.
Metrics:
IMPACT METRIC (mIm_I)METRIC VALUENUMERICAL VALUE
Confidentiality (C)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Integrity (I)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Availability (A)None (A:N)
Low (A:L)
Medium (A:M)
High (A:H)
Critical (A:C)
0
0.25
0.5
0.75
1
Deposit (D)None (D:N)
Low (D:L)
Medium (D:M)
High (D:H)
Critical (D:C)
0
0.25
0.5
0.75
1
Yield (Y)None (Y:N)
Low (Y:L)
Medium (Y:M)
High (Y:H)
Critical (Y:C)
0
0.25
0.5
0.75
1
Impact II is calculated using the following formula:

I=max(mI)+mImax(mI)4I = max(m_I) + \frac{\sum{m_I} - max(m_I)}{4}

1.3 SEVERITY COEFFICIENT

Reversibility (R):
Describes the share of the exploited vulnerability effects that can be reversed. For upgradeable contracts, assume the contract private key is available.
Scope (S):
Captures whether a vulnerability in one vulnerable contract impacts resources in other contracts.
Metrics:
SEVERITY COEFFICIENT (CC)COEFFICIENT VALUENUMERICAL VALUE
Reversibility (rr)None (R:N)
Partial (R:P)
Full (R:F)
1
0.5
0.25
Scope (ss)Changed (S:C)
Unchanged (S:U)
1.25
1
Severity Coefficient CC is obtained by the following product:

C=rsC = rs

The Vulnerability Severity Score SS is obtained by:

S=min(10,EIC10)S = min(10, EIC * 10)

The score is rounded up to 1 decimal places.
SeverityScore Value Range
Critical9 - 10
High7 - 8.9
Medium4.5 - 6.9
Low2 - 4.4
Informational0 - 1.9

2. SCOPE

Files and Repository
(a) Repository: eth.gen-art
(b) Assessed Commit ID: 0a909c8
(c) Items in scope:
  • contracts/Collection.sol
  • contracts/CollectionFactory.sol
  • contracts/MerkleDistributor.sol
Out-of-Scope: contracts/utils/Ownable.sol
Remediation Commit ID:
Out-of-Scope: New features/implementations after the remediation commit IDs.

3. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

1

Low

3

Informational

5

Security analysisRisk levelRemediation Date
Locked EtherMediumSolved - 03/19/2024
Predictable sources of randomnessLowRisk Accepted
Outdated dependency - ERC721ALowSolved - 03/19/2024
Function name typo and conditional logic divergenceLowSolved - 03/19/2024
On-chain state can be used as attack vector to off-chain componentsInformationalAcknowledged
New opcodes are not supported by all chains (solidity version >= 0.8.20)InformationalAcknowledged
Floating pragmaInformationalSolved - 03/19/2024
Events are missing `indexed` fieldsInformationalAcknowledged
Incorrect NatSpec in contract and constructor declarationsInformationalSolved - 03/19/2024

4. Findings & Tech Details

4.1 Locked Ether

// Medium

Description

The Collection contract inherits from ERC721A.sol, which carries payable functions on its original implementation, influenced by EIP-712. Specifically, the functions are: approve, transferFrom, safeTransferFrom(from, to, tokenId) and safeTransferFrom(from, to, tokenId, _data), as follows:

- ERC721A.sol (v.4.2.3) [Line: 425]

    function approve(address to, uint256 tokenId) public payable virtual override {

- ERC721A.sol (v.4.2.3) [Lines: 540-544]

    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public payable virtual override {

- ERC721A.sol (v.4.2.3) [Lines: 616-620]

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) public payable virtual override {

- ERC721A.sol (v.4.2.3) [Lines: 629-634]

    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes memory _data
    ) public payable virtual override {

While it keeps consistency with the original EIP-712 proposal, where the function approve is indeed marked as payable, the contract set under analysis does not seem to handle ether because the mint functionality relies on valid merkle proofs provided by users in order to perform the operation.

Given this information, we can conclude that users could send ETH (native tokens) to the contract mistakenly when interacting with the aforementioned functions. In this scenario, ETH would be locked permanently into the contract because neither ERC721A.sol nor Collection.sol contracts contain a withdraw method to move funds out of the contract.

Proof of Concept

After successfully minting the correct amount of tokens, by providing a valid merkle proof, the owner of the NFT is able to call the approve function in the `ERC721A.sol` contract, which is one of the four payable functions inherited by the Collection.sol contract.

Proof of Concept:

    function test_merkle_mint_and_approve_with_value() public {

        console.log(unicode"🔮", StdStyle.blue("test_merkle_mint\n\n"));

        /**
        1. set merkle proofs
         */

        bytes32[] memory morpheus_proof = new bytes32[](1);
        morpheus_proof[0] = 0x42069d1d9a5a0fbead4b4887d4bb3b204fa18c5cf54215bb92d5d12c143ffcf0;

        bytes32[] memory niobe_proof = new bytes32[](1);
        niobe_proof[0] = 0x7e585b6365b68eda89a77d89440802bf1191ef6075bd01e23d4d10d5b420a726;
        
        uint256 morpheus_quantity = 10;
        uint8 morpheus_index = 0;

        uint256 niobe_quantity = 20;
        uint8 niobe_index = 1;

        /**
        2. mint in Collection contract
         */
        
        // Morpheus mints with merkle proof
        vm.startPrank(morpheus);
        (bool success_mint, ) = address(collection).call{value: 0}(abi.encodeWithSelector(Collection.mint.selector, morpheus_quantity, morpheus_proof, morpheus_index));
        require(success_mint, "Call Reverted");

        // as NFT owner, Morpheus call approve
        (bool success_approve, ) = address(collection).call{value: 50_000 ether}(abi.encodeWithSelector(ERC721A.approve.selector, niobe, 0));
        require(success_approve, "Call Reverted");

    }

Traces:

Traces:
  [401759] NFTFI_GenNFT_Test::test_merkle_mint_and_approve_with_value()
    ├─ [0] console::log("🔮", "\u{1b}[94mtest_merkle_mint\n\n\u{1b}[0m") [staticcall]
    │   └─ ← ()
    ├─ [0] VM::startPrank(0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf)
    │   └─ ← ()
    ├─ [351294] 0x0A2035683FE5587B0B09DB0e757306aD729FE6c1::mint(10, [0x42069d1d9a5a0fbead4b4887d4bb3b204fa18c5cf54215bb92d5d12c143ffcf0], 0)
    │   ├─ emit Claimed(_index: 0, _amount: 10, _merkleProof: [0x42069d1d9a5a0fbead4b4887d4bb3b204fa18c5cf54215bb92d5d12c143ffcf0], _account: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 0)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 1)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 2)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 3)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 4)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 5)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 6)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 7)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 8)
    │   ├─ emit Transfer(from: 0x0000000000000000000000000000000000000000, to: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, tokenId: 9)
    │   ├─ emit Mint(_startTokenId: 0, _quantity: 10, _hashes: [0xbafa5066ad891c32f53b210438633d47e5e918185e2e2d801ddd1dad11108bc3, 0xc7c4e51c0993f729c6797bdcda861bd7151ee8d01ca670597c3867afaae6e305, 0x008366da29b133f615782daa5bd80d412c368c845e9d8fcbab309edd16328a77, 0xbd01bf9d4d956483e36eb2c6bebae55dc3b28726f373510b9dfa7ef156375121, 0x5b4cad1a9547da7f20b78547401c951fd25aaf2ff5d65f388a38891e8576f536, 0xa96228a3c65d3caabb6766c545cc4cfe1357cb5e833f166a15d11c79d02aec9b, 0xd91b54a915e904e5c19718607a5ad7afb0096c4b9651d6869dc320b1ffb71116, 0xdd24dcd6e2ab679ff93f9c554bee52cf86307211d412ad61b6ff399314c26ad4, 0xdd4e0d42b3f692f6beb3b5dafc9c77cae0c13612c715ff118fb8707d43133a50, 0x84cc4d5241f3f1ab3d37cb313452cea7bcb5132cda465ac6ba9a1cd9d9a7ed29], _owner: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf)
    │   └─ ← ()
    ├─ [25046] 0x0A2035683FE5587B0B09DB0e757306aD729FE6c1::approve{value: 50000000000000000000000}(0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528, 0)
    │   ├─ emit Approval(owner: 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf, approved: 0x4CCeBa2d7D2B4fdcE4304d3e09a1fea9fbEb1528, tokenId: 0)
    │   └─ ← ()
    └─ ← ()
BVSS
Recommendation

It is recommended to create a withdraw function with access control, allowing authorized parties and/or administrators to perform safe withdrawals from any remaining ether existent in the contract. For enhanced transparency, an event can also be emitted in these cases.

Code example:

- Error declaration:

    error ZeroBalance();

- Event declaration:

    event AdminWithdrawal(uint256 indexed amount, address indexed to);

- Function declaration:

    function withdraw(address _to) external onlyOwner {
        uint256 _amount = address(this).balance;
        if (_amount == 0) revert ZeroBalance();
        emit AdminWithdrawal(_amount, _to);
        (bool success, ) = msg.sender.call{value: _amount}("");
        require (success, "Collection: Failed to withdrawal");
    }

Additionally, it is not recommended to perform function overrides or custom modifications in the base ERC721A.sol contract, for example, requiring msg.value == 0, considering it heavily relies on low-level, assembly code blocks and any modification could introduce to bugs or vulnerabilities, if not thoroughly tested and validated.


Remediation Plan

SOLVED: The NFTfi team solved the issue by implementing a suggested fix.

Remediation Hash

4.2 Predictable sources of randomness

// Low

Description

The Collection.sol contracts rely on a pseudo-random number generation technique that introduces predictability. The generation of tokenIdRandWordHash in the _mintInternal function is based on elements that are inherently predictable, such as the global variables blockhash(block.number - 1) and msg.sender, and also the incrementing startTokenId.

- contracts/Collection.sol [Lines: 108-117]

        for (; i < _quantity; ) {
            bytes32 tokenIdRandWordHash = keccak256(
                abi.encodePacked(startTokenId + i, blockhash(block.number - 1), msg.sender)
            );
            tokenGenerationParams[startTokenId + i] = tokenIdRandWordHash;
            tokenGenerationParamsEvent[i] = tokenIdRandWordHash;
            unchecked {
                ++i;
            }
        }

The tokenIdRandWordHash is constructed by hashing a combination of the previous blockhash, an incremental token ID and the msg.sender. This approach presents two primary concerns:

  • Predictability of blockhash : Utilizing the hash of the preceding block as part of the randomness source is inherently insecure. Given that the blockhash is publicly available and can be predicted by the time a transaction is mined, it does not provide a robust foundation for randomness, allowing the manipulation of tokenIdRandWordHash.

  • The other components of the hash function, namely the incremented startTokenId + i and msg.sender, are predictable within the context of a transaction: msg.sender remains unchanged, and startTokenId + i follows a sequential order.


This could potentially allow users to gain undue advantage in the NFT minting process, such as preferential access to rare or valuable attributes or traits, by anticipating the associated hashes because they are relying on a predictable source of randomness.


BVSS
Recommendation

Adopting a more robust mechanism for randomness is advisable. Employing a Verifiable Random Function (VRF) from established third-party providers, like Chainlink VRF, is recommended. This would guarantee the unpredictability and integrity of the randomness in tokenIdRandWordHash creation.


Remediation Plan

RISK ACCEPTED: The NFTfi team accepted the risk related to this issue.

Remediation Hash

4.3 Outdated dependency - ERC721A

// Low

Description

During the audit of the Collection.sol contract, it was identified that the contract inherits from ERC721A.sol version 4.2.3. However, the most recent and stable version of ERC721A.sol, according to the official GitHub repository chiru-labs/ERC721A, is version 4.3.0.

- ERC721A.sol (v.4.2.3) [Lines: 1-7]

// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

import './IERC721A.sol';

Using an outdated version can expose the contract to previously addressed vulnerabilities and deprive it of the enhanced features and optimizations introduced in the newer version.

BVSS
Recommendation

An upgrade of ERC721A.sol to version 4.3.0 is recommended to ensure access to the latest improvements and security updates.


The latest version is available in the following GitHub repository:


Remediation Plan

SOLVED: The NFTfi team solved this issue by updating the ERC721A dependency to its latest version.

Remediation Hash

4.4 Function name typo and conditional logic divergence

// Low

Description

Within the Collection.sol contract, there are two similar functions: setGeneraingScript with a typo, and setGeneratingScript, which aim to update the generatingScript variable, but they handle this task differently.

The former function takes its parameter as calldata and includes a conditional security check that prevents updates if a isGeneratingScriptLocked flag is set. On the other hand, the latter function accepts a parameter in memory and lacks this conditional security check, allowing for a broader range of updates.

- contracts/Collection.sol [Lines: 54-58]

    function setGeneraingScript(string calldata _generatingScript) external onlyOwner {
        if (isGeneratingScriptLocked) revert Locked();

        generatingScript = _generatingScript;
    }

- contracts/Collection.sol [Lines: 122-124]

    function setGeneratingScript(string memory _generatingScript) external onlyOwner {
        generatingScript = _generatingScript;
    }

The subtle differences between the two functions can make the contract harder to navigate and could lead to errors in use.


BVSS
Recommendation

Merging these functions into one streamlined version is recommended. Use the name setGeneratingScript, use calldata for the string parameter to keep gas costs lower.

Incorporating the necessary security check within this single function will ensure that script updates are safely managed. This approach simplifies the interface, enhances security, and maintains efficiency, providing a clear, unified method for script updates.

Example:

    function setGeneratingScript(string calldata _generatingScript) external onlyOwner {
        if (isGeneratingScriptLocked) revert Locked();

        generatingScript = _generatingScript;
    }

Remediation Plan

SOLVED: The NFTfi team solved the issue by implementing a suggested fix.

Remediation Hash

4.5 On-chain state can be used as attack vector to off-chain components

// Informational

Description

Users can write arbitrary _generatingScript to contracts state (`string public generatingScript`) when calling createCollection on CollectionFactory.sol contract.

- contracts/CollectionFactory.sol [Lines: 9-29]

    function createCollection(
        bytes32 _merkleRoot,
        address _admin,
        string memory _generatingScript,
        string memory _baseUri,
        uint256 _mintTimeout,
        uint256 _maxSupply
    ) external {
        Collection collectionAddress = new Collection(
            "Nftfi GenArt Collection",
            "NGAC",
            _merkleRoot,
            _admin,
            _generatingScript,
            _baseUri,
            _mintTimeout,
            _maxSupply
        );

        emit CollectionCreated(address(collectionAddress));
    }

This could, potentially, allow users to save "payloads" on-chain, through the contract's state, that could be further executed by the Front-end application and/or the web server, accordingly to the business rules and overall system infrastructure.

If those inputs aren't thoroughly validated in the off-chain premises, on-chain data (crafted payloads stored in generatingScript) can be used as an attack vector for exploring vulnerabilities in the web application, such as Stored Cross-site Scripting (Stored XSS) or HTML injection.

This finding is not directly related to a security issue. However, it's important to bear in mind that any future and unaudited releases of the Front-end application should be thoroughly tested, specially when reading user-provided, non sanitized on-chain data.

Score
Recommendation

Because of inherent design limitations within EVM and Solidity, it is not economically neither technically possible to sanitize the user inputs - in this case, possible malicious payloads - that are written on-chain when calling the createCollection function in the CollectionFactory.sol contract.

It is recommended to test thoroughly any unaudited updates in the User Interface and Front-end application, to make sure that user-provided inputs, specifically those which are read on-chain, are correctly sanitized before interpreted by the browser.


Remediation Plan

ACKNOWLEDGED: The NFTfi team acknowledged the issue.

4.6 New opcodes are not supported by all chains (solidity version >= 0.8.20)

// Informational

Description

Solc compiler version 0.8.20 switches the default target EVM version to Shanghai. The generated bytecode will include PUSH0 opcodes. The recently released Solc compiler version 0.8.25 switches the default target EVM version to Cancun, so it is also important to note that it also adds-up new opcodes such as TSTORE, TLOAD and MCOPY.

Be sure to select the appropriate EVM version in case you intend to deploy on a chain apart from mainnet like L2 chains that may not support PUSH0, TSTORE, TLOAD and/or MCOPY, otherwise deployment of your contracts will fail.

Score
Recommendation

It is important to consider the targeted deployment chains before writing immutable contracts because, in the future, there might exist a need for deploying the contracts in a network that could not support new opcodes from Shanghai or Cancun EVM versions.


Remediation Plan

ACKNOWLEDGED: The NFTfi team acknowledged the issue.

References

4.7 Floating pragma

// Informational

Description

Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively.

During the analysis, both contracts Collection.sol and CollectionFactory.sol were identified using a floating pragma, as follows:

pragma solidity ^0.8.20;
Score
Recommendation

Consider using a specific version of Solidity in your contracts instead of a wide version. For example, instead of pragma solidity ^0.8.20;, use pragma solidity 0.8.25;.


Remediation Plan

SOLVED: The NFTfi team solved the issue by implementing a suggested fix.

Remediation Hash

4.8 Events are missing `indexed` fields

// Informational

Description

Indexed event fields make the data more quickly accessible to off-chain tools that parse events, and add them to a special data structure known as “topics” instead of the data part of the log. A topic can only hold a single word (32 bytes) so if you use a reference type for an indexed argument, the Keccak-256 hash of the value is stored as a topic instead.

Each event can use up to three indexed fields. If there are fewer than three fields, all the fields can be indexed. It is important to note that each index field costs extra gas during emission, so it's not necessarily best to index the maximum allowed fields per event (three indexed fields).

This is specially recommended when gas usage is not particularly of concern for the emission of the events in question, and the benefits of querying those fields in an easier and straight-forward manner surpasses the downsides of gas usage increase.

- contracts/Collection.sol [Line: 13]

	    event Mint(uint256 indexed _startTokenId, uint256 _quantity, bytes32[] _hashes, address indexed _owner);

- contracts/CollectionFactory.sol [Line: 7]

	    event CollectionCreated(address collectionAddress);

- contracts/MerkleDistributor.sol [Line: 22]

	    event Claimed(uint256 _index, uint256 _amount, bytes32[] _merkleProof, address indexed _account);
Score
Recommendation

It is recommended to add the indexed keyword when declaring events, considering the following example:

    event Indexed(
        address indexed from,
        bytes32 indexed id,
        uint indexed value
    );

Remediation Plan

ACKNOWLEDGED: The NFTfi team acknowledged the issue.

References

4.9 Incorrect NatSpec in contract and constructor declarations

// Informational

Description

In the MerkleDistributor.sol contract, the NatSpec specifications are slightly wrong, considering they are referring to the contract TokenLock.sol, which belongs to another NFTIfi's contract set.

- contracts/MerkleDistributor.sol [Lines: 9-16]

/**
 * @title MerkleDistributor
 * @author NFTfi
 * @dev Modified version of Uniswap's MerkleDistributor
 * https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol
 * Main difference: in claim instead of transferring the tokens to the user,
 * we transfer it to the tokenLock contract
 */

- contracts/MerkleDistributor.sol [Lines: 24-28]

    /**
     * @dev Constructor initializes references for NFTfi token and TokenLock contract.
     * It also sets the owner of the contract.
     * @param _admin The initial owner of the contract, usually able to set Merkle roots.
     */

Keeping consistency across NatSpec documentation enhances contract readability and makes it easier both for developers and non-developers to understand the contract's source code.

Score
Recommendation

It is recommended to review the NatSpec specifications for any typos or misalignment.


Remediation Plan

SOLVED: The NFTfi team solved the issue by implementing a suggested fix.

Remediation Hash

5. Automated Testing

Introduction

Halborn used automated testing techniques to enhance the coverage of certain areas of the smart contracts in scope. Among the tools used was Slither, a Solidity static analysis framework. After Halborn verified the smart contracts in the repository and was able to compile them correctly into their ABIs and binary format, Slither was run against the contracts. This tool can statically verify mathematical relationships between Solidity variables to detect invalid or inconsistent usage of the contracts' APIs across the entire code-base.

The security team assessed all findings identified by the Slither software, however, findings with severity Information and Optimization are not included in the below results for the sake of report readability.

slither(01)

The findings obtained as a result of the Slither scan were reviewed, and the one related to Locked Ether was included in the report. The other findings were not included in the report because they were determined false positives.

Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.

© Halborn 2024. All rights reserved.