diff options
Diffstat (limited to 'contracts/erc721/contracts/src/ERC721Token.sol')
-rw-r--r-- | contracts/erc721/contracts/src/ERC721Token.sol | 277 |
1 files changed, 277 insertions, 0 deletions
diff --git a/contracts/erc721/contracts/src/ERC721Token.sol b/contracts/erc721/contracts/src/ERC721Token.sol new file mode 100644 index 000000000..32f70ccf8 --- /dev/null +++ b/contracts/erc721/contracts/src/ERC721Token.sol @@ -0,0 +1,277 @@ +/* + + 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 "./interfaces/IERC721Token.sol"; +import "./interfaces/IERC721Receiver.sol"; +import "@0x/contracts-utils/contracts/src/SafeMath.sol"; + + +contract ERC721Token is + IERC721Token, + SafeMath +{ + // Function selector for ERC721Receiver.onERC721Received + // 0x150b7a02 + bytes4 constant internal ERC721_RECEIVED = bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); + + // Mapping of tokenId => owner + mapping (uint256 => address) internal owners; + + // Mapping of tokenId => approved address + mapping (uint256 => address) internal approvals; + + // Mapping of owner => number of tokens owned + mapping (address => uint256) internal balances; + + // Mapping of owner => operator => approved + mapping (address => mapping (address => bool)) internal operatorApprovals; + + /// @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" + ); + } + } + + /// @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 + { + 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" + ); + } + } + + /// @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 + { + address owner = ownerOf(_tokenId); + require( + msg.sender == owner || isApprovedForAll(owner, msg.sender), + "ERC721_INVALID_SENDER" + ); + + approvals[_tokenId] = _approved; + emit Approval( + owner, + _approved, + _tokenId + ); + } + + /// @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 + { + operatorApprovals[msg.sender][_operator] = _approved; + emit ApprovalForAll( + msg.sender, + _operator, + _approved + ); + } + + /// @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) + external + view + returns (uint256) + { + require( + _owner != address(0), + "ERC721_ZERO_OWNER" + ); + return balances[_owner]; + } + + /// @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 + { + require( + _to != address(0), + "ERC721_ZERO_TO_ADDRESS" + ); + + address owner = ownerOf(_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 + ); + } + + /// @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) + { + address owner = owners[_tokenId]; + require( + owner != address(0), + "ERC721_ZERO_OWNER" + ); + return owner; + } + + /// @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) + { + return approvals[_tokenId]; + } + + /// @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) + { + return operatorApprovals[_owner][_operator]; + } +} |