Last active
January 12, 2019 02:16
-
-
Save aleph-v/2bbd2e6aa8e46e7f83fb33100b82747d to your computer and use it in GitHub Desktop.
Delegated ERC721 with nonce
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.24; | |
import "./IERC721.sol"; | |
import "./IERC721Receiver.sol"; | |
import "../../math/SafeMath.sol"; | |
import "../../utils/Address.sol"; | |
import "../../introspection/ERC165.sol"; | |
/** | |
* @title ERC721 Non-Fungible Token Standard basic implementation with added support for metatransactions | |
* @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md | |
*/ | |
contract ERC721_Delegated 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; | |
//Delegated Constants | |
bytes4 public constant APPROVE = bytes4(keccak256("approve")); | |
bytes4 public constant APPROVE_FOR_ALL = bytes4(keccak256("approveForAll")); | |
bytes4 public constant SAFE_TRANSFER_FROM = bytes4(keccak256("safeTransferFrom")); | |
//Nonces per user | |
mapping(address => uint) private nonces; | |
// 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)')) | |
*/ | |
constructor () public { | |
// register the supported interfaces to conform to ERC721 via ERC165 | |
_registerInterface(_InterfaceId_ERC721); | |
} | |
/** | |
* @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 Gets the nonce of the specified address | |
* @param signer address to check | |
* @return a uint which is the nonce to be signed | |
*/ | |
function nonceOf(address signer) public view returns (uint256){ | |
require( signer != address(0)); | |
return(nonces[signer]); | |
} | |
/** | |
* @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 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 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 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 getApproved(uint256 tokenId) public view returns (address) { | |
require(_exists(tokenId)); | |
return _tokenApprovals[tokenId]; | |
} | |
/** | |
* @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 setApprovalForAll(address to, bool approved) public { | |
require(to != msg.sender); | |
_operatorApprovals[msg.sender][to] = approved; | |
emit ApprovalForAll(msg.sender, to, approved); | |
} | |
/** | |
* @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 isApprovedForAll(address owner, address operator) public view returns (bool) { | |
return _operatorApprovals[owner][operator]; | |
} | |
/** | |
* @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)); | |
_transferFrom(from, to, tokenId); | |
} | |
/** | |
* @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 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 safeTransferFrom(address from, address to, uint256 tokenId, bytes _data) public { | |
transferFrom(from, to, tokenId); | |
// solium-disable-next-line arg-overflow | |
require(_checkOnERC721Received(from, to, tokenId, _data)); | |
} | |
/** | |
* @dev Preforms the same function as approve as if the function was called by the signer address. | |
* To use it the singer needs to sign the hash of the token address, APPROVE bytes, the signers current nonce, | |
* the to address, andthe tokenId from the normal call. | |
* Requires the signer to be the owner of the token or approved for all for the owner. | |
*/ | |
function delagated_approve(address to, uint256 tokenId, address signer, bytes sig) public { | |
bytes32 hash = keccak256(abi.encodePacked(address(this), APPROVE, nonces[signer], to, tokenId)); | |
require(recover(hash, sig) == signer); | |
address owner = ownerOf(tokenId); | |
require(to != owner); | |
require(signer == owner || isApprovedForAll(owner, signer)); | |
_tokenApprovals[tokenId] = to; | |
emit Approval(owner, to, tokenId); | |
nonces[signer]++; | |
} | |
/** | |
* @dev Preforms the same function as approveForAll as if the function was called by the signer address | |
* To use it the signer needs to sign the hash of the token address, the signers current nonce, APPROVE_FOR_ALL bytes, | |
* the person to be approved for all, and the bool to set the approval too. | |
* | |
*/ | |
function delegated_approveForAll(address to, bool approved, address signer, bytes sig) public { | |
bytes32 hash = keccak256(abi.encodePacked(address(this), APPROVE_FOR_ALL, nonces[signer], to, approved)); | |
require(recover(hash, sig) == signer); | |
require(to != signer); | |
_operatorApprovals[signer][to] = approved; | |
emit ApprovalForAll(signer, to, approved); | |
nonces[signer]++; | |
} | |
/** | |
* @dev Preforms the same function as safeTransferFrom as if the function was called by the signer address | |
* To use it the signer needs to sign the hash of the token address, the signer's current nonce, SAFE_TRANSFER_FROM bytes, | |
* the address from, the address to, and the tokenId | |
* It requries the at the signer is owner or the token, approved for all for the token owner, or approved for the token | |
*/ | |
function delegated_safeTransferFrom(address from, address to, uint256 tokenId, address signer, bytes sig) public { | |
bytes32 hash = keccak256(abi.encodePacked(address(this), SAFE_TRANSFER_FROM, nonces[signer], from, to, tokenId)); | |
require(recover(hash, sig) == signer); | |
_isApprovedOrOwner(signer,tokenId); | |
transferFrom(from, to, tokenId); | |
require(_checkOnERC721Received(from, to, tokenId, "")); | |
nonces[signer]++; | |
} | |
/** | |
* @dev Preforms the same function as safeTransferFrom as if the function was called by the signer address | |
* To use it the signer needs to sign the hash of the token address, the signer's current nonce, SAFE_TRANSFER_FROM bytes, | |
* the address from, the address to, the tokenId, and bytes data for the call to safeTransferFrom | |
* It requries the at the signer is owner or the token, approved for all for the token owner, or approved for the token | |
*/ | |
function delegated_safeTransferFrom(address from, address to, uint256 tokenId, bytes _data, address signer, bytes sig) public { | |
bytes32 hash = keccak256(abi.encodePacked(address(this), SAFE_TRANSFER_FROM, nonces[signer], from, to, tokenId, _data)); | |
require(recover(hash, sig) == signer); | |
_isApprovedOrOwner(signer,tokenId); | |
transferFrom(from, to, tokenId); | |
require(_checkOnERC721Received(from, to, tokenId, _data)); | |
nonces[signer]++; | |
} | |
/** | |
* @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 _exists(uint256 tokenId) internal view returns (bool) { | |
address owner = _tokenOwner[tokenId]; | |
return owner != address(0); | |
} | |
/** | |
* @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); | |
// 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 The address that will own the minted token | |
* @param tokenId uint256 ID of the token to be minted | |
*/ | |
function _mint(address to, uint256 tokenId) internal { | |
require(to != address(0)); | |
require(!_exists(tokenId)); | |
_tokenOwner[tokenId] = to; | |
_ownedTokensCount[to] = _ownedTokensCount[to].add(1); | |
emit Transfer(address(0), to, tokenId); | |
} | |
/** | |
* @dev Internal function to burn a specific token | |
* Reverts if the token does not exist | |
* Deprecated, use _burn(uint256) instead. | |
* @param owner owner of the token to burn | |
* @param tokenId uint256 ID of the token being burned | |
*/ | |
function _burn(address owner, uint256 tokenId) internal { | |
require(ownerOf(tokenId) == owner); | |
_clearApproval(tokenId); | |
_ownedTokensCount[owner] = _ownedTokensCount[owner].sub(1); | |
_tokenOwner[tokenId] = address(0); | |
emit Transfer(owner, address(0), tokenId); | |
} | |
/** | |
* @dev Internal function to burn a specific token | |
* Reverts if the token does not exist | |
* @param tokenId uint256 ID of the token being burned | |
*/ | |
function _burn(uint256 tokenId) internal { | |
_burn(ownerOf(tokenId), tokenId); | |
} | |
/** | |
* @dev Internal function to transfer ownership of a given token ID to another address. | |
* As opposed to transferFrom, this imposes no restrictions on msg.sender. | |
* @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) internal { | |
require(ownerOf(tokenId) == from); | |
require(to != address(0)); | |
_clearApproval(tokenId); | |
_ownedTokensCount[from] = _ownedTokensCount[from].sub(1); | |
_ownedTokensCount[to] = _ownedTokensCount[to].add(1); | |
_tokenOwner[tokenId] = to; | |
emit Transfer(from, to, tokenId); | |
} | |
/** | |
* @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 _checkOnERC721Received(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); | |
} | |
/** | |
* @dev Private function to clear current approval of a given token ID | |
* @param tokenId uint256 ID of the token to be transferred | |
*/ | |
function _clearApproval(uint256 tokenId) private { | |
if (_tokenApprovals[tokenId] != address(0)) { | |
_tokenApprovals[tokenId] = address(0); | |
} | |
} | |
uint256 constant N = 115792089210356248762697446949407573529996955224135760342422259061068512044369; | |
//This is the curve order for spec256k | |
function recover(bytes32 hash, bytes sig) internal pure returns(address){ | |
hash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)); | |
bytes32 r; | |
bytes32 s; | |
uint8 v; | |
if (sig.length != 65) return address(0); | |
assembly { | |
r := mload(add(sig, 32)) | |
s := mload(add(sig, 64)) | |
v := byte(0, mload(add(sig, 96))) | |
} | |
if (v < 27) { | |
v += 27; | |
} | |
if (v != 27 && v != 28) return address(0); | |
require(uint(s) < N/2); //We assert that the lower s version of the signature is the valid one | |
return ecrecover(hash, v, r, s); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment