You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
86 lines
4.0 KiB
Markdown
86 lines
4.0 KiB
Markdown
# Design
|
|
|
|
ERC721A enables a near constant gas cost for batch minting via a lazy-initialization mechanism.
|
|
|
|
Token IDs are minted in sequential order (e.g. 0, 1, 2, 3, ...).
|
|
|
|
Regardless of the quantity minted, the `_mint` function only performs 3 `SSTORE` operations:
|
|
|
|
- Initialize the ownership slot at the starting token ID with the address.
|
|
- Update the address' balance.
|
|
- Update the next token ID.
|
|
|
|
> A `Transfer` event will still be emitted for each NFT minted.
|
|
However, emitting an event is an order of magnitude cheaper than a `SSTORE` operation.
|
|
|
|
## Lower Fees
|
|
|
|
ERC721A defers the initialization of token ownership slots from minting to transferring.
|
|
|
|
This allows users to batch mint with low, near-constant transaction fees, and pick a good time to transfer when the network's BASEFEE is lower.
|
|
|
|
Although this has a higher total gas cost (minting + transfers), **it gives significantly lower overall transaction fees in practice**.
|
|
|
|
> Transaction Fee = (BASEFEE + Max Priority Per Gas) * Gas Used
|
|
|
|
Gas savings during high BASEFEE periods matter **more**.
|
|
|
|
As such, ERC721A prioritizes gas savings for the minting phase.
|
|
|
|
### Benchmark
|
|
|
|
To illustrate, we compare OpenZeppelin's ERC721 with ERC721A.
|
|
|
|
| | ERC721 | ERC721A |
|
|
| -------------------------- | ------------ | -------------- |
|
|
| Batch Mint 5 Tokens | 155949 gas | 63748 gas |
|
|
| Transfer 5 Tokens | 226655 gas | 334450 gas |
|
|
| Mint BASEFEE | 200 gwei | 200 gwei |
|
|
| Transfer BASEFEE | 40 gwei | 40 gwei |
|
|
| Total Transaction Fees | 0.0403 ether | 0.0261 ether |
|
|
|
|
Even for conservatively small batch sizes (e.g. 5), we can observe decent savings over the barebones implementation.
|
|
|
|
In practice, the Mint BASEFEE for ERC721 can be much higher.
|
|
|
|
When consecutive blocks hit the block gas limit, [the BASEFEE increases exponentially](https://ethereum.org/en/developers/docs/gas/#base-fee).
|
|
|
|
#### First Transfer vs Subsequent Transfers
|
|
|
|
The main overhead of transferring a token **only occurs during its very first transfer** for an uninitialized slot.
|
|
|
|
| | ERC721 | ERC721A |
|
|
| -------------------------- | ------------ | -------------- |
|
|
| First transfer | 45331 gas | 92822 gas |
|
|
| Subsequent transfers | 45331 gas | 44499 gas |
|
|
|
|
Here, we bulk mint 10 tokens, and compare the transfer costs of the 5th token in the batch.
|
|
|
|
To keep the cost of the `SSTORE` writing to the balance mapping constant, we ensure that the destination addresses have non-zero balances during all transfers.
|
|
|
|
The first transfer with ERC721A will incur the storage overheads:
|
|
|
|
- 2 extra `SSTORE`s (initialize current slot and next slot, both of which are empty).
|
|
- 5 extra `SLOAD`s (read previous slots and next slot).
|
|
|
|
## Balance Mapping
|
|
|
|
ERC721A maintains an internal mapping of address balances. This is an important and deliberate design decision:
|
|
|
|
- The `balanceOf` function is required by the ERC721 standard.
|
|
|
|
We understand that it is tempting to remove the mapping -- it can save a `SSTORE` during mints, and 2 `SSTORE`s during transfers.
|
|
|
|
However, this degrades the `balanceOf` function to become O(n) in complexity -- it must bruteforce through the entire mapping of ownerships. This hampers on-chain interoperability and scalability.
|
|
|
|
- While it is possible to emulate the `balanceOf` function by listening to emitted events, this requires users to use centralized indexing services.
|
|
|
|
In the case of service disruption, the data can get out-of-sync and hard to reconstruct.
|
|
|
|
- In the context of saving gas, we are able to allow whitelist minting achieve the same amount of `SSTORE`s when compared to implementations without the mapping. See `ERC721A._getAux` and `ERC721A._setAux`.
|
|
|
|
The address balance mapping is also used to store the mint and burn counts per address with negligible overhead, which can be very useful for tokenomics.
|
|
|
|
In all, the address balance mapping gives a good balance of features and gas savings, which makes it desirable to keep.
|
|
|