diff --git a/README.md b/README.md index bf589f9..1049818 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ - contracts/external/ERC721/ -- contracts/external/ERC1155/ - - contracts/external/ERC20/InitializableERC20.sol - contracts/Factory/Registries/ @@ -16,4 +14,10 @@ - contracts/GeneralizedFragment/ -- contracts/SmartRoute/proxies/DODONFTProxy.sol \ No newline at end of file +- contracts/SmartRoute/proxies/DODONFTProxy.sol + +- contracts/DODOToken/DODOMysteryBox.sol + +- contracts/external/ERC1155/ + +- contracts/lib/RandomGenerator.sol \ No newline at end of file diff --git a/contracts/DODOToken/DODOMysteryBox.sol b/contracts/DODOToken/DODOMysteryBox.sol new file mode 100644 index 0000000..c03deac --- /dev/null +++ b/contracts/DODOToken/DODOMysteryBox.sol @@ -0,0 +1,138 @@ +/* + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 +*/ +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {IERC20} from "../intf/IERC20.sol"; +import {SafeERC20} from "../lib/SafeERC20.sol"; +import {SafeMath} from "../lib/SafeMath.sol"; +import {IRandomGenerator} from "../lib/RandomGenerator.sol"; +import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; +import {ERC1155} from "../external/ERC1155/ERC1155.sol"; + +contract DODOMysteryBox is ERC1155, InitializableOwnable { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + // ============ Storage ============ + + address public _TICKET_; + uint256 public _TICKET_RESERVE_; + uint256 public _TICKET_UNIT_; // ticket consumed in a single lottery + + address public _RANDOM_GENERATOR_; + uint256[] public _PROB_INTERVAL_; // index => Interval probability + uint256[][] public _PRIZE_SET_; // Interval index => tokenIds + mapping(uint256 => bool) _TOKEN_ID_FLAG_; + + uint256 constant totalInterval = 1000; + + // ============ Event ============= + event ChangeRandomGenerator(address randomGenerator); + event ChangeTicketUnit(uint256 newTicketUnit); + event RetriveTicket(address to, uint256 amount); + event BurnTicket(uint256 amount); + + function init( + address owner, + string memory baseUri, + address randomGenerator, + address ticket, + uint256 ticketUnit, + uint256[] memory probIntervals, + uint256[][] memory prizeSet + ) public { + require(probIntervals.length == prizeSet.length, "DODOMysteryBox:PARAM_NOT_MATCH"); + + initOwner(owner); + _setURI(baseUri); + + _RANDOM_GENERATOR_ = randomGenerator; + _TICKET_ = ticket; + _TICKET_UNIT_ = ticketUnit; + + _setProbInterval(probIntervals); + _setPrizeSet(prizeSet); + } + + function redeemPrize(address to) external { + uint256 ticketBalance = IERC20(_TICKET_).balanceOf(address(this)); + uint256 ticketInput = ticketBalance.sub(_TICKET_RESERVE_); + uint256 ticketNum = ticketInput.div(_TICKET_UNIT_); + require(ticketNum >= 1, "DODOMysteryBox: TICKET_NOT_ENOUGH"); + for (uint256 i = 0; i < ticketNum; i++) { + _redeemSinglePrize(to); + } + _TICKET_RESERVE_ = _TICKET_RESERVE_.add(ticketBalance); + } + + // ============ Internal ============ + + function _redeemSinglePrize(address to) internal { + uint256 random = IRandomGenerator(_RANDOM_GENERATOR_).random() % totalInterval; + uint256 i; + for (i = 0; i < _PROB_INTERVAL_.length; i++) { + if (random <= _PROB_INTERVAL_[i]) { + break; + } + } + require(_PRIZE_SET_[i].length > 0, "EMPTY_PRIZE_SET"); + uint256 prize = _PRIZE_SET_[i][random % _PRIZE_SET_[i].length]; + _mint(to, prize, 1, ""); + } + + function _setProbInterval(uint256[] memory probIntervals) internal { + uint256 sum; + for (uint256 i = 0; i < probIntervals.length; i++) { + require(probIntervals[i] > 0, "DODOMysteryBox: INTERVAL_INVALID"); + sum += probIntervals[i]; + _PROB_INTERVAL_.push(probIntervals[i]); + } + require(sum == totalInterval, "DODOMysteryBox: TOTAL_INTERVAL_INVALID"); + } + + function _setPrizeSet(uint256[][] memory prizeSet) internal { + for (uint256 i = 0; i < prizeSet.length; i++) { + uint256[] memory curPrizes = prizeSet[i]; + require(curPrizes.length > 0, "DODOMysteryBox: PRIZES_INVALID"); + _PRIZE_SET_.push(); + for (uint256 j = 0; j < curPrizes.length; j++) { + uint256 curTokenId = prizeSet[i][j]; + if(_TOKEN_ID_FLAG_[curTokenId]){ + require(false, "DODOMysteryBox: TOKEN_ID_INVALID"); + }else { + _PRIZE_SET_[i].push(curTokenId); + _TOKEN_ID_FLAG_[curTokenId] = true; + } + } + } + } + + // ================= Owner =================== + + function updateRandomGenerator(address newRandomGenerator) external onlyOwner { + require(newRandomGenerator != address(0)); + _RANDOM_GENERATOR_ = newRandomGenerator; + emit ChangeRandomGenerator(newRandomGenerator); + } + + function updateTicketUnit(uint256 newTicketUnit) external onlyOwner { + require(newTicketUnit != 0); + _TICKET_UNIT_ = newTicketUnit; + emit ChangeTicketUnit(newTicketUnit); + } + + function retriveTicket(uint256 amount) external onlyOwner { + _TICKET_RESERVE_ = _TICKET_RESERVE_.sub(amount); + IERC20(_TICKET_).safeTransfer(_OWNER_, amount); + emit RetriveTicket(_OWNER_, amount); + } + + function burnTicket(uint256 amount) external onlyOwner { + _TICKET_RESERVE_ = _TICKET_RESERVE_.sub(amount); + IERC20(_TICKET_).safeTransfer(address(0), amount); + emit BurnTicket(amount); + } +} diff --git a/contracts/external/ERC1155/ERC1155.sol b/contracts/external/ERC1155/ERC1155.sol new file mode 100644 index 0000000..daddffe --- /dev/null +++ b/contracts/external/ERC1155/ERC1155.sol @@ -0,0 +1,273 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +import {IERC1155} from "../../intf/IERC1155.sol"; +import {IERC1155Receiver} from "../../intf/IERC1155Receiver.sol"; +import {IERC1155MetadataURI} from "../../intf/IERC1155MetadataURI.sol"; +import {IERC165} from "../../intf/IERC165.sol"; +import {Strings} from "../../lib/Strings.sol"; +import {Address} from "../../lib/Address.sol"; + + +contract ERC1155 is IERC165, IERC1155, IERC1155MetadataURI { + using Address for address; + + mapping (uint256 => mapping(address => uint256)) private _balances; + + mapping (address => mapping(address => bool)) private _operatorApprovals; + + string private _uri; + + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return interfaceId == type(IERC1155).interfaceId + || interfaceId == type(IERC1155MetadataURI).interfaceId; + } + + function uri(uint256) public view virtual override returns (string memory) { + return _uri; + } + + function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { + require(account != address(0), "ERC1155: balance query for the zero address"); + return _balances[id][account]; + } + + function balanceOfBatch( + address[] memory accounts, + uint256[] memory ids + ) + public + view + virtual + override + returns (uint256[] memory) + { + require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); + + uint256[] memory batchBalances = new uint256[](accounts.length); + + for (uint256 i = 0; i < accounts.length; ++i) { + batchBalances[i] = balanceOf(accounts[i], ids[i]); + } + + return batchBalances; + } + + function setApprovalForAll(address operator, bool approved) public virtual override { + require(msg.sender != operator, "ERC1155: setting approval status for self"); + + _operatorApprovals[msg.sender][operator] = approved; + emit ApprovalForAll(msg.sender, operator, approved); + } + + function isApprovedForAll(address account, address operator) public view virtual override returns (bool) { + return _operatorApprovals[account][operator]; + } + + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) + public + virtual + override + { + require(to != address(0), "ERC1155: transfer to the zero address"); + require( + from == msg.sender || isApprovedForAll(from, msg.sender), + "ERC1155: caller is not owner nor approved" + ); + + address operator = msg.sender; + + _beforeTokenTransfer(operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data); + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + _balances[id][from] = fromBalance - amount; + _balances[id][to] += amount; + + emit TransferSingle(operator, from, to, id, amount); + + _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); + } + + function safeBatchTransferFrom( + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) + public + virtual + override + { + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + require(to != address(0), "ERC1155: transfer to the zero address"); + require( + from == msg.sender || isApprovedForAll(from, msg.sender), + "ERC1155: transfer caller is not owner nor approved" + ); + + address operator = msg.sender; + + _beforeTokenTransfer(operator, from, to, ids, amounts, data); + + for (uint256 i = 0; i < ids.length; ++i) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 fromBalance = _balances[id][from]; + require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); + _balances[id][from] = fromBalance - amount; + _balances[id][to] += amount; + } + + emit TransferBatch(operator, from, to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); + } + + function _setURI(string memory newuri) internal virtual { + _uri = newuri; + } + + function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual { + require(account != address(0), "ERC1155: mint to the zero address"); + + address operator = msg.sender; + + _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data); + + _balances[id][account] += amount; + emit TransferSingle(operator, address(0), account, id, amount); + + _doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data); + } + + function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual { + require(to != address(0), "ERC1155: mint to the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = msg.sender; + + _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); + + for (uint i = 0; i < ids.length; i++) { + _balances[ids[i]][to] += amounts[i]; + } + + emit TransferBatch(operator, address(0), to, ids, amounts); + + _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); + } + + function _burn(address account, uint256 id, uint256 amount) internal virtual { + require(account != address(0), "ERC1155: burn from the zero address"); + + address operator = msg.sender; + + _beforeTokenTransfer(operator, account, address(0), _asSingletonArray(id), _asSingletonArray(amount), ""); + + uint256 accountBalance = _balances[id][account]; + require(accountBalance >= amount, "ERC1155: burn amount exceeds balance"); + _balances[id][account] = accountBalance - amount; + + emit TransferSingle(operator, account, address(0), id, amount); + } + + function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual { + require(account != address(0), "ERC1155: burn from the zero address"); + require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); + + address operator = msg.sender; + + _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); + + for (uint i = 0; i < ids.length; i++) { + uint256 id = ids[i]; + uint256 amount = amounts[i]; + + uint256 accountBalance = _balances[id][account]; + require(accountBalance >= amount, "ERC1155: burn amount exceeds balance"); + _balances[id][account] = accountBalance - amount; + } + + emit TransferBatch(operator, account, address(0), ids, amounts); + } + + function _beforeTokenTransfer( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) + internal + virtual + { } + + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 amount, + bytes memory data + ) + private + { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { + if (response != IERC1155Receiver(to).onERC1155Received.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _doSafeBatchTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256[] memory ids, + uint256[] memory amounts, + bytes memory data + ) + private + { + if (to.isContract()) { + try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (bytes4 response) { + if (response != IERC1155Receiver(to).onERC1155BatchReceived.selector) { + revert("ERC1155: ERC1155Receiver rejected tokens"); + } + } catch Error(string memory reason) { + revert(reason); + } catch { + revert("ERC1155: transfer to non ERC1155Receiver implementer"); + } + } + } + + function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { + uint256[] memory array = new uint256[](1); + array[0] = element; + + return array; + } +} diff --git a/contracts/external/ERC1155/InitializableERC1155.sol b/contracts/external/ERC1155/InitializableERC1155.sol index 3e2e8cf..fafca4b 100644 --- a/contracts/external/ERC1155/InitializableERC1155.sol +++ b/contracts/external/ERC1155/InitializableERC1155.sol @@ -7,23 +7,9 @@ pragma solidity 0.6.9; -import {IERC1155} from "../../intf/IERC1155.sol"; -import {IERC1155Receiver} from "../../intf/IERC1155Receiver.sol"; -import {IERC1155MetadataURI} from "../../intf/IERC1155MetadataURI.sol"; -import {IERC165} from "../../intf/IERC165.sol"; -import {Strings} from "../../lib/Strings.sol"; -import {Address} from "../../lib/Address.sol"; - - -contract InitializableERC1155 is IERC165, IERC1155, IERC1155MetadataURI { - using Address for address; - - mapping (uint256 => mapping(address => uint256)) private _balances; - - mapping (address => mapping(address => bool)) private _operatorApprovals; - - string private _uri; +import {ERC1155} from "./ERC1155.sol"; +contract InitializableERC1155 is ERC1155 { function init( address creator, uint256 amount, @@ -32,251 +18,4 @@ contract InitializableERC1155 is IERC165, IERC1155, IERC1155MetadataURI { _setURI(baseUrI); _mint(creator, 0, amount ,""); } - - function supportsInterface(bytes4 interfaceId) public view override returns (bool) { - return interfaceId == type(IERC1155).interfaceId - || interfaceId == type(IERC1155MetadataURI).interfaceId; - } - - function uri(uint256) public view virtual override returns (string memory) { - return _uri; - } - - function balanceOf(address account, uint256 id) public view virtual override returns (uint256) { - require(account != address(0), "ERC1155: balance query for the zero address"); - return _balances[id][account]; - } - - function balanceOfBatch( - address[] memory accounts, - uint256[] memory ids - ) - public - view - virtual - override - returns (uint256[] memory) - { - require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch"); - - uint256[] memory batchBalances = new uint256[](accounts.length); - - for (uint256 i = 0; i < accounts.length; ++i) { - batchBalances[i] = balanceOf(accounts[i], ids[i]); - } - - return batchBalances; - } - - function setApprovalForAll(address operator, bool approved) public virtual override { - require(msg.sender != operator, "ERC1155: setting approval status for self"); - - _operatorApprovals[msg.sender][operator] = approved; - emit ApprovalForAll(msg.sender, operator, approved); - } - - function isApprovedForAll(address account, address operator) public view virtual override returns (bool) { - return _operatorApprovals[account][operator]; - } - - function safeTransferFrom( - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) - public - virtual - override - { - require(to != address(0), "ERC1155: transfer to the zero address"); - require( - from == msg.sender || isApprovedForAll(from, msg.sender), - "ERC1155: caller is not owner nor approved" - ); - - address operator = msg.sender; - - _beforeTokenTransfer(operator, from, to, _asSingletonArray(id), _asSingletonArray(amount), data); - - uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); - _balances[id][from] = fromBalance - amount; - _balances[id][to] += amount; - - emit TransferSingle(operator, from, to, id, amount); - - _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data); - } - - function safeBatchTransferFrom( - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) - public - virtual - override - { - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - require(to != address(0), "ERC1155: transfer to the zero address"); - require( - from == msg.sender || isApprovedForAll(from, msg.sender), - "ERC1155: transfer caller is not owner nor approved" - ); - - address operator = msg.sender; - - _beforeTokenTransfer(operator, from, to, ids, amounts, data); - - for (uint256 i = 0; i < ids.length; ++i) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - - uint256 fromBalance = _balances[id][from]; - require(fromBalance >= amount, "ERC1155: insufficient balance for transfer"); - _balances[id][from] = fromBalance - amount; - _balances[id][to] += amount; - } - - emit TransferBatch(operator, from, to, ids, amounts); - - _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data); - } - - function _setURI(string memory newuri) internal virtual { - _uri = newuri; - } - - function _mint(address account, uint256 id, uint256 amount, bytes memory data) internal virtual { - require(account != address(0), "ERC1155: mint to the zero address"); - - address operator = msg.sender; - - _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data); - - _balances[id][account] += amount; - emit TransferSingle(operator, address(0), account, id, amount); - - _doSafeTransferAcceptanceCheck(operator, address(0), account, id, amount, data); - } - - function _mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data) internal virtual { - require(to != address(0), "ERC1155: mint to the zero address"); - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - - address operator = msg.sender; - - _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); - - for (uint i = 0; i < ids.length; i++) { - _balances[ids[i]][to] += amounts[i]; - } - - emit TransferBatch(operator, address(0), to, ids, amounts); - - _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data); - } - - function _burn(address account, uint256 id, uint256 amount) internal virtual { - require(account != address(0), "ERC1155: burn from the zero address"); - - address operator = msg.sender; - - _beforeTokenTransfer(operator, account, address(0), _asSingletonArray(id), _asSingletonArray(amount), ""); - - uint256 accountBalance = _balances[id][account]; - require(accountBalance >= amount, "ERC1155: burn amount exceeds balance"); - _balances[id][account] = accountBalance - amount; - - emit TransferSingle(operator, account, address(0), id, amount); - } - - function _burnBatch(address account, uint256[] memory ids, uint256[] memory amounts) internal virtual { - require(account != address(0), "ERC1155: burn from the zero address"); - require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch"); - - address operator = msg.sender; - - _beforeTokenTransfer(operator, account, address(0), ids, amounts, ""); - - for (uint i = 0; i < ids.length; i++) { - uint256 id = ids[i]; - uint256 amount = amounts[i]; - - uint256 accountBalance = _balances[id][account]; - require(accountBalance >= amount, "ERC1155: burn amount exceeds balance"); - _balances[id][account] = accountBalance - amount; - } - - emit TransferBatch(operator, account, address(0), ids, amounts); - } - - function _beforeTokenTransfer( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) - internal - virtual - { } - - function _doSafeTransferAcceptanceCheck( - address operator, - address from, - address to, - uint256 id, - uint256 amount, - bytes memory data - ) - private - { - if (to.isContract()) { - try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) { - if (response != IERC1155Receiver(to).onERC1155Received.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); - } - } catch Error(string memory reason) { - revert(reason); - } catch { - revert("ERC1155: transfer to non ERC1155Receiver implementer"); - } - } - } - - function _doSafeBatchTransferAcceptanceCheck( - address operator, - address from, - address to, - uint256[] memory ids, - uint256[] memory amounts, - bytes memory data - ) - private - { - if (to.isContract()) { - try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (bytes4 response) { - if (response != IERC1155Receiver(to).onERC1155BatchReceived.selector) { - revert("ERC1155: ERC1155Receiver rejected tokens"); - } - } catch Error(string memory reason) { - revert(reason); - } catch { - revert("ERC1155: transfer to non ERC1155Receiver implementer"); - } - } - } - - function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) { - uint256[] memory array = new uint256[](1); - array[0] = element; - - return array; - } } diff --git a/contracts/lib/RandomGenerator.sol b/contracts/lib/RandomGenerator.sol new file mode 100644 index 0000000..0fb5e89 --- /dev/null +++ b/contracts/lib/RandomGenerator.sol @@ -0,0 +1,34 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IRandomGenerator { + function random() external view returns (uint256); +} + +interface IDODOMidPrice { + function getMidPrice() external view returns (uint256 midPrice); +} + +contract RandomGenerator { + address[] internal pools; + + constructor(address[] memory _pools) public { + for (uint256 i = 0; i < pools.length; i++) { + pools.push(_pools[i]); + } + } + + function random() external view returns (uint256) { + uint256 priceSum; + for (uint256 i = 0; i < pools.length; i++) { + priceSum += IDODOMidPrice(pools[i]).getMidPrice(); + } + return uint256(keccak256(abi.encodePacked(blockhash(block.number-1), priceSum))); + } +}