|
@@ -1,217 +1,326 @@
|
|
|
pragma solidity ^0.4.24;
|
|
|
|
|
|
import "./IERC721.sol";
|
|
|
-import "./ERC721Basic.sol";
|
|
|
+import "./IERC721Receiver.sol";
|
|
|
+import "../../math/SafeMath.sol";
|
|
|
+import "../../utils/Address.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
|
|
|
+ * @title ERC721 Non-Fungible Token Standard basic implementation
|
|
|
* @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)'))
|
|
|
+contract ERC721 is ERC165, IERC721 {
|
|
|
+
|
|
|
+ using SafeMath for uint256;
|
|
|
+ using Address for address;
|
|
|
+
|
|
|
+ // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
|
|
|
+ // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
|
|
|
+ bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
|
|
|
+
|
|
|
+ // Mapping from token ID to owner
|
|
|
+ mapping (uint256 => address) private _tokenOwner;
|
|
|
+
|
|
|
+ // Mapping from token ID to approved address
|
|
|
+ mapping (uint256 => address) private _tokenApprovals;
|
|
|
+
|
|
|
+ // Mapping from owner to number of owned token
|
|
|
+ mapping (address => uint256) private _ownedTokensCount;
|
|
|
+
|
|
|
+ // Mapping from owner to operator approvals
|
|
|
+ mapping (address => mapping (address => bool)) private _operatorApprovals;
|
|
|
+
|
|
|
+ bytes4 private constant _InterfaceId_ERC721 = 0x80ac58cd;
|
|
|
+ /*
|
|
|
+ * 0x80ac58cd ===
|
|
|
+ * bytes4(keccak256('balanceOf(address)')) ^
|
|
|
+ * bytes4(keccak256('ownerOf(uint256)')) ^
|
|
|
+ * bytes4(keccak256('approve(address,uint256)')) ^
|
|
|
+ * bytes4(keccak256('getApproved(uint256)')) ^
|
|
|
+ * bytes4(keccak256('setApprovalForAll(address,bool)')) ^
|
|
|
+ * bytes4(keccak256('isApprovedForAll(address,address)')) ^
|
|
|
+ * bytes4(keccak256('transferFrom(address,address,uint256)')) ^
|
|
|
+ * bytes4(keccak256('safeTransferFrom(address,address,uint256)')) ^
|
|
|
+ * bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)'))
|
|
|
*/
|
|
|
|
|
|
- bytes4 private constant _InterfaceId_ERC721Metadata = 0x5b5e139f;
|
|
|
+ constructor()
|
|
|
+ public
|
|
|
+ {
|
|
|
+ // register the supported interfaces to conform to ERC721 via ERC165
|
|
|
+ _registerInterface(_InterfaceId_ERC721);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
- * 0x5b5e139f ===
|
|
|
- * bytes4(keccak256('name()')) ^
|
|
|
- * bytes4(keccak256('symbol()')) ^
|
|
|
- * bytes4(keccak256('tokenURI(uint256)'))
|
|
|
+ * @dev Gets the balance of the specified address
|
|
|
+ * @param owner address to query the balance of
|
|
|
+ * @return uint256 representing the amount owned by the passed address
|
|
|
*/
|
|
|
+ function balanceOf(address owner) public view returns (uint256) {
|
|
|
+ require(owner != address(0));
|
|
|
+ return _ownedTokensCount[owner];
|
|
|
+ }
|
|
|
|
|
|
/**
|
|
|
- * @dev Constructor function
|
|
|
+ * @dev Gets the owner of the specified token ID
|
|
|
+ * @param tokenId uint256 ID of the token to query the owner of
|
|
|
+ * @return owner address currently marked as the owner of the given token ID
|
|
|
*/
|
|
|
- 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);
|
|
|
+ function ownerOf(uint256 tokenId) public view returns (address) {
|
|
|
+ address owner = _tokenOwner[tokenId];
|
|
|
+ require(owner != address(0));
|
|
|
+ return owner;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @dev Gets the token name
|
|
|
- * @return string representing the token name
|
|
|
+ * @dev Approves another address to transfer the given token ID
|
|
|
+ * The zero address indicates there is no approved address.
|
|
|
+ * There can only be one approved address per token at a given time.
|
|
|
+ * Can only be called by the token owner or an approved operator.
|
|
|
+ * @param to address to be approved for the given token ID
|
|
|
+ * @param tokenId uint256 ID of the token to be approved
|
|
|
*/
|
|
|
- function name() external view returns (string) {
|
|
|
- return _name;
|
|
|
+ function approve(address to, uint256 tokenId) public {
|
|
|
+ address owner = ownerOf(tokenId);
|
|
|
+ require(to != owner);
|
|
|
+ require(msg.sender == owner || isApprovedForAll(owner, msg.sender));
|
|
|
+
|
|
|
+ _tokenApprovals[tokenId] = to;
|
|
|
+ emit Approval(owner, to, tokenId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @dev Gets the token symbol
|
|
|
- * @return string representing the token symbol
|
|
|
+ * @dev Gets the approved address for a token ID, or zero if no address set
|
|
|
+ * Reverts if the token ID does not exist.
|
|
|
+ * @param tokenId uint256 ID of the token to query the approval of
|
|
|
+ * @return address currently approved for the given token ID
|
|
|
*/
|
|
|
- function symbol() external view returns (string) {
|
|
|
- return _symbol;
|
|
|
+ function getApproved(uint256 tokenId) public view returns (address) {
|
|
|
+ require(_exists(tokenId));
|
|
|
+ return _tokenApprovals[tokenId];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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
|
|
|
+ * @dev Sets or unsets the approval of a given operator
|
|
|
+ * An operator is allowed to transfer all tokens of the sender on their behalf
|
|
|
+ * @param to operator address to set the approval
|
|
|
+ * @param approved representing the status of the approval to be set
|
|
|
*/
|
|
|
- function tokenURI(uint256 tokenId) public view returns (string) {
|
|
|
- require(_exists(tokenId));
|
|
|
- return _tokenURIs[tokenId];
|
|
|
+ function setApprovalForAll(address to, bool approved) public {
|
|
|
+ require(to != msg.sender);
|
|
|
+ _operatorApprovals[msg.sender][to] = approved;
|
|
|
+ emit ApprovalForAll(msg.sender, to, approved);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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
|
|
|
+ * @dev Tells whether an operator is approved by a given owner
|
|
|
+ * @param owner owner address which you want to query the approval of
|
|
|
+ * @param operator operator address which you want to query the approval of
|
|
|
+ * @return bool whether the given operator is approved by the given owner
|
|
|
*/
|
|
|
- function tokenOfOwnerByIndex(
|
|
|
+ function isApprovedForAll(
|
|
|
address owner,
|
|
|
- uint256 index
|
|
|
+ address operator
|
|
|
)
|
|
|
public
|
|
|
view
|
|
|
- returns (uint256)
|
|
|
+ returns (bool)
|
|
|
{
|
|
|
- require(index < balanceOf(owner));
|
|
|
- return _ownedTokens[owner][index];
|
|
|
+ return _operatorApprovals[owner][operator];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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 Transfers the ownership of a given token ID to another address
|
|
|
+ * Usage of this method is discouraged, use `safeTransferFrom` whenever possible
|
|
|
+ * Requires the msg sender to be the owner, approved, or operator
|
|
|
+ * @param from current owner of the token
|
|
|
+ * @param to address to receive the ownership of the given token ID
|
|
|
+ * @param tokenId uint256 ID of the token to be transferred
|
|
|
+ */
|
|
|
+ function transferFrom(
|
|
|
+ address from,
|
|
|
+ address to,
|
|
|
+ uint256 tokenId
|
|
|
+ )
|
|
|
+ public
|
|
|
+ {
|
|
|
+ require(_isApprovedOrOwner(msg.sender, tokenId));
|
|
|
+ require(to != address(0));
|
|
|
+
|
|
|
+ _clearApproval(from, tokenId);
|
|
|
+ _removeTokenFrom(from, tokenId);
|
|
|
+ _addTokenTo(to, tokenId);
|
|
|
+
|
|
|
+ emit Transfer(from, to, tokenId);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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 Safely transfers the ownership of a given token ID to another address
|
|
|
+ * If the target address is a contract, it must implement `onERC721Received`,
|
|
|
+ * which is called upon a safe transfer, and return the magic value
|
|
|
+ * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
|
|
|
+ * the transfer is reverted.
|
|
|
+ *
|
|
|
+ * Requires the msg sender to be the owner, approved, or operator
|
|
|
+ * @param from current owner of the token
|
|
|
+ * @param to address to receive the ownership of the given token ID
|
|
|
+ * @param tokenId uint256 ID of the token to be transferred
|
|
|
+ */
|
|
|
+ function safeTransferFrom(
|
|
|
+ address from,
|
|
|
+ address to,
|
|
|
+ uint256 tokenId
|
|
|
+ )
|
|
|
+ public
|
|
|
+ {
|
|
|
+ // solium-disable-next-line arg-overflow
|
|
|
+ safeTransferFrom(from, to, tokenId, "");
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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
|
|
|
+ * @dev Safely transfers the ownership of a given token ID to another address
|
|
|
+ * If the target address is a contract, it must implement `onERC721Received`,
|
|
|
+ * which is called upon a safe transfer, and return the magic value
|
|
|
+ * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`; otherwise,
|
|
|
+ * the transfer is reverted.
|
|
|
+ * Requires the msg sender to be the owner, approved, or operator
|
|
|
+ * @param from current owner of the token
|
|
|
+ * @param to address to receive the ownership of the given token ID
|
|
|
+ * @param tokenId uint256 ID of the token to be transferred
|
|
|
+ * @param _data bytes data to send along with a safe transfer check
|
|
|
*/
|
|
|
- function _setTokenURI(uint256 tokenId, string uri) internal {
|
|
|
- require(_exists(tokenId));
|
|
|
- _tokenURIs[tokenId] = uri;
|
|
|
+ function safeTransferFrom(
|
|
|
+ address from,
|
|
|
+ address to,
|
|
|
+ uint256 tokenId,
|
|
|
+ bytes _data
|
|
|
+ )
|
|
|
+ public
|
|
|
+ {
|
|
|
+ transferFrom(from, to, tokenId);
|
|
|
+ // solium-disable-next-line arg-overflow
|
|
|
+ require(_checkAndCallSafeTransfer(from, to, tokenId, _data));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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
|
|
|
+ * @dev Returns whether the specified token exists
|
|
|
+ * @param tokenId uint256 ID of the token to query the existence of
|
|
|
+ * @return whether the token exists
|
|
|
*/
|
|
|
- function _addTokenTo(address to, uint256 tokenId) internal {
|
|
|
- super._addTokenTo(to, tokenId);
|
|
|
- uint256 length = _ownedTokens[to].length;
|
|
|
- _ownedTokens[to].push(tokenId);
|
|
|
- _ownedTokensIndex[tokenId] = length;
|
|
|
+ function _exists(uint256 tokenId) internal view returns (bool) {
|
|
|
+ address owner = _tokenOwner[tokenId];
|
|
|
+ return owner != address(0);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * @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
|
|
|
+ * @dev Returns whether the given spender can transfer a given token ID
|
|
|
+ * @param spender address of the spender to query
|
|
|
+ * @param tokenId uint256 ID of the token to be transferred
|
|
|
+ * @return bool whether the msg.sender is approved for the given token ID,
|
|
|
+ * is an operator of the owner, or is the owner of the token
|
|
|
*/
|
|
|
- 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;
|
|
|
+ function _isApprovedOrOwner(
|
|
|
+ address spender,
|
|
|
+ uint256 tokenId
|
|
|
+ )
|
|
|
+ internal
|
|
|
+ view
|
|
|
+ returns (bool)
|
|
|
+ {
|
|
|
+ address owner = ownerOf(tokenId);
|
|
|
+ // Disable solium check because of
|
|
|
+ // https://github.com/duaraghav8/Solium/issues/175
|
|
|
+ // solium-disable-next-line operator-whitespace
|
|
|
+ return (
|
|
|
+ spender == owner ||
|
|
|
+ getApproved(tokenId) == spender ||
|
|
|
+ isApprovedForAll(owner, spender)
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* @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 to The address 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);
|
|
|
+ require(to != address(0));
|
|
|
+ _addTokenTo(to, tokenId);
|
|
|
+ emit Transfer(address(0), to, 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);
|
|
|
+ _clearApproval(owner, tokenId);
|
|
|
+ _removeTokenFrom(owner, tokenId);
|
|
|
+ emit Transfer(owner, address(0), tokenId);
|
|
|
+ }
|
|
|
|
|
|
- // Clear metadata (if any)
|
|
|
- if (bytes(_tokenURIs[tokenId]).length != 0) {
|
|
|
- delete _tokenURIs[tokenId];
|
|
|
+ /**
|
|
|
+ * @dev Internal function to clear current approval of a given token ID
|
|
|
+ * Reverts if the given address is not indeed the owner of the token
|
|
|
+ * @param owner owner of the token
|
|
|
+ * @param tokenId uint256 ID of the token to be transferred
|
|
|
+ */
|
|
|
+ function _clearApproval(address owner, uint256 tokenId) internal {
|
|
|
+ require(ownerOf(tokenId) == owner);
|
|
|
+ if (_tokenApprovals[tokenId] != address(0)) {
|
|
|
+ _tokenApprovals[tokenId] = address(0);
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- // Reorg all tokens array
|
|
|
- uint256 tokenIndex = _allTokensIndex[tokenId];
|
|
|
- uint256 lastTokenIndex = _allTokens.length.sub(1);
|
|
|
- uint256 lastToken = _allTokens[lastTokenIndex];
|
|
|
-
|
|
|
- _allTokens[tokenIndex] = lastToken;
|
|
|
- _allTokens[lastTokenIndex] = 0;
|
|
|
+ /**
|
|
|
+ * @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 {
|
|
|
+ require(_tokenOwner[tokenId] == address(0));
|
|
|
+ _tokenOwner[tokenId] = to;
|
|
|
+ _ownedTokensCount[to] = _ownedTokensCount[to].add(1);
|
|
|
+ }
|
|
|
|
|
|
- _allTokens.length--;
|
|
|
- _allTokensIndex[tokenId] = 0;
|
|
|
- _allTokensIndex[lastToken] = tokenIndex;
|
|
|
+ /**
|
|
|
+ * @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 {
|
|
|
+ require(ownerOf(tokenId) == from);
|
|
|
+ _ownedTokensCount[from] = _ownedTokensCount[from].sub(1);
|
|
|
+ _tokenOwner[tokenId] = address(0);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @dev Internal function to invoke `onERC721Received` on a target address
|
|
|
+ * The call is not executed if the target address is not a contract
|
|
|
+ * @param from address representing the previous owner of the given token ID
|
|
|
+ * @param to target address that will receive the tokens
|
|
|
+ * @param tokenId uint256 ID of the token to be transferred
|
|
|
+ * @param _data bytes optional data to send along with the call
|
|
|
+ * @return whether the call correctly returned the expected magic value
|
|
|
+ */
|
|
|
+ function _checkAndCallSafeTransfer(
|
|
|
+ address from,
|
|
|
+ address to,
|
|
|
+ uint256 tokenId,
|
|
|
+ bytes _data
|
|
|
+ )
|
|
|
+ internal
|
|
|
+ returns (bool)
|
|
|
+ {
|
|
|
+ if (!to.isContract()) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ bytes4 retval = IERC721Receiver(to).onERC721Received(
|
|
|
+ msg.sender, from, tokenId, _data);
|
|
|
+ return (retval == _ERC721_RECEIVED);
|
|
|
+ }
|
|
|
}
|