21 min read
Overview
Follow this 73-point checklist to never write a bad NFT smart contract again.
What it is
This is a set of best practices when writing NFT contracts on Ethereum.
-
A one-stop resource for common best practices implementing NFT contracts.
-
Meant to summarize each item and to make you aware it exists. For the sake of brevity, each item cannot be described in detail here. Please research if anything is unfamiliar.
-
Some points are fleshed out, detailed, and with code examples, while some are included only as one-sentence reminders or as simple yes/no checklist items.
It’s a useful checklist to check your smart contract against.
But if you are still learning and not writing your own smart contracts yet, it's an NFT learning roadmap:
-
Go through the list
-
Can you implement this feature?
-
No? Read about it. Learn how to implement it. Even if you don’t use it, know what it does and why it’s important.
-
Yes? Move on to the next point.
Please keep in mind that it is not a 100%-complete list. It also does not replace the need for security audits.
Note: This guide is based on a series of videos that we have posted on our YouTube channel. All the videos are included in the appropriate sections below. You can choose to read or watch, depending on what you prefer. Video content is the same, but with more explanation and details.
What’s inside
The 73 points of the checklist are grouped into 8 categories:
-
Preparation (points 1 - 7)
-
Security essentials (8 - 17)
-
Testing (18 - 29)
-
Gas savings (30 - 42)
-
Deployment (43 - 48)
-
Minting (49 - 63)
-
Admin (64 - 70)
-
After launch (71 - 73)
-
Helpful resources
You can go through the list from start to finish. It is grouped into sections that are meant to be chronological, starting with the preparation stage and finishing with the after launch stage. Or you can focus on specific sections, if for example you’re interested only in gas optimization or in the minting stage.
Preparation
If you prefer to watch rather than read, this guide is split into four videos and covers all of the material in this guide.
This video covers preparation (points 1 - 7), security essentials (8 - 17), and testing (18 - 29).
1. Pick NFT type
ERC-721
The current widespread standard.
A few of the ERC-721 NFT projects: CryptoKitties, Crypto Coven, Cool Cats.
A list of ERC-721 Token Contracts on Etherscan.
ERC-1155
-
Batch transfers
-
Supports fungible, semi-fungible and non-fungible tokens
-
Can be reverted in the event of a mistake
Example of ERC-1155 token: adidas Originals NFT
ERC-1155 Token Contracts on Etherscan.
2. Pick the base contract implementation
3. Pick the chain to deploy to
More on all the blockchains to deploy to: What Technology To Deploy An NFT | NFT Phd
4. Pick the metadata storage (on-chain vs off-chain)
More on NFT storage: What Technology To Deploy An NFT | NFT Phd
5. Use the latest Solidity compiler
Solidity - download the latest version from Solidity website
6. Use modern tools: Hardhat, Foundry
Hardhat - flexible, extensible, fast Ethereum development environment
Foundry - fast, portable and modular toolkit for Ethereum application development written in Rust
7. Create a Gnosis Safe for proceeds and royalties
Gnosis Safe - a platform to manage digital assets on Ethereum.
If your project comprises of more than 1 person, an externally owned account (essentially, a wallet) is not a secure way to manage your business’s crypto funds. If an employee goes rogue or is careless with the private key, the funds are gone forever. Even if your business is made up of just yourself, it’s still a poor way to manage funds.
Gnosis Safe solves this. It's a multisig solution, which is essentially a smart contract wallet running on a number of blockchains that requires a minimum number of people to approve a transaction before it can occur (M-of-N).
If for example, you have 3 main stakeholders in your business, you can set up the wallet to require approval from 2 out of 3 (2/3) or all 3 people before the transaction is sent. This assures that no single person could compromise the funds.
Security Essentials
8. Unencrypted private data
Nothing on the blockchain is private, including private variables
9. tx.origin
tx.origin’s only real use case is to check if a smart contract is calling your code. Using it for verification can lead to phishing hacks.
10. encodePacked and hash collisions
The two values will have the same hash. Use abi.encode instead of abi.encodePacked in this circumstance:
Given that different parameters can return the same value, an attacker could exploit this by modifying the position of elements in a previous function call to effectively bypass authorization. It becomes a problem if it's a part of some important admin function.
11. Calling multiple functions
Calling multiple functions in a Solidity function has undefined order
Solidity doesn’t specify which function gets called first in this situation:
method1 may be called before method2, or method2 before method1, depending on the Solidity version.
This is highly problematic if they might cause a state change or reference the same location in memory.
Double check the code can tolerate the functions being called in either order.
12. Exact ether balances
Anybody can change the balance of a smart contract by directly sending ether to it. Even if you override the receive and fallback functions by reverting if msg.value is not zero, another smart contract can selfdestruct and forcibly send ether to another address, bypassing those checks.
If your logic expects a balance of an address to have a precise value or remains static, that assumption can be violated.
See Force Feeding to read more about why relying on exact comparisons to the contract's Ether balance is unreliable.
13. Insecure delegate call
A delegate call gives the delegated address unlimited power. This should only be used with contracts you control, and it is critical to ensure the address to delegate to cannot be altered by an unauthorized user.
14. Not checking for reverts from untrusted contracts
This function can experience denial of service if it doesn’t account for the possibility that an external function call can revert.
If you make a call to an external contract, that function may revert. If this is done intentionally, then your contract will not be able to complete its transaction.
15. Security bugs in Solidity compiler versions
Before committing to using a certain version of the Solidity compiler, check soliditylang.org to see if it has known bugs.
Read through the release announcements: https://blog.soliditylang.org/category/releases/
16. Ensure information sources cannot be manipulated
The most common manifestation of this vulnerability is flash loan attacks. If your contract checks the price of an asset in a pool, someone can use a flash loan to manipulate that asset price and cause unexpected behavior in your contract.
17. Allowing users to store arbitrary strings
If your website displays strings stored on a smart contract, and the smart contract allows users to set arbitrary strings (such as giving NFTs nicknames), then they can inject malicious javascript into the website via a <script>
tag.
Frontend security is also very important, especially when it comes to user input on websites connected to blockchain and in wallets.
Testing
18. Use testing tools
Specific to your blockchain.
Types of tools: formal verification, symbolic execution, linters, and test coverage analyzers.
19. 100% line and branch coverage
A bug in a smart contract could end the company. 100% coverage is annoying, but it’s a price worth paying.
20. Write unit tests
Running a unit test requires creating assertions—simple, informal statements specifying requirements for a smart contract.
It then tests each assertion to see if it holds true under execution.
Examples of contract-related assertions include:
-
"Only the admin can pause the contract"
-
"Non-admins cannot mint new tokens"
-
"The contract reverts on errors"
21. Mutation test
Just because a code is 100% covered doesn’t mean the corner cases are tested.
If you swap a <
with a