Bundle ERC721 extensions into base contract (#2149)
* Add IERC721Metadata implementation into ERC721 * Add IERC721Enumerable into ERC721 * Update ERC721Pausable and ERC721Burnable * Delete ERC721Metadata, ERC721Enumerable and ERC721 (now ERC721) * Update mocks * Update tests * Update contracts/token/ERC721/ERC721.sol Co-Authored-By: Francisco Giordano <frangio.1@gmail.com> * Make ERC721Pausable and ERC721Burnable abstract Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
This commit is contained in:
@ -2,17 +2,19 @@ pragma solidity ^0.6.0;
|
||||
|
||||
import "../../GSN/Context.sol";
|
||||
import "./IERC721.sol";
|
||||
import "./IERC721Metadata.sol";
|
||||
import "./IERC721Enumerable.sol";
|
||||
import "./IERC721Receiver.sol";
|
||||
import "../../introspection/ERC165.sol";
|
||||
import "../../math/SafeMath.sol";
|
||||
import "../../utils/Address.sol";
|
||||
import "../../utils/Counters.sol";
|
||||
import "../../introspection/ERC165.sol";
|
||||
|
||||
/**
|
||||
* @title ERC721 Non-Fungible Token Standard basic implementation
|
||||
* @dev see https://eips.ethereum.org/EIPS/eip-721
|
||||
*/
|
||||
contract ERC721 is Context, ERC165, IERC721 {
|
||||
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata, IERC721Enumerable {
|
||||
using SafeMath for uint256;
|
||||
using Address for address;
|
||||
using Counters for Counters.Counter;
|
||||
@ -33,6 +35,30 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
// Mapping from owner to operator approvals
|
||||
mapping (address => mapping (address => bool)) private _operatorApprovals;
|
||||
|
||||
// Token name
|
||||
string private _name;
|
||||
|
||||
// Token symbol
|
||||
string private _symbol;
|
||||
|
||||
// Optional mapping for token URIs
|
||||
mapping(uint256 => string) private _tokenURIs;
|
||||
|
||||
// Base URI
|
||||
string private _baseURI;
|
||||
|
||||
// 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(keccak256('balanceOf(address)')) == 0x70a08231
|
||||
* bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
|
||||
@ -49,9 +75,32 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
*/
|
||||
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
|
||||
|
||||
constructor () public {
|
||||
/*
|
||||
* bytes4(keccak256('name()')) == 0x06fdde03
|
||||
* bytes4(keccak256('symbol()')) == 0x95d89b41
|
||||
* bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
|
||||
*
|
||||
* => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
|
||||
*/
|
||||
bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
|
||||
|
||||
/*
|
||||
* bytes4(keccak256('totalSupply()')) == 0x18160ddd
|
||||
* bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
|
||||
* bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
|
||||
*
|
||||
* => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
|
||||
*/
|
||||
bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
|
||||
|
||||
constructor (string memory name, string memory symbol) public {
|
||||
_name = name;
|
||||
_symbol = symbol;
|
||||
|
||||
// register the supported interfaces to conform to ERC721 via ERC165
|
||||
_registerInterface(_INTERFACE_ID_ERC721);
|
||||
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
|
||||
_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -77,6 +126,83 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
return owner;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the token name.
|
||||
* @return string representing the token name
|
||||
*/
|
||||
function name() external view override returns (string memory) {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the token symbol.
|
||||
* @return string representing the token symbol
|
||||
*/
|
||||
function symbol() external view override returns (string memory) {
|
||||
return _symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the URI for a given token ID. May return an empty string.
|
||||
*
|
||||
* If the token's URI is non-empty and a base URI was set (via
|
||||
* {_setBaseURI}), it will be added to the token ID's URI as a prefix.
|
||||
*
|
||||
* Reverts if the token ID does not exist.
|
||||
*/
|
||||
function tokenURI(uint256 tokenId) external view override returns (string memory) {
|
||||
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
|
||||
|
||||
string memory _tokenURI = _tokenURIs[tokenId];
|
||||
|
||||
// Even if there is a base URI, it is only appended to non-empty token-specific URIs
|
||||
if (bytes(_tokenURI).length == 0) {
|
||||
return "";
|
||||
} else {
|
||||
// abi.encodePacked is being used to concatenate strings
|
||||
return string(abi.encodePacked(_baseURI, _tokenURI));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the base URI set via {_setBaseURI}. This will be
|
||||
* automatically added as a preffix in {tokenURI} to each token's URI, when
|
||||
* they are non-empty.
|
||||
*/
|
||||
function baseURI() external view returns (string memory) {
|
||||
return _baseURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 override returns (uint256) {
|
||||
require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
|
||||
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 override 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 override returns (uint256) {
|
||||
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
|
||||
return _allTokens[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Approves another address to transfer the given token ID
|
||||
* The zero address indicates there is no approved address.
|
||||
@ -260,6 +386,9 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
|
||||
_beforeTokenTransfer(address(0), to, tokenId);
|
||||
|
||||
_addTokenToOwnerEnumeration(to, tokenId);
|
||||
_addTokenToAllTokensEnumeration(tokenId);
|
||||
|
||||
_tokenOwner[tokenId] = to;
|
||||
_ownedTokensCount[to].increment();
|
||||
|
||||
@ -276,6 +405,17 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
|
||||
_beforeTokenTransfer(owner, address(0), tokenId);
|
||||
|
||||
// Clear metadata (if any)
|
||||
if (bytes(_tokenURIs[tokenId]).length != 0) {
|
||||
delete _tokenURIs[tokenId];
|
||||
}
|
||||
|
||||
_removeTokenFromOwnerEnumeration(owner, tokenId);
|
||||
// Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund
|
||||
_ownedTokensIndex[tokenId] = 0;
|
||||
|
||||
_removeTokenFromAllTokensEnumeration(tokenId);
|
||||
|
||||
// Clear approvals
|
||||
_approve(address(0), tokenId);
|
||||
|
||||
@ -298,6 +438,9 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
|
||||
_beforeTokenTransfer(from, to, tokenId);
|
||||
|
||||
_removeTokenFromOwnerEnumeration(from, tokenId);
|
||||
_addTokenToOwnerEnumeration(to, tokenId);
|
||||
|
||||
// Clear approvals
|
||||
_approve(address(0), tokenId);
|
||||
|
||||
@ -309,6 +452,37 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
emit Transfer(from, to, tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to set the token URI for a given token.
|
||||
*
|
||||
* Reverts if the token ID does not exist.
|
||||
*
|
||||
* TIP: If all token IDs share a prefix (for example, if your URIs look like
|
||||
* `https://api.myproject.com/token/<id>`), use {_setBaseURI} to store
|
||||
* it and save gas.
|
||||
*/
|
||||
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
|
||||
require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
|
||||
_tokenURIs[tokenId] = _tokenURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to set the base URI for all token IDs. It is
|
||||
* automatically added as a prefix to the value returned in {tokenURI}.
|
||||
*/
|
||||
function _setBaseURI(string memory baseURI_) internal virtual {
|
||||
_baseURI = baseURI_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the list of token IDs of the requested owner.
|
||||
* @param owner address owning the tokens
|
||||
* @return uint256[] List of token IDs owned by the requested address
|
||||
*/
|
||||
function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
|
||||
return _ownedTokens[owner];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
|
||||
* The call is not executed if the target address is not a contract.
|
||||
@ -354,6 +528,81 @@ contract ERC721 is Context, ERC165, IERC721 {
|
||||
emit Approval(ownerOf(tokenId), to, tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
_ownedTokensIndex[tokenId] = _ownedTokens[to].length;
|
||||
_ownedTokens[to].push(tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = _ownedTokens[from].length.sub(1);
|
||||
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
|
||||
}
|
||||
|
||||
// Deletes the contents at the last position of the array
|
||||
_ownedTokens[from].pop();
|
||||
|
||||
// Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by
|
||||
// lastTokenId, or just over the end of the array if the token was the last one).
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.sub(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
|
||||
|
||||
// Delete the contents at the last position of the array
|
||||
_allTokens.pop();
|
||||
|
||||
_allTokensIndex[tokenId] = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Hook that is called before any token transfer. This includes minting
|
||||
* and burning.
|
||||
|
||||
@ -7,7 +7,7 @@ import "./ERC721.sol";
|
||||
* @title ERC721 Burnable Token
|
||||
* @dev ERC721 Token that can be irreversibly burned (destroyed).
|
||||
*/
|
||||
contract ERC721Burnable is Context, ERC721 {
|
||||
abstract contract ERC721Burnable is Context, ERC721 {
|
||||
/**
|
||||
* @dev Burns a specific ERC721 token.
|
||||
* @param tokenId uint256 id of the ERC721 token to be burned.
|
||||
|
||||
@ -1,175 +0,0 @@
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "../../GSN/Context.sol";
|
||||
import "./IERC721Enumerable.sol";
|
||||
import "./ERC721.sol";
|
||||
import "../../introspection/ERC165.sol";
|
||||
|
||||
/**
|
||||
* @title ERC-721 Non-Fungible Token with optional enumeration extension logic
|
||||
* @dev See https://eips.ethereum.org/EIPS/eip-721
|
||||
*/
|
||||
contract ERC721Enumerable is Context, 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(keccak256('totalSupply()')) == 0x18160ddd
|
||||
* bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
|
||||
* bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
|
||||
*
|
||||
* => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
|
||||
*/
|
||||
bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
|
||||
|
||||
/**
|
||||
* @dev Constructor function.
|
||||
*/
|
||||
constructor () public {
|
||||
// register the supported interface to conform to ERC721Enumerable via ERC165
|
||||
_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 override returns (uint256) {
|
||||
require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
|
||||
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 override 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 override returns (uint256) {
|
||||
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
|
||||
return _allTokens[index];
|
||||
}
|
||||
|
||||
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
|
||||
super._beforeTokenTransfer(from, to, tokenId);
|
||||
|
||||
if (from == address(0)) {
|
||||
// When minting
|
||||
_addTokenToOwnerEnumeration(to, tokenId);
|
||||
_addTokenToAllTokensEnumeration(tokenId);
|
||||
} else if (to == address(0)) {
|
||||
// When burning
|
||||
_removeTokenFromOwnerEnumeration(from, tokenId);
|
||||
// Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund
|
||||
_ownedTokensIndex[tokenId] = 0;
|
||||
|
||||
_removeTokenFromAllTokensEnumeration(tokenId);
|
||||
} else {
|
||||
_removeTokenFromOwnerEnumeration(from, tokenId);
|
||||
_addTokenToOwnerEnumeration(to, tokenId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the list of token IDs of the requested owner.
|
||||
* @param owner address owning the tokens
|
||||
* @return uint256[] List of token IDs owned by the requested address
|
||||
*/
|
||||
function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
|
||||
return _ownedTokens[owner];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 {
|
||||
_ownedTokensIndex[tokenId] = _ownedTokens[to].length;
|
||||
_ownedTokens[to].push(tokenId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = _ownedTokens[from].length.sub(1);
|
||||
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
|
||||
}
|
||||
|
||||
// Deletes the contents at the last position of the array
|
||||
_ownedTokens[from].pop();
|
||||
|
||||
// Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by
|
||||
// lastTokenId, or just over the end of the array if the token was the last one).
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.sub(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
|
||||
|
||||
// Delete the contents at the last position of the array
|
||||
_allTokens.pop();
|
||||
|
||||
_allTokensIndex[tokenId] = 0;
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "./ERC721Enumerable.sol";
|
||||
import "./ERC721Metadata.sol";
|
||||
|
||||
/**
|
||||
* @title Full ERC721 Token
|
||||
* @dev This implementation includes all the required and some optional functionality of the ERC721 standard
|
||||
* Moreover, it includes approve all functionality using operator terminology.
|
||||
*
|
||||
* See https://eips.ethereum.org/EIPS/eip-721
|
||||
*/
|
||||
contract ERC721Full is ERC721Enumerable, ERC721Metadata {
|
||||
constructor (string memory name, string memory symbol) public ERC721Metadata(name, symbol) { }
|
||||
|
||||
function _beforeTokenTransfer(address from, address to, uint256 tokenId)
|
||||
virtual
|
||||
override(ERC721Enumerable, ERC721Metadata)
|
||||
internal
|
||||
{
|
||||
super._beforeTokenTransfer(from, to, tokenId);
|
||||
}
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "../../GSN/Context.sol";
|
||||
import "./ERC721.sol";
|
||||
import "./IERC721Metadata.sol";
|
||||
import "../../introspection/ERC165.sol";
|
||||
|
||||
contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata {
|
||||
// Token name
|
||||
string private _name;
|
||||
|
||||
// Token symbol
|
||||
string private _symbol;
|
||||
|
||||
// Optional mapping for token URIs
|
||||
mapping(uint256 => string) private _tokenURIs;
|
||||
|
||||
// Base URI
|
||||
string private _baseURI;
|
||||
|
||||
/*
|
||||
* bytes4(keccak256('name()')) == 0x06fdde03
|
||||
* bytes4(keccak256('symbol()')) == 0x95d89b41
|
||||
* bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
|
||||
*
|
||||
* => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
|
||||
*/
|
||||
bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
|
||||
|
||||
/**
|
||||
* @dev Constructor function
|
||||
*/
|
||||
constructor (string memory name, string memory symbol) public {
|
||||
_name = name;
|
||||
_symbol = symbol;
|
||||
|
||||
// register the supported interfaces to conform to ERC721 via ERC165
|
||||
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the token name.
|
||||
* @return string representing the token name
|
||||
*/
|
||||
function name() external view override returns (string memory) {
|
||||
return _name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Gets the token symbol.
|
||||
* @return string representing the token symbol
|
||||
*/
|
||||
function symbol() external view override returns (string memory) {
|
||||
return _symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the URI for a given token ID. May return an empty string.
|
||||
*
|
||||
* If the token's URI is non-empty and a base URI was set (via
|
||||
* {_setBaseURI}), it will be added to the token ID's URI as a prefix.
|
||||
*
|
||||
* Reverts if the token ID does not exist.
|
||||
*/
|
||||
function tokenURI(uint256 tokenId) external view override returns (string memory) {
|
||||
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
|
||||
|
||||
string memory _tokenURI = _tokenURIs[tokenId];
|
||||
|
||||
// Even if there is a base URI, it is only appended to non-empty token-specific URIs
|
||||
if (bytes(_tokenURI).length == 0) {
|
||||
return "";
|
||||
} else {
|
||||
// abi.encodePacked is being used to concatenate strings
|
||||
return string(abi.encodePacked(_baseURI, _tokenURI));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to set the token URI for a given token.
|
||||
*
|
||||
* Reverts if the token ID does not exist.
|
||||
*
|
||||
* TIP: if all token IDs share a prefix (e.g. if your URIs look like
|
||||
* `http://api.myproject.com/token/<id>`), use {_setBaseURI} to store
|
||||
* it and save gas.
|
||||
*/
|
||||
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
|
||||
require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
|
||||
_tokenURIs[tokenId] = _tokenURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to set the base URI for all token IDs. It is
|
||||
* automatically added as a prefix to the value returned in {tokenURI}.
|
||||
*/
|
||||
function _setBaseURI(string memory baseURI) internal virtual {
|
||||
_baseURI = baseURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the base URI set via {_setBaseURI}. This will be
|
||||
* automatically added as a preffix in {tokenURI} to each token's URI, when
|
||||
* they are non-empty.
|
||||
*/
|
||||
function baseURI() external view returns (string memory) {
|
||||
return _baseURI;
|
||||
}
|
||||
|
||||
|
||||
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
|
||||
super._beforeTokenTransfer(from, to, tokenId);
|
||||
|
||||
if (to == address(0)) { // When burning tokens
|
||||
// Clear metadata (if any)
|
||||
if (bytes(_tokenURIs[tokenId]).length != 0) {
|
||||
delete _tokenURIs[tokenId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,7 +7,7 @@ import "../../utils/Pausable.sol";
|
||||
* @title ERC721 Non-Fungible Pausable token
|
||||
* @dev ERC721 modified with pausable transfers.
|
||||
*/
|
||||
contract ERC721Pausable is ERC721, Pausable {
|
||||
abstract contract ERC721Pausable is ERC721, Pausable {
|
||||
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal virtual override {
|
||||
super._beforeTokenTransfer(from, to, tokenId);
|
||||
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
pragma solidity ^0.6.0;
|
||||
|
||||
import "./IERC721.sol";
|
||||
import "./IERC721Enumerable.sol";
|
||||
import "./IERC721Metadata.sol";
|
||||
|
||||
/**
|
||||
* @title ERC-721 Non-Fungible Token Standard, full implementation interface
|
||||
* @dev See https://eips.ethereum.org/EIPS/eip-721
|
||||
*/
|
||||
abstract contract IERC721Full is IERC721, IERC721Enumerable, IERC721Metadata { }
|
||||
Reference in New Issue
Block a user