first version stickers

This commit is contained in:
Ricardo Guilherme Schmidt 2019-01-14 05:50:03 -02:00
parent eebb8b352b
commit 9054fa4ad1
No known key found for this signature in database
GPG Key ID: BFB3F5C8ED618A94
7 changed files with 630 additions and 2 deletions

View File

@ -0,0 +1,23 @@
pragma solidity >=0.5.0 <0.6.0;
import "../../common/Controlled.sol";
import "../../token/UnfungibleToken.sol";
contract Sticker is Controlled, UnfungibleToken {
uint256 public nextId;
mapping (uint256 => bytes32) public dataHash;
function generateToken(address _owner, bytes32 _dataHash) external onlyController {
tokenId = nextId++;
dataHash[tokenId] = _dataHash;
_mint(_owner, tokenId);
}
function destroyToken(address _owner, uint256 _tokenId) external onlyController {
delete dataHash[_tokenId];
_burn(_owner, _tokenId);
}
}

View File

@ -0,0 +1,114 @@
pragma solidity >=0.5.0 <0.6.0;
import "./StickerPack.sol";
import "../../token/ERC20Token.sol";
import "../../token/ApproveAndCallFallBack.sol";
import "../../common/Controlled.sol";
/**
* @dev
*/
contract StickerMarket is Controlled, ApproveAndCallFallBack {
event Register(uint256 packId, bytes32 dataHash, uint256 dataPrice);
event Unregister(uint256 packId);
event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
struct Pack {
bytes32 dataHash; // merkle tree root of "ipfs://stickerdata-json"
uint256 price;
address owner;
}
StickerPack public stickerPack = new StickerPack();
ERC20Token public snt;
mapping(uint256 => Pack) public packs;
uint256 public nextId;
//TODO: add list of packs available
/**
* @notice Constructor
* @param _owner Authority address
* @param _snt SNT token
*/
constructor(
ERC20Token _snt,
uint256 _registerPrice
)
public
{
snt = _snt;
}
function buy(uint256 _packId) external {
_buy(msg.sender, packs[_packId]);
}
function receiveApproval(address _from, uint256 _amount, address _token, bytes calldata _data) external {
require(_token == address(snt), "Bad token");
require(_token == address(msg.sender), "Bad call");
require(_data.length == 36, "Bad data length");
uint256 packId = abiDecodeBuy(_data);
Pack memory pack = packs[packId];
require(pack.price == _amount, "Bad amount");
_buy(_from, pack);
}
function register(bytes32 _dataHash, uint256 _price, address _owner) external onlyController {
uint256 id = nextId++;
packs[id] = Pack(_dataHash, _price, _owner);
emit Register(id, _datahash, _price);
}
function unregister(uint256 _packId) external onlyController {
delete packs[_packId];
emit Unregister(id);
}
function migrateMarket(address _newMarket) external onlyController {
stickerPack.changeController(_newMarket);
}
/**
* @notice This method can be used by the controller to extract mistakenly
* sent tokens to this contract.
* @param _token The address of the token contract that you want to recover
* set to 0 in case you want to extract ether.
*/
function claimTokens(address _token) external onlyController {
if (_token == address(0)) {
address(controller).transfer(address(this).balance);
return;
}
ERC20Token token = ERC20Token(_token);
uint256 balance = token.balanceOf(address(this));
token.transfer(controller, balance);
emit ClaimedTokens(_token, controller, balance);
}
function _buy(address _buyer, Pack memory _pack) internal {
require(_pack.dataHash != bytes32(0), "Bad pack");
require(snt.transferFrom(_from, _pack.owner, _pack.price), "Bad payment");
stickerPack.generateToken(_from, pack.dataHash);
}
/**
* @dev Decodes abi encoded data with selector for "buy(uint256)".
* @param _data Abi encoded data.
* @return Decoded registry call.
*/
function abiDecodeBuy(
bytes _data
)
private
pure
returns(
uint256 packId
)
{
bytes4 sig;
assembly {
sig := mload(add(_data, add(0x20, 0)))
packId := mload(add(_data, 36))
}
require(sig == bytes4(keccak256("buy(uint256)")), "Bad method sig");
}
}

View File

