MEE6 - Smart Contract Audit Report

Audit Summary

MEE6 Audit Report MEE6 is building a new NFT collection with randomized traits and a presale platform.

For this audit, we reviewed the MEE6Core_PROD, MEE6_NFT_PROD, PRESALE, and Library contracts provided to us by the team.

Audit Findings

Please ensure trust in the team prior to investing as they have substantial control in the ecosystem.
Date: January 18th, 2022.
Updated: January 26th, 2022 with additions to prevent minting to contracts, preventing users from sending too much ETH, and implementing a blacklist.
Updated: January 28th, 2022 restricting additional functions to EOAs only.
Updated: March 7th, 2022 with a new Presale contract.

Finding #1 - MEE6_NFT_PROD - Medium (Resolved)

Description: Users added to the blacklist are still able to mint NFTs.
Risk/Impact: Blacklisted bot accounts will be able to mass purchase NFTs as soon as minting is enabled.
Recommendation: Create a modifier that does not allow addresses to mint NFTs if they are on the blacklist. As MEE6 NFTs are created from burning PRESALE NFTs, in order to ensure proper protection from bots, a blacklist should be implemented in the PRESALE contract as well.
Resolution: The team has implemented a blacklist in the MEE6_NFT_PROD contract.

Finding #2 - MEE6_NFT_PROD - Low (Resolved)

Description: Any extra ETH used to reroll an NFT in the reroll() function is not returned to the user.
Risk/Impact: Users may inadvertently send more ETH than required and subsequently lose all of the extra ETH sent.
Recommendation: Require that the amount of ETH sent is equal to the cost of rerolling.
Resolution: The team has added a require statement preventing extra ETH being sent to the this function.

Finding #3 - MEE6_NFT_PROD - Low (Resolved)

Description: Contracts may call the reroll() function and be the recipient of NFTs minted after burning the required NFT.
Risk/Impact: A user can create a contract that calls reroll() and reverts if they receive an undesirable result. Similarly, a user can create a contract that reverts during onERC721Received() after minting if the result is undesirable.
Recommendation: Require that only Externally-Owned Addresses (EOAs) can call the reroll() function. The recipient of minted NFTs should also be an EOA.
Resolved: The team has made changes so that contracts may not be minted an NFT and may not call the reroll function.

Finding #4 - PRESALE - Low (Resolved)

Description: Any extra ETH used to mint in the preSaleMint() and publicSaleMint() functions is not returned to the user.
Risk/Impact: Users may inadvertently send more ETH than required and subsequently lose all of the extra ETH sent.
Recommendation: Require that the amount of ETH sent is equal to the cost of minting.
Resolution: The team has added require statements preventing extra ETH being sent to these functions.

Contracts Overview

PRESALE contract:
  • A maximum of 9999 PRESALE NFTs may be minted.
  • The cost to mint a PRESALE NFT various based on the minting method.
  • When presale is enabled, users on the whitelist are able to mint an alloted number of NFTs, up to 10 at a time. Each user is separately alloted the number of NFTs they may mint and may vary between users.
  • The cost to presale mint an NFT defaults to 0.25 ETH.
  • When public sale is enabled, any user may mint PRESALE NFTs, up to 10 at a time. The public sale takes the form of a Dutch Auction.
  • A public sale mint starts at a specified cost. It then decreases over time at each "increment" until it reaches a floor cost.
  • The cost for each user is dependent on the elapsed time since the start of public minting.
  • NFT metadata that contains information about the NFTs is stored using an off-chain URI endpoint.
  • The owner may mint themselves NFTs at no cost with a limit of 100 NFTs being minted at once.
  • The owner may trigger a withdrawal of all of the ETH collected in the contract at any time.
  • The owner may set the alloted number of NFTs for any address to any value at any time.
  • The owner may set the cost of presale minting an NFT to any value at any time.
  • The owner may update the Dutch Auction starting price, floor price, increment time, and increment value at any time.
  • The owner may toggle presale and public sale at any time.
  • The owner may update the URI endpoint at any time.
  • The owner may pause the contract, disabling all functionality, at any time.
