Halborn Logo

Mini Miners - Seascape


Prepared by:

Halborn Logo

HALBORN

Last Updated 04/26/2024

Date of Engagement by: August 8th, 2022 - August 11th, 2022

Summary

0% of all REPORTED Findings have been addressed

All findings

5

Critical

0

High

0

Medium

1

Low

1

Informational

3


1. INTRODUCTION

Seascape engaged Halborn to conduct a security audit on their smart contracts beginning on August 11th, 2022 and ending on August 12th, 2022. The security assessment was scoped to the smart contract provided in the GitHub repository blocklords/miner-smartcontract

2. AUDIT SUMMARY

The team at Halborn was provided a week for the engagement and assigned two full-time security engineers to audit the security of the smart contract. The security engineers are blockchain and smart-contract security experts with advanced penetration testing, smart-contract hacking, and deep knowledge of multiple blockchain protocols.

The purpose of this audit is to:

    • Ensure that smart contract functions operate as intended

    • Identify potential security issues with the smart contracts

In summary, Halborn identified some security risks that were mostly addressed by Seascape team.

3. TEST APPROACH & 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 audit. 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 audit:

    • 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

    • Scanning of solidity files for vulnerabilities, security hotspots or bugs. (MythX)

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

    • Testnet deployment (Brownie, Remix IDE)

4. SCOPE

IN-SCOPE: The security assessment was scoped to the following smart contract:

    • MineNFTFactory.sol

    • MinerGame.sol

    • MinerNFT.sol

    • NFTTypes.sol

    • CrownsToken.sol

    • MscpToken.sol

Commit ID:

Fixed commit ID:

5. RISK METHODOLOGY

Vulnerabilities or issues observed by Halborn are ranked based on the risk assessment methodology by measuring the LIKELIHOOD of a security incident and the IMPACT should an incident occur. This framework works for communicating the characteristics and impacts of technology vulnerabilities. The quantitative model ensures repeatable and accurate measurement while enabling users to see the underlying vulnerability characteristics that were used to generate the Risk scores. For every vulnerability, a risk level will be calculated on a scale of 5 to 1 with 5 being the highest likelihood or impact.
RISK SCALE - LIKELIHOOD
  • 5 - Almost certain an incident will occur.
  • 4 - High probability of an incident occurring.
  • 3 - Potential of a security incident in the long term.
  • 2 - Low probability of an incident occurring.
  • 1 - Very unlikely issue will cause an incident.
RISK SCALE - IMPACT
  • 5 - May cause devastating and unrecoverable impact or loss.
  • 4 - May cause a significant level of impact or loss.
  • 3 - May cause a partial impact or loss to many.
  • 2 - May cause temporary impact or loss.
  • 1 - May cause minimal or un-noticeable impact.
The risk level is then calculated using a sum of these two values, creating a value of 10 to 1 with 10 being the highest level of security risk.
Critical
High
Medium
Low
Informational
  • 10 - CRITICAL
  • 9 - 8 - HIGH
  • 7 - 6 - MEDIUM
  • 5 - 4 - LOW
  • 3 - 1 - VERY LOW AND INFORMATIONAL

6. SCOPE

Out-of-Scope: New features/implementations after the remediation commit IDs.

7. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

1

Low

1

Informational

3

Impact x Likelihood

HAL-02

HAL-01

HAL-03

HAL-04

HAL-05

Security analysisRisk levelRemediation Date
SIGNATURE NONCES ARE IMPLEMENTED INCORRECTLYMedium-
REENTRANCY IN MINERGAME.EXPORTNFT FUNCTIONLow-
UNUSED STORAGE POINTER IN MINERGAME.GOLDCHANGETOKEN FUNCTIONInformational-
POSSIBLE MISUSE OF PUBLIC FUNCTIONSInformational-
STATE VARIABLES MISSING CONSTANT MODIFIERInformational-

8. Findings & Tech Details

8.1 SIGNATURE NONCES ARE IMPLEMENTED INCORRECTLY

// Medium

Description

In the MinerGame contract a nonce state variable is used to prevent signature replay attacks:

MinerGame.sol

uint256 public nonce;

MinerGame.sol

