GithubHelp home page GithubHelp logo

qd-qd / erc721b Goto Github PK

View Code? Open in Web Editor NEW

This project forked from beskay/erc721b

0.0 1.0 0.0 623 KB

Gas efficient version of OpenZeppelins ERC721 contract

License: GNU General Public License v3.0

Solidity 51.30% JavaScript 48.70%

erc721b's Introduction

ERC721B

A fully compliant implementation of IERC721 with significant gas savings for minting multiple NFTs in a single transaction. Includes the Metadata and Enumerable extension.

The ERC721B standard builds on top of ERC721A from Azuki, reducing the minting fees by around ~17k gas on average and the transfer fees by around ~7k gas.

The table below shows a comparison of gas costs between a standard ERC721Enumerable contract, an ERC721A contract and this ERC721B contract. The two columns on the right show the gas savings compared to an ERC721Enumerable contract and compared to an ERC721A contract.

Table of gas savings

How it works

Removed OpenZeppelins Enumerable implementation

Out of the box, Open Zeppelin ERC721Enumerable comes with an inordinate amount of transfer processing that simply is not needed for the majority of projects. With ERC721B, the method of tracking is moved into view functions, this saves an huge amount of gas when minting or transferring your tokens. For more info on that, I highly recommend reading this medium article from nftchance.

Included optimizations from ERC721A

I included the optimizations from Azukis ERC721A contract, namely updating the owners balance only once per batch mint request, instead of per minted NFT. Thanks to this, minting is cheap -- no matter how many NFTs you mint at once. For more info, see Azukis blog post

Using an array as owner storage

This is actually the "secret ingredient", the reason why this implementation is even more gas efficient than ERC721A. Instead of using mappings to store the owner data, we use an array. So

struct AddressData {
    uint128 balance;
    uint128 numberMinted;
}
// Mapping owner address to address data
mapping(address => AddressData) private _addressData;

uint256 internal currentIndex = 0;

Becomes

// Array which maps token ID to address (index is tokenID)
address[] internal _owners;

The balance variable is substituded with a balanceOf() function call, numberMinted is removed, currentIndex can be replaced with _owners.length. This saves us a few storage writes and therefore some gas.

Following storage writes in the mint function of ERC721A

_addressData[to].balance += uint128(quantity);
_addressData[to].numberMinted += uint128(quantity);

_ownerships[startTokenId].addr = to;
_ownerships[startTokenId].startTimestamp = uint64(block.timestamp);

...

currentIndex = updatedIndex;

are substituded with

_owners.push(to);

In the image below you can see an example layout of the _owners array:

owners array

In this example wallet 0x1234...6789 minted 3 tokens and wallet 0x4567...8745 minted 4 tokens (0x9876...1234 minted an unknown number of tokens since the previous owner isnt shown).

As you can see, an owner is only set for the last minted tokenId, the previous ones keep their default value. Over time (after every token got transferred at least once) all indices will be set to a specific owner.

_checkOnERC721Received

Unlike in the standard ERC721 implementation this is only called once per batch mint. Calling this several times per batch mint is a waste of gas, if the contract confirms the receival of one token, it will accept all additional tokens too. This saves us around 5k gas per additional mint, so it adds up quite a bit.

Please note that this is an experimental feature, it could be that there are some contracts out there which use the onERC721Received function for additional logic, like sending the received NFTs to another wallet or something else, I am not aware of any though.

Installation

npm install --save-dev @beskay/erc721b

How to use

Once installed simply import the contract and inherit from it.

pragma solidity ^0.8.4;

import '@beskay/erc721b/contracts/ERC721B.sol';

contract Example is ERC721B {
  constructor() ERC721B('Example', 'EXMP') {}

  function mint(uint256 quantity) external payable {
    // _safeMint's second argument now takes in a quantity, not a tokenId.
    _safeMint(msg.sender, quantity);
  }
}

You can also take a look at ERC721BMock.sol

Recommendations

Keep max batch size limit low

Even if minting multiple tokens at once is cheap, dont set the max batch limit too high. The higher the max batch limit, the higher are the gas costs of subsequent transfers -- on average. This is due to the ownerOf() function, which iterates over the _owners array until it finds a nonzero element, so gas spent here starts off proportional to the maximum mint batch size. It gradually moves to O(1) as tokens get transferred around in the collection over time.

Dont call balanceOf() and tokenOfOwnerByIndex() on chain

The gas savings by using an _owners array instead of a mapping comes at a cost: The balanceOf() and tokenOfOwnerByIndex() are highly inefficient. They iterate over the complete array, this means if a collection has 10000 NFTs in total, they will iterate over 10k items.

Because of this, calling these functions from another smart contract can become extremely expensive, e.g. calling the balanceOf() function from a 10k NFT project costs around 22 million (!) gas, 2/3 of the block gas limit.

Fortunately, calling those two functions from another smart contract is almost never needed and if it is, you can probably substitute the call off chain: Usually you call balanceOf() to check if someone holds NFTs from a specific project in order to whitelist them (or something similiar). Instead of calling balanceOf() from your smart contract, you can check the tokenIds a wallet holds by calling tokenOfOwnerByIndex() off chain, and then prove it on chain by calling ownerOf(tokenId).

Safety

This is experimental software and is provided on an "as is" and "as available" basis. This contract is not audited yet.

It was not designed with user safety in mind. You should thoroughly read the contract before using it for your own project.

I do not give any warranties and will not be liable for any loss incurred through any use of this codebase.

FAQ

How does it compare to ERC721A?

ERC721A is designed with user safety in mind, ERC721B is designed for maximum gas savings. Its a "lightweight" ERC721A contract, so to say. This contract is still absolutely fine and safe to use, just remember that calling balanceOf() and tokenOfOwnerByIndex() on chain should be avoided.

How can i save even more gas?

If you dont want to support minting to smart contracts, you can use the mint function instead of safeMint. This saves you another ~8k gas, since _checkOnERC721Received wont be called. Make sure that smart contracts wont be able to mint to prevent loss of NFTs, e.g. by using a whitelist.

Why is the contract marked as abstract?

The contract is marked as abstract because the tokenURI function is not implemented.

Acknowledgements

These contracts were inspired by or directly modified from many sources, primarily:

A big influence was also this medium article from nftchance

Contact

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.