MEE6_NFT_PROD Contract:
  • A maximum of 9999 MEE6 NFTs may be minted.
  • When enabled, NFTs are minted by burning a PRESALE NFT sent from the designated burn address.
  • The burn contract was outside the scope of this audit, so we cannot give an assesment in regards to its security.
  • When enabled, users may reroll the stats of their MEE6 NFT for 0.033 ETH. This will regenerate and update the NFT's traits with possibly more rare traits being generated.
  • Blacklisted users are intended to be unable to mint any NFTs, but due to logical issues the blacklist is non-functional.
  • NFT metadata that contains information about the NFTs is stored using an off-chain URI endpoint.
  • The owner may mint themselves one NFT at a time, up to the maximum supply, bypassing the need to burn a PRESALE NFT from the burn address, at any time.
  • As this bypasses the need to burn a PRESALE NFT, this may potentially prevent a user from being able to burn their PRESALE NFT for a MEE6 NFT.
  • The owner may reroll any NFT at no cost at any time.
  • The owner may trigger a withdrawal of all of the ETH collected in the contract at any time. The ETH is then allocated as a Dev, Artist, and Pool fee with each portion of the collected ETH going to a corresponding wallet. Any remaining ETH after fees is sent to the owner.
  • The owner may pause the contract, disabling any transfer functionality, at any time.
  • The owner may toggle both minting and rerolling functionality at any time.
  • The owner may add and remove any address from the blacklist at any time.
  • The owner may update the burn address at any time.
MEE6Core_PROD Contract:
  • This contract is used to randomly generate the stats and traits of a MEE6 NFT.
  • Chainlink is used to generate a verifiably random number off-chain. This is the industry standard and is resistant to manipulation.
  • The verifiably random number is then used in combination with the sender address, blocktime, blocknumber, blockhash and token ID to generate the final "rng" number used to seed the NFT's traits.
  • The request for a random number can only be called by the owner and it is up to their discretion how often to generate a new random number. Once the random number is known, miners/validators may be able to predict/influence the outcome of a reroll.
  • A fee of 2 LINK must be provided to the contract in order to request randomness from Chainlink.

External Threat Results