function importNft(uint256 _nftId, uint8 _v, bytes32 _r, bytes32 _s) external {
  require(_nftId > 0, "MinerGame: nft Id invalid");

  MineNFT nft = MineNFT(mineNft);
  require(nft.ownerOf(_nftId) == msg.sender, "MinerGame: Not mineNft owner");

  {
    bytes memory prefix     = "\x19Ethereum Signed Message:\n32";
    bytes32 message         = keccak256(abi.encodePacked(_nftId, msg.sender, address(this), nonce));
    bytes32 hash            = keccak256(abi.encodePacked(prefix, message));
    address recover         = ecrecover(hash, _v, _r, _s);

    require(recover == verifier, "Verification failed about stakeToken");
  }

  nft.safeTransferFrom(msg.sender, address(this), _nftId);

  nonce++;

  PlayerParams storage _player = player[msg.sender];
  _player.nftId = _nftId;
  _player.stakeTime = block.timestamp;

  mineOwners[_nftId] = msg.sender;

  emit ImportNft(msg.sender, _nftId, block.timestamp);
}

\color{black} \color{white}

MinerGame.sol

  function goldChangeToken(uint256 _gold, uint8 _v, bytes32 _r, bytes32 _s) external {
    require(_gold > 0, "MinerGame: The exchange amount must greater than zero");

    uint256 chainId;   
    assembly {
        chainId := chainid()
    }

    PlayerParams storage _player = player[msg.sender];

    {
      bytes memory prefix     = "\x19Ethereum Signed Message:\n32";
      bytes32 message         = keccak256(abi.encodePacked(_gold, msg.sender, nonce, address(this), chainId));
      bytes32 hash            = keccak256(abi.encodePacked(prefix, message));
      address recover         = ecrecover(hash, _v, _r, _s);

      require(recover == verifier, "Verification failed about stakeToken");
    }

    nonce++;

    uint256 _tokenAmount = _gold * MULTIPLIER / ratio;
    _safeTransfer(token[0], msg.sender, _tokenAmount);

    emit GoldChangeToken(msg.sender, _gold, _tokenAmount, block.timestamp);  

  }
}

\color{black} \color{white}

This nonce variable is increased every time the functions importNft() or goldChangeToken() are called. Although, the signer, does not really know the order in which the users are going to call these functions. Hence, if the backend for example generates a signature for a user and this user does not call the function right after that his signature will be invalid after someone else calls any of those functions.

Score
Impact: 2
Likelihood: 5

8.2 REENTRANCY IN MINERGAME.EXPORTNFT FUNCTION

// Low

Description

In the MinerGame contract the exportNft() function is used to "unstake" the Mine NFT:

MinerGame.sol

function exportNft(uint256 _nftId) external {
  require(mineOwners[_nftId] == msg.sender, "MinerGame: Not the owner");

  MineNFT nft = MineNFT(mineNft);
  nft.safeTransferFrom(address(this), msg.sender, _nftId);

  PlayerParams storage _player = player[msg.sender];
  delete _player.nftId;
  delete _player.stakeTime;

  delete mineOwners[_nftId];

  emit ExportNft(msg.sender, _nftId, block.timestamp);        
}

\color{black} \color{white}

As we can see above, the Mine NFT is sent back to the user with a safeTransferFrom() call. This safeTransferFrom() calls check if the receiver is a smart contract and if so, it calls the _checkOnERC721Received hook.

This passed the control flow to the receiver and opens up a reentrancy vulnerability as the user, in this case the smart contract, got the NFT but the state variables player[msg.sender] and mineOwners[_nftId] are still not deleted/updated.

Score
Impact: 2
Likelihood: 2

8.3 UNUSED STORAGE POINTER IN MINERGAME.GOLDCHANGETOKEN FUNCTION

// Informational

Description

In the MinerGame contract the goldChangeToken() creates an storage pointer to the player mapping but then it does not make any use of it:

MinerGame.sol

