Contract Name:
MysteryBoxSales
Contract Source Code:
pragma solidity 0.5.9;
contract AverageBlockTime {
struct Snapshot {
uint128 timestamp;
uint128 blockNumber;
}
Snapshot snapshot0;
Snapshot snapshot1;
uint256 minUpdateDelay;
constructor(uint256 _minUpdateDelay, uint128 _snapshotTimestamp, uint128 _snapshotBlockNumber) public {
require(block.timestamp - _snapshotTimestamp >= _minUpdateDelay, "require an older snapshot");
require(_snapshotBlockNumber < block.number, "can't use a future snapshot");
minUpdateDelay = _minUpdateDelay;
snapshot0.timestamp = _snapshotTimestamp;
snapshot0.blockNumber = _snapshotBlockNumber;
}
function update() external {
Snapshot memory _snapshot0 = snapshot0;
Snapshot memory _snapshot1 = snapshot1;
if(_snapshot0.timestamp > _snapshot1.timestamp) {
if(block.timestamp - _snapshot1.timestamp >= minUpdateDelay) {
snapshot1.timestamp = uint128(block.timestamp);
snapshot1.blockNumber = uint128(block.number);
}
} else {
if(block.timestamp - _snapshot0.timestamp >= minUpdateDelay) {
snapshot0.timestamp = uint128(block.timestamp);
snapshot0.blockNumber = uint128(block.number);
}
}
}
function getAverageBlockTimeInMicroSeconds() external view returns (uint256) {
Snapshot storage snapshot = snapshot0;
if(snapshot.timestamp > snapshot1.timestamp) {
if(block.timestamp - snapshot.timestamp < minUpdateDelay) {
snapshot = snapshot1;
}
} else {
if(block.timestamp - snapshot1.timestamp >= minUpdateDelay) {
snapshot = snapshot1;
}
}
return ((block.timestamp - snapshot.timestamp) * 1000) / (block.number - snapshot.blockNumber);
}
}
pragma solidity ^0.5.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;
/// @notice Transfers the ownership of an NFT from one address to another address
/// @dev This works identically to the other function with an extra data parameter,
/// except this function just sets data to ""
/// @param _from The current owner of the NFT
/// @param _to The new owner
/// @param _tokenId The NFT to transfer
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external;
/// @notice 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;
/// @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;
/// @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);
function name() external view returns (string memory _name);
function symbol() external view returns (string memory _symbol);
function tokenURI(uint256 _tokenId) external view returns (string memory);
}
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);
}
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 calldata _data) external returns(bytes4);
}
pragma solidity 0.5.9;
import "./Interfaces/ERC721.sol";
import "./AverageBlockTime.sol";
contract MysteryBoxSales is ERC721 {
uint256 lastSaleId;
struct Sale {
ERC721[] nftContracts;
uint256[] tokenIds;
address payable seller;
uint96 revealTime;
address priceToken;
uint128 price;
uint128 fee;
address[] participants;
address payable revealer;
string metadata;
}
mapping (uint256 => Sale) private sales;
// revealTime => revealer => whether revealer was able to reveal in time
mapping (uint256 => mapping(address => bool)) canGetReward;
// revealTime => revealer => priceToken => fee to collect
mapping (uint256 => mapping(address => mapping(address => uint256))) blockHashReward;
AverageBlockTime averageBlockTimeOracle;
constructor(AverageBlockTime _averageBlockTimeOracle) public {
averageBlockTimeOracle = _averageBlockTimeOracle;
}
event MysteryBoxSaleCreated(
uint256 saleId,
string name,
address indexed seller,
uint96 revealTime,
address priceToken,
uint128 price,
uint256[] tokenIds,
ERC721[] tokenContracts,
address indexed revealer,
uint128 fee,
string metadataURI
);
event BlockHash(uint256 indexed revealTime, bytes32 hash); // TODO separate contrat
event MysteryBoxSaleWithdrawn(uint256 indexed saleId, address indexed seller);
event MysteryBoxSaleProceedsWithdrawn(uint256 indexed saleId, address indexed seller, uint256 pay);
event MysteryBoxBought(uint256 indexed saleId, uint256 participantIndex, address indexed buyer);
event MysteryBoxWithdrawn(uint256 indexed saleId, uint256 participantIndex, address indexed buyer);
function createSale(
string calldata _name,
ERC721[] calldata _nftContracts,
uint256[] calldata _tokenIds,
uint96 _revealTime,
address payable _priceToken,
uint128 _price,
address payable _revealer,
uint128 _fee,
string calldata _metadata
)
external {
require(_nftContracts.length == _tokenIds.length, "tokenIds length != nftContracts length");
require(_fee <= _price, "fee too high");
// solium-disable-next-line security/no-block-members
require(_revealTime > block.timestamp, "revealTime in the past");
_escrow(msg.sender, _nftContracts, _tokenIds, _tokenIds.length);
address payable[3] memory addrs;
addrs[0] = msg.sender;
addrs[1] = _priceToken;
addrs[2] = _revealer;
_createSale(_name, _nftContracts, _tokenIds, addrs, [_revealTime, _price, _fee], _metadata);
}
function _createSale(
string memory _name,
ERC721[] memory _nftContracts,
uint256[] memory _tokenIds,
address payable[3] memory _addresses, // seller, priceToken, revealer
uint128[3] memory _values, // _revealTime, price, fee
string memory _metadata
) internal {
uint256 saleId = ++lastSaleId;
sales[saleId] = Sale(
_nftContracts,
_tokenIds,
_addresses[0],
uint96(_values[0]),
_addresses[1],
uint128(_values[1]),
uint128(_values[2]),
new address[](0),
_addresses[2],
_metadata
);
emit MysteryBoxSaleCreated(
saleId,
_name,
_addresses[0],
uint96(_values[0]),
_addresses[1],
uint128(_values[1]),
_tokenIds,
_nftContracts,
_addresses[2],
uint128(_values[2]),
_metadata
);
}
function buy(uint256 _saleId) public payable {
gift(msg.sender, _saleId);
}
function numBoxesLeftToBuy(uint256 _saleId) external view returns(uint256) {
// solium-disable-next-line security/no-block-members
if(block.timestamp < sales[_saleId].revealTime) {
return sales[_saleId].tokenIds.length - sales[_saleId].participants.length;
} else {
return 0;
}
}
function gift(address recipient, uint256 _saleId) public payable { // TODO support erc20 native meta transaction ?
Sale storage sale = sales[_saleId];
address priceToken = sale.priceToken;
uint256 numTokens = sale.tokenIds.length;
uint256 numParticipants = sale.participants.length;
uint256 revealTime = sale.revealTime;
// solium-disable-next-line security/no-block-members
require(block.timestamp < revealTime, "not on sale");
require(numParticipants < numTokens, "all mystery boxes has been purchased");
sale.participants.push(recipient);
// shuffling as we go by swapping tokenIds + corresponding nftContracts
if(numTokens > 2) {
bytes32 lastBlockHash = blockhash(block.number-1);
uint256 swap1 = uint256(keccak256(abi.encodePacked(lastBlockHash, uint8(1)))) % numTokens;
uint256 swap2 = uint256(keccak256(abi.encodePacked(lastBlockHash, uint8(2)))) % numTokens;
if(swap1 == swap2) {
swap1 = (swap2 + 1) % numTokens;
}
uint256 t1 = sale.tokenIds[swap1];
sale.tokenIds[swap1] = sale.tokenIds[swap2];
sale.tokenIds[swap2] = t1;
ERC721 c1 = sale.nftContracts[swap1];
ERC721 c2 = sale.nftContracts[swap2];
if(c1 != c2) {
sale.nftContracts[swap1] = c2;
sale.nftContracts[swap2] = c1;
}
}
{
uint256 price = sale.price;
// record fee to be rewarded to revealer once blockHash is saved
blockHashReward[revealTime][sale.revealer][priceToken] += sale.fee;
if(priceToken == address(0)) {
require(msg.value == price, "msg.value != price");
} else {
require(transferTokenFrom(priceToken, msg.sender, address(this), price), "failed to transfer price");
}
}
emit MysteryBoxBought(_saleId, numParticipants, recipient); // TODO remove and use ERC721 Transfer event below
// ERC721
numBoxes[recipient] ++;
emit Transfer(address(0), recipient, _saleId * 2**128 + numParticipants); // MINT
}
mapping(uint256 => bytes32) blockHashes;
function getBlockHash(uint256 time) external returns(bytes32) {
return blockHashes[time]; // TODO rename blockHashes to blockHashesAtTime
}
function getRevealerReward(uint256 revealTime, address revealer, address priceToken) external returns(uint256) {
return blockHashReward[revealTime][revealer][priceToken];
}
function withdrawRevealerReward(uint256 revealTime, address priceToken) external {
require(canGetReward[revealTime][msg.sender], "did not reveal in time");
uint256 reward = blockHashReward[revealTime][msg.sender][priceToken];
require(reward > 0, "not reward to collect");
blockHashReward[revealTime][msg.sender][priceToken] = 0;
canGetReward[revealTime][msg.sender] = false;
if (priceToken == address(0)) {
msg.sender.transfer(reward);
} else {
require(transferToken(priceToken, msg.sender, reward), "failed to transfer fee to revealer");
}
// TODO event ?
}
function recordBlockHash(uint256 revealTime) external{
_recordBlockHash(revealTime, msg.sender);
}
function _recordBlockHash(uint256 revealTime, address revealer) internal returns(bytes32) {
// solium-disable-next-line security/no-block-members
require(revealTime < block.timestamp, "cannot record a future or present reveal");
// solium-disable-next-line security/no-block-members
uint256 delay = block.timestamp - revealTime;
uint256 averageBlockTimeInMicroSeconds = averageBlockTimeOracle.getAverageBlockTimeInMicroSeconds();
uint256 blockDelay = ((delay * 1000) / averageBlockTimeInMicroSeconds );
require(blockDelay > 0, "cannot get current blockHash, retry later");
uint256 blockNumber = block.number - blockDelay;
bytes32 blockHash = blockHashes[revealTime];
// blockHash > 0 allows revealer to get the reward later of someone alreayd revealed
if (revealer != address(0) && (blockHash > 0 || blockDelay < 256) && !canGetReward[revealTime][revealer]) {
canGetReward[revealTime][revealer] = true;
}
if (blockHash > 0) {
return blockHash; // already recorded
}
if(blockDelay < 256) {
blockHash = blockhash(blockNumber);
} else {
blockHash = blockhash(block.number - (((blockDelay - 1) % 255) + 1));
}
blockHashes[revealTime] = blockHash;
emit BlockHash(revealTime, blockHash);
return blockHash;
}
function withdrawToSeller(uint256 _saleId) external {
withdrawToSellerRange(_saleId, 0, 2**256-1);
}
// solium-disable-next-line security/no-assign-params
function withdrawToSellerRange(uint256 _saleId, uint256 start, uint256 end) public {
Sale storage sale = sales[_saleId];
bytes32 blockHash = blockHashes[sale.revealTime];
address payable seller = sale.seller;
require(blockHash != 0, "sale has not been revealed");
require(seller != address(0), "sale already withdrawn");
uint256 numParticipants = sale.participants.length;
uint256 price = sale.price;
if(price > 0 && numParticipants > 0) {
uint256 pay = numParticipants * (price - sale.fee);
address priceToken = sale.priceToken;
if(address(priceToken) == address(0)) {
seller.transfer(pay);
} else {
require(transferToken(priceToken, seller, pay), "cannot transfer pay to seller");
}
sale.price = 0;
emit MysteryBoxSaleProceedsWithdrawn(_saleId, seller, pay);
}
uint256 numTokens = sale.tokenIds.length;
uint256 firstTokenIndex = getTokenIndexReceived(blockHash, _saleId, numParticipants);
if(end > numTokens - numParticipants) {
end = numTokens - numParticipants;
}
for(uint256 i = start; i < end; i++) {
uint256 tokenIndex = (firstTokenIndex + i) % numTokens;
sale.nftContracts[tokenIndex].transferFrom(address(this), seller, sale.tokenIds[tokenIndex]);
sale.nftContracts[tokenIndex] = ERC721(0);
sale.tokenIds[tokenIndex] = 0;
}
if(end == numTokens - numParticipants) {
emit MysteryBoxSaleWithdrawn(_saleId, seller);
sale.seller = address(0);
}
}
function _splitID(uint256 _tokenId) internal pure returns(uint256 saleId, uint256 participantIndex) {
saleId = _tokenId / 2**128;
participantIndex = uint128(_tokenId);
}
// ERC721 BOX STANDARD ? ////////////////////////////////////////////////
function open(uint256 _tokenId) external {
(uint256 saleId, uint256 participantIndex) = _splitID(_tokenId);
withdrawToken(saleId, participantIndex);
}
function peek(uint256 _tokenId) external view returns(ERC721 nftContract, uint256 tokenId) {
(uint256 saleId, uint256 participantIndex) = _splitID(_tokenId);
bytes32 blockHash = blockHashes[sales[saleId].revealTime];
if(blockHash == 0) {
return (ERC721(0),0);
}
return getTokenReceived(saleId, participantIndex);
}
//////////////////////////////////////////////////////////////////////////
function withdrawTokensInBatch(uint256 _saleId, uint256[] calldata _participantIndices) external {
bytes32 blockHash;
{
uint256 revealTime = sales[_saleId].revealTime;
blockHash = blockHashes[revealTime];
if( blockHash == 0) {
blockHash = _recordBlockHash(revealTime, address(0)); // if no blockHash make it yourself but do not record reward
}
}
uint256 numParticipants = sales[_saleId].participants.length;
uint256 numIndices = _participantIndices.length;
for(uint256 i = 0; i < numIndices; i++) {
uint256 _participantIndex = _participantIndices[i];
require(_participantIndex < numParticipants, "particpantIndex too big");
address participant = sales[_saleId].participants[_participantIndex];
require(participant == msg.sender, "participants not matching");
sales[_saleId].participants[_participantIndex] = address(0);
{
uint256 tokenIndex = getTokenIndexReceived(blockHash, _saleId, _participantIndex);
sales[_saleId].nftContracts[tokenIndex].transferFrom(address(this), participant, sales[_saleId].tokenIds[tokenIndex]);
sales[_saleId].nftContracts[tokenIndex] = ERC721(0);
sales[_saleId].tokenIds[tokenIndex] = 0;
emit MysteryBoxWithdrawn(_saleId, _participantIndex, participant); // TODO remove and use ERC721 Transfer event below ?
}
// ERC721
emit Transfer(participant, address(0), _saleId * 2**128 + _participantIndex); // BURN // no need to reset operator
}
// ERC721
numBoxes[msg.sender] -= numIndices;
}
function withdrawToken(uint256 _saleId, uint256 _participantIndex) public {
uint256 revealTime = sales[_saleId].revealTime;
bytes32 blockHash = blockHashes[revealTime];
if( blockHash == 0) {
blockHash = _recordBlockHash(revealTime, address(0)); // if no blockHash make it yourself but do not record reward
}
require(_participantIndex < sales[_saleId].participants.length, "particpantIndex too big");
address participant = sales[_saleId].participants[_participantIndex];
require(participant == msg.sender, "only participant can withdrawn");
sales[_saleId].participants[_participantIndex] = address(0);
uint256 tokenIndex = getTokenIndexReceived(blockHash, _saleId, _participantIndex);
sales[_saleId].nftContracts[tokenIndex].transferFrom(address(this), participant, sales[_saleId].tokenIds[tokenIndex]);
sales[_saleId].nftContracts[tokenIndex] = ERC721(0);
sales[_saleId].tokenIds[tokenIndex] = 0;
emit MysteryBoxWithdrawn(_saleId, _participantIndex, participant); // TODO remove and use ERC721 Transfer event below ?
// ERC721
numBoxes[participant] --;
emit Transfer(participant, address(0), _saleId * 2**128 + _participantIndex); // BURN
}
function getTokenIndexReceived(bytes32 blockHash, uint256 _saleId, uint256 _participantIndex) internal view returns(uint256 tokenIndex) {
return (uint256(keccak256(abi.encodePacked(blockHash, _saleId))) + _participantIndex) % sales[_saleId].tokenIds.length;
}
function getTokenReceived(uint256 _saleId, uint256 _participantIndex) internal view returns(ERC721 nftContract, uint256 tokenId) {
uint256 index = getTokenIndexReceived(blockHashes[sales[_saleId].revealTime], _saleId, _participantIndex);
return (sales[_saleId].nftContracts[index], sales[_saleId].tokenIds[index]);
}
function isReadyForReveal(
uint256 _saleId
)
external
view
returns
(bool) {
bytes32 blockHash = blockHashes[sales[_saleId].revealTime];
// solium-disable-next-line security/no-block-members
return sales[_saleId].revealTime > 0 && blockHash == 0 && block.timestamp > sales[_saleId].revealTime;
}
function exists(uint256 _saleId) external view returns (bool) {
return sales[_saleId].revealTime > 0;
}
function getRevealedToken(
uint256 _saleId,
uint256 _participantIndex
) external view returns (ERC721 nftContract, uint256 tokenId){
bytes32 blockHash = blockHashes[sales[_saleId].revealTime];
if(blockHash == 0) {
return (ERC721(0),0);
}
return getTokenReceived(_saleId, _participantIndex);
}
function _escrow(
address _seller,
ERC721[] memory _nftContracts,
uint256[] memory _tokenIds,
uint256 _numToEscrow
) internal {
for(uint256 i = 0; i < _numToEscrow; i++){
address ownerOfToken = _nftContracts[i].ownerOf(_tokenIds[i]);
require(ownerOfToken == _seller, "only the owner can be seller");
_nftContracts[i].transferFrom(ownerOfToken, address(this), _tokenIds[i]);
}
}
/// ERC721 ///////////////
mapping(uint256 => address) operators;
mapping(address => mapping(address => bool)) operatorsForAll;
mapping(address => uint256) numBoxes;
function balanceOf(address _owner) external view returns (uint256) {
require(_owner != address(0), "token does not exist");
return numBoxes[_owner];
}
function ownerOf(uint256 _tokenId) public view returns (address){
(uint256 saleId, uint256 participantIndex) = _splitID(_tokenId);
address[] storage participants = sales[saleId].participants;
address owner = participants[participantIndex];
require(owner != address(0), "token does not exist");
return owner;
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) external {
_transferFrom(_from, _to, _tokenId);
require(_checkERC721Receiver(_from, _to, _tokenId, data), "erc721 transfer rejected");
}
function safeTransferFrom(address _from, address _to, uint256 _tokenId) external {
_transferFrom(_from, _to, _tokenId);
require(_checkERC721Receiver(_from, _to, _tokenId, ""), "erc721 transfer rejected");
}
function transferFrom(address _from, address _to, uint256 _tokenId) external {
_transferFrom(_from, _to, _tokenId);
}
function _transferFrom(address _from, address _to, uint256 _tokenId) internal {
require(_to != address(0), "token does not exist");
require(_from != address(0), "from is zero address");
address operator = operators[_tokenId];
require(msg.sender == _from || operatorsForAll[_from][_to] || msg.sender == operator, "not approved");
(uint256 saleId, uint256 participantIndex) = _splitID(_tokenId);
require(sales[saleId].participants[participantIndex] == _from, "current owner != _from");
sales[saleId].participants[participantIndex] = _to;
if (operator != address(0)) {
operators[_tokenId] = address(0);
}
numBoxes[_from] --;
numBoxes[_to] ++;
emit Transfer(_from, _to, _tokenId);
}
function approve(address _approved, uint256 _tokenId) external {
(uint256 saleId, uint256 participantIndex) = _splitID(_tokenId);
require(sales[saleId].participants[participantIndex] == msg.sender, "current owner != msg.sender");
operators[_tokenId] = _approved;
emit Approval(msg.sender, _approved, _tokenId);
}
function setApprovalForAll(address _operator, bool _approved) external{
require(_operator != address(0), "operator is zero address");
operatorsForAll[msg.sender][_operator] = _approved;
emit ApprovalForAll(msg.sender, _operator, _approved);
}
function getApproved(uint256 _tokenId) external view returns (address){
ownerOf(_tokenId);
return operators[_tokenId];
}
function isApprovedForAll(address _owner, address _operator) external view returns (bool){
require(_owner != address(0), "token does not exist");
require(_operator != address(0), "operator is zero address");
return operatorsForAll[_owner][_operator];
}
bytes4 constant ERC721_RECEIVED = 0x150b7a02;
function _checkERC721Receiver(address _from, address _to, uint256 _id, bytes memory _data) internal returns (bool) {
if (!isContract(_to)) {
return true;
}
return (ERC721TokenReceiver(_to).onERC721Received(_from, _from, _id, _data) == ERC721_RECEIVED);
}
/// ERC721 Metadata
function name() external view returns (string memory _name) {
return "Mystery Market";
}
function symbol() external view returns (string memory _symbol) {
return "MYSTERY";
}
function toHexString(uint256 x) internal pure returns (string memory) {
uint256 numZeroes = numZeroesFor(x);
bytes memory s = new bytes(64 - numZeroes);
uint256 start = numZeroes / 2;
for (uint i = start; i < 32; i++) {
byte b = byte(uint8(uint(x) / (2**(8*(31 - i)))));
byte hi = byte(uint8(b) / 16);
byte lo = byte(uint8(b) - 16 * uint8(hi));
uint256 index = 2*(i-start);
s[index] = char(hi);
s[index+1] = char(lo);
}
return string(s);
}
function numZeroesFor(uint256 x) internal pure returns (uint256) {
uint256 numZeroes = 0;
for (uint256 i = 0; i < 32; i++) {
byte b = byte(uint8(uint(x) / (2**(8*(31 - i)))));
if (b != 0) {
break;
}
numZeroes += 2;
}
return numZeroes;
}
function char(byte b) internal pure returns (byte c) {
if (uint8(b) < 10) return byte(uint8(b) + 0x30);
else return byte(uint8(b) + 0x57);
}
function tokenURI(uint256 _tokenId) external view returns (string memory) {
ownerOf(_tokenId);
(uint256 saleId, uint256 participantIndex) = _splitID(_tokenId);
string memory metadataURI = sales[saleId].metadata;
bytes memory bs = bytes(metadataURI);
if (bs.length > 7 &&
bs[0] == 'h' &&
bs[1] == 't' &&
bs[2] == 't' &&
bs[3] == 'p'
) {
return
string(
abi.encodePacked(
metadataURI,
"/metadata_0x",toHexString(_tokenId),
".json"
)
);
} else {
return metadataURI;
}
}
function peekTokenURI(uint256 _tokenId) external view returns (string memory) {
ownerOf(_tokenId);
(uint256 saleId, uint256 participantIndex) = _splitID(_tokenId);
bytes32 blockHash = blockHashes[sales[saleId].revealTime];
if(blockHash != 0) {
(ERC721 nftContract, uint256 tokenId) = getTokenReceived(saleId, participantIndex);
return nftContract.tokenURI(tokenId);
}
return sales[saleId].metadata;
}
/// ERC165
function supportsInterface(bytes4 interfaceID) external view returns (bool){
return interfaceID == 0x01ffc9a7 || interfaceID == 0x80ac58cd || interfaceID == 0x5b5e139f;
}
/// UTILS
function isContract(address addr) internal view returns (bool) {
bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
bytes32 codehash;
// solium-disable-next-line security/no-inline-assembly
assembly {
codehash := extcodehash(addr)
}
return (codehash != 0x0 && codehash != accountHash);
}
function transferToken (
address token,
address receiver,
uint256 amount
)
internal
returns (bool transferred)
{
bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", receiver, amount);
// solium-disable-next-line security/no-inline-assembly
assembly {
let success := call(sub(gas, 10000), token, 0, add(data, 0x20), mload(data), 0, 0)
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize)
switch returndatasize
case 0 { transferred := success }
case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(ptr)))) }
default { transferred := 0 }
}
}
function transferTokenFrom (
address token,
address from,
address receiver,
uint256 amount
)
internal
returns (bool transferred)
{
bytes memory data = abi.encodeWithSignature("transferFrom(address,address,uint256)", from, receiver, amount);
// solium-disable-next-line security/no-inline-assembly
assembly {
let success := call(sub(gas, 10000), token, 0, add(data, 0x20), mload(data), 0, 0)
let ptr := mload(0x40)
returndatacopy(ptr, 0, returndatasize)
switch returndatasize
case 0 { transferred := success }
case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(ptr)))) }
default { transferred := 0 }
}
}
}