From 0f3201d72a181c3dc7bafba9a82c141f91463ce9 Mon Sep 17 00:00:00 2001 From: Amir Bandeali Date: Thu, 26 Jul 2018 16:19:10 -0700 Subject: Optimize ERC721Token --- .../test/DummyERC721Token/DummyERC721Token.sol | 56 +-- .../src/2.0.0/tokens/ERC721Token/ERC721Token.sol | 554 ++++++++------------- .../2.0.0/tokens/ERC721Token/IERC721Receiver.sol | 81 ++- .../src/2.0.0/tokens/ERC721Token/IERC721Token.sol | 198 +++++--- .../tokens/ERC721Token/MintableERC721Token.sol | 83 +++ 5 files changed, 462 insertions(+), 510 deletions(-) create mode 100644 packages/contracts/src/2.0.0/tokens/ERC721Token/MintableERC721Token.sol (limited to 'packages/contracts/src') diff --git a/packages/contracts/src/2.0.0/test/DummyERC721Token/DummyERC721Token.sol b/packages/contracts/src/2.0.0/test/DummyERC721Token/DummyERC721Token.sol index 627746a52..7c64005bd 100644 --- a/packages/contracts/src/2.0.0/test/DummyERC721Token/DummyERC721Token.sol +++ b/packages/contracts/src/2.0.0/test/DummyERC721Token/DummyERC721Token.sol @@ -18,59 +18,35 @@ pragma solidity 0.4.24; -import "../../tokens/ERC721Token/ERC721Token.sol"; +import "../../tokens/ERC721Token/MintableERC721Token.sol"; import "../../utils/Ownable/Ownable.sol"; // solhint-disable no-empty-blocks contract DummyERC721Token is Ownable, - ERC721Token + MintableERC721Token { - /** - * @dev Constructor passes its arguments to the base ERC721Token constructor - * @param name of token - * @param symbol of token - */ - constructor ( - string name, - string symbol - ) - public - ERC721Token(name, symbol) - {} - - /** - * @dev Function to mint a new token - * @dev 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) - public + /// @dev Function to mint a new token + /// Reverts if the given token ID already exists + /// @param _to Address of the beneficiary that will own the minted token + /// @param _tokenId ID of the token to be minted by the msg.sender + function mint(address _to, uint256 _tokenId) + external onlyOwner { - require( - !exists(tokenId), - "Token with tokenId already exists." - ); - _mint(to, tokenId); + _mint(_to, _tokenId); } - /** - * @dev Function to burn a token - * @dev Reverts if the given token ID doesn't exist - * @param tokenId uint256 ID of the token to be minted by the msg.sender - */ - function burn(address owner, uint256 tokenId) - public + /// @dev Function to burn a token + /// Reverts if the given token ID doesn't exist + /// @param _owner Owner of token with given token ID + /// @param _tokenId ID of the token to be burned by the msg.sender + function burn(address _owner, uint256 _tokenId) + external onlyOwner { - require( - exists(tokenId), - "Token with tokenId does not exist." - ); - _burn(owner, tokenId); + _burn(_owner, _tokenId); } } 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..94e084731 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 + /// perator, 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; - } } diff --git a/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Receiver.sol b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Receiver.sol index f2e8f3c88..8e0e32ab2 100644 --- a/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Receiver.sol +++ b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Receiver.sol @@ -1,61 +1,44 @@ /* -The MIT License (MIT) - -Copyright (c) 2016 Smart Contract Solutions, Inc. - -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: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -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. + + Copyright 2018 ZeroEx Intl. + + 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 + + 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. + */ pragma solidity 0.4.24; -/** - * @title ERC721 token receiver interface - * @dev Interface for any contract that wants to support safeTransfers - * rom ERC721 asset contracts. - * Modified from https://github.com/OpenZeppelin/zeppelin-solidity/blob/master/contracts/token/ERC721/ERC721Receiver.sol - */ contract IERC721Receiver { - /** - * @dev Magic value to be returned upon successful reception of an NFT - * Equals to `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))`, - * which can be also obtained as `ERC721Receiver(0).onERC721Received.selector` - */ - bytes4 constant internal ERC721_RECEIVED = 0xf0b9e5ba; - - /** - * @notice Handle the receipt of an NFT - * @dev The ERC721 smart contract calls this function on the recipient - * after a `safetransfer`. This function MAY throw to revert and reject the - * transfer. This function MUST use 50,000 gas or less. Return of other - * than the magic value MUST result in the transaction being reverted. - * Note: the contract address is always the message sender. - * @param _from The sending address - * @param _tokenId The NFT identifier which is being transfered - * @param _data Additional data with no specified format - * @return `bytes4(keccak256("onERC721Received(address,uint256,bytes)"))` - */ + + /// @notice Handle the receipt of an NFT + /// @dev The ERC721 smart contract calls this function on the recipient + /// after a `transfer`. This function MAY throw to revert and reject the + /// transfer. Return of other than the magic value MUST result in the + /// transaction being reverted. + /// Note: the contract address is always the message sender. + /// @param _operator The address which called `safeTransferFrom` function + /// @param _from The address which previously owned the token + /// @param _tokenId The NFT identifier which is being transferred + /// @param _data Additional data with no specified format + /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + /// unless throwing function onERC721Received( + address _operator, address _from, uint256 _tokenId, - bytes _data) - public + bytes _data + ) + external returns (bytes4); } diff --git a/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Token.sol b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Token.sol index 4d57ece38..ac992c80d 100644 --- a/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Token.sol +++ b/packages/contracts/src/2.0.0/tokens/ERC721Token/IERC721Token.sol @@ -1,118 +1,158 @@ /* -The MIT License (MIT) - -Copyright (c) 2016 Smart Contract Solutions, Inc. - -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: - -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. - -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. + + Copyright 2018 ZeroEx Intl. + + 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 + + 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. + */ pragma solidity 0.4.24; -/** - * @title ERC721 Non-Fungible Token Standard basic interface - * @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/ERC721Basic.sol - */ contract IERC721Token { - string internal name_; - string internal symbol_; + /// @dev This emits when ownership of any NFT changes by any mechanism. + /// This event emits when NFTs are created (`from` == 0) and destroyed + /// (`to` == 0). Exception: during contract creation, any number of NFTs + /// may be created and assigned without emitting Transfer. At the time of + /// any transfer, the approved address for that NFT (if any) is reset to none. event Transfer( address indexed _from, address indexed _to, - uint256 _tokenId + uint256 indexed _tokenId ); + /// @dev This emits when the approved address for an NFT is changed or + /// reaffirmed. The zero address indicates there is no approved address. + /// When a Transfer event emits, this also indicates that the approved + /// address for that NFT (if any) is reset to none. event Approval( address indexed _owner, address indexed _approved, - uint256 _tokenId + uint256 indexed _tokenId ); + /// @dev This emits when an operator is enabled or disabled for an owner. + /// The operator can manage all NFTs of the owner. event ApprovalForAll( address indexed _owner, address indexed _operator, bool _approved ); - function name() - public - view - returns (string); - - function symbol() - public - view - returns (string); + /// @notice Transfers the ownership of an NFT from one address to another address + /// @dev Throws unless `msg.sender` is the current owner, an authorized + /// perator, 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; + + /// @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; + + /// @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; + + /// @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; + /// @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 _balance); + returns (uint256); + + /// @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; + /// @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 _owner); + returns (address); - function exists(uint256 _tokenId) + /// @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 (bool _exists); - - function approve(address _to, uint256 _tokenId) - public; - - function getApproved(uint256 _tokenId) - public - view - returns (address _operator); - - function setApprovalForAll(address _operator, bool _approved) - public; - + returns (address); + + /// @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 returns (bool); - - function transferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public; - - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId - ) - public; - - function safeTransferFrom( - address _from, - address _to, - uint256 _tokenId, - bytes _data - ) - public; } diff --git a/packages/contracts/src/2.0.0/tokens/ERC721Token/MintableERC721Token.sol b/packages/contracts/src/2.0.0/tokens/ERC721Token/MintableERC721Token.sol new file mode 100644 index 000000000..85d192779 --- /dev/null +++ b/packages/contracts/src/2.0.0/tokens/ERC721Token/MintableERC721Token.sol @@ -0,0 +1,83 @@ +/* + + Copyright 2018 ZeroEx Intl. + + 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 + + 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. + +*/ + +pragma solidity 0.4.24; + +import "./ERC721Token.sol"; + + +contract MintableERC721Token is + ERC721Token +{ + + /// @dev Function to mint a new token + /// Reverts if the given token ID already exists + /// @param _to Address of the beneficiary that will own the minted token + /// @param _tokenId ID of the token to be minted by the msg.sender + function _mint(address _to, uint256 _tokenId) + internal + { + require( + _to != address(0), + "ERC721_ZERO_TO_ADDRESS" + ); + + address owner = owners[_tokenId]; + require( + owner == address(0), + "ERC721_OWNER_ALREADY_EXISTS" + ); + + owners[_tokenId] = _to; + balances[_to] = safeAdd(balances[_to], 1); + + emit Transfer( + address(0), + _to, + _tokenId + ); + } + + /// @dev Function to burn a token + /// Reverts if the given token ID doesn't exist + /// @param _owner Owner of token with given token ID + /// @param _tokenId ID of the token to be burned by the msg.sender + function _burn(address _owner, uint256 _tokenId) + internal + { + require( + _owner != address(0), + "ERC721_ZERO_OWNER_ADDRESS" + ); + + address owner = owners[_tokenId]; + require( + owner == _owner, + "ERC721_OWNER_MISMATCH" + ); + + owners[_tokenId] = address(0); + balances[_owner] = safeSub(balances[_owner], 1); + + emit Transfer( + _owner, + address(0), + _tokenId + ); + } +} -- cgit v1.2.3