Merge pull request #22 from DODOEX/feature/nftPool

Feature/nft pool
This commit is contained in:
owen05
2021-10-18 10:37:37 +08:00
committed by GitHub
30 changed files with 2430 additions and 56 deletions

View File

@@ -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

View File

@@ -79,5 +79,13 @@ module.exports = {
DODONFTRegistry: "0x8327b093caf1a32985887CE89BD6c8F80A6B7535",
DODONFTProxy: "0x6A51C8A1139B9233C31331D2Cf442E0652329f23",
//=================== NFTPool ==================
DODONFTApprove: "0xaeB5CF31b97dce6134e416129845e01106fFB177",
DODONFTPoolProxy: "0xE55154D09265b18aC7CDAC6E646672A5460389a1",
FilterAdmin: "0x738Ebf387A0CE0eb46b0eF8Fa5DEa2EaE6B1Df51",
FilterERC721V1: "0xbAb9F4ff4A19a0e8EEBC56b06750253228ffAc6E",
FilterERC1155V1: "0xb57Dd5c265dBb13CA014F2332069E90CD0e22e65",
NFTPoolController: "0x9aE501385Bc7996A2A4a1FBb00c8d3820611BCB5"
}
}

View File

@@ -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"
}
}

View File

@@ -78,5 +78,13 @@ module.exports = {
DODONFTRegistry: "0xA7263eb38b9A61B72397c884b5f9bFb5C34A7840",
DODONFTProxy: "0x987e2a3A65A526C66ac66B6F8c84120055363625",
//=================== NFTPool ==================
DODONFTApprove: "0xf740253BDF035620E12f55D61C092aa294E2E5F3",
DODONFTPoolProxy: "0x9D4c342298Ba0C77E75CfD379a27F1420B3Ce45D",
FilterAdmin: "0xD4F77342a08EFF87B67D2F0Cfd34ed8c8E6Aa49e",
FilterERC721V1: "0x5f3178c155cB96f3Fd42E66933efF941a1122D79",
FilterERC1155V1: "0x5258Db198f6E39889bfCA6016786AF562Ab8bE91",
NFTPoolController: "0xfEdBea78dd3D1C301Be7ca7fdd5FB25A3b8C0b80"
}
}

View File

@@ -81,5 +81,13 @@ module.exports = {
DODONFTRegistry: "0x77777FF74856716fd3FF89aD59fcABcCc1bE0522",
DODONFTProxy: "0x181d1F15281E5475c517Fb840A6d31d32BbF65f3",
//=================== NFTPool ==================
DODONFTApprove: "0x0d0463bCfAb32892F9dAf56f88d9671F913baB77",
DODONFTPoolProxy: "0xe5A12788A968671abD2ACce8FFD62c95314D3d90",
FilterAdmin: "0xf8d034B598E06A902f11CD6810926f9C1c1A1f0f",
FilterERC721V1: "0x48Adda32F7e77EdCE23F66497da16373dE20E922",
FilterERC1155V1: "0x2D11eFCd67782C153E16Af1B4B517136993008F0",
NFTPoolController: "0x2CEA100b9E842FFB67CaBebAdb73cE9D200E92dE"
}
}

View File

@@ -74,5 +74,13 @@ module.exports = {
DODONFTRegistry: "0x840135913a2527C3481DB29e323E05F301D33210",
DODONFTProxy: "0xCb0A88A465Da7aB09C09B418F27bFf159A0FE09b",
//=================== NFTPool ==================
DODONFTApprove: "0x91E1c84BA8786B1FaE2570202F0126C0b88F6Ec7",
DODONFTPoolProxy: "0x533AF8ad419fB3082df9C80BE2ad903912a817FB",
FilterAdmin: "0x50D148D0908C602A56884B8628A36470a875EEb2",
FilterERC721V1: "0x697F28107dF60BE8813Ce44103F15e51aEA1D61b",
FilterERC1155V1: "0x072ff3ed5F723FB4E9a83a76755ED5222ea99d7A",
NFTPoolController: "0xe6AafA1c45D9d0C64686c1f1D17B9fe9c7DAB05b"
}
}

View File

@@ -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"
}
}

View File

@@ -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"
}
}

View File

@@ -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);
}
}

View File

@@ -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_;
}
}
}

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

@@ -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";
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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
);
}

View File

@@ -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];
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
};

View File

@@ -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<void> {
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);
})
});
});

View File

@@ -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<void> {
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);
})
});
});

View File

@@ -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;

View File

@@ -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<NFTPoolContext> {
var context = new NFTPoolContext();
await context.init();
return context;
}

View File

@@ -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
},

View File

@@ -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
if [ "$1"x = "erc1155NFTPool"x ]
then
truffle test ./test/NFTPool/erc1155NftPool.test.ts
fi