diff --git a/README.md b/README.md index 07f6e66..0fd71ec 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,17 @@ -# DODO V2: Help 1 Trillion People Issue Token +## Audit Scope -## Audit Report +- contracts/NFTPool/impl/FilterAdmin.sol -[Audited by Peckshield](https://github.com/DODOEX/contractV2/blob/main/audit/PeckShield-Audit-DODOV2-v1.0.pdf) +- contracts/NFTPool/impl/BaseFilterV1.sol -## Bug Bounty 💰 +- contracts/NFTPool/impl/FilterERC721V1.sol -### Rewards +- contracts/NFTPool/impl/FilterERC1155V1.sol -Severity of bugs will be assessed under the [CVSS Risk Rating](https://www.first.org/cvss/calculator/3.0) scale, as follows: +- contracts/NFTPool/impl/Controller.sol - - Critical (9.0-10.0): Up to $100,000 - - High (7.0-8.9): Up to $10,000 - - Medium (4.0-6.9): Up to $5,000 - - Low (0.1-3.9): Up to $1,000 +- contracts/SmartRoute/proxies/DODONFTPoolProxy.sol -In addition to assessing severity, rewards will be considered based on the impact of the discovered vulnerability as well as the level of difficulty in discovering such vulnerability. +- contracts/SmartRoute/DODONFTApprove.sol -### Disclosure -Any vulnerability or bug discovered must be reported only to the following email: contact@dodoex.io; must not be disclosed publicly; must not be disclosed to any other person, entity or email address prior to disclosure to the contact@dodoex.io email; and must not be disclosed in any way other than to the contact@dodoex.io email. In addition, disclosure to contact@dodoex.io must be made promptly following discovery of the vulnerability. Please include as much information about the vulnerability as possible, including: - - - The conditions on which reproducing the bug is contingent. - - The steps needed to reproduce the bug or, preferably, a proof of concept. - - The potential implications of the vulnerability being abused. - -A detailed report of a vulnerability increases the likelihood of a reward and may increase the reward amount. - -Anyone who reports a unique, previously-unreported vulnerability that results in a change to the code or a configuration change and who keeps such vulnerability confidential until it has been resolved by our engineers will be recognized publicly for their contribution, if agreed. - -## Contact Us - -Send E-mail to contact@dodoex.io \ No newline at end of file diff --git a/config/arb-config.js b/config/arb-config.js index 1abdaa5..c22c80a 100644 --- a/config/arb-config.js +++ b/config/arb-config.js @@ -79,5 +79,13 @@ module.exports = { DODONFTRegistry: "0x8327b093caf1a32985887CE89BD6c8F80A6B7535", DODONFTProxy: "0x6A51C8A1139B9233C31331D2Cf442E0652329f23", + + //=================== NFTPool ================== + DODONFTApprove: "0xaeB5CF31b97dce6134e416129845e01106fFB177", + DODONFTPoolProxy: "0xE55154D09265b18aC7CDAC6E646672A5460389a1", + FilterAdmin: "0x738Ebf387A0CE0eb46b0eF8Fa5DEa2EaE6B1Df51", + FilterERC721V1: "0xbAb9F4ff4A19a0e8EEBC56b06750253228ffAc6E", + FilterERC1155V1: "0xb57Dd5c265dBb13CA014F2332069E90CD0e22e65", + NFTPoolController: "0x9aE501385Bc7996A2A4a1FBb00c8d3820611BCB5" } } \ No newline at end of file diff --git a/config/bsc-config.js b/config/bsc-config.js index a513605..16077dd 100644 --- a/config/bsc-config.js +++ b/config/bsc-config.js @@ -100,6 +100,14 @@ module.exports = { //================= DropsV2 ================== DropsFeeModel: "0x6de96b1a41FF2E8264925B4C5C0564C46DC0C67d", - DropsProxy: "0xC05A30468d039381AaBaB6dcaC31078DB2C3323B" + DropsProxy: "0xC05A30468d039381AaBaB6dcaC31078DB2C3323B", + + //=================== NFTPool ================== + DODONFTApprove: "0x8f697865Fb43236683a174feE87fD84dB64C7A6c", + DODONFTPoolProxy: "0x509D3775F684fA4d19C9C99B961202e309B5B965", + FilterAdmin: "0xDe8A380e84998986A59bE6519FF172d40c0F9d41", + FilterERC721V1: "0x33364198d93648D3E976aA8625097567791c301F", + FilterERC1155V1: "0x7635694249B1bb39476a6aB28Cc6B17c1E3cAEe1", + NFTPoolController: "0x4187aab02f9E3AbdEb9a6cC71397a7A839113634" } } \ No newline at end of file diff --git a/config/eth-config.js b/config/eth-config.js index 60621cb..ebd1a83 100644 --- a/config/eth-config.js +++ b/config/eth-config.js @@ -78,5 +78,13 @@ module.exports = { DODONFTRegistry: "0xA7263eb38b9A61B72397c884b5f9bFb5C34A7840", DODONFTProxy: "0x987e2a3A65A526C66ac66B6F8c84120055363625", + + //=================== NFTPool ================== + DODONFTApprove: "0xf740253BDF035620E12f55D61C092aa294E2E5F3", + DODONFTPoolProxy: "0x9D4c342298Ba0C77E75CfD379a27F1420B3Ce45D", + FilterAdmin: "0xD4F77342a08EFF87B67D2F0Cfd34ed8c8E6Aa49e", + FilterERC721V1: "0x5f3178c155cB96f3Fd42E66933efF941a1122D79", + FilterERC1155V1: "0x5258Db198f6E39889bfCA6016786AF562Ab8bE91", + NFTPoolController: "0xfEdBea78dd3D1C301Be7ca7fdd5FB25A3b8C0b80" } } \ No newline at end of file diff --git a/config/heco-config.js b/config/heco-config.js index 98c1d8e..3c28da6 100644 --- a/config/heco-config.js +++ b/config/heco-config.js @@ -81,5 +81,13 @@ module.exports = { DODONFTRegistry: "0x77777FF74856716fd3FF89aD59fcABcCc1bE0522", DODONFTProxy: "0x181d1F15281E5475c517Fb840A6d31d32BbF65f3", + + //=================== NFTPool ================== + DODONFTApprove: "0x0d0463bCfAb32892F9dAf56f88d9671F913baB77", + DODONFTPoolProxy: "0xe5A12788A968671abD2ACce8FFD62c95314D3d90", + FilterAdmin: "0xf8d034B598E06A902f11CD6810926f9C1c1A1f0f", + FilterERC721V1: "0x48Adda32F7e77EdCE23F66497da16373dE20E922", + FilterERC1155V1: "0x2D11eFCd67782C153E16Af1B4B517136993008F0", + NFTPoolController: "0x2CEA100b9E842FFB67CaBebAdb73cE9D200E92dE" } } \ No newline at end of file diff --git a/config/matic-config.js b/config/matic-config.js index 99d32d9..5e567c3 100644 --- a/config/matic-config.js +++ b/config/matic-config.js @@ -74,5 +74,13 @@ module.exports = { DODONFTRegistry: "0x840135913a2527C3481DB29e323E05F301D33210", DODONFTProxy: "0xCb0A88A465Da7aB09C09B418F27bFf159A0FE09b", + + //=================== NFTPool ================== + DODONFTApprove: "0x91E1c84BA8786B1FaE2570202F0126C0b88F6Ec7", + DODONFTPoolProxy: "0x533AF8ad419fB3082df9C80BE2ad903912a817FB", + FilterAdmin: "0x50D148D0908C602A56884B8628A36470a875EEb2", + FilterERC721V1: "0x697F28107dF60BE8813Ce44103F15e51aEA1D61b", + FilterERC1155V1: "0x072ff3ed5F723FB4E9a83a76755ED5222ea99d7A", + NFTPoolController: "0xe6AafA1c45D9d0C64686c1f1D17B9fe9c7DAB05b" } } \ No newline at end of file diff --git a/config/ok-config.js b/config/ok-config.js index 854e242..fa14cfa 100644 --- a/config/ok-config.js +++ b/config/ok-config.js @@ -75,15 +75,12 @@ module.exports = { DODONFTRegistry: "0x327344B382EE1b44FB0a72945fCDCC7243200dD7", DODONFTProxy: "0x326c788c4C236f2bceC9476C66F8593Aa31be4Fc", - //================= DropsV1 ================= - MysteryBoxV1: "", - RandomGenerator: "", - RandomPool: [ - "", - ], - - //================= DropsV2 ================== - DropsFeeModel: "", - DropsProxy: "" + //=================== NFTPool ================== + DODONFTApprove: "0x0E3CA67AdB97E8FD07E516AFd869d1886E932F59", + DODONFTPoolProxy: "0x4599ed18F34cFE06820E3684bF0aACB8D75c644d", + FilterAdmin: "0x6fdDB76c93299D985f4d3FC7ac468F9A168577A4", + FilterERC721V1: "0xd0e1aA51dF0896c126Ce6F8A064E551e0DD3D39b", + FilterERC1155V1: "0x0672952Fab6BD1336C57AE09E49DB6D3e78B1896", + NFTPoolController: "0x8735AAd3BEae15487a017EE32cb11d8fd593e036" } } \ No newline at end of file diff --git a/config/rinkeby-config.js b/config/rinkeby-config.js index bfed8c2..ce92f43 100644 --- a/config/rinkeby-config.js +++ b/config/rinkeby-config.js @@ -74,20 +74,20 @@ module.exports = { //DODOMineV2Factory: "0x3932E00a51d0D3b85C8Eb7C3ED0FcCB0dF98B3FF" //================== NFT ==================== - BuyoutModel: "0x98F5aF1E7Fb03A085D2a28713995e4A923860288", - Fragment: "0xDF7eccee9f5C92D1Baf036DB9410456f9382E045", - NFTCollateralVault: "0x23d72eA97a9E43411Eeb908d128DF337aD334582", - DODONFTRouteHelper: "0xb0Ca341b6fbdC607A507D821780e29f9601a58B3", + BuyoutModel: "0x1A18Ccf68040f660Ac7f83B4911d17398eDbC79f", + Fragment: "0xe06ed1f2EBeD2b3c723c02C977c83b3150724f53", + NFTCollateralVault: "0x9d9A560661eD783F99188EFC2cFd5F37bCC30609", + DODONFTRouteHelper: "0xC08D918400859272442CC71fc8cC3b1a69835B4a", InitializableERC721: "0xC0ccfC832BD45Cd3A2d62e47FE92Fc50DD2210ac", InitializableERC1155: "0x9DC9086B65cCBec43F92bFa37Db81150Ed1DDDed", NFTTokenFactory: "0xd2BffcCBC1F2a7356f8DaBB55B33E47D62de1bB1", - DodoNftErc721: "0x3Bc20358B31aD498d5a245B36bC993DDBE9A4405", - DodoNftErc1155: "0xc498F36eF82Aa5dBE0ecF6DD56DD55398E80E13D", + DodoNftErc721: "0x78B7AFf2E5fA95B1E7E16679645FB65a850ed6AB", + DodoNftErc1155: "0x4C455532af01bc34a0Ec60fDd63c68FE41068c63", - DODONFTRegistry: "0x69efeCA5070Cb22c1094cffEbacafC09c058c139", - DODONFTProxy: "0x0CF019E13C6527BD34eC6c8323F11aB5DF6f0922", + DODONFTRegistry: "0xfa391c0Ed6898e0C6186605d69e877f1317Bb506", + DODONFTProxy: "0xc83c4aFdF216C7D0E15D50B9e1658298320A9551", //================= DropsV1 ================= MysteryBoxV1: "", @@ -101,5 +101,14 @@ module.exports = { //DODODropsV2: "0x4A2b9f63AE41cF3003A494F2d8Fcd9Ed850b9A6f" // DropsERC721: "0x3df8d553275781C777f432A74EEE9099226B9d13", // DropsERC1155: "0x3a8EcF30428bd4e33Cd7011533DFd596F7705c8F", + + //=================== NFTPool ================== + DODONFTApprove: "0x5a93021C4a2072E0F3daA646deBab0C9A8E3feE6", + FilterAdmin: "0x729f7f44bf64Ce814716b6261e267DbE6cdf021c", + FilterERC721V1: "0x47E2C563cDCd7F36B4E77cc33a6A5c152663f915", + FilterERC1155V1: "0x55e2e1fe50FfaBd4fE3712Bd1aBfc9307a44c7F4", + DODONFTPoolProxy: "0x81AD954B2Ed65d85d3023Eeb2D8DF6A512D4cd59", + NFTPoolController: "0xf5d24499dD76C3791ee6D19aa206f55b72270415" + } } \ No newline at end of file diff --git a/contracts/NFTPool/impl/BaseFilterV1.sol b/contracts/NFTPool/impl/BaseFilterV1.sol new file mode 100644 index 0000000..c003f11 --- /dev/null +++ b/contracts/NFTPool/impl/BaseFilterV1.sol @@ -0,0 +1,349 @@ +/* + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {IFilterAdmin} from "../intf/IFilterAdmin.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; + +contract BaseFilterV1 is InitializableOwnable, ReentrancyGuard { + using SafeMath for uint256; + + //=================== Event =================== + event NftInOrder(address user, uint256 receiveAmount); + event TargetOutOrder(address user, uint256 paidAmount); + event RandomOutOrder(address user, uint256 paidAmount); + + event ChangeNFTInPrice(uint256 newGsStart, uint256 newCr, bool toggleFlag); + event ChangeNFTRandomOutPrice(uint256 newGsStart, uint256 newCr, bool toggleFlag); + event ChangeNFTTargetOutPrice(uint256 newGsStart, uint256 newCr, bool toggleFlag); + event ChangeNFTAmountRange(uint256 maxNFTAmount, uint256 minNFTAmount); + event ChangeTokenIdRange(uint256 nftIdStart, uint256 nftIdEnd); + event ChangeTokenIdMap(uint256 tokenIds, bool isRegistered); + event ChangeFilterName(string newFilterName); + + //=================== Storage =================== + string public _FILTER_NAME_; + + address public _NFT_COLLECTION_; + uint256 public _NFT_ID_START_; + uint256 public _NFT_ID_END_ = uint256(-1); + + //tokenId => isRegistered + mapping(uint256 => bool) public _SPREAD_IDS_REGISTRY_; + + //tokenId => amount + mapping(uint256 => uint256) public _NFT_RESERVE_; + + uint256[] public _NFT_IDS_; + //tokenId => index + 1 of _NFT_IDS_ + mapping(uint256 => uint256) public _TOKENID_IDX_; + uint256 public _TOTAL_NFT_AMOUNT_; + uint256 public _MAX_NFT_AMOUNT_; + uint256 public _MIN_NFT_AMOUNT_; + + // GS -> Geometric sequence + // CR -> Common Ratio + + //For Deposit NFT IN to Pool + uint256 public _GS_START_IN_; + uint256 public _CR_IN_; + bool public _NFT_IN_TOGGLE_ = false; + + //For NFT Random OUT from Pool + uint256 public _GS_START_RANDOM_OUT_; + uint256 public _CR_RANDOM_OUT_; + bool public _NFT_RANDOM_OUT_TOGGLE_ = false; + + //For NFT Target OUT from Pool + uint256 public _GS_START_TARGET_OUT_; + uint256 public _CR_TARGET_OUT_; + bool public _NFT_TARGET_OUT_TOGGLE_ = false; + + modifier onlySuperOwner() { + require(msg.sender == IFilterAdmin(_OWNER_)._OWNER_(), "ONLY_SUPER_OWNER"); + _; + } + + //==================== Query Prop ================== + + function isNFTValid(address nftCollectionAddress, uint256 nftId) external view returns (bool) { + if (nftCollectionAddress == _NFT_COLLECTION_) { + return isNFTIDValid(nftId); + } else { + return false; + } + } + + function isNFTIDValid(uint256 nftId) public view returns (bool) { + return (nftId >= _NFT_ID_START_ && nftId <= _NFT_ID_END_) || _SPREAD_IDS_REGISTRY_[nftId]; + } + + function getAvaliableNFTInAmount() public view returns (uint256) { + if (_MAX_NFT_AMOUNT_ <= _TOTAL_NFT_AMOUNT_) { + return 0; + } else { + return _MAX_NFT_AMOUNT_ - _TOTAL_NFT_AMOUNT_; + } + } + + function getAvaliableNFTOutAmount() public view returns (uint256) { + if (_TOTAL_NFT_AMOUNT_ <= _MIN_NFT_AMOUNT_) { + return 0; + } else { + return _TOTAL_NFT_AMOUNT_ - _MIN_NFT_AMOUNT_; + } + } + + function getNFTIndexById(uint256 tokenId) public view returns (uint256) { + require(_TOKENID_IDX_[tokenId] > 0, "TOKEN_ID_NOT_EXSIT"); + return _TOKENID_IDX_[tokenId] - 1; + } + + //==================== Query Price ================== + + function queryNFTIn(uint256 NFTInAmount) + public + view + returns ( + uint256 rawReceive, + uint256 received + ) + { + require(NFTInAmount <= getAvaliableNFTInAmount(), "EXCEDD_IN_AMOUNT"); + (rawReceive, received) = _queryNFTIn(_TOTAL_NFT_AMOUNT_,_TOTAL_NFT_AMOUNT_ + NFTInAmount); + } + + function _queryNFTIn(uint256 start, uint256 end) internal view returns(uint256 rawReceive, uint256 received) { + rawReceive = _geometricCalc( + _GS_START_IN_, + _CR_IN_, + start, + end + ); + (,, received) = IFilterAdmin(_OWNER_).queryMintFee(rawReceive); + } + + function queryNFTTargetOut(uint256 NFTOutAmount) + public + view + returns ( + uint256 rawPay, + uint256 pay + ) + { + require(NFTOutAmount <= getAvaliableNFTOutAmount(), "EXCEED_OUT_AMOUNT"); + (rawPay, pay) = _queryNFTTargetOut(_TOTAL_NFT_AMOUNT_ - NFTOutAmount, _TOTAL_NFT_AMOUNT_); + } + + function _queryNFTTargetOut(uint256 start, uint256 end) internal view returns(uint256 rawPay, uint256 pay) { + rawPay = _geometricCalc( + _GS_START_TARGET_OUT_, + _CR_TARGET_OUT_, + start, + end + ); + (,, pay) = IFilterAdmin(_OWNER_).queryBurnFee(rawPay); + } + + function queryNFTRandomOut(uint256 NFTOutAmount) + public + view + returns ( + uint256 rawPay, + uint256 pay + ) + { + require(NFTOutAmount <= getAvaliableNFTOutAmount(), "EXCEED_OUT_AMOUNT"); + (rawPay, pay) = _queryNFTRandomOut(_TOTAL_NFT_AMOUNT_ - NFTOutAmount, _TOTAL_NFT_AMOUNT_); + } + + function _queryNFTRandomOut(uint256 start, uint256 end) internal view returns(uint256 rawPay, uint256 pay) { + rawPay = _geometricCalc( + _GS_START_RANDOM_OUT_, + _CR_RANDOM_OUT_, + start, + end + ); + (,, pay) = IFilterAdmin(_OWNER_).queryBurnFee(rawPay); + } + + // ============ Math ============= + + function _geometricCalc( + uint256 a0, + uint256 q, + uint256 start, + uint256 end + ) internal view returns (uint256) { + if (q == DecimalMath.ONE) { + return end.sub(start).mul(a0); + } + //q^n + uint256 qn = DecimalMath.powFloor(q, end); + //q^m + uint256 qm = DecimalMath.powFloor(q, start); + if (q < DecimalMath.ONE) { + //Sn=a0*(1 - q^n)/(1-q) + //Sn-Sm = a0*(q^m - q^n)/(1-q) + return a0.mul(qm.sub(qn)).div(DecimalMath.ONE.sub(q)); + } else { + //Sn=a0*(q^n - 1)/(q - 1) + //Sn-Sm = a0*(q^n - q^m)/(q-1) + return a0.mul(qn.sub(qm)).div(q.sub(DecimalMath.ONE)); + } + } + + function _getRandomNum() public view returns (uint256 randomNum) { + randomNum = uint256( + keccak256(abi.encodePacked(tx.origin, blockhash(block.number - 1), gasleft())) + ); + } + + // ================= Ownable ================ + + function changeNFTInPrice( + uint256 newGsStart, + uint256 newCr, + bool toggleFlag + ) external onlySuperOwner { + _changeNFTInPrice(newGsStart, newCr, toggleFlag); + } + + function _changeNFTInPrice( + uint256 newGsStart, + uint256 newCr, + bool toggleFlag + ) internal { + require(newCr != 0, "CR_INVALID"); + _GS_START_IN_ = newGsStart; + _CR_IN_ = newCr; + _NFT_IN_TOGGLE_ = toggleFlag; + + emit ChangeNFTInPrice(newGsStart, newCr, toggleFlag); + } + + function changeNFTRandomOutPrice( + uint256 newGsStart, + uint256 newCr, + bool toggleFlag + ) external onlySuperOwner { + _changeNFTRandomOutPrice(newGsStart, newCr, toggleFlag); + } + + function _changeNFTRandomOutPrice( + uint256 newGsStart, + uint256 newCr, + bool toggleFlag + ) internal { + require(newCr != 0, "CR_INVALID"); + _GS_START_RANDOM_OUT_ = newGsStart; + _CR_RANDOM_OUT_ = newCr; + _NFT_RANDOM_OUT_TOGGLE_ = toggleFlag; + + emit ChangeNFTRandomOutPrice(newGsStart, newCr, toggleFlag); + } + + function changeNFTTargetOutPrice( + uint256 newGsStart, + uint256 newCr, + bool toggleFlag + ) external onlySuperOwner { + _changeNFTTargetOutPrice(newGsStart, newCr, toggleFlag); + } + + function _changeNFTTargetOutPrice( + uint256 newGsStart, + uint256 newCr, + bool toggleFlag + ) internal { + require(newCr != 0, "CR_INVALID"); + _GS_START_TARGET_OUT_ = newGsStart; + _CR_TARGET_OUT_ = newCr; + _NFT_TARGET_OUT_TOGGLE_ = toggleFlag; + + emit ChangeNFTTargetOutPrice(newGsStart, newCr, toggleFlag); + } + + function changeNFTAmountRange(uint256 maxNFTAmount, uint256 minNFTAmount) + external + onlySuperOwner + { + _changeNFTAmountRange(maxNFTAmount, minNFTAmount); + } + + function _changeNFTAmountRange(uint256 maxNFTAmount, uint256 minNFTAmount) internal { + require(maxNFTAmount >= minNFTAmount, "AMOUNT_INVALID"); + _MAX_NFT_AMOUNT_ = maxNFTAmount; + _MIN_NFT_AMOUNT_ = minNFTAmount; + + emit ChangeNFTAmountRange(maxNFTAmount, minNFTAmount); + } + + function changeTokenIdRange(uint256 nftIdStart, uint256 nftIdEnd) external onlySuperOwner { + _changeTokenIdRange(nftIdStart, nftIdEnd); + } + + function _changeTokenIdRange(uint256 nftIdStart, uint256 nftIdEnd) internal { + require(nftIdStart <= nftIdEnd, "TOKEN_RANGE_INVALID"); + + _NFT_ID_START_ = nftIdStart; + _NFT_ID_END_ = nftIdEnd; + + emit ChangeTokenIdRange(nftIdStart, nftIdEnd); + } + + function changeTokenIdMap(uint256[] memory tokenIds, bool[] memory isRegistered) + external + onlySuperOwner + { + _changeTokenIdMap(tokenIds, isRegistered); + } + + function _changeTokenIdMap(uint256[] memory tokenIds, bool[] memory isRegistered) internal { + require(tokenIds.length == isRegistered.length, "PARAM_NOT_MATCH"); + + for (uint256 i = 0; i < tokenIds.length; i++) { + _SPREAD_IDS_REGISTRY_[tokenIds[i]] = isRegistered[i]; + emit ChangeTokenIdMap(tokenIds[i], isRegistered[i]); + } + } + + function changeFilterName(string memory newFilterName) + external + onlySuperOwner + { + _changeFilterName(newFilterName); + } + + function _changeFilterName(string memory newFilterName) internal { + _FILTER_NAME_ = newFilterName; + emit ChangeFilterName(newFilterName); + } + + + function resetFilter( + string memory filterName, + bool[] memory toggles, + uint256[] memory numParams, //0 - startId, 1 - endId, 2 - maxAmount, 3 - minAmount + uint256[] memory priceRules, + uint256[] memory spreadIds, + bool[] memory isRegistered + ) external onlySuperOwner { + _changeFilterName(filterName); + _changeNFTInPrice(priceRules[0], priceRules[1], toggles[0]); + _changeNFTRandomOutPrice(priceRules[2], priceRules[3], toggles[1]); + _changeNFTTargetOutPrice(priceRules[4], priceRules[5], toggles[2]); + + _changeNFTAmountRange(numParams[2], numParams[3]); + _changeTokenIdRange(numParams[0], numParams[1]); + + _changeTokenIdMap(spreadIds, isRegistered); + } +} diff --git a/contracts/NFTPool/impl/Controller.sol b/contracts/NFTPool/impl/Controller.sol new file mode 100644 index 0000000..63ce782 --- /dev/null +++ b/contracts/NFTPool/impl/Controller.sol @@ -0,0 +1,87 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; + +contract Controller is InitializableOwnable { + using SafeMath for uint256; + + uint256 public _GLOBAL_NFT_IN_FEE_RATE_ = 0; + uint256 public _GLOBAL_NFT_OUT_FEE_RATE_ = 0; + + struct FilterAdminFeeRateInfo { + uint256 nftInFeeRate; + uint256 nftOutFeeRate; + bool isOpen; + } + + mapping(address => FilterAdminFeeRateInfo) filterAdminFeeRates; + + mapping(address => bool) public isEmergencyWithdrawOpen; + + //==================== Event ===================== + event SetEmergencyWithdraw(address filter, bool isOpen); + event SetFilterAdminFeeRateInfo(address filterAdmin, uint256 nftInFee, uint256 nftOutFee, bool isOpen); + event SetGlobalParam(uint256 nftInFee, uint256 nftOutFee); + + //==================== Ownable ==================== + + function setFilterAdminFeeRateInfo( + address filterAdminAddr, + uint256 nftInFeeRate, + uint256 nftOutFeeRate, + bool isOpen + ) external onlyOwner { + require(nftInFeeRate <= 1e18 && nftOutFeeRate <= 1e18, "FEE_RATE_TOO_LARGE"); + FilterAdminFeeRateInfo memory feeRateInfo = FilterAdminFeeRateInfo({ + nftInFeeRate: nftInFeeRate, + nftOutFeeRate: nftOutFeeRate, + isOpen: isOpen + }); + filterAdminFeeRates[filterAdminAddr] = feeRateInfo; + + emit SetFilterAdminFeeRateInfo(filterAdminAddr, nftInFeeRate, nftOutFeeRate, isOpen); + } + + function setGlobalParam(uint256 nftInFeeRate, uint256 nftOutFeeRate) external onlyOwner { + require(nftInFeeRate <= 1e18 && nftOutFeeRate <= 1e18, "FEE_RATE_TOO_LARGE"); + _GLOBAL_NFT_IN_FEE_RATE_ = nftInFeeRate; + _GLOBAL_NFT_OUT_FEE_RATE_ = nftOutFeeRate; + + emit SetGlobalParam(nftInFeeRate, nftOutFeeRate); + } + + function setEmergencyWithdraw(address filter, bool isOpen) external onlyOwner { + isEmergencyWithdrawOpen[filter] = isOpen; + emit SetEmergencyWithdraw(filter, isOpen); + } + + //===================== View ======================== + function getMintFeeRate(address filterAdminAddr) external view returns (uint256) { + FilterAdminFeeRateInfo memory filterAdminFeeRateInfo = filterAdminFeeRates[filterAdminAddr]; + + if (filterAdminFeeRateInfo.isOpen) { + return filterAdminFeeRateInfo.nftInFeeRate; + } else { + return _GLOBAL_NFT_IN_FEE_RATE_; + } + } + + function getBurnFeeRate(address filterAdminAddr) external view returns (uint256) { + FilterAdminFeeRateInfo memory filterAdminFeeInfo = filterAdminFeeRates[filterAdminAddr]; + + if (filterAdminFeeInfo.isOpen) { + return filterAdminFeeInfo.nftOutFeeRate; + } else { + return _GLOBAL_NFT_OUT_FEE_RATE_; + } + } +} diff --git a/contracts/NFTPool/impl/FilterAdmin.sol b/contracts/NFTPool/impl/FilterAdmin.sol new file mode 100644 index 0000000..cb88bcd --- /dev/null +++ b/contracts/NFTPool/impl/FilterAdmin.sol @@ -0,0 +1,139 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {InitializableInternalMintableERC20} from "../../external/ERC20/InitializableInternalMintableERC20.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {IController} from "../intf/IController.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; + +contract FilterAdmin is InitializableInternalMintableERC20 { + using SafeMath for uint256; + + // ============ Storage ============ + address[] public _FILTERS_; + mapping(address => bool) public _FILTER_REGISTRY_; + uint256 public _FEE_RATE_; + address public _CONTROLLER_; + address public _MAINTAINER_; + uint256 public _INIT_SUPPLY_; + + // ============ Event ============ + event ChangeFeeRate(uint256 fee); + event AddFilter(address filter); + + function init( + address owner, + uint256 initSupply, + string memory name, + string memory symbol, + uint256 feeRate, + address controller, + address maintainer, + address[] memory filters + ) external { + require(feeRate <= DecimalMath.ONE, "FEE_RATE_TOO_LARGE"); + super.init(owner, initSupply, name, symbol, 18); + _INIT_SUPPLY_ = initSupply; + _FEE_RATE_ = feeRate; + _CONTROLLER_ = controller; + _MAINTAINER_ = maintainer; + _FILTERS_ = filters; + for (uint256 i = 0; i < filters.length; i++) { + _FILTER_REGISTRY_[filters[i]] = true; + } + } + + function mintFragTo(address to, uint256 rawAmount) external returns (uint256) { + require(isRegisteredFilter(msg.sender), "FILTER_NOT_REGISTERED"); + + (uint256 poolFee, uint256 mtFee, uint256 received) = queryMintFee(rawAmount); + if (poolFee > 0) _mint(_OWNER_, poolFee); + if (mtFee > 0) _mint(_MAINTAINER_, mtFee); + + _mint(to, received); + return received; + } + + function burnFragFrom(address from, uint256 rawAmount) external returns (uint256) { + require(isRegisteredFilter(msg.sender), "FILTER_NOT_REGISTERED"); + + (uint256 poolFee, uint256 mtFee, uint256 paid) = queryBurnFee(rawAmount); + if (poolFee > 0) _mint(_OWNER_, poolFee); + if (mtFee > 0) _mint(_MAINTAINER_, mtFee); + + _burn(from, paid); + return paid; + } + + //================ View ================ + function queryMintFee(uint256 rawAmount) + view + public + returns ( + uint256 poolFee, + uint256 mtFee, + uint256 afterChargedAmount + ) + { + uint256 mtFeeRate = IController(_CONTROLLER_).getMintFeeRate(address(this)); + poolFee = DecimalMath.mulFloor(rawAmount, _FEE_RATE_); + mtFee = DecimalMath.mulFloor(rawAmount, mtFeeRate); + afterChargedAmount = rawAmount.sub(poolFee).sub(mtFee); + } + + function queryBurnFee(uint256 rawAmount) + view + public + returns ( + uint256 poolFee, + uint256 mtFee, + uint256 afterChargedAmount + ) + { + uint256 mtFeeRate = IController(_CONTROLLER_).getBurnFeeRate(address(this)); + poolFee = DecimalMath.mulFloor(rawAmount, _FEE_RATE_); + mtFee = DecimalMath.mulFloor(rawAmount, mtFeeRate); + afterChargedAmount = rawAmount.add(poolFee).add(mtFee); + } + + function isRegisteredFilter(address filter) public view returns (bool) { + return _FILTER_REGISTRY_[filter]; + } + + function getFilters() public view returns (address[] memory) { + return _FILTERS_; + } + + //================= Owner ================ + function addFilter(address[] memory filters) external onlyOwner { + for(uint256 i = 0; i < filters.length; i++) { + require(!isRegisteredFilter(filters[i]), "FILTER_ALREADY_EXIST"); + _FILTERS_.push(filters[i]); + _FILTER_REGISTRY_[filters[i]] = true; + emit AddFilter(filters[i]); + } + } + + function changeFeeRate(uint256 newFeeRate) external onlyOwner { + require(newFeeRate <= DecimalMath.ONE, "FEE_RATE_TOO_LARGE"); + _FEE_RATE_ = newFeeRate; + emit ChangeFeeRate(newFeeRate); + } + + function directTransferOwnership(address newOwner) external onlyOwner { + emit OwnershipTransferred(_OWNER_, newOwner); + _OWNER_ = newOwner; + } + + //================= Support ================ + function version() external pure virtual returns (string memory) { + return "FILTER ADMIN 1.0.0"; + } +} diff --git a/contracts/NFTPool/impl/FilterERC1155V1.sol b/contracts/NFTPool/impl/FilterERC1155V1.sol new file mode 100644 index 0000000..87943de --- /dev/null +++ b/contracts/NFTPool/impl/FilterERC1155V1.sol @@ -0,0 +1,226 @@ +/* + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {IFilterAdmin} from "../intf/IFilterAdmin.sol"; +import {IController} from "../intf/IController.sol"; +import {IERC1155} from "../../intf/IERC1155.sol"; +import {IERC1155Receiver} from "../../intf/IERC1155Receiver.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {BaseFilterV1} from "./BaseFilterV1.sol"; + +contract FilterERC1155V1 is IERC1155Receiver, BaseFilterV1 { + using SafeMath for uint256; + + //=============== Event ================== + event FilterInit(address filterAdmin, address nftCollection, string name); + event NftIn(uint256 tokenId, uint256 amount); + event TargetOut(uint256 tokenId, uint256 amount); + event RandomOut(uint256 tokenId, uint256 amount); + event EmergencyWithdraw(address nftContract,uint256 tokenId, uint256 amount, address to); + + function init( + address filterAdmin, + address nftCollection, + bool[] memory toggles, + string memory filterName, + uint256[] memory numParams, //0 - startId, 1 - endId, 2 - maxAmount, 3 - minAmount + uint256[] memory priceRules, + uint256[] memory spreadIds + ) external { + initOwner(filterAdmin); + + _changeFilterName(filterName); + _NFT_COLLECTION_ = nftCollection; + + _changeNFTInPrice(priceRules[0], priceRules[1], toggles[0]); + _changeNFTRandomOutPrice(priceRules[2], priceRules[3], toggles[1]); + _changeNFTTargetOutPrice(priceRules[4], priceRules[5], toggles[2]); + + _changeNFTAmountRange(numParams[2], numParams[3]); + + _changeTokenIdRange(numParams[0], numParams[1]); + for (uint256 i = 0; i < spreadIds.length; i++) { + _SPREAD_IDS_REGISTRY_[spreadIds[i]] = true; + emit ChangeTokenIdMap(spreadIds[i], true); + } + + emit FilterInit(filterAdmin, nftCollection, filterName); + } + + // ================= Trading ================ + + function ERC1155In(uint256[] memory tokenIds, address to) + external + preventReentrant + returns (uint256 received) + { + uint256 avaliableNFTInAmount = getAvaliableNFTInAmount(); + uint256 originTotalNftAmount = _TOTAL_NFT_AMOUNT_; + + uint256 totalAmount = 0; + for (uint256 i = 0; i < tokenIds.length; i++) { + uint256 tokenId = tokenIds[i]; + require(isNFTIDValid(tokenId), "NFT_ID_NOT_SUPPORT"); + uint256 inAmount = _maintainERC1155In(tokenId); + totalAmount = totalAmount.add(inAmount); + emit NftIn(tokenId, inAmount); + } + require(totalAmount <= avaliableNFTInAmount, "EXCEDD_IN_AMOUNT"); + (uint256 rawReceive, ) = _queryNFTIn(originTotalNftAmount, originTotalNftAmount + totalAmount); + received = IFilterAdmin(_OWNER_).mintFragTo(to, rawReceive); + + emit NftInOrder(to, received); + } + + function ERC1155TargetOut( + uint256[] memory tokenIds, + uint256[] memory amounts, + address to, + uint256 maxBurnAmount + ) external preventReentrant returns (uint256 paid) { + require(tokenIds.length == amounts.length, "PARAM_INVALID"); + uint256 avaliableNFTOutAmount = getAvaliableNFTOutAmount(); + uint256 originTotalNftAmount = _TOTAL_NFT_AMOUNT_; + + uint256 totalAmount = 0; + for (uint256 i = 0; i < tokenIds.length; i++) { + totalAmount = totalAmount.add(amounts[i]); + _transferOutERC1155(to, tokenIds[i], amounts[i]); + emit TargetOut(tokenIds[i], amounts[i]); + } + require(totalAmount <= avaliableNFTOutAmount, "EXCEED_OUT_AMOUNT"); + (uint256 rawPay, ) = _queryNFTTargetOut(originTotalNftAmount - totalAmount, originTotalNftAmount); + paid = IFilterAdmin(_OWNER_).burnFragFrom(msg.sender, rawPay); + require(paid <= maxBurnAmount, "BURN_AMOUNT_EXCEED"); + + emit TargetOutOrder(msg.sender, paid); + } + + function ERC1155RandomOut(uint256 amount, address to, uint256 maxBurnAmount) + external + preventReentrant + returns (uint256 paid) + { + (uint256 rawPay, ) = queryNFTRandomOut(amount); + paid = IFilterAdmin(_OWNER_).burnFragFrom(msg.sender, rawPay); + require(paid <= maxBurnAmount, "BURN_AMOUNT_EXCEED"); + + for (uint256 i = 0; i < amount; i++) { + uint256 randomNum = _getRandomNum() % _TOTAL_NFT_AMOUNT_; + uint256 sum; + for (uint256 j = 0; j < _NFT_IDS_.length; j++) { + uint256 tokenId = _NFT_IDS_[j]; + sum = sum.add(_NFT_RESERVE_[tokenId]); + if (sum >= randomNum) { + _transferOutERC1155(to, tokenId, 1); + emit RandomOut(tokenId, 1); + break; + } + } + } + + emit RandomOutOrder(msg.sender, paid); + } + + // ============ Transfer ============= + + function onERC1155Received( + address, + address, + uint256, + uint256, + bytes calldata + ) external override returns (bytes4) { + return IERC1155Receiver.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata, + uint256[] calldata, + bytes calldata + ) external override returns (bytes4) { + return IERC1155Receiver.onERC1155BatchReceived.selector; + } + + function _transferOutERC1155( + address to, + uint256 tokenId, + uint256 amount + ) internal { + require(_TOKENID_IDX_[tokenId] > 0, "TOKENID_NOT_EXIST"); + IERC1155(_NFT_COLLECTION_).safeTransferFrom(address(this), to, tokenId, amount, ""); + _maintainERC1155Out(tokenId); + } + + function emergencyWithdraw( + address[] memory nftContract, + uint256[] memory tokenIds, + uint256[] memory amounts, + address to + ) external onlySuperOwner { + require( + nftContract.length == tokenIds.length && nftContract.length == amounts.length, + "PARAM_INVALID" + ); + address controller = IFilterAdmin(_OWNER_)._CONTROLLER_(); + require( + IController(controller).isEmergencyWithdrawOpen(address(this)), + "EMERGENCY_WITHDRAW_NOT_OPEN" + ); + + for (uint256 i = 0; i < nftContract.length; i++) { + uint256 tokenId = tokenIds[i]; + IERC1155(nftContract[i]).safeTransferFrom(address(this), to, tokenId, amounts[i], ""); + if (_NFT_RESERVE_[tokenId] > 0 && nftContract[i] == _NFT_COLLECTION_) { + _maintainERC1155Out(tokenId); + } + emit EmergencyWithdraw(nftContract[i],tokenIds[i], amounts[i], to); + } + } + + function _maintainERC1155Out(uint256 tokenId) internal { + uint256 currentAmount = IERC1155(_NFT_COLLECTION_).balanceOf(address(this), tokenId); + uint256 outAmount = _NFT_RESERVE_[tokenId].sub(currentAmount); + _NFT_RESERVE_[tokenId] = currentAmount; + _TOTAL_NFT_AMOUNT_ = _TOTAL_NFT_AMOUNT_.sub(outAmount); + if (currentAmount == 0) { + uint256 index = _TOKENID_IDX_[tokenId] - 1; + if(index != _NFT_IDS_.length - 1) { + uint256 lastTokenId = _NFT_IDS_[_NFT_IDS_.length - 1]; + _NFT_IDS_[index] = lastTokenId; + _TOKENID_IDX_[lastTokenId] = index + 1; + } + _NFT_IDS_.pop(); + _TOKENID_IDX_[tokenId] = 0; + } + } + + function _maintainERC1155In(uint256 tokenId) internal returns (uint256 inAmount) { + uint256 currentAmount = IERC1155(_NFT_COLLECTION_).balanceOf(address(this), tokenId); + inAmount = currentAmount.sub(_NFT_RESERVE_[tokenId]); + if (_NFT_RESERVE_[tokenId] == 0 && currentAmount > 0) { + _NFT_IDS_.push(tokenId); + _TOKENID_IDX_[tokenId] = _NFT_IDS_.length; + } + _NFT_RESERVE_[tokenId] = currentAmount; + _TOTAL_NFT_AMOUNT_ = _TOTAL_NFT_AMOUNT_.add(inAmount); + } + + // ============ Support ============ + + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId; + } + + function version() external pure virtual returns (string memory) { + return "FILTER_1_ERC1155 1.0.0"; + } +} diff --git a/contracts/NFTPool/impl/FilterERC721V1.sol b/contracts/NFTPool/impl/FilterERC721V1.sol new file mode 100644 index 0000000..894bc71 --- /dev/null +++ b/contracts/NFTPool/impl/FilterERC721V1.sol @@ -0,0 +1,192 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {IFilterAdmin} from "../intf/IFilterAdmin.sol"; +import {IController} from "../intf/IController.sol"; +import {IERC721} from "../../intf/IERC721.sol"; +import {IERC721Receiver} from "../../intf/IERC721Receiver.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; +import {BaseFilterV1} from "./BaseFilterV1.sol"; + +contract FilterERC721V1 is IERC721Receiver, BaseFilterV1 { + using SafeMath for uint256; + + //============== Event ================= + event FilterInit(address filterAdmin, address nftCollection, string name); + event NftIn(uint256 tokenId); + event TargetOut(uint256 tokenId); + event RandomOut(uint256 tokenId); + event EmergencyWithdraw(address nftContract,uint256 tokenId, address to); + + function init( + address filterAdmin, + address nftCollection, + bool[] memory toggles, + string memory filterName, + uint256[] memory numParams, //0 - startId, 1 - endId, 2 - maxAmount, 3 - minAmount + uint256[] memory priceRules, + uint256[] memory spreadIds + ) external { + initOwner(filterAdmin); + + _changeFilterName(filterName); + _NFT_COLLECTION_ = nftCollection; + _changeNFTInPrice(priceRules[0], priceRules[1], toggles[0]); + _changeNFTRandomOutPrice(priceRules[2], priceRules[3], toggles[1]); + _changeNFTTargetOutPrice(priceRules[4], priceRules[5], toggles[2]); + + _changeNFTAmountRange(numParams[2], numParams[3]); + + _changeTokenIdRange(numParams[0], numParams[1]); + for (uint256 i = 0; i < spreadIds.length; i++) { + _SPREAD_IDS_REGISTRY_[spreadIds[i]] = true; + emit ChangeTokenIdMap(spreadIds[i], true); + } + + emit FilterInit(filterAdmin, nftCollection, filterName); + } + + // ================= Trading ================ + + function ERC721In(uint256[] memory tokenIds, address to) + external + preventReentrant + returns (uint256 received) + { + require(tokenIds.length <= getAvaliableNFTInAmount(), "EXCEDD_IN_AMOUNT"); + uint256 originTotalNftAmount = _TOTAL_NFT_AMOUNT_; + for (uint256 i = 0; i < tokenIds.length; i++) { + uint256 tokenId = tokenIds[i]; + require(isNFTIDValid(tokenId), "NFT_ID_NOT_SUPPORT"); + require( + _NFT_RESERVE_[tokenId] == 0 && + IERC721(_NFT_COLLECTION_).ownerOf(tokenId) == address(this), + "NFT_NOT_SEND" + ); + _NFT_IDS_.push(tokenId); + _TOKENID_IDX_[tokenId] = _NFT_IDS_.length; + _NFT_RESERVE_[tokenId] = 1; + + emit NftIn(tokenId); + } + _TOTAL_NFT_AMOUNT_ = _NFT_IDS_.length; + (uint256 rawReceive, ) = _queryNFTIn(originTotalNftAmount, originTotalNftAmount + tokenIds.length); + received = IFilterAdmin(_OWNER_).mintFragTo(to, rawReceive); + + emit NftInOrder(to, received); + } + + function ERC721TargetOut(uint256[] memory tokenIds, address to, uint256 maxBurnAmount) + external + preventReentrant + returns (uint256 paid) + { + (uint256 rawPay, ) = queryNFTTargetOut(tokenIds.length); + paid = IFilterAdmin(_OWNER_).burnFragFrom(msg.sender, rawPay); + require(paid <= maxBurnAmount, "BURN_AMOUNT_EXCEED"); + + for (uint256 i = 0; i < tokenIds.length; i++) { + _transferOutERC721(to, tokenIds[i]); + + emit TargetOut(tokenIds[i]); + } + _TOTAL_NFT_AMOUNT_ = _NFT_IDS_.length; + + emit TargetOutOrder(msg.sender, paid); + } + + function ERC721RandomOut(uint256 amount, address to, uint256 maxBurnAmount) + external + preventReentrant + returns (uint256 paid) + { + (uint256 rawPay, ) = queryNFTRandomOut(amount); + paid = IFilterAdmin(_OWNER_).burnFragFrom(msg.sender, rawPay); + require(paid <= maxBurnAmount, "BURN_AMOUNT_EXCEED"); + for (uint256 i = 0; i < amount; i++) { + uint256 index = _getRandomNum() % _NFT_IDS_.length; + uint256 tokenId = _NFT_IDS_[index]; + _transferOutERC721(to, tokenId); + emit RandomOut(tokenId); + } + _TOTAL_NFT_AMOUNT_ = _NFT_IDS_.length; + + emit RandomOutOrder(msg.sender, paid); + } + + // ============ Transfer ============= + + function onERC721Received( + address, + address, + uint256, + bytes calldata + ) external override returns (bytes4) { + return IERC721Receiver.onERC721Received.selector; + } + + function _transferOutERC721(address to, uint256 tokenId) internal { + require(_TOKENID_IDX_[tokenId] > 0, "TOKENID_NOT_EXIST"); + uint256 index = _TOKENID_IDX_[tokenId] - 1; + require(index < _NFT_IDS_.length, "INDEX_INVALID"); + IERC721(_NFT_COLLECTION_).safeTransferFrom(address(this), to, tokenId); + if(index != _NFT_IDS_.length - 1) { + uint256 lastTokenId = _NFT_IDS_[_NFT_IDS_.length - 1]; + _NFT_IDS_[index] = lastTokenId; + _TOKENID_IDX_[lastTokenId] = index + 1; + } + _NFT_IDS_.pop(); + _NFT_RESERVE_[tokenId] = 0; + _TOKENID_IDX_[tokenId] = 0; + } + + function emergencyWithdraw( + address[] memory nftContract, + uint256[] memory tokenIds, + address to + ) external onlySuperOwner { + require(nftContract.length == tokenIds.length, "PARAM_INVALID"); + address controller = IFilterAdmin(_OWNER_)._CONTROLLER_(); + require( + IController(controller).isEmergencyWithdrawOpen(address(this)), + "EMERGENCY_WITHDRAW_NOT_OPEN" + ); + + for (uint256 i = 0; i < nftContract.length; i++) { + uint256 tokenId = tokenIds[i]; + if (_NFT_RESERVE_[tokenId] > 0 && nftContract[i] == _NFT_COLLECTION_) { + uint256 index = getNFTIndexById(tokenId); + if(index != _NFT_IDS_.length - 1) { + uint256 lastTokenId = _NFT_IDS_[_NFT_IDS_.length - 1]; + _NFT_IDS_[index] = lastTokenId; + _TOKENID_IDX_[lastTokenId] = index + 1; + } + _NFT_IDS_.pop(); + _NFT_RESERVE_[tokenId] = 0; + _TOKENID_IDX_[tokenId] = 0; + } + IERC721(nftContract[i]).safeTransferFrom(address(this), to, tokenIds[i]); + emit EmergencyWithdraw(nftContract[i],tokenIds[i],to); + } + _TOTAL_NFT_AMOUNT_ = _NFT_IDS_.length; + } + + // ============ Support ============ + + function supportsInterface(bytes4 interfaceId) public view returns (bool) { + return interfaceId == type(IERC721Receiver).interfaceId; + } + + function version() external pure virtual returns (string memory) { + return "FILTER_1_ERC721 1.0.0"; + } +} diff --git a/contracts/NFTPool/intf/IController.sol b/contracts/NFTPool/intf/IController.sol new file mode 100644 index 0000000..224a6f9 --- /dev/null +++ b/contracts/NFTPool/intf/IController.sol @@ -0,0 +1,16 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IController { + function getMintFeeRate(address filterAdminAddr) external view returns (uint256); + + function getBurnFeeRate(address filterAdminAddr) external view returns (uint256); + + function isEmergencyWithdrawOpen(address filter) external view returns (bool); +} diff --git a/contracts/NFTPool/intf/IFilter.sol b/contracts/NFTPool/intf/IFilter.sol new file mode 100644 index 0000000..753851d --- /dev/null +++ b/contracts/NFTPool/intf/IFilter.sol @@ -0,0 +1,55 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IFilter { + function init( + address filterAdmin, + address nftCollection, + bool[] memory toggles, + string memory filterName, + uint256[] memory numParams, + uint256[] memory priceRules, + uint256[] memory spreadIds + ) external; + + function isNFTValid(address nftCollectionAddress, uint256 nftId) external view returns (bool); + + function _NFT_COLLECTION_() external view returns (address); + + function queryNFTIn(uint256 NFTInAmount) + external + view + returns (uint256 rawReceive, uint256 received); + + function queryNFTTargetOut(uint256 NFTOutAmount) + external + view + returns (uint256 rawPay, uint256 pay); + + function queryNFTRandomOut(uint256 NFTOutAmount) + external + view + returns (uint256 rawPay, uint256 pay); + + function ERC721In(uint256[] memory tokenIds, address to) external returns (uint256 received); + + function ERC721TargetOut(uint256[] memory tokenIds, address to) external returns (uint256 paid); + + function ERC721RandomOut(uint256 amount, address to) external returns (uint256 paid); + + function ERC1155In(uint256[] memory tokenIds, address to) external returns (uint256 received); + + function ERC1155TargetOut( + uint256[] memory tokenIds, + uint256[] memory amounts, + address to + ) external returns (uint256 paid); + + function ERC1155RandomOut(uint256 amount, address to) external returns (uint256 paid); +} diff --git a/contracts/NFTPool/intf/IFilterAdmin.sol b/contracts/NFTPool/intf/IFilterAdmin.sol new file mode 100644 index 0000000..29e68d5 --- /dev/null +++ b/contracts/NFTPool/intf/IFilterAdmin.sol @@ -0,0 +1,47 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IFilterAdmin { + function _OWNER_() external view returns (address); + + function _CONTROLLER_() external view returns (address); + + function init( + address owner, + uint256 initSupply, + string memory name, + string memory symbol, + uint256 feeRate, + address controller, + address maintainer, + address[] memory filters + ) external; + + function mintFragTo(address to, uint256 rawAmount) external returns (uint256 received); + + function burnFragFrom(address from, uint256 rawAmount) external returns (uint256 paid); + + function queryMintFee(uint256 rawAmount) + external + view + returns ( + uint256 poolFee, + uint256 mtFee, + uint256 afterChargedAmount + ); + + function queryBurnFee(uint256 rawAmount) + external + view + returns ( + uint256 poolFee, + uint256 mtFee, + uint256 afterChargedAmount + ); +} diff --git a/contracts/SmartRoute/DODONFTApprove.sol b/contracts/SmartRoute/DODONFTApprove.sol new file mode 100644 index 0000000..5267231 --- /dev/null +++ b/contracts/SmartRoute/DODONFTApprove.sol @@ -0,0 +1,105 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +import {IERC721} from "../intf/IERC721.sol"; +import {IERC1155} from "../intf/IERC1155.sol"; +import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; + +/** + * @title DODONFTApprove + * @author DODO Breeder + * + * @notice Handle NFT authorizations in DODO platform + */ +contract DODONFTApprove is InitializableOwnable { + + // ============ Storage ============ + uint256 private constant _TIMELOCK_DURATION_ = 3 days; + mapping (address => bool) public _IS_ALLOWED_PROXY_; + uint256 public _TIMELOCK_; + address public _PENDING_ADD_DODO_PROXY_; + + // ============ Events ============ + event AddDODOProxy(address dodoProxy); + event RemoveDODOProxy(address oldProxy); + + // ============ Modifiers ============ + modifier notLocked() { + require( + _TIMELOCK_ <= block.timestamp, + "AddProxy is timelocked" + ); + _; + } + + function init(address owner, address[] memory proxies) external { + initOwner(owner); + for(uint i = 0; i < proxies.length; i++) + _IS_ALLOWED_PROXY_[proxies[i]] = true; + } + + function unlockAddProxy(address newDodoProxy) external onlyOwner { + _TIMELOCK_ = block.timestamp + _TIMELOCK_DURATION_; + _PENDING_ADD_DODO_PROXY_ = newDodoProxy; + } + + function lockAddProxy() public onlyOwner { + _PENDING_ADD_DODO_PROXY_ = address(0); + _TIMELOCK_ = 0; + } + + + function addDODOProxy() external onlyOwner notLocked() { + _IS_ALLOWED_PROXY_[_PENDING_ADD_DODO_PROXY_] = true; + lockAddProxy(); + emit AddDODOProxy(_PENDING_ADD_DODO_PROXY_); + } + + function removeDODOProxy (address oldDodoProxy) external onlyOwner { + _IS_ALLOWED_PROXY_[oldDodoProxy] = false; + emit RemoveDODOProxy(oldDodoProxy); + } + + + function claimERC721( + address nftContract, + address who, + address dest, + uint256 tokenId + ) external { + require(_IS_ALLOWED_PROXY_[msg.sender], "DODONFTApprove:Access restricted"); + IERC721(nftContract).safeTransferFrom(who, dest, tokenId); + } + + function claimERC1155( + address nftContract, + address who, + address dest, + uint256 tokenId, + uint256 amount + ) external { + require(_IS_ALLOWED_PROXY_[msg.sender], "DODONFTApprove:Access restricted"); + IERC1155(nftContract).safeTransferFrom(who, dest, tokenId, amount, ""); + } + + function claimERC1155Batch( + address nftContract, + address who, + address dest, + uint256[] memory tokenIds, + uint256[] memory amounts + ) external { + require(_IS_ALLOWED_PROXY_[msg.sender], "DODONFTApprove:Access restricted"); + IERC1155(nftContract).safeBatchTransferFrom(who, dest, tokenIds, amounts, ""); + } + + function isAllowedProxy(address _proxy) external view returns (bool) { + return _IS_ALLOWED_PROXY_[_proxy]; + } +} diff --git a/contracts/SmartRoute/intf/IDODOV2Proxy01.sol b/contracts/SmartRoute/intf/IDODOV2Proxy01.sol index 5940c06..806b34f 100644 --- a/contracts/SmartRoute/intf/IDODOV2Proxy01.sol +++ b/contracts/SmartRoute/intf/IDODOV2Proxy01.sol @@ -132,4 +132,16 @@ interface IDODOV2Proxy01 { uint256 deadLine ) external payable returns (uint256 returnAmount); + // function mixSwap( + // address fromToken, + // address toToken, + // uint256 fromTokenAmount, + // uint256 minReturnAmount, + // address[] memory mixAdapters, + // address[] memory mixPairs, + // address[] memory assetTo, + // uint256 directions, + // bool isIncentive, + // uint256 deadLine + // ) external payable returns (uint256 returnAmount); } diff --git a/contracts/SmartRoute/proxies/DODONFTPoolProxy.sol b/contracts/SmartRoute/proxies/DODONFTPoolProxy.sol new file mode 100644 index 0000000..ad47e13 --- /dev/null +++ b/contracts/SmartRoute/proxies/DODONFTPoolProxy.sol @@ -0,0 +1,296 @@ +/* + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; +import {ICloneFactory} from "../../lib/CloneFactory.sol"; +import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; +import {IFilter} from "../../NFTPool/intf/IFilter.sol"; +import {IFilterAdmin} from "../../NFTPool/intf/IFilterAdmin.sol"; +import {IDODONFTApprove} from "../../intf/IDODONFTApprove.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {SafeERC20} from "../../lib/SafeERC20.sol"; + +contract DODONFTPoolProxy is InitializableOwnable, ReentrancyGuard { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + // ============ Storage ============ + address constant _ETH_ADDRESS_ = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + mapping(uint256 => address) public _FILTER_TEMPLATES_; + address public _FILTER_ADMIN_TEMPLATE_; + address public _MAINTAINER_; + address public _CONTROLLER_; + address public immutable _CLONE_FACTORY_; + address public immutable _DODO_NFT_APPROVE_; + address public immutable _DODO_APPROVE_; + + mapping (address => bool) public isWhiteListed; + + // ============ Event ============== + event SetFilterTemplate(uint256 idx, address filterTemplate); + event Erc721In(address filter, address to, uint256 received); + event Erc1155In(address filter, address to, uint256 received); + + event CreateLiteNFTPool(address newFilterAdmin, address filterAdminOwner); + event CreateNFTPool(address newFilterAdmin, address filterAdminOwner, address filter); + event CreateFilterV1(address newFilterAdmin, address newFilterV1, address nftCollection, uint256 filterTemplateKey); + event Erc721toErc20(address nftContract, uint256 tokenId, address toToken, uint256 returnAmount); + + event ChangeMaintainer(address newMaintainer); + event ChangeContoller(address newController); + event ChangeFilterAdminTemplate(address newFilterAdminTemplate); + event ChangeWhiteList(address contractAddr, bool isAllowed); + + constructor( + address cloneFactory, + address filterAdminTemplate, + address controllerModel, + address defaultMaintainer, + address dodoNftApprove, + address dodoApprove + ) public { + _CLONE_FACTORY_ = cloneFactory; + _FILTER_ADMIN_TEMPLATE_ = filterAdminTemplate; + _CONTROLLER_ = controllerModel; + _MAINTAINER_ = defaultMaintainer; + _DODO_NFT_APPROVE_ = dodoNftApprove; + _DODO_APPROVE_ = dodoApprove; + } + + // ================ ERC721 In and Out =================== + function erc721In( + address filter, + address nftCollection, + uint256[] memory tokenIds, + address to, + uint256 minMintAmount + ) external { + for(uint256 i = 0; i < tokenIds.length; i++) { + require(IFilter(filter).isNFTValid(nftCollection,tokenIds[i]), "NOT_REGISTRIED"); + IDODONFTApprove(_DODO_NFT_APPROVE_).claimERC721(nftCollection, msg.sender, filter, tokenIds[i]); + } + uint256 received = IFilter(filter).ERC721In(tokenIds, to); + require(received >= minMintAmount, "MINT_AMOUNT_NOT_ENOUGH"); + + emit Erc721In(filter, to, received); + } + + // ================== ERC1155 In and Out =================== + function erc1155In( + address filter, + address nftCollection, + uint256[] memory tokenIds, + uint256[] memory amounts, + address to, + uint256 minMintAmount + ) external { + for(uint256 i = 0; i < tokenIds.length; i++) { + require(IFilter(filter).isNFTValid(nftCollection,tokenIds[i]), "NOT_REGISTRIED"); + } + IDODONFTApprove(_DODO_NFT_APPROVE_).claimERC1155Batch(nftCollection, msg.sender, filter, tokenIds, amounts); + uint256 received = IFilter(filter).ERC1155In(tokenIds, to); + require(received >= minMintAmount, "MINT_AMOUNT_NOT_ENOUGH"); + + emit Erc1155In(filter, to, received); + } + + // ================== Create NFTPool =================== + function createLiteNFTPool( + address filterAdminOwner, + string[] memory infos, // 0 => fragName, 1 => fragSymbol + uint256[] memory numParams //0 - initSupply, 1 - fee + ) external returns(address newFilterAdmin) { + newFilterAdmin = ICloneFactory(_CLONE_FACTORY_).clone(_FILTER_ADMIN_TEMPLATE_); + + address[] memory filters = new address[](0); + + IFilterAdmin(newFilterAdmin).init( + filterAdminOwner, + numParams[0], + infos[0], + infos[1], + numParams[1], + _CONTROLLER_, + _MAINTAINER_, + filters + ); + + emit CreateLiteNFTPool(newFilterAdmin, filterAdminOwner); + } + + + + function createNewNFTPoolV1( + address filterAdminOwner, + address nftCollection, + uint256 filterKey, //1 => FilterERC721V1, 2 => FilterERC1155V1 + string[] memory infos, // 0 => filterName, 1 => fragName, 2 => fragSymbol + uint256[] memory numParams,//0 - initSupply, 1 - fee + bool[] memory toggles, + uint256[] memory filterNumParams, //0 - startId, 1 - endId, 2 - maxAmount, 3 - minAmount + uint256[] memory priceRules, + uint256[] memory spreadIds + ) external returns(address newFilterAdmin) { + newFilterAdmin = ICloneFactory(_CLONE_FACTORY_).clone(_FILTER_ADMIN_TEMPLATE_); + + address filterV1 = createFilterV1( + filterKey, + newFilterAdmin, + nftCollection, + toggles, + infos[0], + filterNumParams, + priceRules, + spreadIds + ); + + address[] memory filters = new address[](1); + filters[0] = filterV1; + + IFilterAdmin(newFilterAdmin).init( + filterAdminOwner, + numParams[0], + infos[1], + infos[2], + numParams[1], + _CONTROLLER_, + _MAINTAINER_, + filters + ); + + emit CreateNFTPool(newFilterAdmin, filterAdminOwner, filterV1); + } + + // ================== Create Filter =================== + function createFilterV1( + uint256 key, + address filterAdmin, + address nftCollection, + bool[] memory toggles, + string memory filterName, + uint256[] memory numParams, //0 - startId, 1 - endId, 2 - maxAmount, 3 - minAmount + uint256[] memory priceRules, + uint256[] memory spreadIds + ) public returns(address newFilterV1) { + newFilterV1 = ICloneFactory(_CLONE_FACTORY_).clone(_FILTER_TEMPLATES_[key]); + + emit CreateFilterV1(filterAdmin, newFilterV1, nftCollection, key); + + IFilter(newFilterV1).init( + filterAdmin, + nftCollection, + toggles, + filterName, + numParams, + priceRules, + spreadIds + ); + } + + + // ================== NFT ERC20 Swap ====================== + function erc721ToErc20( + address filterAdmin, + address filter, + address nftContract, + uint256 tokenId, + address toToken, + address dodoProxy, + bytes memory dodoSwapData + ) + external + preventReentrant + { + IDODONFTApprove(_DODO_NFT_APPROVE_).claimERC721(nftContract, msg.sender, filter, tokenId); + + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = tokenId; + + uint256 receivedFragAmount = IFilter(filter).ERC721In(tokenIds, address(this)); + + _generalApproveMax(filterAdmin, _DODO_APPROVE_, receivedFragAmount); + + require(isWhiteListed[dodoProxy], "Not Whitelist Proxy Contract"); + (bool success, ) = dodoProxy.call(dodoSwapData); + require(success, "API_SWAP_FAILED"); + + uint256 returnAmount = _generalBalanceOf(toToken, address(this)); + + _generalTransfer(toToken, msg.sender, returnAmount); + + emit Erc721toErc20(nftContract, tokenId, toToken, returnAmount); + } + + + //====================== Ownable ======================== + function changeMaintainer(address newMaintainer) external onlyOwner { + _MAINTAINER_ = newMaintainer; + emit ChangeMaintainer(newMaintainer); + } + + function changeFilterAdminTemplate(address newFilterAdminTemplate) external onlyOwner { + _FILTER_ADMIN_TEMPLATE_ = newFilterAdminTemplate; + emit ChangeFilterAdminTemplate(newFilterAdminTemplate); + } + + function changeController(address newController) external onlyOwner { + _CONTROLLER_ = newController; + emit ChangeContoller(newController); + } + + function setFilterTemplate(uint256 idx, address newFilterTemplate) external onlyOwner { + _FILTER_TEMPLATES_[idx] = newFilterTemplate; + emit SetFilterTemplate(idx, newFilterTemplate); + } + + function changeWhiteList(address contractAddr, bool isAllowed) external onlyOwner { + isWhiteListed[contractAddr] = isAllowed; + emit ChangeWhiteList(contractAddr, isAllowed); + } + + //======================= Internal ===================== + function _generalApproveMax( + address token, + address to, + uint256 amount + ) internal { + uint256 allowance = IERC20(token).allowance(address(this), to); + if (allowance < amount) { + if (allowance > 0) { + IERC20(token).safeApprove(to, 0); + } + IERC20(token).safeApprove(to, uint256(-1)); + } + } + + function _generalBalanceOf( + address token, + address who + ) internal view returns (uint256) { + if (token == _ETH_ADDRESS_) { + return who.balance; + } else { + return IERC20(token).balanceOf(who); + } + } + + function _generalTransfer( + address token, + address payable to, + uint256 amount + ) internal { + if (amount > 0) { + if (token == _ETH_ADDRESS_) { + to.transfer(amount); + } else { + IERC20(token).safeTransfer(to, amount); + } + } + } +} \ No newline at end of file diff --git a/contracts/external/ERC20/InitializableFragERC20.sol b/contracts/external/ERC20/InitializableFragERC20.sol index 63f3495..c4c12af 100644 --- a/contracts/external/ERC20/InitializableFragERC20.sol +++ b/contracts/external/ERC20/InitializableFragERC20.sol @@ -39,7 +39,7 @@ contract InitializableFragERC20 { emit Transfer(address(0), _creator, _totalSupply); } - function decimals() public view returns (uint8) { + function decimals() public pure returns (uint8) { return 18; } diff --git a/contracts/external/ERC20/InitializableInternalMintableERC20.sol b/contracts/external/ERC20/InitializableInternalMintableERC20.sol new file mode 100644 index 0000000..ec3eb49 --- /dev/null +++ b/contracts/external/ERC20/InitializableInternalMintableERC20.sol @@ -0,0 +1,98 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; + +contract InitializableInternalMintableERC20 is InitializableOwnable { + using SafeMath for uint256; + + string public name; + uint8 public decimals; + string public symbol; + uint256 public totalSupply; + + mapping(address => uint256) internal balances; + mapping(address => mapping(address => uint256)) internal allowed; + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); + event Mint(address indexed user, uint256 value); + event Burn(address indexed user, uint256 value); + + function init( + address _creator, + uint256 _initSupply, + string memory _name, + string memory _symbol, + uint8 _decimals + ) public { + initOwner(_creator); + name = _name; + symbol = _symbol; + decimals = _decimals; + totalSupply = _initSupply; + balances[_creator] = _initSupply; + emit Transfer(address(0), _creator, _initSupply); + } + + function transfer(address to, uint256 amount) public virtual returns (bool) { + require(to != address(0), "TO_ADDRESS_IS_EMPTY"); + require(amount <= balances[msg.sender], "BALANCE_NOT_ENOUGH"); + + balances[msg.sender] = balances[msg.sender].sub(amount); + balances[to] = balances[to].add(amount); + emit Transfer(msg.sender, to, amount); + return true; + } + + function balanceOf(address owner) public view returns (uint256 balance) { + return balances[owner]; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + require(to != address(0), "TO_ADDRESS_IS_EMPTY"); + require(amount <= balances[from], "BALANCE_NOT_ENOUGH"); + require(amount <= allowed[from][msg.sender], "ALLOWANCE_NOT_ENOUGH"); + + balances[from] = balances[from].sub(amount); + balances[to] = balances[to].add(amount); + allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount); + emit Transfer(from, to, amount); + return true; + } + + function approve(address spender, uint256 amount) public virtual returns (bool) { + allowed[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function allowance(address owner, address spender) public view returns (uint256) { + return allowed[owner][spender]; + } + + function _mint(address user, uint256 value) internal { + balances[user] = balances[user].add(value); + totalSupply = totalSupply.add(value); + emit Mint(user, value); + emit Transfer(address(0), user, value); + } + + function _burn(address user, uint256 value) internal { + balances[user] = balances[user].sub(value); + totalSupply = totalSupply.sub(value); + emit Burn(user, value); + emit Transfer(user, address(0), value); + } +} diff --git a/contracts/intf/IDODONFTApprove.sol b/contracts/intf/IDODONFTApprove.sol new file mode 100644 index 0000000..16d8dfd --- /dev/null +++ b/contracts/intf/IDODONFTApprove.sol @@ -0,0 +1,18 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IDODONFTApprove { + function isAllowedProxy(address _proxy) external view returns (bool); + + function claimERC721(address nftContract, address who, address dest, uint256 tokenId) external; + + function claimERC1155(address nftContract, address who, address dest, uint256 tokenId, uint256 amount) external; + + function claimERC1155Batch(address nftContract, address who, address dest, uint256[] memory tokenIds, uint256[] memory amounts) external; +} diff --git a/contracts/lib/DecimalMath.sol b/contracts/lib/DecimalMath.sol index 542bc77..5a11899 100644 --- a/contracts/lib/DecimalMath.sol +++ b/contracts/lib/DecimalMath.sol @@ -45,4 +45,19 @@ library DecimalMath { function reciprocalCeil(uint256 target) internal pure returns (uint256) { return uint256(10**36).divCeil(target); } + + function powFloor(uint256 target, uint256 e) internal pure returns (uint256) { + if (e == 0) { + return 10 ** 18; + } else if (e == 1) { + return target; + } else { + uint p = powFloor(target, e.div(2)); + p = p.mul(p) / (10**18); + if (e % 2 == 1) { + p = p.mul(target) / (10**18); + } + return p; + } + } } diff --git a/migrations/7_deploy_nftPool.js b/migrations/7_deploy_nftPool.js new file mode 100644 index 0000000..6559caa --- /dev/null +++ b/migrations/7_deploy_nftPool.js @@ -0,0 +1,104 @@ +const fs = require("fs"); +const { deploySwitch } = require('../truffle-config.js') +const file = fs.createWriteStream("../deploy-nft.txt", { 'flags': 'a' }); +let logger = new console.Console(file, file); +const { GetConfig } = require("../configAdapter.js") + +const DODONFTApprove = artifacts.require("DODONFTApprove"); +const FilterAdmin = artifacts.require("FilterAdmin"); +const FilterERC721V1 = artifacts.require("FilterERC721V1"); +const FilterERC1155V1 = artifacts.require("FilterERC1155V1"); +const DODONFTPoolProxy = artifacts.require("DODONFTPoolProxy") +const Controller = artifacts.require("Controller"); + +module.exports = async (deployer, network, accounts) => { + let CONFIG = GetConfig(network, accounts) + if (CONFIG == null) return; + //Need Deploy first + let DODOApproveAddress = CONFIG.DODOApprove; + let CloneFactoryAddress = CONFIG.CloneFactory; + + if (DODOApproveAddress == "" || CloneFactoryAddress == "") return; + + let DODONFTApproveAddress = CONFIG.DODONFTApprove; + let FilterAdminAddress = CONFIG.FilterAdmin; + let FilterERC721V1Address = CONFIG.FilterERC721V1; + let FilterERC1155V1Address = CONFIG.FilterERC1155V1; + + let DODONFTPoolProxyAddress = CONFIG.DODONFTPoolProxy; + let ControllerAddress = CONFIG.NFTPoolController; + + let multiSigAddress = CONFIG.multiSigAddress; + + if (deploySwitch.NFT_POOL) { + logger.log("===================================================="); + logger.log("network type: " + network); + logger.log("Deploy time: " + new Date().toLocaleString()); + logger.log("Deploy type: NFT_POOL"); + + if (FilterAdminAddress == "") { + await deployer.deploy(FilterAdmin); + FilterAdminAddress = FilterAdmin.address; + logger.log("FilterAdminAddress: ", FilterAdminAddress); + } + + if (FilterERC721V1Address == "") { + await deployer.deploy(FilterERC721V1); + FilterERC721V1Address = FilterERC721V1.address; + logger.log("FilterERC721V1Address: ", FilterERC721V1Address); + } + + if (FilterERC1155V1Address == "") { + await deployer.deploy(FilterERC1155V1); + FilterERC1155V1Address = FilterERC1155V1.address; + logger.log("FilterERC1155V1Address: ", FilterERC1155V1Address); + } + + if (ControllerAddress == "") { + await deployer.deploy(Controller); + ControllerAddress = Controller.address; + logger.log("ControllerAddress: ", ControllerAddress); + const ControllerInstance = await Controller.at(ControllerAddress); + var tx = await ControllerInstance.initOwner(multiSigAddress); + logger.log("Init Controller Tx:", tx.tx); + } + + if (DODONFTApproveAddress == "") { + await deployer.deploy(DODONFTApprove); + DODONFTApproveAddress = DODONFTApprove.address; + logger.log("DODONFTApproveAddress: ", DODONFTApproveAddress); + } + + if (DODONFTPoolProxyAddress == "") { + await deployer.deploy( + DODONFTPoolProxy, + CloneFactoryAddress, + FilterAdminAddress, + ControllerAddress, + multiSigAddress, + DODONFTApproveAddress, + DODOApproveAddress + ); + DODONFTPoolProxyAddress = DODONFTPoolProxy.address; + logger.log("DODONFTPoolProxyAddress: ", DODONFTPoolProxyAddress); + + const DODONFTPoolProxyInstance = await DODONFTPoolProxy.at(DODONFTPoolProxyAddress); + var tx = await DODONFTPoolProxyInstance.initOwner(multiSigAddress); + logger.log("Init DODONFTPoolProxy Tx:", tx.tx); + } + + + if (network == 'kovan' || network == 'rinkeby') { + var tx; + const DODONFTPoolProxyInstance = await DODONFTPoolProxy.at(DODONFTPoolProxyAddress); + tx = await DODONFTPoolProxyInstance.setFilterTemplate(1, FilterERC721V1Address); + logger.log("DODONFTPoolProxy SetFilterTemplate 1 tx: ", tx.tx); + tx = await DODONFTPoolProxyInstance.setFilterTemplate(2, FilterERC1155V1Address); + logger.log("DODONFTPoolProxy SetFilterTemplate 2 tx: ", tx.tx); + + const DODONFTApproveInstance = await DODONFTApprove.at(DODONFTApproveAddress); + var tx = await DODONFTApproveInstance.init(multiSigAddress, [DODONFTPoolProxyAddress]); + logger.log("DODONFTApprove init tx: ", tx.tx); + } + } +}; diff --git a/test/NFTPool/erc1155NftPool.test.ts b/test/NFTPool/erc1155NftPool.test.ts new file mode 100644 index 0000000..1320dc3 --- /dev/null +++ b/test/NFTPool/erc1155NftPool.test.ts @@ -0,0 +1,225 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +// import * as assert from 'assert'; + +import { decimalStr, MAX_UINT256 } from '../utils/Converter'; +import { logGas } from '../utils/Log'; +import { NFTPoolContext, getNFTPoolContext } from '../utils/NFTPoolContext'; +import { assert } from 'chai'; +import * as contracts from '../utils/Contracts'; +import BigNumber from 'bignumber.js'; +const truffleAssert = require('truffle-assertions'); + +let maker: string; +let user: string; + +async function init(ctx: NFTPoolContext): Promise { + maker = ctx.SpareAccounts[0]; + user = ctx.SpareAccounts[1]; +} + +async function createNFTPool(ctx: NFTPoolContext) { + var tx = await logGas(ctx.DODONFTPoolProxy.methods.createNewNFTPoolV1( + maker, + ctx.DodoNft1155.options.address, + 2, + ['Filter01', 'FRAG', 'FRAG'], + [decimalStr("10000000"), decimalStr("0.005")], + [true, true, true], + [0, 4, 12, 4], + [decimalStr("1"), decimalStr("0.9"), decimalStr("1"), decimalStr("0.9"), decimalStr("2"), decimalStr("0.9")], + [7] + ), ctx.sendParam(maker), "createNewNFTPoolV1"); + + var newFilterAdmin = tx.events['CreateNFTPool'].returnValues['newFilterAdmin'] + var filter = tx.events['CreateNFTPool'].returnValues['filter'] + + return [newFilterAdmin, filter]; +} + +async function mintNFT(ctx: NFTPoolContext, amount) { + var tx = await ctx.DodoNft1155.methods.mint( + "http://projectowen.oss-cn-beijing.aliyuncs.com/2021-09-19-035145.png", + amount + ).send(ctx.sendParam(user)); + + var tokenId = tx.events['DODONFTMint'].returnValues['tokenId'] + return tokenId +} + +async function erc1155In(ctx: NFTPoolContext) { + var [filterAdmin, filter] = await createNFTPool(ctx) + var tokenIds = [] + var amounts = [] + for (var i = 0; i < 5; i++) { + var curTokenId = await mintNFT(ctx, 2); + tokenIds.push(curTokenId); + amounts.push(2); + } + + await ctx.DodoNft1155.methods.setApprovalForAll( + ctx.DODONFTApprove.options.address, + true + ).send(ctx.sendParam(user)) + + await ctx.DODONFTPoolProxy.methods.erc1155In( + filter, + ctx.DodoNft1155.options.address, + tokenIds, + amounts, + user, + 1 + ).send(ctx.sendParam(user)); + + return [filterAdmin, filter] +} + +describe("ERC1155-NFTPool", () => { + let snapshotId: string; + let ctx: NFTPoolContext; + + before(async () => { + ctx = await getNFTPoolContext(); + await init(ctx); + }); + + beforeEach(async () => { + snapshotId = await ctx.EVM.snapshot(); + }); + + afterEach(async () => { + await ctx.EVM.reset(snapshotId); + }); + + describe("ERC1155-NFTPool", () => { + it('erc1155In', async () => { + var [filterAdmin, filter] = await createNFTPool(ctx) + var tokenIds = [] + var amounts = [] + for (var i = 0; i < 4; i++) { + var curTokenId = await mintNFT(ctx, 2); + tokenIds.push(curTokenId); + amounts.push(2); + } + + var beforeBalanceTokenId0 = await ctx.DodoNft1155.methods.balanceOf(filter, 0).call(); + assert.equal(beforeBalanceTokenId0, 0) + + await logGas(ctx.DodoNft1155.methods.setApprovalForAll( + ctx.DODONFTApprove.options.address, + true + ), ctx.sendParam(user), "ApproveNFT"); + + var filterAdminInstance = contracts.getContractWithAddress(contracts.FILTER_ADMIN, filterAdmin); + + var beforeBalance = await filterAdminInstance.methods.balanceOf(user).call(); + console.log("beforeBalance:", beforeBalance); + + var tx = await logGas(ctx.DODONFTPoolProxy.methods.erc1155In( + filter, + ctx.DodoNft1155.options.address, + tokenIds, + amounts, + user, + 1 + ), ctx.sendParam(user), "erc1155In"); + + + assert.equal( + tx.events['Erc1155In'].returnValues['received'], + '5666851260500000000' + ) + + var afterBalanceTokenId0 = await ctx.DodoNft1155.methods.balanceOf(filter, 0).call(); + assert.equal(afterBalanceTokenId0, 2) + + }) + + it('ERC1155TargetOut', async () => { + var [, filter] = await erc1155In(ctx); + + var filterInstance = contracts.getContractWithAddress(contracts.FILTER_ERC1155_V1, filter); + + var beforeAmount = await ctx.DodoNft1155.methods.balanceOf(filter, 0).call(); + assert.equal(beforeAmount, 2) + + //maker targetout + var tx = await logGas(filterInstance.methods.ERC1155TargetOut( + [0, 1, 3], + [2, 1, 1], + maker, + MAX_UINT256, + ), ctx.sendParam(maker), "Erc1155TargetOut"); + + var paid = tx.events['TargetOutOrder'].returnValues['paidAmount'] + assert.equal(paid, "3673527453990000000"); + + var maxNftOutAmount = await filterInstance.methods.getAvaliableNFTOutAmount().call(); + var totalNftAmount = await filterInstance.methods._TOTAL_NFT_AMOUNT_().call(); + + assert.equal(maxNftOutAmount, 2); + assert.equal(totalNftAmount, 6); + + var afterAmount = await ctx.DodoNft1155.methods.balanceOf(maker, 0).call(); + assert.equal(afterAmount, 2) + }) + + + it('ERC721RandomOut', async () => { + var [, filter] = await erc1155In(ctx); + + var filterInstance = contracts.getContractWithAddress(contracts.FILTER_ERC1155_V1, filter); + + //maker randomOut + var tx = await logGas(filterInstance.methods.ERC1155RandomOut( + 3, + maker, + MAX_UINT256, + ), ctx.sendParam(maker), "Erc1155RandomOut"); + + var paid = tx.events['RandomOutOrder'].returnValues['paidAmount'] + assert.equal(paid, "1302665521995000000"); + + var maxNftOutAmount = await filterInstance.methods.getAvaliableNFTOutAmount().call(); + var totalNftAmount = await filterInstance.methods._TOTAL_NFT_AMOUNT_().call(); + + assert.equal(maxNftOutAmount, 3); + assert.equal(totalNftAmount, 7); + }) + + it('emergencyWithdraw', async () => { + var [filterAdmin, filter] = await erc1155In(ctx); + await ctx.Controller.methods.setEmergencyWithdraw(filter, true).send(ctx.sendParam(ctx.Deployer)); + + var beforeAmount = await ctx.DodoNft1155.methods.balanceOf(filter, 0).call(); + assert.equal(beforeAmount, 2) + + var filterInstance = contracts.getContractWithAddress(contracts.FILTER_ERC1155_V1, filter); + + await logGas(filterInstance.methods.emergencyWithdraw( + [ctx.DodoNft1155.options.address, ctx.DodoNft1155.options.address, ctx.DodoNft1155.options.address], + [0, 1, 4], + [1, 2, 2], + maker + ), ctx.sendParam(maker), "EmergencyWithdraw") + + + var afterAmount = await ctx.DodoNft1155.methods.balanceOf(maker, 0).call(); + assert.equal(afterAmount, 1) + afterAmount = await ctx.DodoNft1155.methods.balanceOf(filter, 0).call(); + assert.equal(afterAmount, 1) + + var maxNftOutAmount = await filterInstance.methods.getAvaliableNFTOutAmount().call(); + var totalNftAmount = await filterInstance.methods._TOTAL_NFT_AMOUNT_().call(); + + assert.equal(maxNftOutAmount, 1); + assert.equal(totalNftAmount, 5); + }) + }); +}); + diff --git a/test/NFTPool/erc721NftPool.test.ts b/test/NFTPool/erc721NftPool.test.ts new file mode 100644 index 0000000..f5dbc32 --- /dev/null +++ b/test/NFTPool/erc721NftPool.test.ts @@ -0,0 +1,239 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +// import * as assert from 'assert'; + +import { decimalStr, MAX_UINT256 } from '../utils/Converter'; +import { logGas } from '../utils/Log'; +import { NFTPoolContext, getNFTPoolContext } from '../utils/NFTPoolContext'; +import { assert } from 'chai'; +import * as contracts from '../utils/Contracts'; +import BigNumber from 'bignumber.js'; +import { StringLiteralLike } from 'typescript'; +const truffleAssert = require('truffle-assertions'); + +let maker: string; +let user: string; + +async function init(ctx: NFTPoolContext): Promise { + maker = ctx.SpareAccounts[0]; + user = ctx.SpareAccounts[1]; +} + +async function createNFTPool(ctx: NFTPoolContext) { + var tx = await logGas(ctx.DODONFTPoolProxy.methods.createNewNFTPoolV1( + maker, + ctx.DodoNft.options.address, + 1, + ['Filter01', 'FRAG', 'FRAG'], + [decimalStr("10000000"), decimalStr("0.005")], + [true, true, true], + [0, 4, 5, 1], + [decimalStr("1"), decimalStr("0.9"), decimalStr("1"), decimalStr("0.9"), decimalStr("2"), decimalStr("0.9")], + [7] + ), ctx.sendParam(maker), "createNewNFTPoolV1"); + + var newFilterAdmin = tx.events['CreateNFTPool'].returnValues['newFilterAdmin'] + var filter = tx.events['CreateNFTPool'].returnValues['filter'] + + return [newFilterAdmin, filter]; +} + +async function mintNFT(ctx: NFTPoolContext) { + var tx = await ctx.DodoNft.methods.mint( + "http://projectowen.oss-cn-beijing.aliyuncs.com/2021-09-19-035145.png" + ).send(ctx.sendParam(user)); + + var tokenId = tx.events['DODONFTMint'].returnValues['tokenId'] + return tokenId +} + +async function erc721In(ctx: NFTPoolContext) { + var [filterAdmin, filter] = await createNFTPool(ctx) + var tokenIds = [] + for (var i = 0; i < 5; i++) { + var curTokenId = await mintNFT(ctx); + tokenIds.push(curTokenId); + } + + await ctx.DodoNft.methods.setApprovalForAll( + ctx.DODONFTApprove.options.address, + true + ).send(ctx.sendParam(user)) + + await ctx.DODONFTPoolProxy.methods.erc721In( + filter, + ctx.DodoNft.options.address, + tokenIds, + user, + 1 + ).send(ctx.sendParam(user)); + + return [filterAdmin, filter] +} + +describe("ERC721-NFTPool", () => { + let snapshotId: string; + let ctx: NFTPoolContext; + + before(async () => { + ctx = await getNFTPoolContext(); + await init(ctx); + }); + + beforeEach(async () => { + snapshotId = await ctx.EVM.snapshot(); + }); + + afterEach(async () => { + await ctx.EVM.reset(snapshotId); + }); + + describe("ERC721-NFTPool", () => { + + it("createNewNFTPoolV1", async () => { + var tx = await logGas(ctx.DODONFTPoolProxy.methods.createNewNFTPoolV1( + maker, + ctx.DodoNft.options.address, + 1, + ['Filter01', 'FRAG', 'FRAG'], + [decimalStr("10000000"), decimalStr("0.005")], + [true, true, true], + [0, 3, 2, 1], + [decimalStr("1"), decimalStr("1.1"), decimalStr("1"), decimalStr("1.1"), decimalStr("2"), decimalStr("1.1")], + [5] + ), ctx.sendParam(maker), "createNewNFTPoolV1"); + + var newFilterAdmin = tx.events['CreateNFTPool'].returnValues['newFilterAdmin'] + var filter = tx.events['CreateNFTPool'].returnValues['filter'] + + console.log("newFilterAdmin:", newFilterAdmin) + console.log("filterV1:", filter) + + assert.equal( + tx.events['CreateNFTPool'].returnValues['filterAdminOwner'], + maker + ) + }); + + it('erc721In', async () => { + var [filterAdmin, filter] = await createNFTPool(ctx) + var tokenIds = [] + for (var i = 0; i < 4; i++) { + var curTokenId = await mintNFT(ctx); + tokenIds.push(curTokenId); + } + + await logGas(ctx.DodoNft.methods.setApprovalForAll( + ctx.DODONFTApprove.options.address, + true + ), ctx.sendParam(user), "ApproveNFT"); + + var filterAdminInstance = contracts.getContractWithAddress(contracts.FILTER_ADMIN, filterAdmin); + + var beforeBalance = await filterAdminInstance.methods.balanceOf(user).call(); + console.log("beforeBalance:", beforeBalance); + + var tx = await logGas(ctx.DODONFTPoolProxy.methods.erc721In( + filter, + ctx.DodoNft.options.address, + tokenIds, + user, + 1 + ), ctx.sendParam(user), "erc721In"); + + var afterBalance = await filterAdminInstance.methods.balanceOf(user).call(); + console.log("afterBalance:", afterBalance); + + assert.equal( + tx.events['Erc721In'].returnValues['received'], + '3421805000000000000' + ) + }) + + it('ERC721TargetOut', async () => { + var [, filter] = await erc721In(ctx); + + var filterInstance = contracts.getContractWithAddress(contracts.FILTER_ERC721_V1, filter); + + var beforeOwner = await ctx.DodoNft.methods.ownerOf(0).call(); + assert.equal(beforeOwner, filter) + + //maker targetout + var tx = await logGas(filterInstance.methods.ERC721TargetOut( + [0, 1, 3], + maker, + MAX_UINT256, + ), ctx.sendParam(maker), "Erc721TargetOut"); + + var paid = tx.events['TargetOutOrder'].returnValues['paidAmount'] + + assert.equal(paid, "4412151000000000000"); + + var maxNftOutAmount = await filterInstance.methods.getAvaliableNFTOutAmount().call(); + var totalNftAmount = await filterInstance.methods._TOTAL_NFT_AMOUNT_().call(); + var tokenId2 = await filterInstance.methods.getNFTIndexById(2).call(); + + assert.equal(maxNftOutAmount, 1); + assert.equal(totalNftAmount, 2); + assert.equal(tokenId2, 1); + + var afterOwner = await ctx.DodoNft.methods.ownerOf(0).call(); + assert.equal(afterOwner, maker) + }) + + + it('ERC721RandomOut', async () => { + var [, filter] = await erc721In(ctx); + + var filterInstance = contracts.getContractWithAddress(contracts.FILTER_ERC721_V1, filter); + + //maker randomOut + var tx = await logGas(filterInstance.methods.ERC721RandomOut( + 3, + maker, + MAX_UINT256, + ), ctx.sendParam(maker), "Erc721RandomOut"); + + var paid = tx.events['RandomOutOrder'].returnValues['paidAmount'] + assert.equal(paid, "2206075500000000000"); + + var maxNftOutAmount = await filterInstance.methods.getAvaliableNFTOutAmount().call(); + var totalNftAmount = await filterInstance.methods._TOTAL_NFT_AMOUNT_().call(); + + assert.equal(maxNftOutAmount, 1); + assert.equal(totalNftAmount, 2); + }) + + it('emergencyWithdraw', async () => { + var [filterAdmin, filter] = await erc721In(ctx); + await ctx.Controller.methods.setEmergencyWithdraw(filter, true).send(ctx.sendParam(ctx.Deployer)); + + var beforeOwner = await ctx.DodoNft.methods.ownerOf(0).call(); + assert.equal(beforeOwner, filter) + + var filterInstance = contracts.getContractWithAddress(contracts.FILTER_ERC721_V1, filter); + + await logGas(filterInstance.methods.emergencyWithdraw( + [ctx.DodoNft.options.address, ctx.DodoNft.options.address, ctx.DodoNft.options.address], + [0, 1, 4], + maker + ), ctx.sendParam(maker), "EmergencyWithdraw") + + + var afterOwner = await ctx.DodoNft.methods.ownerOf(0).call(); + assert.equal(afterOwner, maker) + + var maxNftOutAmount = await filterInstance.methods.getAvaliableNFTOutAmount().call(); + var totalNftAmount = await filterInstance.methods._TOTAL_NFT_AMOUNT_().call(); + + assert.equal(maxNftOutAmount, 1); + assert.equal(totalNftAmount, 2); + }) + }); +}); + diff --git a/test/utils/Contracts.ts b/test/utils/Contracts.ts index 20190e9..0bbe568 100644 --- a/test/utils/Contracts.ts +++ b/test/utils/Contracts.ts @@ -74,6 +74,16 @@ export const DROPS_ERC1155 = "DropsERC1155" export const DROPS_FEE_MODEL = "DropsFeeModel" export const DROPS_PROXY = "DODODropsProxy" +export const DODO_NFT = "DODONFT" +export const DODO_NFT_1155 = "DODONFT1155" + +export const FILTER_ERC721_V1 = "FilterERC721V1" +export const FILTER_ERC1155_V1 = "FilterERC1155V1" +export const FILTER_ADMIN = "FilterAdmin" +export const CONTROLLER = "Controller" +export const DODO_NFT_APPROVE = "DODONFTApprove" +export const DODO_NFT_POOL_PROXY = "DODONFTPoolProxy" + interface ContractJson { abi: any; diff --git a/test/utils/NFTPoolContext.ts b/test/utils/NFTPoolContext.ts new file mode 100644 index 0000000..410ada1 --- /dev/null +++ b/test/utils/NFTPoolContext.ts @@ -0,0 +1,111 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +import BigNumber from 'bignumber.js'; +import Web3 from 'web3'; +import { Contract } from 'web3-eth-contract'; + +import * as contracts from './Contracts'; +import { decimalStr, mweiStr } from './Converter'; +import { EVM, getDefaultWeb3 } from './EVM'; +import * as log from './Log'; + +BigNumber.config({ + EXPONENTIAL_AT: 1000, + DECIMAL_PLACES: 80, +}); + + +export class NFTPoolContext { + EVM: EVM; + Web3: Web3; + + FilterAdmin: Contract; + FilterERC721V1: Contract; + FilterERC1155V1: Contract; + Controller: Contract; + DODONFTApprove: Contract; + DODONFTPoolProxy: Contract; + + //nft token + DodoNft: Contract; + DodoNft1155: Contract; + + Deployer: string; + Maintainer: string; + SpareAccounts: string[]; + + constructor() { } + + async init() { + this.EVM = new EVM(); + this.Web3 = getDefaultWeb3(); + const allAccounts = await this.Web3.eth.getAccounts(); + this.Deployer = allAccounts[0]; + this.Maintainer = allAccounts[1]; + this.SpareAccounts = allAccounts.slice(2, 10); + + + this.DodoNft = await contracts.newContract(contracts.DODO_NFT); + this.DodoNft1155 = await contracts.newContract(contracts.DODO_NFT_1155); + + await this.DodoNft.methods.init(this.Deployer, "DODONFT", "DODONFT").send(this.sendParam(this.Deployer)); + await this.DodoNft1155.methods.initOwner(this.Deployer).send(this.sendParam(this.Deployer)); + + var cloneFactory = await contracts.newContract( + contracts.CLONE_FACTORY_CONTRACT_NAME + ); + var filterAdminTemplate = await contracts.newContract(contracts.FILTER_ADMIN) + var filterERC721V1Template = await contracts.newContract(contracts.FILTER_ERC721_V1) + var filterERC1155V1Template = await contracts.newContract(contracts.FILTER_ERC1155_V1) + + this.Controller = await contracts.newContract(contracts.CONTROLLER) + await this.Controller.methods.initOwner(this.Deployer).send(this.sendParam(this.Deployer)); + + this.DODONFTApprove = await contracts.newContract( + contracts.DODO_NFT_APPROVE + ); + + this.DODONFTPoolProxy = await contracts.newContract(contracts.DODO_NFT_POOL_PROXY, + [ + cloneFactory.options.address, + filterAdminTemplate.options.address, + this.Controller.options.address, + this.Deployer, + this.DODONFTApprove.options.address, + "0x0000000000000000000000000000000000000000" //TODO:ERC721 => ERC20 DODOApprove + ] + ) + + await this.DODONFTPoolProxy.methods.initOwner(this.Deployer).send(this.sendParam(this.Deployer)); + await this.DODONFTPoolProxy.methods.setFilterTemplate(1, filterERC721V1Template.options.address).send(this.sendParam(this.Deployer)); + await this.DODONFTPoolProxy.methods.setFilterTemplate(2, filterERC1155V1Template.options.address).send(this.sendParam(this.Deployer)); + + await this.DODONFTApprove.methods.init(this.Deployer, [this.DODONFTPoolProxy.options.address]).send(this.sendParam(this.Deployer)); + + console.log(log.blueText("[Init NFTPool context]")); + } + + sendParam(sender, value = "0") { + return { + from: sender, + gas: process.env["COVERAGE"] ? 10000000000 : 7000000, + gasPrice: mweiStr("1000"), + value: decimalStr(value), + }; + } + + async mintTestToken(to: string, token: Contract, amount: string) { + await token.methods.mint(to, amount).send(this.sendParam(this.Deployer)); + } +} + +export async function getNFTPoolContext(): Promise { + var context = new NFTPoolContext(); + await context.init(); + return context; +} diff --git a/truffle-config.js b/truffle-config.js index 2e56567..4892ba4 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -63,7 +63,8 @@ module.exports = { COLLECTIONS: false, MYSTERYBOX_V1: false, Drops_V2: false, - MineV3: false + MineV3: false, + NFT_POOL: false }, networks: { @@ -110,7 +111,7 @@ module.exports = { return new HDWalletProvider(privKey, "https://mainnet.infura.io/v3/" + infuraId); }, gas: 4000000, - gasPrice: 50000000000, + gasPrice: 65000000000, network_id: 1, skipDryRun: true }, diff --git a/truffle-test.sh b/truffle-test.sh index d17e2ec..4df5dc4 100644 --- a/truffle-test.sh +++ b/truffle-test.sh @@ -1,5 +1,5 @@ #!/bin/bash -# truffle compile --all +truffle compile --all if [ "$1"x = "proxy-dpp"x ] then @@ -71,12 +71,12 @@ then truffle test ./test/DODODrops/dropsV2.test.ts fi -# if [ "$1"x = "route-incentive"x ] -# then -# truffle test ./test/Route/Incentive.test.ts -# fi +if [ "$1"x = "erc721NFTPool"x ] +then + truffle test ./test/NFTPool/erc721NftPool.test.ts +fi -# if [ "$1"x = "route"x ] -# then -# truffle test ./test/Route/route.test.ts -# fi \ No newline at end of file +if [ "$1"x = "erc1155NFTPool"x ] +then + truffle test ./test/NFTPool/erc1155NftPool.test.ts +fi \ No newline at end of file