@ -0,0 +1,47 @@
pragma solidity >=0.5.0 <0.6.0;
import "../../common/Controlled.sol";
import "../../token/UnfungibleToken.sol";
import "./Sticker.sol";
contract StickerPack is Controlled, UnfungibleToken {
Sticker public sticker = new Sticker();
uint256 public nextId;
mapping(uint256 => bytes32) public dataHash;
mapping(bytes32 => bool) public unpacked;
function unpack(uint256 _tokenId, bytes32[] memory _proof, bytes32 _stickerData) external {
address owner = ownerOf(_tokenId);
require(owner == msg.sender, "Unauthorized");
require(MerkleProof.verifyProof(_proof, dataHash[_tokenId], _stickerData), "Invalid proof");
bytes32 unpackedHash = keccak256(abi.encodePacked(_tokenId, _stickerData));
require(!unpacked[unpackedHash], "Duplicate unpacking");
unpacked[unpackedHash] = true;
sticker.generateToken(owner, _stickerData);
}
function repack(uint256 _packId, uint256 _stickerToken) external {
address owner = ownerOf(_packToken);
require(owner == ownerOf(_stickerToken) && owner == msg.sender, "Unauthorized");
bytes32 stickerData = sticker.dataHash(_stickerToken);
bytes32 unpackedHash = keccak256(abi.encodePacked(_packId, stickerData));
require(unpacked[unpackedHash],"Bad operation");
delete unpacked[unpackedHash];
sticker.destroyToken(owner, _tokenId2);
}
function generateToken(address _owner, bytes32 _dataHash) external onlyController {
tokenId = nextId++;
dataHash[tokenId] = _dataHash;
_mint(_owner, tokenId);
}
function isStickerInPack(uint256 _packId, bytes32[] _proof, bytes32 _stickerData) public view returns (bool){
require(MerkleProof.verifyProof(_proof, dataHash[_tokenId], _stickerData), "Invalid proof");
bytes32 unpackedHash = keccak256(abi.encodePacked(_tokenId, _stickerData));
return !unpacked[unpackedHash];
}
}

View File

@ -1,5 +1,5 @@
pragma solidity >=0.5.0 <0.6.0;
contract ApproveAndCallFallBack {
function receiveApproval(address from, uint256 _amount, address _token, bytes memory _data) public;
interface ApproveAndCallFallBack {
function receiveApproval(address from, uint256 _amount, address _token, bytes calldata _data) external;
}

139
contracts/token/ERC721.sol Normal file
View File

@ -0,0 +1,139 @@
pragma solidity >=0.5.0 <0.6.0;
/**
* @title ERC-721 Non-Fungible Token Standard
* @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
* Note: the ERC-165 identifier for this interface is 0x80ac58cd
*/
interface ERC721 /* is ERC165 */ {
/**
* @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 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 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);
/**
* @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);
/**
* @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) external view returns (address);
/**
* @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 calldata _data) external payable;
/**
* @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 payable;
/**
* @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) external payable;
/**
* @notice Set or reaffirm the approved address for an NFT
* @dev The zero address indicates there is no approved address.
* @dev 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 payable;
/**
* @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 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) external view 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) external view returns (bool);
}
interface ERC165 {
/**
* @notice Query if a contract implements an interface
* @param interfaceID The interface identifier, as specified in ERC-165
* @dev Interface identification is specified in ERC-165. This function
* uses less than 30,000 gas.
* @return `true` if the contract implements `interfaceID` and
* `interfaceID` is not 0xffffffff, `false` otherwise
*/
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

View File

@ -0,0 +1,19 @@
pragma solidity >=0.5.0 <0.6.0;
interface ERC721TokenReceiver {
/**
* @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.
* @notice 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) external returns(bytes4);
}

View File

@ -0,0 +1,286 @@
pragma solidity >=0.5.0 <0.6.0;
import "./ERC721.sol";
import "./ERC721Receiver.sol";
import "../common/SafeMath.sol";
/**
* @title ERC721 Non-Fungible Token Standard basic implementation
* @dev see https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
*/
contract UnfungibleToken is ERC165, ERC721 {
using SafeMath for uint256;
// 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)'))
*/
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), "Invalid address");
return _ownedTokensCount[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), "Invalid address");
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, "Bad operation");
require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "Unauthorized");
_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), "Bad token id");
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, "Bad operation");
_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), "Unauthorized");
_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), "Unauthorized");
}
/**
* @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), "Invalid address");
require(!_exists(tokenId), "Bad operation");
_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, "Unauthorized");
_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, "Unauthorized");
require(to != address(0), "Invalid address");
_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);
}
}
}