Smart Contract Audit

MXS Games
May 21, 2024

Summary

Vidma team has conducted a smart contract audit for the given codebase.

The contracts are in good condition. Based on the fixes provided by the Ammbr team and on the quality and security of the codebase provided, Vidma team can give a score of 95 to the audited smart contracts.

During the auditing process, the Vidma team has found a couple of informational issues, 7 issues with a low level of severity, 1 issue with a medium level of severity, and 3 issues with a critical level of severity.

Severity of the issue
Total found
Resolved
Unresolved
Critical
3 issues
3 issues
0 issues
High
3 issues
3 issues
0 issues
Medium
3 issues
3 issues
0 issues
Low
3 issues
3 issues
0 issues
Informational
3 issues
3 issues
0 issues
Low
3 issues
3 issues
0 issues

The contracts are in good condition. Based on the fixes provided by the Ammbr team and on the quality and security of the codebase provided, Vidma team can give a score of 95 to the audited smart contracts.

Based on the given findings, risk level, performance, and code style, Vidma team can grant the following overall score:

Please mind that this audit does not certify the definite reliability and security level of the contract. This document describes all vulnerabilities, typos, performance issues, and security issues found by Vidma auditing team. If the code is under development, we recommend run one more audit once the code is finalized.

Summary

Vidma is pleased to present this audit report outlining our assessment of code, smart contracts, and other important audit insights and suggestions for management, developers, and users.

The security audit of the MetaXSeed smart contract revealed several vulnerabilities, most of which did not significantly impact the contract’s ability to operate. However, prompt action was taken by the client to address these concerns, implementing comprehensive fixes.

After the second review, Vidma audit team confirms that all known issues are resolved and the contract is secure and operational. The changes are reflected in this version of the report accordingly.

During the audit process, the Vidma team found several issues, including those with critical severity. A detailed summary and the current state are displayed in the table below.

Severity of the issue Issue severity
Critical
High
Medium
Low
Informational
Total
Severity of the issue Issue severity Total found Resolved Invalid Unresolved
Critical 1 issues 1 issues 0 issues 0 issues
High 0 issues 0 issues 0 issues 0 issues
Medium 3 issues 3 issues 0 issues 0 issues
Low 4 issues 4 issue 0 issue 0 issues
Informational 4 issues 4 issues 0 issues 0 issues
Total 12 issues 12 issues 0 issue 0 issues

After evaluating the findings in this report and the final state after fixes, the Vidma auditors can state that the contracts are fully operational and secure. Under the given circumstances, we set the following risk level:

High Confidence

To set the codebase quality mark, our auditors are evaluating the initial commit given for the scope of the audit and the last commit with the fixes. This approach helps us adequately and sequentially evaluate the quality of the code. Code style, optimization of the contracts, the number of issues, and risk level of the issues are all taken into consideration. The Vidma team has developed a transparent evaluation codebase quality system presented below.

Severity of the issue
Issue severity
Total found
Resolved
Critical
1
10
High
0.8
7
Medium
0.5
5
Low
0.2
0.5
Informational
0
0.1
Please note that the points are deducted out of 100 for each and every issue on the list of findings (according to the current status of the issue). Issues marked as “not valid” are not subject to point deduction.
Codebase quality:
96.70

Evaluating the initial commit and the last commit with the fixes, Vidma audit team set the following codebase quality mark.

Score
Based on the overall result of the audit and the state of the final reviewed commit, the Vidma audit team grants the following score:

96.70

In addition to manual check and static analysis, the auditing team has conducted a number of integrated autotests to ensure the given codebase has an adequate performance and security level. The test results and coverage can be found in the accompanying section of this audit report.

Please be aware that this audit does not certify the definitive reliability and security level of the contract. This document describes all vulnerabilities, typos, performance issues, and security issues found by the Vidma audit team.
If the code is still under development, we highly recommend running one more audit once the code is finalized.

Scope of work

Gaming L1 with a large library games (Mobile & PC) generating revenue from advertisements and micro transactions. Token utility includes gas fees and NFT purchase currency for all our games. Players can play any game, use the same digital assets in multiple games and be rewarded daily for the activities. Web 2.5 infrastructure to get access to 3 billion players in Google Play & Apple iOS stores.

