Squirrelly Squirrels - Smart Contract Audit Report
Audit Summary
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.Resolution: The team has implemented a conditional requirement to confirm the purchase amount does not cause the supply to exceed the maximum supply limit.require( totalSupply().add(_numberOfTokens) <= MAX_SUPPLY.sub(reserve), "Purchase would exceed max supply" );
Finding #2 - SquirrellySquirrels - Low
Description: The claimReserve function is vulnerable to reenctrancy although it can only be called by the owner address.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.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); }
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.Recommendation: We recommend declaring these functions external for gas savings on each call.requestStartingIndex, claimReserve, flipSaleState, flipPresaleState, setAllowListMerkleRoot, mintPresale, mint, walletOfOwner, setHiddenMetadataUri, setUriPrefix, setUriSuffix, setRevealed, withdraw
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 Category | Notes | Result |
---|---|---|
Arbitrary Storage Write | N/A | PASS |
Arbitrary Jump | N/A | PASS |
Centralization of Control | N/A | PASS |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Deprecated Opcodes | N/A | PASS |
Ether Thief | N/A | PASS |
Exceptions | N/A | PASS |
External Calls | N/A | PASS |
Integer Over/Underflow | N/A | PASS |
Logical Issues | N/A | PASS |
Multiple Sends | N/A | PASS |
Suicide | N/A | PASS |
State Change External Calls | N/A | PASS |
Unchecked Retval | N/A | PASS |
User Supplied Assertion | N/A | PASS |
Critical Solidity Compiler | N/A | PASS |
Overall Contract Safety | PASS |
SquirrellySquirrels Contract
Function Graph
Inheritence Chart
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