|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/ERC721Enumerable.sol)
|
|
|
|
|
|
|
|
pragma solidity ^0.8.20;
|
|
|
|
|
|
|
|
import {ERC721} from "../ERC721.sol";
|
|
|
|
import {IERC721Enumerable} from "./IERC721Enumerable.sol";
|
|
|
|
import {IERC165} from "../../../utils/introspection/ERC165.sol";
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev This implements an optional extension of {ERC721} defined in the ERC that adds enumerability
|
|
|
|
* of all the token ids in the contract as well as all token ids owned by each account.
|
|
|
|
*
|
|
|
|
* CAUTION: {ERC721} extensions that implement custom `balanceOf` logic, such as {ERC721Consecutive},
|
|
|
|
* interfere with enumerability and should not be used together with {ERC721Enumerable}.
|
|
|
|
*/
|
|
|
|
abstract contract ERC721Enumerable is ERC721, IERC721Enumerable {
|
|
|
|
mapping(address owner => mapping(uint256 index => uint256)) private _ownedTokens;
|
|
|
|
mapping(uint256 tokenId => uint256) private _ownedTokensIndex;
|
|
|
|
|
|
|
|
uint256[] private _allTokens;
|
|
|
|
mapping(uint256 tokenId => uint256) private _allTokensIndex;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev An `owner`'s token query was out of bounds for `index`.
|
|
|
|
*
|
|
|
|
* NOTE: The owner being `address(0)` indicates a global out of bounds index.
|
|
|
|
*/
|
|
|
|
error ERC721OutOfBoundsIndex(address owner, uint256 index);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Batch mint is not allowed.
|
|
|
|
*/
|
|
|
|
error ERC721EnumerableForbiddenBatchMint();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC165-supportsInterface}.
|
|
|
|
*/
|
|
|
|
function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721) returns (bool) {
|
|
|
|
return interfaceId == type(IERC721Enumerable).interfaceId || super.supportsInterface(interfaceId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC721Enumerable-tokenOfOwnerByIndex}.
|
|
|
|
*/
|
|
|
|
function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) {
|
|
|
|
if (index >= balanceOf(owner)) {
|
|
|
|
revert ERC721OutOfBoundsIndex(owner, index);
|
|
|
|
}
|
|
|
|
return _ownedTokens[owner][index];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC721Enumerable-totalSupply}.
|
|
|
|
*/
|
|
|
|
function totalSupply() public view virtual returns (uint256) {
|
|
|
|
return _allTokens.length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {IERC721Enumerable-tokenByIndex}.
|
|
|
|
*/
|
|
|
|
function tokenByIndex(uint256 index) public view virtual returns (uint256) {
|
|
|
|
if (index >= totalSupply()) {
|
|
|
|
revert ERC721OutOfBoundsIndex(address(0), index);
|
|
|
|
}
|
|
|
|
return _allTokens[index];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev See {ERC721-_update}.
|
|
|
|
*/
|
|
|
|
function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) {
|
|
|
|
address previousOwner = super._update(to, tokenId, auth);
|
|
|
|
|
|
|
|
if (previousOwner == address(0)) {
|
|
|
|
_addTokenToAllTokensEnumeration(tokenId);
|
|
|
|
} else if (previousOwner != to) {
|
|
|
|
_removeTokenFromOwnerEnumeration(previousOwner, tokenId);
|
|
|
|
}
|
|
|
|
if (to == address(0)) {
|
|
|
|
_removeTokenFromAllTokensEnumeration(tokenId);
|
|
|
|
} else if (previousOwner != to) {
|
|
|
|
_addTokenToOwnerEnumeration(to, tokenId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return previousOwner;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Private function to add a token to this extension's ownership-tracking data structures.
|
|
|
|
* @param to address representing the new owner of the given token ID
|
|
|
|
* @param tokenId uint256 ID of the token to be added to the tokens list of the given address
|
|
|
|
*/
|
|
|
|
function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
|
|
|
|
uint256 length = balanceOf(to) - 1;
|
|
|
|
_ownedTokens[to][length] = tokenId;
|
|
|
|
_ownedTokensIndex[tokenId] = length;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
|
|
|
|
_allTokensIndex[tokenId] = _allTokens.length;
|
|
|
|
_allTokens.push(tokenId);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
|
|
|
|
* while the token is not assigned a new owner, the `_ownedTokensIndex` mapping is _not_ updated: this allows for
|
|
|
|
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
|
|
|
|
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
|
|
|
|
* @param from address representing the previous owner of the given token ID
|
|
|
|
* @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
|
|
|
|
*/
|
|
|
|
function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
|
|
|
|
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
|
|
|
|
// then delete the last slot (swap and pop).
|
|
|
|
|
|
|
|
uint256 lastTokenIndex = balanceOf(from);
|
|
|
|
uint256 tokenIndex = _ownedTokensIndex[tokenId];
|
|
|
|
|
|
|
|
// When the token to delete is the last token, the swap operation is unnecessary
|
|
|
|
if (tokenIndex != lastTokenIndex) {
|
|
|
|
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
|
|
|
|
|
|
|
|
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
|
|
|
|
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
|
|
|
|
}
|
|
|
|
|
|
|
|
// This also deletes the contents at the last position of the array
|
|
|
|
delete _ownedTokensIndex[tokenId];
|
|
|
|
delete _ownedTokens[from][lastTokenIndex];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @dev Private function to remove a token from this extension's token tracking data structures.
|
|
|
|
* This has O(1) time complexity, but alters the order of the _allTokens array.
|
|
|
|
* @param tokenId uint256 ID of the token to be removed from the tokens list
|
|
|
|
*/
|
|
|
|
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
|
|
|
|
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
|
|
|
|
// then delete the last slot (swap and pop).
|
|
|
|
|
|
|
|
uint256 lastTokenIndex = _allTokens.length - 1;
|
|
|
|
uint256 tokenIndex = _allTokensIndex[tokenId];
|
|
|
|
|
|
|
|
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
|
|
|
|
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
|
|
|
|
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
|
|
|
|
uint256 lastTokenId = _allTokens[lastTokenIndex];
|
|
|
|
|
|
|
|
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
|
|
|
|
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
|
|
|
|
|
|
|
|
// This also deletes the contents at the last position of the array
|
|
|
|
delete _allTokensIndex[tokenId];
|
|
|
|
_allTokens.pop();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* See {ERC721-_increaseBalance}. We need that to account tokens that were minted in batch
|
|
|
|
*/
|
|
|
|
function _increaseBalance(address account, uint128 amount) internal virtual override {
|
|
|
|
if (amount > 0) {
|
|
|
|
revert ERC721EnumerableForbiddenBatchMint();
|
|
|
|
}
|
|
|
|
super._increaseBalance(account, amount);
|
|
|
|
}
|
|
|
|
}
|