Vulnerability CategoryNotesResult
Arbitrary Storage WriteN/APASS
Arbitrary JumpN/APASS
Centralization of Control
  • The team retains ownership controls outlined above.
  • The cost of minting PRESALE NFTs is uncapped.
  • The owner may prevent NFT transfers by pausing the MEE6_NFT_PROD contract.
  • WARNING
    Delegate Call to Untrusted ContractN/APASS
    Dependence on Predictable Variables
  • Random generation of NFT traits may be predicted/influenced.
  • WARNING
    Deprecated OpcodesN/APASS
    Ether ThiefN/APASS
    ExceptionsN/APASS
    External CallsN/APASS
    Integer Over/UnderflowN/APASS
    Logical IssuesN/APASS
    Multiple SendsN/APASS
    SuicideN/APASS
    State Change External CallsN/APASS
    Unchecked RetvalN/APASS
    User Supplied AssertionN/APASS
    Critical Solidity CompilerN/APASS
    Overall Contract Safety PASS

    Library Contract

    smart_contract_audit_company

    token_audit

    
     ($) = payable function
     # = non-constant function
    
      + [Lib] Library 
        - [Int] encode
        - [Int] toString
        - [Int] parseInt
        - [Int] substring
        - [Int] isContract
    
    							

    MEE6Core_PROD Contract

    smart_contract_audit_company

    token_audit

    
     ($) = payable function
     # = non-constant function
    
     + [Int] LinkTokenInterface 
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] balanceOf
        - [Ext] decimals
        - [Ext] decreaseApproval #
        - [Ext] increaseApproval #
        - [Ext] name
        - [Ext] symbol
        - [Ext] totalSupply
        - [Ext] transfer #
        - [Ext] transferAndCall #
        - [Ext] transferFrom #
    
     +  VRFRequestIDBase 
        - [Int] makeVRFInputSeed
        - [Int] makeRequestId
    
     +  VRFConsumerBase (VRFRequestIDBase)
        - [Int] fulfillRandomness #
        - [Int] requestRandomness #
        - [Pub] Constructor #
        - [Ext] rawFulfillRandomness #
    
     +  Context 
        - [Int] _msgSender
        - [Int] _msgData
    
     +  Ownable (Context)
        - [Pub] Constructor #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     + [Lib] Counters 
        - [Int] current
        - [Int] increment #
        - [Int] decrement #
        - [Int] reset #
    
     +  MEE6Core_PROD (VRFConsumerBase, Ownable)
        - [Pub] Constructor #
           - modifiers: VRFConsumerBase
        - [Pub] getRandomNumber #
           - modifiers: onlyOwner
        - [Pub] getToken
        - [Int] fulfillRandomness #
        - [Int] convert8
        - [Pub] getMetadataFromRNG
        - [Int] setMetadata #
    
    							

    MEE6_NFT_PROD Contract

    smart_contract_audit_company

    token_audit

    
     ($) = payable function
     # = non-constant function
    
     + [Int] IERC165 
        - [Ext] supportsInterface
    
     + [Int] IERC721 (IERC165)
        - [Ext] balanceOf
        - [Ext] ownerOf
        - [Ext] safeTransferFrom #
        - [Ext] transferFrom #
        - [Ext] approve #
        - [Ext] getApproved
        - [Ext] setApprovalForAll #
        - [Ext] isApprovedForAll
        - [Ext] safeTransferFrom #
    
     + [Int] IERC721Receiver 
        - [Ext] onERC721Received #
    
     + [Int] IERC721Metadata (IERC721)
        - [Ext] name
        - [Ext] symbol
        - [Ext] tokenURI
    
     + [Lib] Address 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Int] functionStaticCall
        - [Int] functionStaticCall
        - [Int] functionDelegateCall #
        - [Int] functionDelegateCall #
        - [Int] verifyCallResult
    
     +  Context 
        - [Int] _msgSender
        - [Int] _msgData
    
     + [Lib] Strings 
        - [Int] toString
        - [Int] toHexString
        - [Int] toHexString
    
     +  ERC165 (IERC165)
        - [Pub] supportsInterface
    
     +  ERC721 (Context, ERC165, IERC721, IERC721Metadata)
        - [Pub] Constructor #
        - [Pub] supportsInterface
        - [Pub] balanceOf
        - [Pub] ownerOf
        - [Pub] name
        - [Pub] symbol
        - [Pub] tokenURI
        - [Int] _baseURI
        - [Pub] approve #
        - [Pub] getApproved
        - [Pub] setApprovalForAll #
        - [Pub] isApprovedForAll
        - [Pub] transferFrom #
        - [Pub] safeTransferFrom #
        - [Pub] safeTransferFrom #
        - [Int] _safeTransfer #
        - [Int] _exists
        - [Int] _isApprovedOrOwner
        - [Int] _safeMint #
        - [Int] _safeMint #
        - [Int] _mint #
        - [Int] _burn #
        - [Int] _transfer #
        - [Int] _approve #
        - [Int] _setApprovalForAll #
        - [Prv] _checkOnERC721Received #
        - [Int] _beforeTokenTransfer #
    
     + [Int] IERC721Enumerable (IERC721)
        - [Ext] totalSupply
        - [Ext] tokenOfOwnerByIndex
        - [Ext] tokenByIndex
    
     +  ERC721Enumerable (ERC721, IERC721Enumerable)
        - [Pub] supportsInterface
        - [Pub] tokenOfOwnerByIndex
        - [Pub] totalSupply
        - [Pub] tokenByIndex
        - [Int] _beforeTokenTransfer #
        - [Prv] _addTokenToOwnerEnumeration #
        - [Prv] _addTokenToAllTokensEnumeration #
        - [Prv] _removeTokenFromOwnerEnumeration #
        - [Prv] _removeTokenFromAllTokensEnumeration #
    
     +  ERC721URIStorage (ERC721)
        - [Pub] tokenURI
        - [Int] _setTokenURI #
        - [Int] _burn #
    
     +  Pausable (Context)
        - [Pub] Constructor #
        - [Pub] paused
        - [Int] _pause #
           - modifiers: whenNotPaused
        - [Int] _unpause #
           - modifiers: whenPaused
    
     +  ERC721Pausable (ERC721, Pausable)
        - [Int] _beforeTokenTransfer #
    
     + [Int] IERC1155Receiver (IERC165)
        - [Ext] onERC1155Received #
        - [Ext] onERC1155BatchReceived #
    
     + [Int] IERC1155 (IERC165)
        - [Ext] balanceOf
        - [Ext] balanceOfBatch
        - [Ext] setApprovalForAll #
        - [Ext] isApprovedForAll
        - [Ext] safeTransferFrom #
        - [Ext] safeBatchTransferFrom #
    
     + [Int] IERC1155MetadataURI (IERC1155)
        - [Ext] uri
    
     +  ERC1155 (Context, ERC165, IERC1155, IERC1155MetadataURI)
        - [Pub] Constructor #
        - [Pub] supportsInterface
        - [Pub] uri
        - [Pub] balanceOf
        - [Pub] balanceOfBatch
        - [Pub] setApprovalForAll #
        - [Pub] isApprovedForAll
        - [Pub] safeTransferFrom #
        - [Pub] safeBatchTransferFrom #
        - [Int] _safeTransferFrom #
        - [Int] _safeBatchTransferFrom #
        - [Int] _setURI #
        - [Int] _mint #
        - [Int] _mintBatch #
        - [Int] _burn #
        - [Int] _burnBatch #
        - [Int] _setApprovalForAll #
        - [Int] _beforeTokenTransfer #
        - [Prv] _doSafeTransferAcceptanceCheck #
        - [Prv] _doSafeBatchTransferAcceptanceCheck #
        - [Prv] _asSingletonArray
    
     +  ERC1155Burnable (ERC1155)
        - [Pub] burn #
        - [Pub] burnBatch #
    
     + [Lib] Counters 
        - [Int] current
        - [Int] increment #
        - [Int] decrement #
        - [Int] reset #
    
     +  Ownable (Context)
        - [Pub] Constructor #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     + [Int] LinkTokenInterface 
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] balanceOf
        - [Ext] decimals
        - [Ext] decreaseApproval #
        - [Ext] increaseApproval #
        - [Ext] name
        - [Ext] symbol
        - [Ext] totalSupply
        - [Ext] transfer #
        - [Ext] transferAndCall #
        - [Ext] transferFrom #
    
     +  VRFRequestIDBase 
        - [Int] makeVRFInputSeed
        - [Int] makeRequestId
    
     +  VRFConsumerBase (VRFRequestIDBase)
        - [Int] fulfillRandomness #
        - [Int] requestRandomness #
        - [Pub] Constructor #
        - [Ext] rawFulfillRandomness #
    
     +  MEE6Core_PROD (VRFConsumerBase, Ownable)
        - [Pub] Constructor #
           - modifiers: VRFConsumerBase
        - [Pub] getRandomNumber #
           - modifiers: onlyOwner
        - [Pub] getToken
        - [Int] fulfillRandomness #
        - [Int] convert8
        - [Pub] getMetadataFromRNG
        - [Int] setMetadata #
    
     + [Lib] Library 
        - [Int] encode
        - [Int] toString
        - [Int] parseInt
        - [Int] substring
        - [Int] isContract
    
     +  MEE6_NFT_PROD (ERC721, ERC721Enumerable, ERC721URIStorage, Pausable, Ownable, MEE6Core_PROD, IERC721Receiver)
        - [Ext] Fallback ($)
        - [Ext] Fallback ($)
        - [Pub] Constructor #
           - modifiers: ERC721
        - [Pub] supportsInterface
        - [Pub] safeMint #
           - modifiers: onlyOwner
        - [Ext] updateERC1155BurnAddress #
           - modifiers: onlyOwner
        - [Pub] startMintingProcess #
           - modifiers: onlyOwner
        - [Pub] pauseMintingProcess #
           - modifiers: onlyOwner
        - [Pub] startRerollProcess #
           - modifiers: onlyOwner
        - [Pub] pauseRerollProcess #
           - modifiers: onlyOwner
        - [Pub] pause #
           - modifiers: onlyOwner
        - [Pub] unpause #
           - modifiers: onlyOwner
        - [Ext] reroll ($)
        - [Pub] owner_reroll #
           - modifiers: onlyOwner
        - [Pub] setImageUrl #
           - modifiers: onlyOwner
        - [Pub] generateMetadata
        - [Pub] tokenURI
        - [Ext] tokensOfOwner
        - [Pub] onERC721Received #
        - [Ext] onERC1155Received #
        - [Ext] onERC1155BatchReceived #
        - [Prv] _onERC1155Received #
        - [Pub] addToBlacklist #
           - modifiers: onlyOwner
        - [Pub] setMetadataProvenance #
           - modifiers: onlyOwner
        - [Pub] withdrawAll ($)
           - modifiers: onlyOwner
        - [Int] _beforeTokenTransfer #
           - modifiers: whenNotPaused
        - [Int] _burn #
    
    							

    PRESALE Contract

    smart_contract_audit_company

    token_audit

    
     ($) = payable function
     # = non-constant function
    
     + [Int] IERC165 
        - [Ext] supportsInterface
    
     + [Int] IERC1155 (IERC165)
        - [Ext] balanceOf
        - [Ext] balanceOfBatch
        - [Ext] setApprovalForAll #
        - [Ext] isApprovedForAll
        - [Ext] safeTransferFrom #
        - [Ext] safeBatchTransferFrom #
    
     + [Int] IERC1155Receiver (IERC165)
        - [Ext] onERC1155Received #
        - [Ext] onERC1155BatchReceived #
    
     + [Int] IERC1155MetadataURI (IERC1155)
        - [Ext] uri
    
     + [Lib] Address 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Int] functionStaticCall
        - [Int] functionStaticCall
        - [Int] functionDelegateCall #
        - [Int] functionDelegateCall #
        - [Int] verifyCallResult
    
     +  Context 
        - [Int] _msgSender
        - [Int] _msgData
    
     +  ERC165 (IERC165)
        - [Pub] supportsInterface
    
     +  ERC1155 (Context, ERC165, IERC1155, IERC1155MetadataURI)
        - [Pub] Constructor #
        - [Pub] supportsInterface
        - [Pub] uri
        - [Pub] balanceOf
        - [Pub] balanceOfBatch
        - [Pub] setApprovalForAll #
        - [Pub] isApprovedForAll
        - [Pub] safeTransferFrom #
        - [Pub] safeBatchTransferFrom #
        - [Int] _safeTransferFrom #
        - [Int] _safeBatchTransferFrom #
        - [Int] _setURI #
        - [Int] _mint #
        - [Int] _mintBatch #
        - [Int] _burn #
        - [Int] _burnBatch #
        - [Int] _setApprovalForAll #
        - [Int] _beforeTokenTransfer #
        - [Prv] _doSafeTransferAcceptanceCheck #
        - [Prv] _doSafeBatchTransferAcceptanceCheck #
        - [Prv] _asSingletonArray
    
     +  ERC1155Burnable (ERC1155)
        - [Pub] burn #
        - [Pub] burnBatch #
    
     +  Pausable (Context)
        - [Pub] Constructor #
        - [Pub] paused
        - [Int] _pause #
           - modifiers: whenNotPaused
        - [Int] _unpause #
           - modifiers: whenPaused
    
     +  ERC1155Pausable (ERC1155, Pausable)
        - [Int] _beforeTokenTransfer #
    
     +  Ownable (Context)
        - [Pub] Constructor #
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
    
     + [Int] IERC20 
        - [Ext] totalSupply
        - [Ext] balanceOf
        - [Ext] transfer #
        - [Ext] allowance
        - [Ext] approve #
        - [Ext] transferFrom #
    
     +  PRESALE (ERC1155, Ownable, ERC1155Burnable, ERC1155Pausable)
        - [Ext] Fallback ($)
        - [Ext] Receive Ether ($)
        - [Pub] Constructor #
           - modifiers: ERC1155
        - [Pub] dutchAuctionPrice
        - [Pub] setDutchAuctionPrice #
           - modifiers: onlyOwner
        - [Pub] getAuctionInformation
        - [Pub] hasPublicSaleStarted
        - [Pub] setURI #
           - modifiers: onlyOwner
        - [Pub] enablePublicSale #
           - modifiers: onlyOwner
        - [Pub] setPublicSaleTimestamp #
           - modifiers: onlyOwner
        - [Pub] stopPublicSale #
           - modifiers: onlyOwner
        - [Pub] enablePreSale #
           - modifiers: onlyOwner
        - [Pub] stopPreSale #
           - modifiers: onlyOwner
        - [Pub] setStartingPrice #
           - modifiers: onlyOwner
        - [Pub] getStartingPrice
        - [Pub] claimedSupply
        - [Ext] setWhitelisted #
           - modifiers: onlyOwner
        - [Ext] preSaleMint ($)
        - [Pub] isWalletWhitelisted
        - [Pub] pause #
           - modifiers: onlyOwner
        - [Pub] unpause #
           - modifiers: onlyOwner
        - [Ext] publicSaleMint ($)
        - [Ext] ownerMint ($)
           - modifiers: onlyOwner
        - [Pub] withdrawAll ($)
           - modifiers: onlyOwner
        - [Int] _beforeTokenTransfer #