Within the scope of this audit, two independent auditors thoroughly investigated the given codebase and analyzed the overall security and performance of the smart contracts.

The audit was conducted from May 8th to May 20th. The outcome is disclosed in this document.

The scope of work for the given audit consists of the following contracts:

  • MetaXSeeds.

The source code was taken from the following source:

https://github.com/0xShadowScripter/NFT-MetaXSeed

Initial commit submitted for the audit:

0cebd82da7580d1d3ef3f31679c399e6bec578cc

Last commit reviewed by the auditing team:

9a29849ca42a105f1e3a0f4c78a44cfa7b8b55f0

Workflow of the auditing process

Vidma audit team uses the most sophisticated and contemporary methods and well-developed techniques to ensure contracts are free of vulnerabilities and security risks. The overall workflow consists of the following phases:

Phase 1: The research phase

Research

After the Audit kick-off, our security team conducts research on the contract’s logic and expected behavior of the audited contract.

Documentation reading

Vidma auditors do a deep dive into your tech documentation with the aim of discovering all the behavior patterns of your codebase and analyzing the potential audit and testing scenarios.

The outcome

At this point, the Vidma auditors are ready to kick off the process. We set the auditing strategies and methods and are prepared to conduct the first audit part.

Phase 2: Manual part of the audit

Manual check

During the manual phase of the audit, the Vidma team manually looks through the code in order to find any security issues, typos, or discrepancies with the logic of the contract. The initial commit as stated in the agreement is taken into consideration.

Static analysis check

Static analysis tools are used to find any other vulnerabilities in smart contracts that were missed after a manual check.

The outcome

An interim report with the list of issues.

Phase 3: Testing part of the audit

Integration tests

Within the testing part, Vidma auditors run integration tests using the Truffle or Hardhat testing framework. The test coverage and the test results are inserted in the accompanying section of this audit report.

The outcome

Second interim report with the list of new issues found during the testing part of the audit process.

Structure and organization of the findings

For simplicity in reviewing the findings in this report, Vidma auditors classify  the findings in accordance with the severity level of the issues. (from most critical to least critical).

All issues are marked as “Resolved” or “Unresolved”, depending on if they have been fixed by project team or not. The issues with “Not Relevant” status are left on the list of findings but are not eligible for the score points deduction.

The latest commit with the fixes reviewed by the auditors is indicated in the “Scope of Work” section of the report.

The Vidma team always provides a detailed description of the issues and recommendations on how to fix them.

Classification of found issues is graded according to 6 levels of severity described below:

Critical
The issue affects the contract in such a way that funds may be lost or allocated incorrectly, or the issue could result in a significant loss.
Example: Underflow/overflow, precisions, locked funds.
High
The issue significantly affects the ability of the contract to compile or operate. These are potential security or operational issues.
Example: Compilation errors, pausing/unpausing of some functionality, a random value, recursion, the logic that can use all gas from block (too many iterations in the loop), no limitations for locking period, cooldown, arithmetic errors which can cause underflow, etc.
Medium
The issue slightly impacts the contract’s ability to operate by slightly hindering its intended behavior.
Example: Absence of emergency withdrawal of funds, using assert for parameter sanitization.
Low
The issue doesn’t contain operational or security risks, but are more related to optimization of the codebase.
Example: Unused variables, inappropriate function visibility (public instead of external), useless importing of SCs, misuse or disuse of constant and immutable, absent indexing of parameters in events, absent events to track important state changes, absence of getters for important variables, usage of string as a key instead of a hash, etc.
Informational
Are classified as every point that increases onboarding time and code reading, as well as the issues which have no impact on the contract’s ability to operate.
Example: Code style, NatSpec, typos, license, refactoring, naming convention (or unclear naming), layout order, functions order, lack of any type of documentation.

Manual Report

Total supply exceeds max supply

Critical TC – 01 |  Resolved

There are requirements _tokenIdCounter.current() <= maxSupply in safeMint(), lazyMint() functions. It makes possible to mint one extra token after reaching max supply.

Steps to reproduce: deploy contract with max supply value = 5, mint 5 tokens with any minting function, mint one extra token with safeMint() or lazyMint().

Recommendation:

Change requirements from _tokenIdCounter.current() <= maxSupply to _tokenIdCounter.current() < maxSupply in safeMint() and lazyMint() functions.