function goldChangeToken(uint256 _gold, uint8 _v, bytes32 _r, bytes32 _s) external {
  require(_gold > 0, "MinerGame: The exchange amount must greater than zero");

  uint256 chainId;   
  assembly {
      chainId := chainid()
  }

  PlayerParams storage _player = player[msg.sender];

  {
    bytes memory prefix     = "\x19Ethereum Signed Message:\n32";
    bytes32 message         = keccak256(abi.encodePacked(_gold, msg.sender, nonce, address(this), chainId));
    bytes32 hash            = keccak256(abi.encodePacked(prefix, message));
    address recover         = ecrecover(hash, _v, _r, _s);

    require(recover == verifier, "Verification failed about stakeToken");
  }

  nonce++;

  uint256 _tokenAmount = _gold * MULTIPLIER / ratio;
  _safeTransfer(token[0], msg.sender, _tokenAmount);

  emit GoldChangeToken(msg.sender, _gold, _tokenAmount, block.timestamp);  

}

\color{black} \color{white}

Score
Impact: 1
Likelihood: 1

8.4 POSSIBLE MISUSE OF PUBLIC FUNCTIONS

// Informational

Description

In multiple contracts there are functions marked as public but they are never directly called within the same contract or in any of their descendants:

MineNFTFactory.sol

  • mint() (MineNFTFactory.sol#34-37)
  • setNft() (MineNFTFactory.sol#42-44)
  • addAdmin() (MineNFTFactory.sol#47-50)
  • renounceAdmin() (MineNFTFactory.sol#53-56)
  • addGenerator() (MineNFTFactory.sol#85-88)
  • removeGenerator() (MineNFTFactory.sol#91-94)

MineNFT.sol

  • mint() (MineNFT.sol#35-46)
  • setOwner() (MineNFT.sol#48-50)
  • setFactory() (MineNFT.sol#52-54)
  • setBaseUri() (MineNFT.sol#56-58)

MinerGame.sol

  • withdraw() (MinerGame.sol#188-195)
  • addToken() (MinerGame.sol#198-206)
  • setScale() (MinerGame.sol#209-212)

CrownsToken.sol

  • burn() (CrownsToken.sol#96-98)
  • burnFrom() (CrownsToken.sol#111-118)
  • name() (CrownsToken.sol#127-129)
  • symbol() (CrownsToken.sol#135-137)
  • decimals() (CrownsToken.sol#148-150)
  • totalSupply() (CrownsToken.sol#155-157)
  • balanceOf() (CrownsToken.sol#162-164)
  • transfer() (CrownsToken.sol#173-176)
  • approve() (CrownsToken.sol#203-206)
  • transferFrom() (CrownsToken.sol#217-227)
  • increaseAllowance() (CrownsToken.sol#241-244)
  • decreaseAllowance() (CrownsToken.sol#260-268)

MscpToken.sol

  • burn() (MscpToken.sol#96-98)
  • burnFrom() (MscpToken.sol#111-118)
  • name() (MscpToken.sol#127-129)
  • symbol() (MscpToken.sol#135-137)
  • decimals() (MscpToken.sol#148-150)
  • totalSupply() (MscpToken.sol#155-157)
  • balanceOf() (MscpToken.sol#162-164)
  • transfer() (MscpToken.sol#173-176)
  • approve() (MscpToken.sol#203-206)
  • transferFrom() (MscpToken.sol#217-227)
  • increaseAllowance() (MscpToken.sol#241-244)
  • decreaseAllowance() (MscpToken.sol#260-268)
Score
Impact: 1
Likelihood: 1

8.5 STATE VARIABLES MISSING CONSTANT MODIFIER

// Informational

Description

State variables can be declared as constant or immutable. In both cases, the variables cannot be modified after the contract has been constructed. For constant variables, the value has to be fixed at compile-time, while for immutable, it can still be assigned at construction time. The following state variables are missing the constant modifier:

CrownsToken.sol

  • Line 30: uint256 public limitSupply = 1111111111000000000000000000; /// 1.1 billion

MscpToken.sol

  • Line 30: uint256 public limitSupply = 1111111111000000000000000000; /// 1.1 billion
Score
Impact: 1
Likelihood: 1

9. Automated Testing

STATIC ANALYSIS REPORT

Description

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.

Slither results

MineNFTFactory.sol

MinerGame.sol

MineNFT.sol

NFTTypes.sol No issues found by Slither.

CrownsToken.sol

MscpToken.sol

  • The reentrancies flagged were checked individually and are all false positives except the one mentioned in the findings.

  • No major issues found by Slither.

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.