Audit: Bored Ape Yacht Club
In recent months, non-fungible tokens (NFTs) have become increasingly popular in the world of cryptocurrency. One of the most well-known NFT projects is the Bored Ape Yacht Club (BAYC), which has garnered significant attention and sales on the OpenSea marketplace.
The Bored Ape Yacht Club (BAYC) has taken the NFT world by storm, with a total volume of 699.335ETH (~$828.086.800) on OpenSea. But with great success comes scrutiny, and it's important to take a closer look at the smart contract behind this popular project.
In this article, we'll conduct a code review of the BAYC smart contract to gain a better understanding of its functionality and potential issues. We'll explore the various state variables, functions, and logic in the contract, and discuss any areas of concern. To follow along, you can view the BAYC smart contract on Etherscan at this link: https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#code.
The BAYC smart contract implements the ERC721 non-fungible token standard and includes several functions for minting, reserving, and managing tokens.
The Bored Ape Yacht Club smart contract includes several state variables, each with its own purpose. These variables include a string called BAYC_PROVENANCE
, which holds a provenance hash, and two uint256 variables called startingIndexBlock
and startingIndex
, which are used to determine the starting index for a collection. There are also various constants, such as apePrice
, which holds the price of a token in wei, and maxApePurchase
, which holds the maximum number of tokens that can be purchased at one time. The MAX_APES
variable holds the maximum number of tokens that can be created, and the saleIsActive
variable indicates whether the sale of tokens is currently active. Finally, there's the REVEAL_TIMESTAMP
variable, which holds a reveal timestamp.
The constructor function takes four arguments, including the name and symbol of the contract, the maximum NFT supply, and the start time for the sale. This function initializes the ERC721 contract with these values, sets the MAX_APES
and REVEAL_TIMESTAMP
variables, and increments the REVEAL_TIMESTAMP
by 9 days (set on deployment to Friday, April 30, 2021, at 10:00:00 PM).
The Bored Ape Yacht Club smart contract includes several functions, each with its own purpose. The first function we'll examine is withdraw()
, which allows the owner to withdraw the ETH balance of the contract. This function is marked as onlyOwner
`, meaning that only the designated owner can perform this action. When called, the function retrieves the ETH balance of the contract and transfers it to the owner's address.
Another interesting function in the BAYC contract is reserveApes()
. This function allows the owner to set aside 30 apes for themselves. However, upon closer inspection, it appears that the function does not check if the number of reserved apes exceeds the MAX_APES
supply, meaning that the owner can keep minting 30 new Apes every time they call this function. Given the current value of each Bored Ape, this could potentially result in a significant profit for the owner. It's worth noting that the function is also marked as onlyOwner
, so only the designated owner can reserve these apes.
They called the function short after they deployed the smart contract:
https://etherscan.io/tx/0xcfb197f62ec5c7f0e71a11ec0c4a0e394a3aa41db5386e85526f86c84b3f2796
In addition to withdraw()
and reserveApes()
, the Bored Ape Yacht Club smart contract includes several other functions. setRevealTimestamp()
allows the owner to set the REVEAL_TIMESTAMP
variable. This function is also marked as onlyOwner
, so only the designated owner can modify this value.
The setProvenanceHash()
function allows the owner of the Bored Ape Yacht Club contract to set the BAYC_PROVENANCE
state variable to a unique identifier that can be used to verify the authenticity and ownership of a physical or digital item. However, the purpose of storing this value in the contract is not clear from the code provided, as it is not used in any other functions or referenced in any other way.
It is possible that this feature could be used in the future to provide additional verification and authentication for the Bored Ape Yacht Club collection, but at present it does not appear to have any practical use within the context of the contract.
setBaseURI()
allows the owner to set the base URI
for the contract. This function is also marked as onlyOwner
, so only the designated owner can modify the base URI
.
flipSaleState()
allows the owner to pause or activate the sale of tokens. This function is also marked as onlyOwner
, so only the designated owner can modify the sale state.
The mintApe()
function is a critical function in the Bored Ape Yacht Club smart contract, as it allows users to purchase and mint new Bored Ape NFTs. This function includes several checks to ensure that the transaction is valid and complies with the rules of the contract. First, the function checks that the sale is currently active using the saleIsActive
boolean. If it's not, then the transaction will fail with the message "Sale must be active to mint Ape."
The function also checks that the number of tokens being purchased does not exceed the maximum allowed, which is set to 20 tokens per transaction using the maxApePurchase
constant. Additionally, the function checks that the purchase does not exceed the maximum supply of tokens, which is set by the MAX_APES
variable.
Finally, the function checks that the value of ether being sent is sufficient to purchase the requested number of tokens. If all of these checks pass, the function will create the requested number of tokens and assign them to the sender using the _safeMint()
function. The startingIndexBlock
is also set if it hasn't already been set and the conditions are met.
The setStartingIndex()
function is used to set the starting index for the Bored Ape Yacht Club collection. This function is called after the sale has ended, and the starting index is required for the reveal of the metadata for each token. The function first checks that the starting index has not already been set and that the startingIndexBlock
variable has been set. If these conditions are not met, the function will fail.
Next, the function calculates the starting index using the block hash of the startingIndexBlock
and the maximum number of tokens allowed. If the function is called too late, the calculation may use a different block hash to ensure that the starting index is not predictable. Finally, the function adjusts the starting index to ensure that it is not set to zero, which could interfere with the default sequence of the tokens.
Overall, the setStartingIndex()
function ensures that the starting index for the Bored Ape Yacht Club collection is set in a secure and predictable manner to allow for the reveal of the metadata for each token.
The emergencySetStartingIndexBlock()
function is included as a safety measure to allow the owner of the Bored Ape Yacht Club contract to set the starting index block if it has not been set already. This function can only be called by the owner and will fail if the starting index has already been set.
In the event that the startingIndexBlock
has not been set, the owner can call this function to set the starting index block manually. This ensures that the starting index can be calculated correctly and that the reveal of the metadata for each token can proceed as expected.
The emergencySetStartingIndexBlock()
function is a useful fallback option in the event that the starting index block has not been set correctly, providing an additional level of security and ensuring that the reveal of the metadata for each token proceeds smoothly.
In conclusion, the Bored Ape Yacht Club contract appears to be well-structured and follows standard practices for implementing an ERC721 contract. The state variables and functions have been designed to provide the necessary functionality for managing the collection, including the ability to mint and reserve tokens, set the base URI, and pause and activate sales.
The contract's developer(s) have taken certain precautions to ensure that the contract functions as intended, such as including checks to prevent the purchase of more tokens than the maximum supply, and setting aside a limited number of tokens for the owner. However, the reserveApes()
function does not include a check to prevent the owner from setting aside more tokens than the maximum supply, which could potentially lead to the creation of unlimited tokens.