Re-Audit:

There is no need to change requirement in the safeMintBatch() function. It causes a new issue when max supply will not be reached with the safeMintBatch() function. Requirement changes should be reverted into:

require(
            _tokenIdCounter.current() + tokenIds.length <= maxSupply,
            "Batch mint would exceed max supply"
        );

Transaction failed with setTokenTransferabilityRange()

Medium TM – 01 |  Resolved

If some tokens from range are burned or just do not exist transaction will be failed for all tokens from range. To prevent transaction failure, nonexistent tokens may be skipped.

Recommendation:

Consider replace requirement into branching:

require(_exists(tokenId), "Token does not exist");
            transferableTokens[tokenId] = transferable;
            emit TokenTransferabilityUpdated(tokenId, transferable);

into:

		if(_exists(tokenId)) {
       transferableTokens[tokenId] = transferable;
       emit TokenTransferabilityUpdated(tokenId, transferable);
            }

The same can be used for the array in the setTokenTransferabilities() function.

Invalid checking of expiry date

Medium TM – 02 |  Resolved

There is incorrect checking of expiry parameter in the lazyMint() function. If there is value less than block.timestamp, it will work but it shouldn't.

Expiry date should be bigger than current timestamp: block.timestamp < expiry. Expiry is the date when the signature is considered invalid.

Recommendation:

Change requirement:

	require(expiry <= block.timestamp + WEEK, "Signature has expired");

into:

	require(expiry > block.timestamp && expiry <= block.timestamp + WEEK, "Signature has expired");

or:

	require(expiry > block.timestamp && expiry <= block.timestamp + 1 weeks, "Signature has expired");

and remove the WEEK variable.

The batchLazyMint() can be called only by owner

Medium TM – 03 |  Resolved

There is a lazyMint() function that can be called by anyone with a signature but the batchLazyMint() function can be called only by the owner. I suppose it also should be called by anyone, at least it is secured with a signature.

Recommendation:

Consider removing the onlyOwner modifier from the batchLazyMint() function.

Floating pragma ^0.8.2

Low ML – 01 |  Resolved

The current version of solc in contract MetaXSeed is ^0.8.2 and it is better to lock the pragma to a specific version.

Contracts should be deployed with the same compiler version and flag that they 
have been tested 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.

Recommendation:

Consider locking pragma to a specific version.

Visibility of function

Low ML – 02 |  Resolved

The functions addSigner(), setGlobalTransferability(), safeMint(), safeMintBatch(), batchLazyMint(), burn(), setTokenURI(), setTokenTransferabilityRange(), setTokenTransferabilityRange(), totalSupply(), tokensOfOwner() of MetaXSeed contract can be marked as external, it will save gas during function execution.

The lazyMint() function may also be marked as external but it is called inside another batchLazyMint() function of contract. There could be a good practice to create an internal _lazyMint() function and call it inside external lazyMint() and batchLazyMint() functions with passed parameters from these functions.

Recommendation:

Consider changing visibility to external if possible, it will bring saving gas during function execution.

Unnecessary import of ECDSA.sol

Low ML – 03 |  Resolved

MetaXSeed.sol imports ECDSA.sol from the openzeppelin library but it is already imported from another imported file - draft-EIP712.sol.

Recommendation:

Consider deleting unused import, it will save gas during deployment.

Unnecessary signature requirement

Low TL – 01 |  Resolved

There is unimportant require(signer != address(0), "Invalid signature") in the lazyMint() function. It could be missed because zero signer (address(0)) cannot be added to the contract: there is the same requirement in the addSigner() function.

Recommendation:

Consider removing the require(signer != address(0), "Invalid signature").

Order of layout

Informational  MI – 01 |  Resolved

The layout contract elements in MetaXSeed are not fully logically grouped.

The contract elements should be grouped and ordered in the following way:

  • Pragma statements;
  • Import statements;
  • Interfaces;
  • Libraries;
  • Contract.

Inside each contract, library or interface, use the following order:

  • Library declarations (using directive);
  • Type declarations;
  • State variables;
  • Events;
  • Modifiers;
  • Functions.

Ordering helps readers to navigate the code and find the elements more quickly.

Recommendation:

Consider changing the order of layout according to solidity documentation: Order of Layout.

Order of functions

Informational  MI – 02 |  Resolved

The functions in contract MetaXSeed are not grouped according to their visibility and order.

Functions should be grouped according to their visibility and ordered in the following way:

  • constructor;
  • receive function (if exists);
  • fallback function (if exists);
  • external;
  • public;
  • internal;
  • private.

Ordering helps readers navigate across the code, identify which functions they can call and find the constructor and fallback definitions easier.

Recommendation:

Consider changing functions order according to solidity documentation: Order of Functions.

Wrong naming convention

Informational  MI – 03 |  Resolved

According to the Solidity Style Guide, internal functions and variables should begin from the underscore. In the MetaXSeed contract there are: tokenIdCounter, SIGNATURE_VERSION, SIGNING_DOMAIN, LAZY_MINT_TYPEHASH, WEEK, which didn’t fit the naming convention.

Recommendation:

Consider following the Solidity Style Guide, and naming conventions.

Re-Audit:

Changes were missed for tokenIdCounter.

Function parameters orders mismatched

Informational  TI – 01 |  Resolved

There are mismatched parameters order in safeMint() and safeMintBatch() functions. It will be more comfortable if functions have logically similar parameters order.

Recommendation:

Change safeMint() function parameters order into:

	function safeMint(
        address to,
        uint256 tokenId,
        string memory tokenURI,
        bool transferable
    )

Test Results

To verify the security of the contract and the performance, a number of integration tests were carried out using the Hardhat testing framework.

In this section, we provide both tests written by MXS Games and tests written by Vidma auditors.

MXS Games Coverage – 91.43%

Vidma Coverage – 100%

Industry Standard – 95%

It is important to note that Vidma auditors do not modify, edit or add tests to the existing tests provided in the MXS Games repository. We write totally separate tests with code coverage of a minimum of 95% to meet the industry standards.

Tests written by MXS Games

Test Coverage

File
contracts\
MetaXSeeds.sol
All Files
File % Stmts % Branch % Funcs % Lines
contracts\ 91.30 63.33 84.21 89.66
MetaXSeeds.sol 91.30 63.33 84.21 89.66
All Files 91.30 63.33 84.21 89.66

Test Results

  • Batch Lazy Minting
    • salt1 Uint8Array(32) [ 34,  90, 126, 121, 236, 188, 238,   7, 166, 208, 102,  27, 211, 150, 105, 140, 236, 197,  17, 128, 137,   9, 111,  89, 9, 183, 216, 206, 166, 122, 242, 114 ]
    • salt2 Uint8Array(32) [ 175,  19, 200, 115,  85, 181,  19, 113, 223, 222, 244,  38, 115,  17, 163,  58, 59,  52, 109, 232,   7, 243, 249,  39, 158,  41, 238,  97, 172, 223,  35, 200 ]
    signature1
    • 0x13f150187df4f2ee68c4713bb8d9371f03e4c34ab65d037def53260aa 4e8309e752d1a1a7926ebf233b388ff3d519b32a23aa12a3ba87e2d4c2d 26788c02898b1b 
    signature2
    • 0xbd207f3b2574a269b22f7e16ec8a462f07353404272af621eaf656fd3 c86efe250e3cc00355ab0b1ef6a8606a631b319cf47cbda9e7cc9bb4f7c 5cc47c49cdbc1c 
    BigNumber { value: "5" }
  • should lazily mint 5 tokens with valid signatures using batchLazyMinting(394ms)
  • MetaXSeed Contract - Burn Functionality
    • should burn a token and decrement the token counter(129ms)
    • should prevent unauthorized users from burning a token(64ms)
    • MetaXSeed Contract Tests
      • Lazy Minting
        • should recognize an authorized signer(41ms)
        • should lazily mint a token with a valid signature(117ms)
        • should fail to lazily mint with an expired signature(64ms)
      • Transferability and Total Supply
    • initialSupply BigNumber { value: "1" }
    • finalSupply BigNumber { value: "2" }
      • should correctly report total supply(82ms)
    • MetaXSeed Contract
      • Ownership and Access Control Tests
        • should allow owner to add a new signer
        • should allow owner to remove a signer(65ms)
        • should prevent non-owners from adding signers
        • should prevent non-owners from removing signers
    • Minting Functionality Tests
      • should mint a token(66ms)
      • should batch mint tokens(114ms)
      • should fail to mint beyond max supply(231ms)
      • should fail to batch mint beyond max supply(199ms)
      • should allow the owner to update the token URI(81ms)
      • should prevent non-owners from updating the token URI(109ms)
    • MetaXSeed Contract
      • Global and Individual Transferability
        • Should prevent transfer if global and individual transferability are false(76ms)
        • Should allow transfer if global transferability is true(93ms)
        • Should allow transfer if individual token transferability is true even if global is false(93ms)
    • Setting Transferability Range
      • Should set transferability for a range of tokens(128ms)
