pragma solidity ^0.4.24; import "./IERC721Enumerable.sol"; import "./ERC721.sol"; import "../../introspection/ERC165.sol"; contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable { // Mapping from owner to list of owned token IDs mapping(address => uint256[]) private _ownedTokens; // Mapping from token ID to index of the owner tokens list mapping(uint256 => uint256) private _ownedTokensIndex; // Array with all token ids, used for enumeration uint256[] private _allTokens; // Mapping from token id to position in the allTokens array mapping(uint256 => uint256) private _allTokensIndex; bytes4 private constant _InterfaceId_ERC721Enumerable = 0x780e9d63; /** * 0x780e9d63 === * bytes4(keccak256('totalSupply()')) ^ * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ * bytes4(keccak256('tokenByIndex(uint256)')) */ /** * @dev Constructor function */ constructor() public { // register the supported interface to conform to ERC721 via ERC165 _registerInterface(_InterfaceId_ERC721Enumerable); } /** * @dev Gets the token ID at a given index of the tokens list of the requested owner * @param owner address owning the tokens list to be accessed * @param index uint256 representing the index to be accessed of the requested tokens list * @return uint256 token ID at the given index of the tokens list owned by the requested address */ function tokenOfOwnerByIndex( address owner, uint256 index ) public view returns (uint256) { require(index < balanceOf(owner)); return _ownedTokens[owner][index]; } /** * @dev Gets the total amount of tokens stored by the contract * @return uint256 representing the total amount of tokens */ function totalSupply() public view returns (uint256) { return _allTokens.length; } /** * @dev Gets the token ID at a given index of all the tokens in this contract * Reverts if the index is greater or equal to the total number of tokens * @param index uint256 representing the index to be accessed of the tokens list * @return uint256 token ID at the given index of the tokens list */ function tokenByIndex(uint256 index) public view returns (uint256) { require(index < totalSupply()); return _allTokens[index]; } /** * @dev Internal function to add a token ID to the list of a given address * @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 _addTokenTo(address to, uint256 tokenId) internal { super._addTokenTo(to, tokenId); uint256 length = _ownedTokens[to].length; _ownedTokens[to].push(tokenId); _ownedTokensIndex[tokenId] = length; } /** * @dev Internal function to remove a token ID from the list of a given address * @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 _removeTokenFrom(address from, uint256 tokenId) internal { super._removeTokenFrom(from, tokenId); // To prevent a gap in the array, we store the last token in the index of the token to delete, and // then delete the last slot. uint256 tokenIndex = _ownedTokensIndex[tokenId]; uint256 lastTokenIndex = _ownedTokens[from].length.sub(1); uint256 lastToken = _ownedTokens[from][lastTokenIndex]; _ownedTokens[from][tokenIndex] = lastToken; // This also deletes the contents at the last position of the array _ownedTokens[from].length--; // Note that this will handle single-element arrays. In that case, both tokenIndex and lastTokenIndex are going to // be zero. Then we can make sure that we will remove tokenId from the ownedTokens list since we are first swapping // the lastToken to the first position, and then dropping the element placed in the last position of the list _ownedTokensIndex[tokenId] = 0; _ownedTokensIndex[lastToken] = tokenIndex; } /** * @dev Internal function to mint a new token * Reverts if the given token ID already exists * @param to address the beneficiary that will own the minted token * @param tokenId uint256 ID of the token to be minted by the msg.sender */ function _mint(address to, uint256 tokenId) internal { super._mint(to, tokenId); _allTokensIndex[tokenId] = _allTokens.length; _allTokens.push(tokenId); } /** * @dev Internal function to burn a specific token * Reverts if the token does not exist * @param owner owner of the token to burn * @param tokenId uint256 ID of the token being burned by the msg.sender */ function _burn(address owner, uint256 tokenId) internal { super._burn(owner, tokenId); // Reorg all tokens array uint256 tokenIndex = _allTokensIndex[tokenId]; uint256 lastTokenIndex = _allTokens.length.sub(1); uint256 lastToken = _allTokens[lastTokenIndex]; _allTokens[tokenIndex] = lastToken; _allTokens[lastTokenIndex] = 0; _allTokens.length--; _allTokensIndex[tokenId] = 0; _allTokensIndex[lastToken] = tokenIndex; } }