aboutsummaryrefslogtreecommitdiffstats
path: root/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol
diff options
context:
space:
mode:
Diffstat (limited to 'packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol')
-rw-r--r--packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol554
1 files changed, 212 insertions, 342 deletions
diff --git a/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol b/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol
index 60603aa19..530f080c0 100644
--- a/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol
+++ b/packages/contracts/src/2.0.0/tokens/ERC721Token/ERC721Token.sol
@@ -1,26 +1,19 @@
/*
-The MIT License (MIT)
-Copyright (c) 2016 Smart Contract Solutions, Inc.
+ Copyright 2018 ZeroEx Intl.
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
-The above copyright notice and this permission notice shall be included
-in all copies or substantial portions of the Software.
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
-OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
pragma solidity 0.4.24;
@@ -30,179 +23,250 @@ import "./IERC721Receiver.sol";
import "../../utils/SafeMath/SafeMath.sol";
-/**
- * @title ERC721 Non-Fungible Token Standard basic implementation
- * @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
- * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721BasicToken.sol
- */
contract ERC721Token is
IERC721Token,
SafeMath
{
- // Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`
- // which can be also obtained as `ERC721Receiver(0).onERC721Received.selector`
- bytes4 constant internal ERC721_RECEIVED = 0xf0b9e5ba;
+ // Function selector for ERC721Receiver.onERC721Received
+ // 0x150b7a02
+ bytes4 constant internal ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"));
- // Mapping from token ID to owner
- mapping (uint256 => address) internal tokenOwner;
+ // Mapping of tokenId => owner
+ mapping (uint256 => address) internal owners;
- // Mapping from token ID to approved address
- mapping (uint256 => address) internal tokenApprovals;
+ // Mapping of tokenId => approved address
+ mapping (uint256 => address) internal approvals;
- // Mapping from owner to number of owned token
- mapping (address => uint256) internal ownedTokensCount;
+ // Mapping of owner => number of tokens owned
+ mapping (address => uint256) internal balances;
- // Mapping from owner to operator approvals
+ // Mapping of owner => operator => approved
mapping (address => mapping (address => bool)) internal operatorApprovals;
- /**
- * @dev Guarantees msg.sender is owner of the given token
- * @param _tokenId uint256 ID of the token to validate its ownership belongs to msg.sender
- */
- modifier onlyOwnerOf(uint256 _tokenId) {
- require(ownerOf(_tokenId) == msg.sender);
- _;
- }
-
- /**
- * @dev Checks msg.sender can transfer a token, by being owner, approved, or operator
- * @param _tokenId uint256 ID of the token to validate
- */
- modifier canTransfer(uint256 _tokenId) {
- require(isApprovedOrOwner(msg.sender, _tokenId));
- _;
+ /// @notice Transfers the ownership of an NFT from one address to another address
+ /// @dev Throws unless `msg.sender` is the current owner, an authorized
+ /// operator, or the approved address for this NFT. Throws if `_from` is
+ /// not the current owner. Throws if `_to` is the zero address. Throws if
+ /// `_tokenId` is not a valid NFT. When transfer is complete, this function
+ /// checks if `_to` is a smart contract (code size > 0). If so, it calls
+ /// `onERC721Received` on `_to` and throws if the return value is not
+ /// `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
+ /// @param _from The current owner of the NFT
+ /// @param _to The new owner
+ /// @param _tokenId The NFT to transfer
+ /// @param _data Additional data with no specified format, sent in call to `_to`
+ function safeTransferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId,
+ bytes _data
+ )
+ external
+ {
+ transferFrom(
+ _from,
+ _to,
+ _tokenId
+ );
+
+ uint256 receiverCodeSize;
+ assembly {
+ receiverCodeSize := extcodesize(_to)
+ }
+ if (receiverCodeSize > 0) {
+ bytes4 selector = IERC721Receiver(_to).onERC721Received(
+ msg.sender,
+ _from,
+ _tokenId,
+ _data
+ );
+ require(
+ selector == ERC721_RECEIVED,
+ "ERC721_INVALID_SELECTOR"
+ );
+ }
}
- constructor (
- string _name,
- string _symbol)
- public
+ /// @notice Transfers the ownership of an NFT from one address to another address
+ /// @dev This works identically to the other function with an extra data parameter,
+ /// except this function just sets data to "".
+ /// @param _from The current owner of the NFT
+ /// @param _to The new owner
+ /// @param _tokenId The NFT to transfer
+ function safeTransferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId
+ )
+ external
{
- name_ = _name;
- symbol_ = _symbol;
+ transferFrom(
+ _from,
+ _to,
+ _tokenId
+ );
+
+ uint256 receiverCodeSize;
+ assembly {
+ receiverCodeSize := extcodesize(_to)
+ }
+ if (receiverCodeSize > 0) {
+ bytes4 selector = IERC721Receiver(_to).onERC721Received(
+ msg.sender,
+ _from,
+ _tokenId,
+ ""
+ );
+ require(
+ selector == ERC721_RECEIVED,
+ "ERC721_INVALID_SELECTOR"
+ );
+ }
}
- /**
- * @dev Gets the token name
- * @return string representing the token name
- */
- function name()
- public
- view
- returns (string)
+ /// @notice Change or reaffirm the approved address for an NFT
+ /// @dev The zero address indicates there is no approved address.
+ /// Throws unless `msg.sender` is the current NFT owner, or an authorized
+ /// operator of the current owner.
+ /// @param _approved The new approved NFT controller
+ /// @param _tokenId The NFT to approve
+ function approve(address _approved, uint256 _tokenId)
+ external
{
- return name_;
+ address owner = ownerOf(_tokenId);
+ require(
+ msg.sender == owner || isApprovedForAll(owner, msg.sender),
+ "ERC721_INVALID_SENDER"
+ );
+
+ approvals[_tokenId] = _approved;
+ emit Approval(
+ owner,
+ _approved,
+ _tokenId
+ );
}
- /**
- * @dev Gets the token symbol
- * @return string representing the token symbol
- */
- function symbol()
- public
- view
- returns (string)
+ /// @notice Enable or disable approval for a third party ("operator") to manage
+ /// all of `msg.sender`'s assets
+ /// @dev Emits the ApprovalForAll event. The contract MUST allow
+ /// multiple operators per owner.
+ /// @param _operator Address to add to the set of authorized operators
+ /// @param _approved True if the operator is approved, false to revoke approval
+ function setApprovalForAll(address _operator, bool _approved)
+ external
{
- return symbol_;
+ operatorApprovals[msg.sender][_operator] = _approved;
+ emit ApprovalForAll(
+ msg.sender,
+ _operator,
+ _approved
+ );
}
-
- /**
- * @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
- */
+
+ /// @notice Count all NFTs assigned to an owner
+ /// @dev NFTs assigned to the zero address are considered invalid, and this
+ /// function throws for queries about the zero address.
+ /// @param _owner An address for whom to query the balance
+ /// @return The number of NFTs owned by `_owner`, possibly zero
function balanceOf(address _owner)
- public
+ external
view
returns (uint256)
{
- require(_owner != address(0));
- return ownedTokensCount[_owner];
+ require(
+ _owner != address(0),
+ "ERC721_ZERO_OWNER"
+ );
+ return balances[_owner];
}
- /**
- * @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
- */
- function ownerOf(uint256 _tokenId)
- public
- view
- returns (address)
- {
- address owner = tokenOwner[_tokenId];
- require(owner != address(0));
- return owner;
- }
-
- /**
- * @dev Returns whether the specified token exists
- * @param _tokenId uint256 ID of the token to query the existance of
- * @return whether the token exists
- */
- function exists(uint256 _tokenId)
+ /// @notice Transfer ownership of an NFT -- THE CALLER IS RESPONSIBLE
+ /// TO CONFIRM THAT `_to` IS CAPABLE OF RECEIVING NFTS OR ELSE
+ /// THEY MAY BE PERMANENTLY LOST
+ /// @dev Throws unless `msg.sender` is the current owner, an authorized
+ /// operator, or the approved address for this NFT. Throws if `_from` is
+ /// not the current owner. Throws if `_to` is the zero address. Throws if
+ /// `_tokenId` is not a valid NFT.
+ /// @param _from The current owner of the NFT
+ /// @param _to The new owner
+ /// @param _tokenId The NFT to transfer
+ function transferFrom(
+ address _from,
+ address _to,
+ uint256 _tokenId
+ )
public
- view
- returns (bool)
{
- address owner = tokenOwner[_tokenId];
- return owner != address(0);
- }
+ require(
+ _to != address(0),
+ "ERC721_ZERO_TO_ADDRESS"
+ );
- /**
- * @dev Approves another address to transfer the given token ID
- * @dev The zero address indicates there is no approved address.
- * @dev There can only be one approved address per token at a given time.
- * @dev 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 approve(address _to, uint256 _tokenId)
- public
- {
address owner = ownerOf(_tokenId);
- require(_to != owner);
- require(msg.sender == owner || isApprovedForAll(owner, msg.sender));
-
- if (getApproved(_tokenId) != address(0) || _to != address(0)) {
- tokenApprovals[_tokenId] = _to;
- emit Approval(owner, _to, _tokenId);
+ require(
+ _from == owner,
+ "ERC721_OWNER_MISMATCH"
+ );
+
+ address spender = msg.sender;
+ address approvedAddress = getApproved(_tokenId);
+ require(
+ spender == owner ||
+ isApprovedForAll(owner, spender) ||
+ approvedAddress == spender,
+ "ERC721_INVALID_SPENDER"
+ );
+
+ if (approvedAddress != address(0)) {
+ approvals[_tokenId] = address(0);
}
+
+ owners[_tokenId] = _to;
+ balances[_from] = safeSub(balances[_from], 1);
+ balances[_to] = safeAdd(balances[_to], 1);
+
+ emit Transfer(
+ _from,
+ _to,
+ _tokenId
+ );
}
- /**
- * @dev Gets the approved address for a token ID, or zero if no address set
- * @param _tokenId uint256 ID of the token to query the approval of
- * @return address currently approved for a the given token ID
- */
- function getApproved(uint256 _tokenId)
+ /// @notice Find the owner of an NFT
+ /// @dev NFTs assigned to zero address are considered invalid, and queries
+ /// about them do throw.
+ /// @param _tokenId The identifier for an NFT
+ /// @return The address of the owner of the NFT
+ function ownerOf(uint256 _tokenId)
public
view
returns (address)
{
- return tokenApprovals[_tokenId];
+ address owner = owners[_tokenId];
+ require(
+ owner != address(0),
+ "ERC721_ZERO_OWNER"
+ );
+ return owner;
}
- /**
- * @dev Sets or unsets the approval of a given operator
- * @dev 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 setApprovalForAll(address _to, bool _approved)
+ /// @notice Get the approved address for a single NFT
+ /// @dev Throws if `_tokenId` is not a valid NFT.
+ /// @param _tokenId The NFT to find the approved address for
+ /// @return The approved address for this NFT, or the zero address if there is none
+ function getApproved(uint256 _tokenId)
public
+ view
+ returns (address)
{
- require(_to != msg.sender);
- operatorApprovals[msg.sender][_to] = _approved;
- emit ApprovalForAll(msg.sender, _to, _approved);
+ return approvals[_tokenId];
}
- /**
- * @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
- */
+ /// @notice Query if an address is an authorized operator for another address
+ /// @param _owner The address that owns the NFTs
+ /// @param _operator The address that acts on behalf of the owner
+ /// @return True if `_operator` is an approved operator for `_owner`, false otherwise
function isApprovedForAll(address _owner, address _operator)
public
view
@@ -210,198 +274,4 @@ contract ERC721Token is
{
return operatorApprovals[_owner][_operator];
}
-
- /**
- * @dev Transfers the ownership of a given token ID to another address
- * @dev Usage of this method is discouraged, use `safeTransferFrom` whenever possible
- * @dev 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
- canTransfer(_tokenId)
- {
- require(_from != address(0));
- require(_to != address(0));
-
- clearApproval(_from, _tokenId);
- removeTokenFrom(_from, _tokenId);
- addTokenTo(_to, _tokenId);
-
- emit Transfer(_from, _to, _tokenId);
- }
-
- /**
- * @dev Safely transfers the ownership of a given token ID to another address
- * @dev 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,uint256,bytes)"))`; otherwise,
- * the transfer is reverted.
- * @dev 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
- canTransfer(_tokenId)
- {
- // solium-disable-next-line arg-overflow
- safeTransferFrom(_from, _to, _tokenId, "");
- }
-
- /**
- * @dev Safely transfers the ownership of a given token ID to another address
- * @dev 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,uint256,bytes)"))`; otherwise,
- * the transfer is reverted.
- * @dev 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 safeTransferFrom(
- address _from,
- address _to,
- uint256 _tokenId,
- bytes _data)
- public
- canTransfer(_tokenId)
- {
- transferFrom(_from, _to, _tokenId);
- // solium-disable-next-line arg-overflow
- require(checkAndCallSafeTransfer(_from, _to, _tokenId, _data));
- }
-
- /**
- * @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 isApprovedOrOwner(address _spender, uint256 _tokenId)
- internal
- view
- returns (bool)
- {
- address owner = ownerOf(_tokenId);
- return _spender == owner || getApproved(_tokenId) == _spender || isApprovedForAll(owner, _spender);
- }
-
- /**
- * @dev Internal function to mint a new token
- * @dev Reverts if the given token ID already exists
- * @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
- {
- require(_to != address(0));
- addTokenTo(_to, _tokenId);
- emit Transfer(address(0), _to, _tokenId);
- }
-
- /**
- * @dev Internal function to burn a specific token
- * @dev Reverts if the token does not exist
- * @param _tokenId uint256 ID of the token being burned by the msg.sender
- */
- function _burn(address _owner, uint256 _tokenId)
- internal
- {
- clearApproval(_owner, _tokenId);
- removeTokenFrom(_owner, _tokenId);
- emit Transfer(_owner, address(0), _tokenId);
- }
-
- /**
- * @dev Internal function to clear current approval of a given token ID
- * @dev 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);
- emit Approval(_owner, address(0), _tokenId);
- }
- }
-
- /**
- * @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] = safeAdd(ownedTokensCount[_to], 1);
- }
-
- /**
- * @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] = safeSub(ownedTokensCount[_from], 1);
- tokenOwner[_tokenId] = address(0);
- }
-
- /**
- * @dev Internal function to invoke `onERC721Received` on a target address
- * @dev 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 (!isContract(_to)) {
- return true;
- }
- bytes4 retval = IERC721Receiver(_to).onERC721Received(_from, _tokenId, _data);
- return (retval == ERC721_RECEIVED);
- }
-
- function isContract(address addr)
- internal
- view
- returns (bool)
- {
- uint256 size;
- // XXX Currently there is no better way to check if there is a contract in an address
- // than to check the size of the code at that address.
- // See https://ethereum.stackexchange.com/a/14016/36603
- // for more details about how this works.
- // TODO Check this again before the Serenity release, because all addresses will be
- // contracts then.
- assembly { size := extcodesize(addr) } // solium-disable-line security/no-inline-assembly
- return size > 0;
- }
}