21 passing(4s)

Tests written by Vidma auditors

Test Coverage

File
contracts\
MetaXSeeds.sol
All Files
File % Stmts % Branch % Funcs % Lines
contracts\ 100.00 100.00 100.00 100.00
MetaXSeeds.sol 100.00 100.00 100.00 100.00
All Files 100.00 100.00 100.00 100.00

Test Results

Contract: MetaXSeeds

  • Deployment & Initialization
    • get
      • name(45ms)
      • symbol
      • signer
      • maxSupply
  • Functions
    • addSigner
      • should add signer correctly and emit event(44ms)
      • should fail if passed signer address is zero address
      • should fail if caller is not the owner
    • removeSigner
      • should remove signer correctly and emit event(58ms)
      • should fail if passed signer address is zero address
      • should fail if caller is not the owner
    • setGlobalTransferability
      • should set global transferable variable correctly(42ms)
      • should fail if caller is not the owner
    • safeMint
      • should mint nft for user correctly and emit event(102ms)
      • should fail if caller is not the owner
      • should fail if token is already deployed with same id
      • should fail if max supply is reached(90ms)
    • safeMintBatch
      • should mint nfts for user correctly and emit event(313ms)
      • should fail if caller is not the owner(40ms)
      • should fail if passed arrays mismatched
      • should fail if max supply is reached
      • should fail if token was minted before with same id(51ms)
    • lazyMint
      • should mint nft for user correctly and emit event(143ms)
      • should fail if token is already deployed with same id(47ms)
      • should fail if max supply is reached(120ms)
      • should fail if expiry has been expired(108ms)
      • should fail if salt has already used(143ms)
      • should fail if extracted signer will be invalid(114ms)
    • verifySignature
      • should return valid signer as expected(44ms)
    • batchLazyMint
      • should mint nfts for user correctly and emit event(150ms)
      • should fail if arrays lengths mismatched(75ms)
      • should fail if token from batch is already minted(153ms)
    • burn
      • should burn nft with passed id correctly and emit event(171ms)
      • should burn nft to cover counter decrement(95ms)
      • should fail if caller is not approved and not owner of the nft(114ms)
      • should fail if token is not exist
    • setTokenURI
      • should set token URI correctly and emit event(98ms)
      • should fail if caller is not the owner
      • should fail if token with passed id is not exist
    • setTokenTransferabilityRange
      • should set transferability range correctly and emit event(131ms)
      • should fail if caller is not the owner
      • should fail if token range will be passed incorrectly
      • should not fail if one token is not exist from range(92ms)
    • setTokenTransferabilities
      • should set transferabilities correctly and emit event(162ms)
      • should fail if caller is not the owner
      • should not fail if token is not exist from passed array(145ms)
    • transfer
      • should transfer correctly and emit event(129ms)
      • should transfer correctly if global transferability is disabled and emit event(156ms)
      • should fail if global transferability is disabled and token own(116ms)
48 passing(4s)

We are delighted to have a chance to work with the MXS Games team and contribute to your company's success by reviewing and certifying the security of your smart contracts.

The statements made in this document should be interpreted neither as investment or legal advice, nor should its authors be held accountable for decisions made based on this document.

Vidma is a security audit company helping crypto companies ensure their code and products operate safely and as intended, enabling founders to sleep soundly at night. We specialize in auditing DeFi protocols, layer one protocols, and marketplace solutions. Our team consists of experienced and internationally trained specialists. Our company is based in Ukraine, known for its strong engineering, cryptography, and cybersecurity culture.
4