OpenZeppelin / OpenZeppelin/openzeppelin-contracts
ERC721Consecutive Meets ERC721Enumerable: A Compatibility Proof-of-Concept
Nobody has claimed this yet.
- Dominant language
- Solidity
- Stars
- 27.2k
- Forks
- 12.4k
- Avg merge
- 2d 19h
- Merged PRs (30d)
- 33
Description
🧐 Motivation
ERC721Consecutive make batch minting possible but it is not IERC721Enumerable compatible.
the following contract Is a proof of concept that shows with some minor changes to ERC721Consecutive and ERC721Enumerable.
it can be merge to one contract. with following features:
- batch minting during constructor.
- single minting after deployment.
- single burning of a token.
- full IERC721Enumerable compatibility.
- all of the indexing functions are O(1)
📝 Details
the main idea is , how to find the indices of tokens that are not initialized by single minting (like in ERC721Enumerable), while the mappings are empty and they have default value uint 0.
as an example.
tokenByIndex(uint256 index)
since the tokens that are created from batch minting start from tokenId 0 to totalConsecutiveSupply() -1
and Index of tokens have not initialized we can consider TokenIndex the same as tokenId
and if new tokens were minted we initialize them _allIndexToTokenId and _allTokenIdToIndex
and if we burned a token we can initialize the index by a swap operation if it's necessary see _removeTokenFromAllTokensEnumeration(uint256 tokenId)
upon reading the index if _allIndexToTokenId and _allTokenIdToIndex have not initialized and the value inside the mapping was uint(0) it means the token was batch minted and the value is not initialized, so the tokenId is the same as index and the index is the same as tokenId
note:
note: to remove the ambiguity of value uint(0) with default value of mapping that is also uint(0) while populating the
_allIndexToTokenId and _allTokenIdToIndex we write the values + 1 and when we want to access the value if we -1
see tokenByIndex(uint256 index) and _indexByToken(uint256 tokenId) and _addTokenToAllTokensEnumeration(uint256 tokenId) and _removeTokenFromAllTokensEnumeration(uint256 tokenId)
//list of all tokenId available(some values are available virtauly to access use tokenByIndex(_index))
mapping(uint256 => uint256) private _allIndexToTokenId;
//mapping of tokenId to index in _allIndexToTokenId(some value are available virtual to access use _indexByToken(tokenId))
mapping(uint256 => uint256) private _allTokenIdToIndex;
/**
* @dev See {IERC721Enumerable-tokenByIndex}.
*/
/**
* @dev handling tokens index virtually
*/
function tokenByIndex(uint256 index)
public
view
virtual
override
returns (uint256)
{
require(
index < ERC721CE.totalSupply(),
"ERC721Enumerable: global index out of bounds"
);
uint256 virtualIndex = _allIndexToTokenId[index];
//if mapping is empty, index is the same as tokenId, since they are all sequential and start from 0
if (virtualIndex == 0) {
return index;
}
return virtualIndex - 1; //decrement one (-1) to get the value, overflow is impossible because the virtualIndex is not 0.
}
////provied the token Index in the list of all tokens that have been created.
function _indexByToken(uint256 tokenId) private view returns (uint256) {
uint256 virtualIndex = _allTokenIdToIndex[tokenId];
//if mapping is empty, tokenId is the same as index, since they are all sequential and start from 0
if (virtualIndex == 0) {
return tokenId;
}
return virtualIndex - 1; //decrement one (-1) to get the value,overflow is impossible becuase the virtualIndex is not 0.
}
/**
* @dev Private function to add a token to this extension's token tracking data structures.
* @param tokenId uint256 ID of the token to be added to the tokens list
* write values + 1 to avoid confusion to mapping default value of uint (uint(0))
*/
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
uint256 _index = totalSupply();
// + 1 to remove the ambiguity of value with default value(uint 0) in mapping of _allIndexToTokenId and _allTokenIdToIndex
_allIndexToTokenId[_index] = tokenId + 1;
_allTokenIdToIndex[tokenId] = _index + 1;
}
/**
* @dev Private function to remove a token from this extension's token tracking data structures.
* This has O(1) time complexity, but works with to new mapping _allIndexToTokenId, _allTokenIdToIndex
* functionality is more similar to _removeTokenFromOwnerEnumeration(address from, uint256 tokenId)
* @param tokenId uint256 ID of the token to be removed from the tokens list
*/
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
uint256 lastIndex = totalSupply() - 1;
// uint256 tokenIndex = tokenByIndex(tokenId);
uint256 tokenIndex = _indexByToken(tokenId);
if (lastIndex != tokenIndex) {
uint256 lastTokenId = tokenByIndex(lastIndex);
// + 1 to remove the ambiguity of value with default value(uint 0) in mapping of _allIndexToTokenId and _allTokenIdToIndex
_allIndexToTokenId[tokenIndex] = lastTokenId + 1;
_allTokenIdToIndex[lastTokenId] = tokenIndex + 1;
}
delete _allIndexToTokenId[lastIndex];
delete _allTokenIdToIndex[tokenId];
}
this implementation can be a stand alone extension ERC721ConsecutiveEnumerable.sol or it can be break down as a changes to ERC721Consecutive.sol and ERC721Enumerable to make them compatible with each other.
github - ERC721ConsecutiveEnumerable
Goerli testnet : 0xaF8fA6fC07Da285a94f37148218fFd170eE827ff
thank you for your time.
sciNFTist.eth
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reviewing ERC721Consecutive.sol and ERC721Enumerable, then compare their enumeration entry points with the linked ERC721ConsecutiveEnumerable proof of concept. Done means a decided implementation that supports constructor batch minting, post-deployment minting, token burning, full IERC721Enumerable compatibility, and O(1) indexing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- solidity
- Domain
- blockchain
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100