Note: All 8 questions in this RACE are based on the InSecureumApe contract. This is the same contract you will see for all the 8 questions in this RACE. InSecureumApe is adapted from a well-known contract. The question is below the shown contract.
pragmasolidity ^0.7.0;import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4/contracts/access/Ownable.sol";import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4/contracts/token/ERC721/ERC721.sol";import"https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4/contracts/math/SafeMath.sol";contractInSecureumApeisERC721, Ownable {usingSafeMathforuint256;stringpublic IA_PROVENANCE ="";uint256public startingIndexBlock;uint256public startingIndex;uint256publicconstant apePrice =800000000000000000; //0.08 ETHuintpublicconstant maxApePurchase =20;uint256public MAX_APES;boolpublic saleIsActive =false;uint256public REVEAL_TIMESTAMP; constructor(string memory name, string memory symbol, uint256 maxNftSupply, uint256 saleStart) ERC721(name, symbol) {
MAX_APES = maxNftSupply; REVEAL_TIMESTAMP = saleStart + (86400*9); }functionwithdraw() publiconlyOwner {uint balance =address(this).balance; msg.sender.transfer(balance); }functionreserveApes() publiconlyOwner {uint supply =totalSupply();uint i;for (i =0; i <30; i++) {_safeMint(msg.sender, supply + i); } }functionsetRevealTimestamp(uint256 revealTimeStamp) publiconlyOwner { REVEAL_TIMESTAMP = revealTimeStamp; }functionsetProvenanceHash(stringmemory provenanceHash) publiconlyOwner { IA_PROVENANCE = provenanceHash; }functionsetBaseURI(stringmemory baseURI) publiconlyOwner {_setBaseURI(baseURI); }functionflipSaleState() publiconlyOwner { saleIsActive =!saleIsActive; }functionmintApe(uint numberOfTokens) publicpayable {require(saleIsActive,"Sale must be active to mint Ape");require(numberOfTokens < maxApePurchase,"Can only mint 20 tokens at a time");require(totalSupply().add(numberOfTokens) <= MAX_APES,"Purchase would exceed max supply of Apes");require(apePrice.mul(numberOfTokens) <= msg.value,"Ether value sent is not correct");for(uint i =0; i < numberOfTokens; i++) {uint mintIndex =totalSupply();if (totalSupply() < MAX_APES) {_safeMint(msg.sender, mintIndex); } } // If we haven't set the starting index and this is either 1) the last saleable token or 2) the first token to be sold after
// the end of pre-sale, set the starting index blockif (startingIndexBlock ==0&& (totalSupply() == MAX_APES || block.timestamp >= REVEAL_TIMESTAMP)) { startingIndexBlock = block.number; } }functionsetStartingIndex() public {require(startingIndex ==0,"Starting index is already set");require(startingIndexBlock !=0,"Starting index block must be set"); startingIndex =uint(blockhash(startingIndexBlock)) % MAX_APES;if (block.number.sub(startingIndexBlock) >255) { startingIndex =uint(blockhash(block.number -1)) % MAX_APES; }if (startingIndex ==0) { startingIndex = startingIndex.add(1); } }functionemergencySetStartingIndexBlock() publiconlyOwner {require(startingIndex ==0,"Starting index is already set"); startingIndexBlock = block.number; }}
Question 1 ✅
The mint price of an InSecureumApe is:
My comment:
Question 2 ✅
The security concern(s) with InSecureumApe access control is/are
Question 3 ✅
The security concern(s) with InSecureumApe constructor is/are
Question 4 ✅
The total number of InSecureumApes that can ever be minted is
Question 5 ✅
The public minting of InSecureumApes
Question 6 ✅
The security concerns with InSecureumApe is/are
Question 7 - This one tricky
The starting index determination
Comment:
You can read about how this is used for post-mint reveal randomization in this article.
The 9-day delay of the REVEAL_TIMESTAMP variable can be overriden at any point in time, it can also be triggered earlier if the totalSupply matches MAX_APES exactly, or be triggered at any time by the owner via emergencySetStartingIndexBlock().
It accounts for the block hash access limitation by falling back to using the hash of the previous block instead.
Question 8 ✅
Potential gas optimization(s) in InSecureumApe is/are
Comment:
Whenever storage variables are read from multiple times, they should be cached in memory to safe gas. This is missing for MAX_APES in mintApe() and startingIndexBlock in setStartingIndex().
All state variables are zero-initialized by default, therefore there's no need to manually set saleIsActive to false, for example.
The state variable MAX_APES is only set once during construction and should be immutable to save gas