pragma solidity ^0.4.24; import "./IERC721.sol"; import "./ERC721Basic.sol"; import "../../introspection/ERC165.sol"; /** * @title Full ERC721 Token * This implementation includes all the required and some optional functionality of the ERC721 standard * Moreover, it includes approve all functionality using operator terminology * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md */ contract ERC721 is ERC165, ERC721Basic, IERC721 { // Token name string internal name_; // Token symbol string internal symbol_; // 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_; // Optional mapping for token URIs mapping(uint256 => string) private tokenURIs_; bytes4 private constant InterfaceId_ERC721Enumerable = 0x780e9d63; /** * 0x780e9d63 === * bytes4(keccak256('totalSupply()')) ^ * bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^ * bytes4(keccak256('tokenByIndex(uint256)')) */ bytes4 private constant InterfaceId_ERC721Metadata = 0x5b5e139f; /** * 0x5b5e139f === * bytes4(keccak256('name()')) ^ * bytes4(keccak256('symbol()')) ^ * bytes4(keccak256('tokenURI(uint256)')) */ /** * @dev Constructor function */ constructor(string _name, string _symbol) public { name_ = _name; symbol_ = _symbol; // register the supported interfaces to conform to ERC721 via ERC165 _registerInterface(InterfaceId_ERC721Enumerable); _registerInterface(InterfaceId_ERC721Metadata); } /** * @dev Gets the token name * @return string representing the token name */ function name() external view returns (string) { return name_; } /** * @dev Gets the token symbol * @return string representing the token symbol */ function symbol() external view returns (string) { return symbol_; } /** * @dev Returns an URI for a given token ID * Throws if the token ID does not exist. May return an empty string. * @param _tokenId uint256 ID of the token to query */ function tokenURI(uint256 _tokenId) public view returns (string) { require(_exists(_tokenId)); return tokenURIs_[_tokenId]; } /** * @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 set the token URI for a given token * Reverts if the token ID does not exist * @param _tokenId uint256 ID of the token to set its URI * @param _uri string URI to assign */ function _setTokenURI(uint256 _tokenId, string _uri) internal { require(_exists(_tokenId)); tokenURIs_[_tokenId] = _uri; } /** * @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); // Clear metadata (if any) if (bytes(tokenURIs_[_tokenId]).length != 0) { delete tokenURIs_[_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; } }