1948 lines
68 KiB
Solidity
1948 lines
68 KiB
Solidity
pragma solidity ^0.5.0;
|
|
|
|
/**
|
|
* @dev Wrappers over Solidity's arithmetic operations with added overflow
|
|
* checks.
|
|
*
|
|
* Arithmetic operations in Solidity wrap on overflow. This can easily result
|
|
* in bugs, because programmers usually assume that an overflow raises an
|
|
* error, which is the standard behavior in high level programming languages.
|
|
* `SafeMath` restores this intuition by reverting the transaction when an
|
|
* operation overflows.
|
|
*
|
|
* Using this library instead of the unchecked operations eliminates an entire
|
|
* class of bugs, so it's recommended to use it always.
|
|
*/
|
|
library SafeMath {
|
|
/**
|
|
* @dev Returns the addition of two unsigned integers, reverting on
|
|
* overflow.
|
|
*
|
|
* Counterpart to Solidity's `+` operator.
|
|
*
|
|
* Requirements:
|
|
* - Addition cannot overflow.
|
|
*/
|
|
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
uint256 c = a + b;
|
|
require(c >= a, "SafeMath: addition overflow");
|
|
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the subtraction of two unsigned integers, reverting on
|
|
* overflow (when the result is negative).
|
|
*
|
|
* Counterpart to Solidity's `-` operator.
|
|
*
|
|
* Requirements:
|
|
* - Subtraction cannot overflow.
|
|
*/
|
|
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
require(b <= a, "SafeMath: subtraction overflow");
|
|
uint256 c = a - b;
|
|
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the multiplication of two unsigned integers, reverting on
|
|
* overflow.
|
|
*
|
|
* Counterpart to Solidity's `*` operator.
|
|
*
|
|
* Requirements:
|
|
* - Multiplication cannot overflow.
|
|
*/
|
|
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
|
|
// benefit is lost if 'b' is also tested.
|
|
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
|
|
if (a == 0) {
|
|
return 0;
|
|
}
|
|
|
|
uint256 c = a * b;
|
|
require(c / a == b, "SafeMath: multiplication overflow");
|
|
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the integer division of two unsigned integers. Reverts on
|
|
* division by zero. The result is rounded towards zero.
|
|
*
|
|
* Counterpart to Solidity's `/` operator. Note: this function uses a
|
|
* `revert` opcode (which leaves remaining gas untouched) while Solidity
|
|
* uses an invalid opcode to revert (consuming all remaining gas).
|
|
*
|
|
* Requirements:
|
|
* - The divisor cannot be zero.
|
|
*/
|
|
function div(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
// Solidity only automatically asserts when dividing by 0
|
|
require(b > 0, "SafeMath: division by zero");
|
|
uint256 c = a / b;
|
|
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
|
|
|
|
return c;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
|
|
* Reverts when dividing by zero.
|
|
*
|
|
* Counterpart to Solidity's `%` operator. This function uses a `revert`
|
|
* opcode (which leaves remaining gas untouched) while Solidity uses an
|
|
* invalid opcode to revert (consuming all remaining gas).
|
|
*
|
|
* Requirements:
|
|
* - The divisor cannot be zero.
|
|
*/
|
|
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
|
|
require(b != 0, "SafeMath: modulo by zero");
|
|
return a % b;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @dev Collection of functions related to the address type,
|
|
*/
|
|
library Address {
|
|
/**
|
|
* @dev Returns true if `account` is a contract.
|
|
*
|
|
* This test is non-exhaustive, and there may be false-negatives: during the
|
|
* execution of a contract's constructor, its address will be reported as
|
|
* not containing a contract.
|
|
*
|
|
* > It is unsafe to assume that an address for which this function returns
|
|
* false is an externally-owned account (EOA) and not a contract.
|
|
*/
|
|
function isContract(address account) internal view returns (bool) {
|
|
// This method relies in extcodesize, which returns 0 for contracts in
|
|
// construction, since the code is only stored at the end of the
|
|
// constructor execution.
|
|
|
|
uint256 size;
|
|
// solhint-disable-next-line no-inline-assembly
|
|
assembly { size := extcodesize(account) }
|
|
return size > 0;
|
|
}
|
|
}
|
|
|
|
|
|
contract Controlled {
|
|
event NewController(address controller);
|
|
/// @notice The address of the controller is the only address that can call
|
|
/// a function with this modifier
|
|
modifier onlyController {
|
|
require(msg.sender == controller, "Unauthorized");
|
|
_;
|
|
}
|
|
|
|
address payable public controller;
|
|
|
|
constructor() internal {
|
|
controller = msg.sender;
|
|
}
|
|
|
|
/// @notice Changes the controller of the contract
|
|
/// @param _newController The new controller of the contract
|
|
function changeController(address payable _newController) public onlyController {
|
|
controller = _newController;
|
|
emit NewController(_newController);
|
|
}
|
|
}
|
|
|
|
|
|
// Abstract contract for the full ERC 20 Token standard
|
|
// https://github.com/ethereum/EIPs/issues/20
|
|
|
|
interface ERC20Token {
|
|
|
|
/**
|
|
* @notice send `_value` token to `_to` from `msg.sender`
|
|
* @param _to The address of the recipient
|
|
* @param _value The amount of token to be transferred
|
|
* @return Whether the transfer was successful or not
|
|
*/
|
|
function transfer(address _to, uint256 _value) external returns (bool success);
|
|
|
|
/**
|
|
* @notice `msg.sender` approves `_spender` to spend `_value` tokens
|
|
* @param _spender The address of the account able to transfer the tokens
|
|
* @param _value The amount of tokens to be approved for transfer
|
|
* @return Whether the approval was successful or not
|
|
*/
|
|
function approve(address _spender, uint256 _value) external returns (bool success);
|
|
|
|
/**
|
|
* @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
|
|
* @param _from The address of the sender
|
|
* @param _to The address of the recipient
|
|
* @param _value The amount of token to be transferred
|
|
* @return Whether the transfer was successful or not
|
|
*/
|
|
function transferFrom(address _from, address _to, uint256 _value) external returns (bool success);
|
|
|
|
/**
|
|
* @param _owner The address from which the balance will be retrieved
|
|
* @return The balance
|
|
*/
|
|
function balanceOf(address _owner) external view returns (uint256 balance);
|
|
|
|
/**
|
|
* @param _owner The address of the account owning tokens
|
|
* @param _spender The address of the account able to transfer the tokens
|
|
* @return Amount of remaining tokens allowed to spent
|
|
*/
|
|
function allowance(address _owner, address _spender) external view returns (uint256 remaining);
|
|
|
|
/**
|
|
* @notice return total supply of tokens
|
|
*/
|
|
function totalSupply() external view returns (uint256 supply);
|
|
|
|
event Transfer(address indexed from, address indexed to, uint256 value);
|
|
event Approval(address indexed owner, address indexed spender, uint256 value);
|
|
}
|
|
|
|
|
|
/**
|
|
* @dev Interface of the ERC165 standard, as defined in the
|
|
* [EIP](https://eips.ethereum.org/EIPS/eip-165).
|
|
*
|
|
* Implementers can declare support of contract interfaces, which can then be
|
|
* queried by others (`ERC165Checker`).
|
|
*
|
|
* For an implementation, see `ERC165`.
|
|
*/
|
|
interface IERC165 {
|
|
/**
|
|
* @dev Returns true if this contract implements the interface defined by
|
|
* `interfaceId`. See the corresponding
|
|
* [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
|
|
* to learn more about how these ids are created.
|
|
*
|
|
* This function call must use less than 30 000 gas.
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId) external view returns (bool);
|
|
}
|
|
|
|
|
|
/**
|
|
* @title ERC721 token receiver interface
|
|
* @dev Interface for any contract that wants to support safeTransfers
|
|
* from ERC721 asset contracts.
|
|
*/
|
|
contract IERC721Receiver {
|
|
/**
|
|
* @notice Handle the receipt of an NFT
|
|
* @dev The ERC721 smart contract calls this function on the recipient
|
|
* after a `safeTransfer`. This function MUST return the function selector,
|
|
* otherwise the caller will revert the transaction. The selector to be
|
|
* returned can be obtained as `this.onERC721Received.selector`. This
|
|
* function MAY throw to revert and reject the transfer.
|
|
* Note: the ERC721 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 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
|
|
*/
|
|
function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data)
|
|
public returns (bytes4);
|
|
}
|
|
|
|
|
|
/**
|
|
* @title Counters
|
|
* @author Matt Condon (@shrugs)
|
|
* @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
|
|
* of elements in a mapping, issuing ERC721 ids, or counting request ids.
|
|
*
|
|
* Include with `using Counters for Counters.Counter;`
|
|
* Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath
|
|
* overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
|
|
* directly accessed.
|
|
*/
|
|
library Counters {
|
|
using SafeMath for uint256;
|
|
|
|
struct Counter {
|
|
// This variable should never be directly accessed by users of the library: interactions must be restricted to
|
|
// the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
|
|
// this feature: see https://github.com/ethereum/solidity/issues/4637
|
|
uint256 _value; // default: 0
|
|
}
|
|
|
|
function current(Counter storage counter) internal view returns (uint256) {
|
|
return counter._value;
|
|
}
|
|
|
|
function increment(Counter storage counter) internal {
|
|
counter._value += 1;
|
|
}
|
|
|
|
function decrement(Counter storage counter) internal {
|
|
counter._value = counter._value.sub(1);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @dev Implementation of the `IERC165` interface.
|
|
*
|
|
* Contracts may inherit from this and call `_registerInterface` to declare
|
|
* their support of an interface.
|
|
*/
|
|
contract ERC165 is IERC165 {
|
|
/*
|
|
* bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
|
|
*/
|
|
bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
|
|
|
|
/**
|
|
* @dev Mapping of interface ids to whether or not it's supported.
|
|
*/
|
|
mapping(bytes4 => bool) private _supportedInterfaces;
|
|
|
|
constructor () internal {
|
|
// Derived contracts need only register support for their own interfaces,
|
|
// we register support for ERC165 itself here
|
|
_registerInterface(_INTERFACE_ID_ERC165);
|
|
}
|
|
|
|
/**
|
|
* @dev See `IERC165.supportsInterface`.
|
|
*
|
|
* Time complexity O(1), guaranteed to always use less than 30 000 gas.
|
|
*/
|
|
function supportsInterface(bytes4 interfaceId) external view returns (bool) {
|
|
return _supportedInterfaces[interfaceId];
|
|
}
|
|
|
|
/**
|
|
* @dev Registers the contract as an implementer of the interface defined by
|
|
* `interfaceId`. Support of the actual ERC165 interface is automatic and
|
|
* registering its interface id is not required.
|
|
*
|
|
* See `IERC165.supportsInterface`.
|
|
*
|
|
* Requirements:
|
|
*
|
|
* - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
|
|
*/
|
|
function _registerInterface(bytes4 interfaceId) internal {
|
|
require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
|
|
_supportedInterfaces[interfaceId] = true;
|
|
}
|
|
}
|
|
|
|
|
|
contract ERC20Receiver {
|
|
|
|
event TokenDeposited(address indexed token, address indexed sender, uint256 amount);
|
|
event TokenWithdrawn(address indexed token, address indexed sender, uint256 amount);
|
|
|
|
mapping (address => mapping(address => uint256)) tokenBalances;
|
|
|
|
constructor() public {
|
|
}
|
|
|
|
function depositToken(
|
|
ERC20Token _token
|
|
)
|
|
external
|
|
{
|
|
_depositToken(
|
|
msg.sender,
|
|
_token,
|
|
_token.allowance(
|
|
msg.sender,
|
|
address(this)
|
|
)
|
|
);
|
|
}
|
|
|
|
function withdrawToken(
|
|
ERC20Token _token,
|
|
uint256 _amount
|
|
)
|
|
external
|
|
{
|
|
_withdrawToken(msg.sender, _token, _amount);
|
|
}
|
|
|
|
function depositToken(
|
|
ERC20Token _token,
|
|
uint256 _amount
|
|
)
|
|
external
|
|
{
|
|
require(_token.allowance(msg.sender, address(this)) >= _amount, "Bad argument");
|
|
_depositToken(msg.sender, _token, _amount);
|
|
}
|
|
|
|
function tokenBalanceOf(
|
|
ERC20Token _token,
|
|
address _from
|
|
)
|
|
external
|
|
view
|
|
returns(uint256 fromTokenBalance)
|
|
{
|
|
return tokenBalances[address(_token)][_from];
|
|
}
|
|
|
|
function _depositToken(
|
|
address _from,
|
|
ERC20Token _token,
|
|
uint256 _amount
|
|
)
|
|
private
|
|
{
|
|
require(_amount > 0, "Bad argument");
|
|
if (_token.transferFrom(_from, address(this), _amount)) {
|
|
tokenBalances[address(_token)][_from] += _amount;
|
|
emit TokenDeposited(address(_token), _from, _amount);
|
|
}
|
|
}
|
|
|
|
function _withdrawToken(
|
|
address _from,
|
|
ERC20Token _token,
|
|
uint256 _amount
|
|
)
|
|
private
|
|
{
|
|
require(_amount > 0, "Bad argument");
|
|
require(tokenBalances[address(_token)][_from] >= _amount, "Insufficient funds");
|
|
tokenBalances[address(_token)][_from] -= _amount;
|
|
require(_token.transfer(_from, _amount), "Transfer fail");
|
|
emit TokenWithdrawn(address(_token), _from, _amount);
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @dev Required interface of an ERC721 compliant contract.
|
|
*/
|
|
contract IERC721 is IERC165 {
|
|
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
|
|
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
|
|
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
|
|
|
|
/**
|
|
* @dev Returns the number of NFTs in `owner`'s account.
|
|
*/
|
|
function balanceOf(address owner) public view returns (uint256 balance);
|
|
|
|
/**
|
|
* @dev Returns the owner of the NFT specified by `tokenId`.
|
|
*/
|
|
function ownerOf(uint256 tokenId) public view returns (address owner);
|
|
|
|
/**
|
|
* @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
|
|
* another (`to`).
|
|
*
|
|
*
|
|
*
|
|
* Requirements:
|
|
* - `from`, `to` cannot be zero.
|
|
* - `tokenId` must be owned by `from`.
|
|
* - If the caller is not `from`, it must be have been allowed to move this
|
|
* NFT by either `approve` or `setApproveForAll`.
|
|
*/
|
|
function safeTransferFrom(address from, address to, uint256 tokenId) public;
|
|
/**
|
|
* @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
|
|
* another (`to`).
|
|
*
|
|
* Requirements:
|
|
* - If the caller is not `from`, it must be approved to move this NFT by
|
|
* either `approve` or `setApproveForAll`.
|
|
*/
|
|
function transferFrom(address from, address to, uint256 tokenId) public;
|
|
function approve(address to, uint256 tokenId) public;
|
|
function getApproved(uint256 tokenId) public view returns (address operator);
|
|
|
|
function setApprovalForAll(address operator, bool _approved) public;
|
|
function isApprovedForAll(address owner, address operator) public view returns (bool);
|
|
|
|
|
|
function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
|
|
}
|
|
|
|
|
|
/**
|
|
* @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
|
|
* @dev See https://eips.ethereum.org/EIPS/eip-721
|
|
*/
|
|
contract IERC721Enumerable is IERC721 {
|
|
function totalSupply() public view returns (uint256);
|
|
function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256 tokenId);
|
|
|
|
function tokenByIndex(uint256 index) public view returns (uint256);
|
|
}
|
|
|
|
|
|
/**
|
|
* @title ERC-721 Non-Fungible Token Standard, optional metadata extension
|
|
* @dev See https://eips.ethereum.org/EIPS/eip-721
|
|
*/
|
|
contract IERC721Metadata is IERC721 {
|
|
function name() external view returns (string memory);
|
|
function symbol() external view returns (string memory);
|
|
function tokenURI(uint256 tokenId) external view returns (string memory);
|
|
}
|
|
|
|
|
|
contract TokenClaimer {
|
|
event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
|
|
|
|
function claimTokens(address _token) external;
|
|
/**
|
|
* @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 withdrawBalance(address _token, address payable _destination)
|
|
internal
|
|
{
|
|
uint256 balance;
|
|
if (_token == address(0)) {
|
|
balance = address(this).balance;
|
|
address(_destination).transfer(balance);
|
|
} else {
|
|
ERC20Token token = ERC20Token(_token);
|
|
balance = token.balanceOf(address(this));
|
|
token.transfer(_destination, balance);
|
|
}
|
|
emit ClaimedTokens(_token, _destination, balance);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @title ERC721 Non-Fungible Token Standard basic implementation
|
|
* @dev see https://eips.ethereum.org/EIPS/eip-721
|
|
*/
|
|
contract ERC721 is ERC165, IERC721 {
|
|
using SafeMath for uint256;
|
|
using Address for address;
|
|
using Counters for Counters.Counter;
|
|
|
|
// 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 => Counters.Counter) private _ownedTokensCount;
|
|
|
|
// Mapping from owner to operator approvals
|
|
mapping (address => mapping (address => bool)) private _operatorApprovals;
|
|
|
|
/*
|
|
* bytes4(keccak256('balanceOf(address)')) == 0x70a08231
|
|
* bytes4(keccak256('ownerOf(uint256)')) == 0x6352211e
|
|
* bytes4(keccak256('approve(address,uint256)')) == 0x095ea7b3
|
|
* bytes4(keccak256('getApproved(uint256)')) == 0x081812fc
|
|
* bytes4(keccak256('setApprovalForAll(address,bool)')) == 0xa22cb465
|
|
* bytes4(keccak256('isApprovedForAll(address,address)')) == 0xe985e9c
|
|
* bytes4(keccak256('transferFrom(address,address,uint256)')) == 0x23b872dd
|
|
* bytes4(keccak256('safeTransferFrom(address,address,uint256)')) == 0x42842e0e
|
|
* bytes4(keccak256('safeTransferFrom(address,address,uint256,bytes)')) == 0xb88d4fde
|
|
*
|
|
* => 0x70a08231 ^ 0x6352211e ^ 0x095ea7b3 ^ 0x081812fc ^
|
|
* 0xa22cb465 ^ 0xe985e9c ^ 0x23b872dd ^ 0x42842e0e ^ 0xb88d4fde == 0x80ac58cd
|
|
*/
|
|
bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
|
|
|
|
constructor () public {
|
|
// register the supported interfaces to conform to ERC721 via ERC165
|
|
_registerInterface(_INTERFACE_ID_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), "ERC721: balance query for the zero address");
|
|
|
|
return _ownedTokensCount[owner].current();
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the owner of the specified token ID.
|
|
* @param tokenId uint256 ID of the token to query the owner of
|
|
* @return 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), "ERC721: owner query for nonexistent token");
|
|
|
|
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, "ERC721: approval to current owner");
|
|
|
|
require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
|
|
"ERC721: approve caller is not owner nor approved for all"
|
|
);
|
|
|
|
_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), "ERC721: approved query for nonexistent token");
|
|
|
|
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, "ERC721: approve to caller");
|
|
|
|
_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 {
|
|
//solhint-disable-next-line max-line-length
|
|
require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
|
|
|
|
_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 {
|
|
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 memory _data) public {
|
|
transferFrom(from, to, tokenId);
|
|
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
|
|
}
|
|
|
|
/**
|
|
* @dev Returns whether the specified token exists.
|
|
* @param tokenId uint256 ID of the token to query the existence of
|
|
* @return bool 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) {
|
|
require(_exists(tokenId), "ERC721: operator query for nonexistent token");
|
|
address owner = ownerOf(tokenId);
|
|
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), "ERC721: mint to the zero address");
|
|
require(!_exists(tokenId), "ERC721: token already minted");
|
|
|
|
_tokenOwner[tokenId] = to;
|
|
_ownedTokensCount[to].increment();
|
|
|
|
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, "ERC721: burn of token that is not own");
|
|
|
|
_clearApproval(tokenId);
|
|
|
|
_ownedTokensCount[owner].decrement();
|
|
_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, "ERC721: transfer of token that is not own");
|
|
require(to != address(0), "ERC721: transfer to the zero address");
|
|
|
|
_clearApproval(tokenId);
|
|
|
|
_ownedTokensCount[from].decrement();
|
|
_ownedTokensCount[to].increment();
|
|
|
|
_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.
|
|
*
|
|
* This function is deprecated.
|
|
* @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 bool whether the call correctly returned the expected magic value
|
|
*/
|
|
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _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);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @title ERC-721 Non-Fungible Token Standard, full implementation interface
|
|
* @dev See https://eips.ethereum.org/EIPS/eip-721
|
|
*/
|
|
contract IERC721Full is IERC721, IERC721Enumerable, IERC721Metadata {
|
|
// solhint-disable-previous-line no-empty-blocks
|
|
}
|
|
|
|
|
|
/**
|
|
* @title ERC-721 Non-Fungible Token with optional enumeration extension logic
|
|
* @dev See https://eips.ethereum.org/EIPS/eip-721
|
|
*/
|
|
contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
|
|
// Mapping from owner to list of owned token IDs
|
|
mapping(address => uint256[]) private _ownedTokens;
|
|
|
|
// Mapping from token ID to index of the owner tokens list
|
|
mapping(uint256 => uint256) private _ownedTokensIndex;
|
|
|
|
// Array with all token ids, used for enumeration
|
|
uint256[] private _allTokens;
|
|
|
|
// Mapping from token id to position in the allTokens array
|
|
mapping(uint256 => uint256) private _allTokensIndex;
|
|
|
|
/*
|
|
* bytes4(keccak256('totalSupply()')) == 0x18160ddd
|
|
* bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
|
|
* bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
|
|
*
|
|
* => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
|
|
*/
|
|
bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
|
|
|
|
/**
|
|
* @dev Constructor function.
|
|
*/
|
|
constructor () public {
|
|
// register the supported interface to conform to ERC721Enumerable via ERC165
|
|
_registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the token ID at a given index of the tokens list of the requested owner.
|
|
* @param owner address owning the tokens list to be accessed
|
|
* @param index uint256 representing the index to be accessed of the requested tokens list
|
|
* @return uint256 token ID at the given index of the tokens list owned by the requested address
|
|
*/
|
|
function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
|
|
require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
|
|
return _ownedTokens[owner][index];
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the total amount of tokens stored by the contract.
|
|
* @return uint256 representing the total amount of tokens
|
|
*/
|
|
function totalSupply() public view returns (uint256) {
|
|
return _allTokens.length;
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the token ID at a given index of all the tokens in this contract
|
|
* Reverts if the index is greater or equal to the total number of tokens.
|
|
* @param index uint256 representing the index to be accessed of the tokens list
|
|
* @return uint256 token ID at the given index of the tokens list
|
|
*/
|
|
function tokenByIndex(uint256 index) public view returns (uint256) {
|
|
require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
|
|
return _allTokens[index];
|
|
}
|
|
|
|
/**
|
|
* @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 {
|
|
super._transferFrom(from, to, tokenId);
|
|
|
|
_removeTokenFromOwnerEnumeration(from, tokenId);
|
|
|
|
_addTokenToOwnerEnumeration(to, tokenId);
|
|
}
|
|
|
|
/**
|
|
* @dev Internal function to mint a new token.
|
|
* 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
|
|
*/
|
|
function _mint(address to, uint256 tokenId) internal {
|
|
super._mint(to, tokenId);
|
|
|
|
_addTokenToOwnerEnumeration(to, tokenId);
|
|
|
|
_addTokenToAllTokensEnumeration(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 {
|
|
super._burn(owner, tokenId);
|
|
|
|
_removeTokenFromOwnerEnumeration(owner, tokenId);
|
|
// Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund
|
|
_ownedTokensIndex[tokenId] = 0;
|
|
|
|
_removeTokenFromAllTokensEnumeration(tokenId);
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the list of token IDs of the requested owner.
|
|
* @param owner address owning the tokens
|
|
* @return uint256[] List of token IDs owned by the requested address
|
|
*/
|
|
function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
|
|
return _ownedTokens[owner];
|
|
}
|
|
|
|
/**
|
|
* @dev Private function to add a token to this extension's ownership-tracking data structures.
|
|
* @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 _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
|
|
_ownedTokensIndex[tokenId] = _ownedTokens[to].length;
|
|
_ownedTokens[to].push(tokenId);
|
|
}
|
|
|
|
/**
|
|
* @dev Private function to add a token to this extension's token tracking data structures.
|
|
* @param tokenId uint256 ID of the token to be added to the tokens list
|
|
*/
|
|
function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
|
|
_allTokensIndex[tokenId] = _allTokens.length;
|
|
_allTokens.push(tokenId);
|
|
}
|
|
|
|
/**
|
|
* @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
|
|
* while the token is not assigned a new owner, the _ownedTokensIndex mapping is _not_ updated: this allows for
|
|
* gas optimizations e.g. when performing a transfer operation (avoiding double writes).
|
|
* This has O(1) time complexity, but alters the order of the _ownedTokens array.
|
|
* @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 _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
|
|
// To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
|
|
// then delete the last slot (swap and pop).
|
|
|
|
uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
|
|
uint256 tokenIndex = _ownedTokensIndex[tokenId];
|
|
|
|
// When the token to delete is the last token, the swap operation is unnecessary
|
|
if (tokenIndex != lastTokenIndex) {
|
|
uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
|
|
|
|
_ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
|
|
_ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
|
|
}
|
|
|
|
// This also deletes the contents at the last position of the array
|
|
_ownedTokens[from].length--;
|
|
|
|
// Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by
|
|
// lastTokenId, or just over the end of the array if the token was the last one).
|
|
}
|
|
|
|
/**
|
|
* @dev Private function to remove a token from this extension's token tracking data structures.
|
|
* This has O(1) time complexity, but alters the order of the _allTokens array.
|
|
* @param tokenId uint256 ID of the token to be removed from the tokens list
|
|
*/
|
|
function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
|
|
// To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
|
|
// then delete the last slot (swap and pop).
|
|
|
|
uint256 lastTokenIndex = _allTokens.length.sub(1);
|
|
uint256 tokenIndex = _allTokensIndex[tokenId];
|
|
|
|
// When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
|
|
// rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
|
|
// an 'if' statement (like in _removeTokenFromOwnerEnumeration)
|
|
uint256 lastTokenId = _allTokens[lastTokenIndex];
|
|
|
|
_allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
|
|
_allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
|
|
|
|
// This also deletes the contents at the last position of the array
|
|
_allTokens.length--;
|
|
_allTokensIndex[tokenId] = 0;
|
|
}
|
|
}
|
|
|
|
|
|
contract ERC721Metadata is ERC165, ERC721, IERC721Metadata {
|
|
// Token name
|
|
string private _name;
|
|
|
|
// Token symbol
|
|
string private _symbol;
|
|
|
|
// Optional mapping for token URIs
|
|
mapping(uint256 => string) private _tokenURIs;
|
|
|
|
/*
|
|
* bytes4(keccak256('name()')) == 0x06fdde03
|
|
* bytes4(keccak256('symbol()')) == 0x95d89b41
|
|
* bytes4(keccak256('tokenURI(uint256)')) == 0xc87b56dd
|
|
*
|
|
* => 0x06fdde03 ^ 0x95d89b41 ^ 0xc87b56dd == 0x5b5e139f
|
|
*/
|
|
bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
|
|
|
|
/**
|
|
* @dev Constructor function
|
|
*/
|
|
constructor (string memory name, string memory symbol) public {
|
|
_name = name;
|
|
_symbol = symbol;
|
|
|
|
// register the supported interfaces to conform to ERC721 via ERC165
|
|
_registerInterface(_INTERFACE_ID_ERC721_METADATA);
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the token name.
|
|
* @return string representing the token name
|
|
*/
|
|
function name() external view returns (string memory) {
|
|
return _name;
|
|
}
|
|
|
|
/**
|
|
* @dev Gets the token symbol.
|
|
* @return string representing the token symbol
|
|
*/
|
|
function symbol() external view returns (string memory) {
|
|
return _symbol;
|
|
}
|
|
|
|
/**
|
|
* @dev Returns an URI for a given token ID.
|
|
* Throws if the token ID does not exist. May return an empty string.
|
|
* @param tokenId uint256 ID of the token to query
|
|
*/
|
|
function tokenURI(uint256 tokenId) external view returns (string memory) {
|
|
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
|
|
return _tokenURIs[tokenId];
|
|
}
|
|
|
|
/**
|
|
* @dev Internal function to set the token URI for a given token.
|
|
* Reverts if the token ID does not exist.
|
|
* @param tokenId uint256 ID of the token to set its URI
|
|
* @param uri string URI to assign
|
|
*/
|
|
function _setTokenURI(uint256 tokenId, string memory uri) internal {
|
|
require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
|
|
_tokenURIs[tokenId] = uri;
|
|
}
|
|
|
|
/**
|
|
* @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 by the msg.sender
|
|
*/
|
|
function _burn(address owner, uint256 tokenId) internal {
|
|
super._burn(owner, tokenId);
|
|
|
|
// Clear metadata (if any)
|
|
if (bytes(_tokenURIs[tokenId]).length != 0) {
|
|
delete _tokenURIs[tokenId];
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @title Full ERC721 Token
|
|
* This implementation includes all the required and some optional functionality of the ERC721 standard
|
|
* Moreover, it includes approve all functionality using operator terminology
|
|
* @dev see https://eips.ethereum.org/EIPS/eip-721
|
|
*/
|
|
contract ERC721Full is ERC721, ERC721Enumerable, ERC721Metadata {
|
|
constructor (string memory name, string memory symbol) public ERC721Metadata(name, symbol) {
|
|
// solhint-disable-previous-line no-empty-blocks
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
|
*/
|
|
contract StickerPack is Controlled, TokenClaimer, ERC721Full("Status Sticker Pack","STKP") {
|
|
|
|
mapping(uint256 => uint256) public tokenPackId; //packId
|
|
uint256 public tokenCount; //tokens buys
|
|
|
|
/**
|
|
* @notice controller can generate tokens at will
|
|
* @param _owner account being included new token
|
|
* @param _packId pack being minted
|
|
* @return tokenId created
|
|
*/
|
|
function generateToken(address _owner, uint256 _packId)
|
|
external
|
|
onlyController
|
|
returns (uint256 tokenId)
|
|
{
|
|
tokenId = tokenCount++;
|
|
tokenPackId[tokenId] = _packId;
|
|
_mint(_owner, tokenId);
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
{
|
|
withdrawBalance(_token, controller);
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
interface ApproveAndCallFallBack {
|
|
function receiveApproval(address from, uint256 _amount, address _token, bytes calldata _data) external;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
|
* StickerMarket allows any address register "StickerPack" which can be sold to any address in form of "StickerPack", an ERC721 token.
|
|
*/
|
|
contract StickerMarket is Controlled, TokenClaimer, ApproveAndCallFallBack {
|
|
using SafeMath for uint256;
|
|
|
|
event ClaimedTokens(address indexed _token, address indexed _controller, uint256 _amount);
|
|
event MarketState(State state);
|
|
event RegisterFee(uint256 value);
|
|
event BurnRate(uint256 value);
|
|
|
|
enum State { Invalid, Open, BuyOnly, Controlled, Closed }
|
|
|
|
State public state = State.Open;
|
|
uint256 registerFee;
|
|
uint256 burnRate;
|
|
|
|
//include global var to set burn rate/percentage
|
|
ERC20Token public snt; //payment token
|
|
StickerPack public stickerPack;
|
|
StickerType public stickerType;
|
|
|
|
/**
|
|
* @dev can only be called when market is open or by controller on Controlled state
|
|
*/
|
|
modifier marketManagement {
|
|
require(state == State.Open || (msg.sender == controller && state == State.Controlled), "Market Disabled");
|
|
_;
|
|
}
|
|
|
|
/**
|
|
* @dev can only be called when market is open or buy-only state.
|
|
*/
|
|
modifier marketSell {
|
|
require(state == State.Open || state == State.BuyOnly || (msg.sender == controller && state == State.Controlled), "Market Disabled");
|
|
_;
|
|
}
|
|
|
|
/**
|
|
* @param _snt SNT token
|
|
*/
|
|
constructor(
|
|
ERC20Token _snt,
|
|
StickerPack _stickerPack,
|
|
StickerType _stickerType
|
|
)
|
|
public
|
|
{
|
|
require(address(_snt) != address(0), "Bad _snt parameter");
|
|
require(address(_stickerPack) != address(0), "Bad _stickerPack parameter");
|
|
require(address(_stickerType) != address(0), "Bad _stickerType parameter");
|
|
snt = _snt;
|
|
stickerPack = _stickerPack;
|
|
stickerType = _stickerType;
|
|
}
|
|
|
|
/**
|
|
* @dev Mints NFT StickerPack in `msg.sender` account, and Transfers SNT using user allowance
|
|
* emit NonfungibleToken.Transfer(`address(0)`, `msg.sender`, `tokenId`)
|
|
* @notice buy a pack from market pack owner, including a StickerPack's token in msg.sender account with same metadata of `_packId`
|
|
* @param _packId id of market pack
|
|
* @param _destination owner of token being brought
|
|
* @param _price agreed price
|
|
* @return tokenId generated StickerPack token
|
|
*/
|
|
function buyToken(
|
|
uint256 _packId,
|
|
address _destination,
|
|
uint256 _price
|
|
)
|
|
external
|
|
returns (uint256 tokenId)
|
|
{
|
|
return buy(msg.sender, _packId, _destination, _price);
|
|
}
|
|
|
|
/**
|
|
* @dev emits StickerMarket.Register(`packId`, `_urlHash`, `_price`, `_contenthash`)
|
|
* @notice Registers to sell a sticker pack
|
|
* @param _price cost in wei to users minting this pack
|
|
* @param _donate value between 0-10000 representing percentage of `_price` that is donated to StickerMarket at every buy
|
|
* @param _category listing category
|
|
* @param _owner address of the beneficiary of buys
|
|
* @param _contenthash EIP1577 pack contenthash for listings
|
|
* @param _fee Fee msg.sender agrees to pay for this registration
|
|
* @return packId Market position of Sticker Pack data.
|
|
*/
|
|
function registerPack(
|
|
uint256 _price,
|
|
uint256 _donate,
|
|
bytes4[] calldata _category,
|
|
address _owner,
|
|
bytes calldata _contenthash,
|
|
uint256 _fee
|
|
)
|
|
external
|
|
returns(uint256 packId)
|
|
{
|
|
packId = register(msg.sender, _category, _owner, _price, _donate, _contenthash, _fee);
|
|
}
|
|
|
|
/**
|
|
* @notice MiniMeToken ApproveAndCallFallBack forwarder for registerPack and buyToken
|
|
* @param _from account calling "approve and buy"
|
|
* @param _value must be exactly whats being consumed
|
|
* @param _token must be exactly SNT contract
|
|
* @param _data abi encoded call
|
|
*/
|
|
function receiveApproval(
|
|
address _from,
|
|
uint256 _value,
|
|
address _token,
|
|
bytes calldata _data
|
|
)
|
|
external
|
|
{
|
|
require(_token == address(snt), "Bad token");
|
|
require(_token == address(msg.sender), "Bad call");
|
|
bytes4 sig = abiDecodeSig(_data);
|
|
bytes memory cdata = slice(_data,4,_data.length-4);
|
|
if(sig == this.buyToken.selector){
|
|
require(cdata.length == 96, "Bad data length");
|
|
(uint256 packId, address owner, uint256 price) = abi.decode(cdata, (uint256, address, uint256));
|
|
require(_value == price, "Bad price value");
|
|
buy(_from, packId, owner, price);
|
|
} else if(sig == this.registerPack.selector) {
|
|
require(cdata.length >= 188, "Bad data length");
|
|
(uint256 price, uint256 donate, bytes4[] memory category, address owner, bytes memory contenthash, uint256 fee) = abi.decode(cdata, (uint256,uint256,bytes4[],address,bytes,uint256));
|
|
require(_value == fee, "Bad fee value");
|
|
register(_from, category, owner, price, donate, contenthash, fee);
|
|
} else {
|
|
revert("Bad call");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice changes market state, only controller can call.
|
|
* @param _state new state
|
|
*/
|
|
function setMarketState(State _state)
|
|
external
|
|
onlyController
|
|
{
|
|
state = _state;
|
|
emit MarketState(_state);
|
|
}
|
|
|
|
/**
|
|
* @notice changes register fee, only controller can call.
|
|
* @param _value total SNT cost of registration
|
|
*/
|
|
function setRegisterFee(uint256 _value)
|
|
external
|
|
onlyController
|
|
{
|
|
registerFee = _value;
|
|
emit RegisterFee(_value);
|
|
}
|
|
|
|
/**
|
|
* @notice changes burn rate percentage, only controller can call.
|
|
* @param _value new value between 0 and 10000
|
|
*/
|
|
function setBurnRate(uint256 _value)
|
|
external
|
|
onlyController
|
|
{
|
|
burnRate = _value;
|
|
require(_value <= 10000, "cannot be more then 100.00%");
|
|
emit BurnRate(_value);
|
|
}
|
|
|
|
/**
|
|
* @notice controller can generate packs at will
|
|
* @param _price cost in wei to users minting with _urlHash metadata
|
|
* @param _donate optional amount of `_price` that is donated to StickerMarket at every buy
|
|
* @param _category listing category
|
|
* @param _owner address of the beneficiary of buys
|
|
* @param _contenthash EIP1577 pack contenthash for listings
|
|
* @return packId Market position of Sticker Pack data.
|
|
*/
|
|
function generatePack(
|
|
uint256 _price,
|
|
uint256 _donate,
|
|
bytes4[] calldata _category,
|
|
address _owner,
|
|
bytes calldata _contenthash
|
|
)
|
|
external
|
|
onlyController
|
|
returns(uint256 packId)
|
|
{
|
|
packId = stickerType.generatePack(_price, _donate, _category, _owner, _contenthash);
|
|
}
|
|
|
|
/**
|
|
* @notice removes all market data about a marketed pack, can only be called by market controller
|
|
* @param _packId pack being purged
|
|
* @param _limit limits categories being purged
|
|
*/
|
|
function purgePack(uint256 _packId, uint256 _limit)
|
|
external
|
|
onlyController
|
|
{
|
|
stickerType.purgePack(_packId, _limit);
|
|
}
|
|
|
|
/**
|
|
* @notice controller can generate tokens at will
|
|
* @param _owner account being included new token
|
|
* @param _packId pack being minted
|
|
* @return tokenId created
|
|
*/
|
|
function generateToken(address _owner, uint256 _packId)
|
|
external
|
|
onlyController
|
|
returns (uint256 tokenId)
|
|
{
|
|
return stickerPack.generateToken(_owner, _packId);
|
|
}
|
|
|
|
/**
|
|
* @notice Change controller of stickerType
|
|
* @param _newController new controller of stickerType.
|
|
*/
|
|
function migrate(address payable _newController)
|
|
external
|
|
onlyController
|
|
{
|
|
require(_newController != address(0), "Cannot unset controller");
|
|
stickerType.changeController(_newController);
|
|
stickerPack.changeController(_newController);
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
{
|
|
withdrawBalance(_token, controller);
|
|
}
|
|
|
|
/**
|
|
* @notice returns pack data of token
|
|
* @param _tokenId user token being queried
|
|
* @return categories, registration time and contenthash
|
|
*/
|
|
function getTokenData(uint256 _tokenId)
|
|
external
|
|
view
|
|
returns (
|
|
bytes4[] memory category,
|
|
uint256 timestamp,
|
|
bytes memory contenthash
|
|
)
|
|
{
|
|
return stickerType.getPackSummary(stickerPack.tokenPackId(_tokenId));
|
|
}
|
|
|
|
/**
|
|
* @dev charges registerFee and register new pack to owner
|
|
* @param _caller payment account
|
|
* @param _category listing category
|
|
* @param _owner address of the beneficiary of buys
|
|
* @param _price cost in wei to users minting this pack
|
|
* @param _donate value between 0-10000 representing percentage of `_price` that is donated to StickerMarket at every buy
|
|
* @param _contenthash EIP1577 pack contenthash for listings
|
|
* @param _fee Fee msg.sender agrees to pay for this registrion
|
|
* @return created packId
|
|
*/
|
|
function register(
|
|
address _caller,
|
|
bytes4[] memory _category,
|
|
address _owner,
|
|
uint256 _price,
|
|
uint256 _donate,
|
|
bytes memory _contenthash,
|
|
uint256 _fee
|
|
)
|
|
internal
|
|
marketManagement
|
|
returns(uint256 packId)
|
|
{
|
|
require(_fee == registerFee, "Unexpected fee");
|
|
if(registerFee > 0){
|
|
require(snt.transferFrom(_caller, controller, registerFee), "Bad payment");
|
|
}
|
|
packId = stickerType.generatePack(_price, _donate, _category, _owner, _contenthash);
|
|
}
|
|
|
|
/**
|
|
* @dev transfer SNT from buyer to pack owner and mint sticker pack token
|
|
* @param _caller payment account
|
|
* @param _packId id of market pack
|
|
* @param _destination owner of token being brought
|
|
* @param _price agreed price
|
|
* @return created tokenId
|
|
*/
|
|
function buy(
|
|
address _caller,
|
|
uint256 _packId,
|
|
address _destination,
|
|
uint256 _price
|
|
)
|
|
internal
|
|
marketSell
|
|
returns (uint256 tokenId)
|
|
{
|
|
(
|
|
address pack_owner,
|
|
bool pack_mintable,
|
|
uint256 pack_price,
|
|
uint256 pack_donate
|
|
) = stickerType.getPaymentData(_packId);
|
|
require(pack_owner != address(0), "Bad pack");
|
|
require(pack_mintable, "Disabled");
|
|
uint256 amount = pack_price;
|
|
require(_price == amount, "Wrong price");
|
|
require(amount > 0, "Unauthorized");
|
|
if(amount > 0 && burnRate > 0) {
|
|
uint256 burned = amount.mul(burnRate).div(10000);
|
|
amount = amount.sub(burned);
|
|
require(snt.transferFrom(_caller, Controlled(address(snt)).controller(), burned), "Bad burn");
|
|
}
|
|
if(amount > 0 && pack_donate > 0) {
|
|
uint256 donate = amount.mul(pack_donate).div(10000);
|
|
amount = amount.sub(donate);
|
|
require(snt.transferFrom(_caller, controller, donate), "Bad donate");
|
|
}
|
|
if(amount > 0) {
|
|
require(snt.transferFrom(_caller, pack_owner, amount), "Bad payment");
|
|
}
|
|
return stickerPack.generateToken(_destination, _packId);
|
|
}
|
|
|
|
/**
|
|
* @dev decodes sig of abi encoded call
|
|
* @param _data abi encoded data
|
|
* @return sig (first 4 bytes)
|
|
*/
|
|
function abiDecodeSig(bytes memory _data) private pure returns(bytes4 sig){
|
|
assembly {
|
|
sig := mload(add(_data, add(0x20, 0)))
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @dev get a slice of byte array
|
|
* @param _bytes source
|
|
* @param _start pointer
|
|
* @param _length size to read
|
|
* @return sliced bytes
|
|
*/
|
|
function slice(bytes memory _bytes, uint _start, uint _length) private pure returns (bytes memory) {
|
|
require(_bytes.length >= (_start + _length));
|
|
|
|
bytes memory tempBytes;
|
|
|
|
assembly {
|
|
switch iszero(_length)
|
|
case 0 {
|
|
// Get a location of some free memory and store it in tempBytes as
|
|
// Solidity does for memory variables.
|
|
tempBytes := mload(0x40)
|
|
|
|
// The first word of the slice result is potentially a partial
|
|
// word read from the original array. To read it, we calculate
|
|
// the length of that partial word and start copying that many
|
|
// bytes into the array. The first word we copy will start with
|
|
// data we don't care about, but the last `lengthmod` bytes will
|
|
// land at the beginning of the contents of the new array. When
|
|
// we're done copying, we overwrite the full first word with
|
|
// the actual length of the slice.
|
|
let lengthmod := and(_length, 31)
|
|
|
|
// The multiplication in the next line is necessary
|
|
// because when slicing multiples of 32 bytes (lengthmod == 0)
|
|
// the following copy loop was copying the origin's length
|
|
// and then ending prematurely not copying everything it should.
|
|
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
|
|
let end := add(mc, _length)
|
|
|
|
for {
|
|
// The multiplication in the next line has the same exact purpose
|
|
// as the one above.
|
|
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
|
|
} lt(mc, end) {
|
|
mc := add(mc, 0x20)
|
|
cc := add(cc, 0x20)
|
|
} {
|
|
mstore(mc, mload(cc))
|
|
}
|
|
|
|
mstore(tempBytes, _length)
|
|
|
|
//update free-memory pointer
|
|
//allocating the array padded to 32 bytes like the compiler does now
|
|
mstore(0x40, and(add(mc, 31), not(31)))
|
|
}
|
|
//if we want a zero-length slice let's just return a zero-length array
|
|
default {
|
|
tempBytes := mload(0x40)
|
|
|
|
mstore(0x40, add(tempBytes, 0x20))
|
|
}
|
|
}
|
|
|
|
return tempBytes;
|
|
}
|
|
|
|
|
|
// For ABI/web3.js purposes
|
|
// fired by StickerType
|
|
event Register(uint256 indexed packId, uint256 dataPrice, bytes contenthash);
|
|
// fired by StickerPack and MiniMeToken
|
|
event Transfer(
|
|
address indexed from,
|
|
address indexed to,
|
|
uint256 indexed value
|
|
);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @author Ricardo Guilherme Schmidt (Status Research & Development GmbH)
|
|
* StickerMarket allows any address register "StickerPack" which can be sold to any address in form of "StickerPack", an ERC721 token.
|
|
*/
|
|
contract StickerType is Controlled, TokenClaimer, ERC721Full("Status Sticker Pack Authorship","STKA") {
|
|
using SafeMath for uint256;
|
|
event Register(uint256 indexed packId, uint256 dataPrice, bytes contenthash, bool mintable);
|
|
event PriceChanged(uint256 indexed packId, uint256 dataPrice);
|
|
event MintabilityChanged(uint256 indexed packId, bool mintable);
|
|
event ContenthashChanged(uint256 indexed packid, bytes contenthash);
|
|
event Categorized(bytes4 indexed category, uint256 indexed packId);
|
|
event Uncategorized(bytes4 indexed category, uint256 indexed packId);
|
|
event Unregister(uint256 indexed packId);
|
|
|
|
struct Pack {
|
|
bytes4[] category;
|
|
bool mintable;
|
|
uint256 timestamp;
|
|
uint256 price; //in "wei"
|
|
uint256 donate; //in "percent"
|
|
bytes contenthash;
|
|
}
|
|
|
|
uint256 registerFee;
|
|
uint256 burnRate;
|
|
|
|
mapping(uint256 => Pack) public packs;
|
|
uint256 public packCount; //pack registers
|
|
|
|
|
|
//auxilary views
|
|
mapping(bytes4 => uint256[]) private availablePacks; //array of available packs
|
|
mapping(bytes4 => mapping(uint256 => uint256)) private availablePacksIndex; //position on array of available packs
|
|
mapping(uint256 => mapping(bytes4 => uint256)) private packCategoryIndex;
|
|
|
|
/**
|
|
* Can only be called by the pack owner, or by the controller if pack exists.
|
|
*/
|
|
modifier packOwner(uint256 _packId) {
|
|
address owner = ownerOf(_packId);
|
|
require((msg.sender == owner) || (owner != address(0) && msg.sender == controller), "Unauthorized");
|
|
_;
|
|
}
|
|
|
|
/**
|
|
* @notice controller can generate packs at will
|
|
* @param _price cost in wei to users minting with _urlHash metadata
|
|
* @param _donate optional amount of `_price` that is donated to StickerMarket at every buy
|
|
* @param _category listing category
|
|
* @param _owner address of the beneficiary of buys
|
|
* @param _contenthash EIP1577 pack contenthash for listings
|
|
* @return packId Market position of Sticker Pack data.
|
|
*/
|
|
function generatePack(
|
|
uint256 _price,
|
|
uint256 _donate,
|
|
bytes4[] calldata _category,
|
|
address _owner,
|
|
bytes calldata _contenthash
|
|
)
|
|
external
|
|
onlyController
|
|
returns(uint256 packId)
|
|
{
|
|
require(_donate <= 10000, "Bad argument, _donate cannot be more then 100.00%");
|
|
packId = packCount++;
|
|
_mint(_owner, packId);
|
|
packs[packId] = Pack(new bytes4[](0), true, block.timestamp, _price, _donate, _contenthash);
|
|
emit Register(packId, _price, _contenthash, true);
|
|
for(uint i = 0;i < _category.length; i++){
|
|
addAvailablePack(packId, _category[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice removes all market data about a marketed pack, can only be called by market controller
|
|
* @param _packId position to be deleted
|
|
* @param _limit limit of categories to cleanup
|
|
*/
|
|
function purgePack(uint256 _packId, uint256 _limit)
|
|
external
|
|
onlyController
|
|
{
|
|
bytes4[] memory _category = packs[_packId].category;
|
|
uint limit;
|
|
if(_limit == 0) {
|
|
limit = _category.length;
|
|
} else {
|
|
require(_limit <= _category.length, "Bad limit");
|
|
limit = _limit;
|
|
}
|
|
|
|
uint256 len = _category.length;
|
|
if(len > 0){
|
|
len--;
|
|
}
|
|
for(uint i = 0; i < limit; i++){
|
|
removeAvailablePack(_packId, _category[len-i]);
|
|
}
|
|
|
|
if(packs[_packId].category.length == 0){
|
|
_burn(ownerOf(_packId), _packId);
|
|
delete packs[_packId];
|
|
emit Unregister(_packId);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* @notice changes contenthash of `_packId`, can only be called by controller
|
|
* @param _packId which market position is being altered
|
|
* @param _contenthash new contenthash
|
|
*/
|
|
function setPackContenthash(uint256 _packId, bytes calldata _contenthash)
|
|
external
|
|
onlyController
|
|
{
|
|
emit ContenthashChanged(_packId, _contenthash);
|
|
packs[_packId].contenthash = _contenthash;
|
|
}
|
|
|
|
/**
|
|
* @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
|
|
{
|
|
withdrawBalance(_token, controller);
|
|
}
|
|
|
|
/**
|
|
* @notice changes price of `_packId`, can only be called when market is open
|
|
* @param _packId pack id changing price settings
|
|
* @param _price cost in wei to users minting this pack
|
|
* @param _donate value between 0-10000 representing percentage of `_price` that is donated to StickerMarket at every buy
|
|
*/
|
|
function setPackPrice(uint256 _packId, uint256 _price, uint256 _donate)
|
|
external
|
|
packOwner(_packId)
|
|
{
|
|
require(_donate <= 10000, "Bad argument, _donate cannot be more then 100.00%");
|
|
emit PriceChanged(_packId, _price);
|
|
packs[_packId].price = _price;
|
|
packs[_packId].donate = _donate;
|
|
}
|
|
|
|
/**
|
|
* @notice add caregory in `_packId`, can only be called when market is open
|
|
* @param _packId pack adding category
|
|
* @param _category category to list
|
|
*/
|
|
function addPackCategory(uint256 _packId, bytes4 _category)
|
|
external
|
|
packOwner(_packId)
|
|
{
|
|
addAvailablePack(_packId, _category);
|
|
}
|
|
|
|
/**
|
|
* @notice remove caregory in `_packId`, can only be called when market is open
|
|
* @param _packId pack removing category
|
|
* @param _category category to unlist
|
|
*/
|
|
function removePackCategory(uint256 _packId, bytes4 _category)
|
|
external
|
|
packOwner(_packId)
|
|
{
|
|
removeAvailablePack(_packId, _category);
|
|
}
|
|
|
|
/**
|
|
* @notice Changes if pack is enabled for sell
|
|
* @param _packId position edit
|
|
* @param _mintable true to enable sell
|
|
*/
|
|
function setPackState(uint256 _packId, bool _mintable)
|
|
external
|
|
packOwner(_packId)
|
|
{
|
|
emit MintabilityChanged(_packId, _mintable);
|
|
packs[_packId].mintable = _mintable;
|
|
}
|
|
|
|
/**
|
|
* @notice read available market ids in a category (might be slow)
|
|
* @param _category listing category
|
|
* @return array of market id registered
|
|
*/
|
|
function getAvailablePacks(bytes4 _category)
|
|
external
|
|
view
|
|
returns (uint256[] memory availableIds)
|
|
{
|
|
return availablePacks[_category];
|
|
}
|
|
|
|
/**
|
|
* @notice count total packs in a category
|
|
* @param _category listing category
|
|
* @return total number of packs in category
|
|
*/
|
|
function getCategoryLength(bytes4 _category)
|
|
external
|
|
view
|
|
returns (uint256 size)
|
|
{
|
|
size = availablePacks[_category].length;
|
|
}
|
|
|
|
/**
|
|
* @notice read a packId in the category list at a specific index
|
|
* @param _category listing category
|
|
* @param _index index
|
|
* @return packId on index
|
|
*/
|
|
function getCategoryPack(bytes4 _category, uint256 _index)
|
|
external
|
|
view
|
|
returns (uint256 packId)
|
|
{
|
|
packId = availablePacks[_category][_index];
|
|
}
|
|
|
|
/**
|
|
* @notice returns all data from pack in market
|
|
* @param _packId pack id being queried
|
|
* @return categories, owner, mintable, price, donate and contenthash
|
|
*/
|
|
function getPackData(uint256 _packId)
|
|
external
|
|
view
|
|
returns (
|
|
bytes4[] memory category,
|
|
address owner,
|
|
bool mintable,
|
|
uint256 timestamp,
|
|
uint256 price,
|
|
bytes memory contenthash
|
|
)
|
|
{
|
|
Pack memory pack = packs[_packId];
|
|
return (
|
|
pack.category,
|
|
ownerOf(_packId),
|
|
pack.mintable,
|
|
pack.timestamp,
|
|
pack.price,
|
|
pack.contenthash
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice returns all data from pack in market
|
|
* @param _packId pack id being queried
|
|
* @return categories, owner, mintable, price, donate and contenthash
|
|
*/
|
|
function getPackSummary(uint256 _packId)
|
|
external
|
|
view
|
|
returns (
|
|
bytes4[] memory category,
|
|
uint256 timestamp,
|
|
bytes memory contenthash
|
|
)
|
|
{
|
|
Pack memory pack = packs[_packId];
|
|
return (
|
|
pack.category,
|
|
pack.timestamp,
|
|
pack.contenthash
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @notice returns payment data for migrated contract
|
|
* @param _packId pack id being queried
|
|
* @return owner, mintable, price and donate
|
|
*/
|
|
function getPaymentData(uint256 _packId)
|
|
external
|
|
view
|
|
returns (
|
|
address owner,
|
|
bool mintable,
|
|
uint256 price,
|
|
uint256 donate
|
|
)
|
|
{
|
|
Pack memory pack = packs[_packId];
|
|
return (
|
|
ownerOf(_packId),
|
|
pack.mintable,
|
|
pack.price,
|
|
pack.donate
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @dev adds id from "available list"
|
|
* @param _packId altered pack
|
|
* @param _category listing category
|
|
*/
|
|
function addAvailablePack(uint256 _packId, bytes4 _category) private {
|
|
require(packCategoryIndex[_packId][_category] == 0, "Duplicate categorization");
|
|
availablePacksIndex[_category][_packId] = availablePacks[_category].push(_packId);
|
|
packCategoryIndex[_packId][_category] = packs[_packId].category.push(_category);
|
|
emit Categorized(_category, _packId);
|
|
}
|
|
|
|
/**
|
|
* @dev remove id from "available list"
|
|
* @param _packId altered pack
|
|
* @param _category listing category
|
|
*/
|
|
function removeAvailablePack(uint256 _packId, bytes4 _category) private {
|
|
uint pos = availablePacksIndex[_category][_packId];
|
|
require(pos > 0, "Not categorized [1]");
|
|
delete availablePacksIndex[_category][_packId];
|
|
if(pos != availablePacks[_category].length){
|
|
uint256 movedElement = availablePacks[_category][availablePacks[_category].length-1]; //tokenId;
|
|
availablePacks[_category][pos-1] = movedElement;
|
|
availablePacksIndex[_category][movedElement] = pos;
|
|
}
|
|
availablePacks[_category].length--;
|
|
|
|
uint pos2 = packCategoryIndex[_packId][_category];
|
|
require(pos2 > 0, "Not categorized [2]");
|
|
delete packCategoryIndex[_packId][_category];
|
|
if(pos2 != packs[_packId].category.length){
|
|
bytes4 movedElement2 = packs[_packId].category[packs[_packId].category.length-1]; //tokenId;
|
|
packs[_packId].category[pos2-1] = movedElement2;
|
|
packCategoryIndex[_packId][movedElement2] = pos2;
|
|
}
|
|
packs[_packId].category.length--;
|
|
emit Uncategorized(_category, _packId);
|
|
|
|
}
|
|
|
|
} |