Squirrelly Squirrels - Smart Contract Audit Report

Audit Summary

SquirrellySquirrels Audit Report Squirrel Wallet is building a new NFT collection with a presale and public sale format.

For this audit, we reviewed the SquirrellySquirrels contract provided to us by the project team.

Audit Findings

Date: January 18th, 2022.
Updated: January 20th, 2022 to address issues resolved by the team.

Finding #1 - SquirrellySquirrels - Medium

Description: The presale mint is limited only by each users individuals presale purchase amount but not by the maximum supply amount.
Risk/Impact: The presale could mint more NFTs than the maximum supply limit if enough users are eligible.
Recommendation: The project team should implement a conditional requirement that the purchase amount does not exceed the maximum supply limit similar to the existing condition within the mint() function.
require(
  totalSupply().add(_numberOfTokens) <= MAX_SUPPLY.sub(reserve),
  "Purchase would exceed max supply"
);
Resolution: The team has implemented a conditional requirement to confirm the purchase amount does not cause the supply to exceed the maximum supply limit.

Finding #2 - SquirrellySquirrels - Low

Description: The claimReserve function is vulnerable to reenctrancy although it can only be called by the owner address.
function claimReserve(address _to, uint256 _reserveAmount) public onlyOwner {
    require(
      _reserveAmount > 0 && _reserveAmount <= reserve,
      "Not enough reserve left for team"
    );
    uint256 supply = totalSupply();
    for (uint256 i = 0; i < _reserveAmount; i++) {
      _safeMint(_to, supply + i);
    }
    reserve = reserve.sub(_reserveAmount);
}
Risk/Impact: Since the deduction from the total reserve amount takes place at the end of the function, in the case where the recipient is the owner and the owner is a contract, the recipient can call the claimReserve() function repeatedly via the onERC721Received() fallback function required by the call to _safeMint() surpassing the maximum reserve amount of 300.
Recommendation: We recommend restructuring the logic so that the _reserveAmount is deducted from the reserve prior to minting the NFTs.
Resolution: The team has restructured the logic so that the _reserveAmount is deducted prior to minting the NFTs to the user.

Finding #3 - SquirrellySquirrels - Low

Description: Any extra ETH used to mint NFTs in the mintPresale() and the mint() functions are 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: In the mintCompliance modifier, ensure the amount of ETH sent is equal to the minting price.
Resolution: The team has implemented conditions that ensure only the correct amount of ETH is accepted.

Finding #4 - SquirrellySquirrels - Informational

Description: The following functions are declared public, but are never called internally.

requestStartingIndex, claimReserve, flipSaleState, flipPresaleState, setAllowListMerkleRoot, mintPresale, mint, walletOfOwner, setHiddenMetadataUri, setUriPrefix, setUriSuffix, setRevealed, withdraw
Recommendation: We recommend declaring these functions external for gas savings on each call.
Resolution: The team has declared these functions external.

Contract Overview

  • This contract is used to facilitate a presale and a public sale for the SquirrellySquirrels NFTs.
  • The maximum total supply of SS NFTs is 10,000.
  • Of the 10,000 SS NFTs, 9,700 are available to be minted by the public, and the remaining 300 are reserved for the owner to mint.
  • While the presale is active, eligible users can each purchase up to 5 NFTs in groups of 1, 3, or 5 per transaction until the maximum total supply has been reached.
  • The contract uses an off-chain generated Merkle tree provided by the owner to store and verify addresses that are eligible to mint during the presale.
  • While the public sale is active, users can each purchase 1, 3, or 5 NFTs per transaction until the maximum total supply has been reached.
  • SS NFT sale prices are determined by limited purchase amounts with a starting price of 0.16 ETH for 1 NFT, 0.39 ETH for 3 NFTs, and 0.45 ETH for 5 NFTs.
  • In the event that the address receiving an NFT is a contract, the contract must have implemented the onERC721Received() function in order to successfully receive the NFT.
  • The base URI value is set on deployment, but is not meant to be the final base URI. This is intended so that users will not know which SS NFT they have received until the true base URI is revealed.
  • The contract will only accept the correct amount of ETH that is required for the specified amount of NFTs to be purchased.
  • The owner can withdraw all funds from the contract at any time.
  • The owner can use the "claim reserve" functionality to mint up to 300 NFTs to any address at any time.
  • The owner can toggle the presale and public sale at any time.
  • The owner can set the root of the Merkle tree at any time, effectively controlling the presale whitelist.
  • The owner can set the metadata URI, URI prefix, and URI suffix at any time.
  • This contract complies with the ERC721 standard.
  • As the contracts are developed with Solidity 0.8.x, they are protected from overflow/underflow attacks.

External Threat Results

Vulnerability CategoryNotesResult
Arbitrary Storage WriteN/APASS
Arbitrary JumpN/APASS
Centralization of ControlN/APASS
Delegate Call to Untrusted ContractN/APASS
Dependence on Predictable VariablesN/APASS
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

SquirrellySquirrels Contract

Function Graph

Bridge Contract Graph

Inheritence Chart

Multi-file Token

Functions Overview

												
($) = 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]  #
    - [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 #

 + [Lib] SafeMath 
    - [Int] tryAdd
    - [Int] trySub
    - [Int] tryMul
    - [Int] tryDiv
    - [Int] tryMod
    - [Int] add
    - [Int] sub
    - [Int] mul
    - [Int] div
    - [Int] mod
    - [Int] sub
    - [Int] div
    - [Int] mod

 + [Lib] MerkleProof 
    - [Int] verify
    - [Int] processProof

 +  Ownable (Context)
    - [Pub]  #
    - [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]  #
    - [Ext] rawFulfillRandomness #

 +  SquirrellySquirrels (VRFConsumerBase, ERC721Enumerable, Ownable)
    - [Pub]  #
       - modifiers: ERC721,VRFConsumerBase
    - [Pub] requestStartingIndex #
       - modifiers: onlyOwner
    - [Int] fulfillRandomness #
    - [Pub] claimReserve #
       - modifiers: onlyOwner
    - [Pub] flipSaleState #
       - modifiers: onlyOwner
    - [Pub] flipPresaleState #
       - modifiers: onlyOwner
    - [Pub] isAllowListed
    - [Pub] setAllowListMerkleRoot #
       - modifiers: onlyOwner
    - [Pub] mintPresale ($)
       - modifiers: mintCompliance
    - [Pub] mint ($)
       - modifiers: mintCompliance
    - [Pub] walletOfOwner
    - [Pub] tokenURI
    - [Pub] setHiddenMetadataUri #
       - modifiers: onlyOwner
    - [Pub] setUriPrefix #
       - modifiers: onlyOwner
    - [Pub] setUriSuffix #
       - modifiers: onlyOwner
    - [Pub] setRevealed #
       - modifiers: onlyOwner
    - [Pub] withdraw #
       - modifiers: onlyOwner
    - [Int] _baseURI