diff --git a/README.md b/README.md index 18ea3e9..79a63e7 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ - contracts/NFTPool/impl/FilterModel01.sol -- contracts/NFTPool/impl/NFTPoolFeeModel.sol +- contracts/NFTPool/impl/ControllerModel.sol - contracts/external/ERC20/InitializableInternalMintableERC20.sol diff --git a/contracts/NFTPool/impl/ControllerModel.sol b/contracts/NFTPool/impl/ControllerModel.sol new file mode 100644 index 0000000..b651837 --- /dev/null +++ b/contracts/NFTPool/impl/ControllerModel.sol @@ -0,0 +1,100 @@ +/* + + 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 {IERC20} from "../../intf/IERC20.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; + +contract ControllerModel is InitializableOwnable { + using SafeMath for uint256; + + uint256 public _GLOBAL_NFT_IN_FEE_ = 0; + uint256 public _GLOBAL_NFT_RANDOM_OUT_FEE_ = 0; + uint256 public _GLOBAL_NFT_TARGET_OUT_FEE_ = 50000000000000000;//0.05 + + struct FilterAdminFeeInfo { + uint256 nftInFee; + uint256 nftRandomOutFee; + uint256 nftTargetOutFee; + bool isSet; + } + + mapping(address => FilterAdminFeeInfo) filterAdminFees; + + mapping(address => bool) isEmergencyWithdraw; + + //==================== Event ===================== + event SetEmergencyWithdraw(address filter, bool isOpen); + + //==================== Ownable ==================== + + function addFilterAdminFeeInfo(address filterAdminAddr, uint256 nftInFee, uint256 nftRandomOutFee, uint256 nftTargetOutFee) external onlyOwner { + FilterAdminFeeInfo memory filterAdmin = FilterAdminFeeInfo({ + nftInFee: nftInFee, + nftRandomOutFee: nftRandomOutFee, + nftTargetOutFee: nftTargetOutFee, + isSet: true + }); + filterAdminFees[filterAdminAddr] = filterAdmin; + } + + function setFilterAdminFeeInfo(address filterAdminAddr, uint256 nftInFee, uint256 nftRandomOutFee, uint256 nftTargetOutFee) external onlyOwner { + filterAdminFees[filterAdminAddr].nftInFee = nftInFee; + filterAdminFees[filterAdminAddr].nftRandomOutFee = nftRandomOutFee; + filterAdminFees[filterAdminAddr].nftTargetOutFee = nftTargetOutFee; + } + + function setGlobalParam(uint256 nftInFee, uint256 nftRandomOutFee, uint256 nftTargetOutFee) external onlyOwner { + _GLOBAL_NFT_IN_FEE_ = nftInFee; + _GLOBAL_NFT_RANDOM_OUT_FEE_ = nftRandomOutFee; + _GLOBAL_NFT_TARGET_OUT_FEE_ = nftTargetOutFee; + } + + function setEmergencyWithdraw(address filter, bool isOpen) external onlyOwner { + isEmergencyWithdraw[filter] = isOpen; + emit SetEmergencyWithdraw(filter, isOpen); + } + + //===================== View ======================== + function getNFTInFee(address filterAdminAddr, address) external view returns(uint256) { + FilterAdminFeeInfo memory FilterAdminFeeInfo = filterAdminFees[filterAdminAddr]; + + if(FilterAdminFeeInfo.isSet) { + return FilterAdminFeeInfo.nftInFee; + }else { + return _GLOBAL_NFT_IN_FEE_; + } + } + + function getNFTRandomOutFee(address filterAdminAddr, address) external view returns(uint256) { + FilterAdminFeeInfo memory FilterAdminFeeInfo = filterAdminFees[filterAdminAddr]; + + if(FilterAdminFeeInfo.isSet) { + return FilterAdminFeeInfo.nftRandomOutFee; + }else { + return _GLOBAL_NFT_RANDOM_OUT_FEE_; + } + } + + function getNFTTargetOutFee(address filterAdminAddr, address) external view returns(uint256) { + FilterAdminFeeInfo memory FilterAdminFeeInfo = filterAdminFees[filterAdminAddr]; + + if(FilterAdminFeeInfo.isSet) { + return FilterAdminFeeInfo.nftTargetOutFee; + }else { + return _GLOBAL_NFT_TARGET_OUT_FEE_; + } + } + + function getEmergencySwitch(address filter) external view returns(bool) { + return isEmergencyWithdraw[filter]; + } + +} diff --git a/contracts/NFTPool/impl/FilterAdmin.sol b/contracts/NFTPool/impl/FilterAdmin.sol index 2284311..831635d 100644 --- a/contracts/NFTPool/impl/FilterAdmin.sol +++ b/contracts/NFTPool/impl/FilterAdmin.sol @@ -11,7 +11,7 @@ pragma experimental ABIEncoderV2; import {InitializableInternalMintableERC20} from "../../external/ERC20/InitializableInternalMintableERC20.sol"; import {SafeMath} from "../../lib/SafeMath.sol"; import {IFilterModel} from "../intf/IFilterModel.sol"; -import {IFeeModel} from "../intf/IFeeModel.sol"; +import {IControllerModel} from "../intf/IControllerModel.sol"; import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; import {DecimalMath} from "../../lib/DecimalMath.sol"; @@ -21,7 +21,7 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { // ============ Storage ============ address[] public _FILTER_REGISTRY_; uint256 public _FEE_; - address public _MT_FEE_MODEL_; + address public _CONTROLLER_MODEL_; address public _DEFAULT_MAINTAINER_; function init( @@ -36,14 +36,15 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { super.init(_owner, 0, _name, _symbol, 18); _FILTER_REGISTRY_ = filters; _FEE_ = fee; - _MT_FEE_MODEL_ = mtFeeModel; + _CONTROLLER_MODEL_ = mtFeeModel; _DEFAULT_MAINTAINER_ = defaultMaintainer; } function ERC721In( address filter, address nftContract, - uint256[] memory tokenIds + uint256[] memory tokenIds, + uint256 minMintAmount ) external preventReentrant @@ -62,14 +63,17 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { if(poolFeeAmount > 0) _mint(_OWNER_, poolFeeAmount); if(mtFeeAmount > 0) _mint(_DEFAULT_MAINTAINER_, mtFeeAmount); - _mint(msg.sender, totalPrice.sub(mtFeeAmount).sub(poolFeeAmount)); + uint256 actualMintAmount = totalPrice.sub(mtFeeAmount).sub(poolFeeAmount); + require(actualMintAmount >= minMintAmount, "MINT_AMOUNT_NOT_ENOUGH"); + _mint(msg.sender, actualMintAmount); } function ERC1155In( address filter, address nftContract, uint256[] memory tokenIds, - uint256[] memory amounts + uint256[] memory amounts, + uint256 minMintAmount ) external preventReentrant @@ -87,16 +91,18 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { (uint256 poolFeeAmount, uint256 mtFeeAmount) = _nftInFeeTransfer(totalPrice); if(poolFeeAmount > 0) _mint(_OWNER_, poolFeeAmount); if(mtFeeAmount > 0) _mint(_DEFAULT_MAINTAINER_, mtFeeAmount); - - _mint(msg.sender, totalPrice.sub(mtFeeAmount).sub(poolFeeAmount)); + + uint256 actualMintAmount = totalPrice.sub(mtFeeAmount).sub(poolFeeAmount); + require(actualMintAmount >= minMintAmount, "MINT_AMOUNT_NOT_ENOUGH"); + _mint(msg.sender, actualMintAmount); IFilterModel(filter).transferBatchInERC1155(nftContract, msg.sender, tokenIds, amounts); } - function ERC721RandomOut( address filter, - uint256 times + uint256 times, + uint256 maxBurnAmount ) external preventReentrant @@ -116,14 +122,15 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { if(poolFeeAmount > 0) _mint(_OWNER_, poolFeeAmount); if(mtFeeAmount > 0) _mint(_DEFAULT_MAINTAINER_, mtFeeAmount); + require(totalPrice <= maxBurnAmount, "EXTRA_BURN_AMOUNT"); _burn(msg.sender, totalPrice); } - //TODO: amount == 1 function ERC1155RandomOut( address filter, - uint256 times + uint256 times, + uint256 maxBurnAmount ) external preventReentrant @@ -144,13 +151,15 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { if(poolFeeAmount > 0) _mint(_OWNER_, poolFeeAmount); if(mtFeeAmount > 0) _mint(_DEFAULT_MAINTAINER_, mtFeeAmount); + require(totalPrice <= maxBurnAmount, "EXTRA_BURN_AMOUNT"); _burn(msg.sender, totalPrice); } function ERC721TargetOut( address filter, address nftContract, - uint256[] memory tokenIds + uint256[] memory tokenIds, + uint256 maxBurnAmount ) external preventReentrant @@ -168,6 +177,7 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { if(poolFeeAmount > 0) _mint(_OWNER_, poolFeeAmount); if(mtFeeAmount > 0) _mint(_DEFAULT_MAINTAINER_, mtFeeAmount); + require(totalPrice <= maxBurnAmount, "EXTRA_BURN_AMOUNT"); _burn(msg.sender, totalPrice); } @@ -175,7 +185,8 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { address filter, address nftContract, uint256[] memory tokenIds, - uint256[] memory amounts + uint256[] memory amounts, + uint256 maxBurnAmount ) external preventReentrant @@ -192,6 +203,7 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { if(poolFeeAmount > 0) _mint(_OWNER_, poolFeeAmount); if(mtFeeAmount > 0) _mint(_DEFAULT_MAINTAINER_, mtFeeAmount); + require(totalPrice <= maxBurnAmount, "EXTRA_BURN_AMOUNT"); _burn(msg.sender, totalPrice); IFilterModel(filter).transferBatchOutERC1155(nftContract, msg.sender, tokenIds, amounts); @@ -223,19 +235,19 @@ contract FilterAdmin is InitializableInternalMintableERC20, ReentrancyGuard { //=============== Internal ============== function _nftInFeeTransfer(uint256 totalPrice) internal returns (uint256 poolFeeAmount, uint256 mtFeeAmount) { - uint256 mtFeeRate = IFeeModel(_MT_FEE_MODEL_).getNFTInFee(address(this), msg.sender); + uint256 mtFeeRate = IControllerModel(_CONTROLLER_MODEL_).getNFTInFee(address(this), msg.sender); poolFeeAmount = DecimalMath.mulFloor(totalPrice, _FEE_); mtFeeAmount = DecimalMath.mulFloor(totalPrice, mtFeeRate); } function _nftRandomOutFeeTransfer(uint256 totalPrice) internal returns (uint256 poolFeeAmount, uint256 mtFeeAmount) { - uint256 mtFeeRate = IFeeModel(_MT_FEE_MODEL_).getNFTRandomOutFee(address(this), msg.sender); + uint256 mtFeeRate = IControllerModel(_CONTROLLER_MODEL_).getNFTRandomOutFee(address(this), msg.sender); poolFeeAmount = DecimalMath.mulFloor(totalPrice, _FEE_); mtFeeAmount = DecimalMath.mulFloor(totalPrice, mtFeeRate); } function _nftTargetOutFeeTransfer(uint256 totalPrice) internal returns (uint256 poolFeeAmount, uint256 mtFeeAmount) { - uint256 mtFeeRate = IFeeModel(_MT_FEE_MODEL_).getNFTTargetOutFee(address(this), msg.sender); + uint256 mtFeeRate = IControllerModel(_CONTROLLER_MODEL_).getNFTTargetOutFee(address(this), msg.sender); poolFeeAmount = DecimalMath.mulFloor(totalPrice, _FEE_); mtFeeAmount = DecimalMath.mulFloor(totalPrice, mtFeeRate); } diff --git a/contracts/NFTPool/impl/FilterModel01.sol b/contracts/NFTPool/impl/FilterModel01.sol index f307daa..7a765d3 100644 --- a/contracts/NFTPool/impl/FilterModel01.sol +++ b/contracts/NFTPool/impl/FilterModel01.sol @@ -11,6 +11,7 @@ pragma experimental ABIEncoderV2; import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; import {SafeMath} from "../../lib/SafeMath.sol"; import {IFilterAdmin} from "../intf/IFilterAdmin.sol"; +import {IControllerModel} from "../intf/IControllerModel.sol"; import {IERC721} from "../../intf/IERC721.sol"; import {IERC721Receiver} from "../../intf/IERC721Receiver.sol"; import {DecimalMath} from "../../lib/DecimalMath.sol"; @@ -142,7 +143,11 @@ contract FilterModel01 is InitializableOwnable, IERC721Receiver { //Pseudorandomness function getRandomOutId() external view returns (address nftCollection, uint256 nftId) { uint256 nftAmount = _TOKEN_IDS_.length; - uint256 idx = uint256(keccak256(abi.encodePacked(blockhash(block.number-1), gasleft(), msg.sender, nftAmount))) % nftAmount; + uint256 sumSeed; + for(uint256 i = 0; i < gasleft() % 10; i++) { + sumSeed = sumSeed.add(uint256(keccak256(abi.encodePacked(blockhash(block.number-1), gasleft(), msg.sender, nftAmount)))); + } + uint256 idx = sumSeed % nftAmount; nftCollection = _NFT_COLLECTION_; nftId = _TOKEN_IDS_[idx]; } @@ -176,19 +181,9 @@ contract FilterModel01 is InitializableOwnable, IERC721Receiver { // ================= Ownable ================ function transferOutERC721(address nftContract, address assetTo, uint256 nftId) external onlyOwner { require(nftContract == _NFT_COLLECTION_, "WRONG_NFT_COLLECTION"); - uint256[] memory tokenIds = _TOKEN_IDS_; - uint256 i; - for (; i < tokenIds.length; i++) { - if (tokenIds[i] == nftId) { - tokenIds[i] = tokenIds[tokenIds.length - 1]; - break; - } - } - require(i < tokenIds.length, "NOT_EXIST_ID"); - _TOKEN_IDS_ = tokenIds; - _TOKEN_IDS_.pop(); - - IERC721(nftContract).safeTransferFrom(address(this), assetTo, nftId); + bool isRemove = removeTokenId(nftId); + if(isRemove) + IERC721(nftContract).safeTransferFrom(address(this), assetTo, nftId); } function transferInERC721(address nftContract, address assetFrom, uint256 nftId) external onlyOwner { @@ -259,6 +254,20 @@ contract FilterModel01 is InitializableOwnable, IERC721Receiver { } } + function emergencyWithdraw(address[] memory nftContract, uint256[] memory tokenIds, address assetTo) external { + require(msg.sender == IFilterAdmin(_OWNER_)._OWNER_(), "ACCESS_RESTRICTED"); + require(nftContract.length == tokenIds.length, "PARAM_INVALID"); + address controllerModel = IFilterAdmin(_OWNER_)._CONTROLLER_MODEL_(); + require(IControllerModel(controllerModel).getEmergencySwitch(address(this)), "NOT_OPEN"); + + for(uint256 i = 0; i< nftContract.length; i++) { + if(nftContract[i] == _NFT_COLLECTION_) { + removeTokenId(tokenIds[i]); + } + IERC721(nftContract[i]).safeTransferFrom(address(this), assetTo, tokenIds[i]); + } + } + // ============ Callback ============ function onERC721Received( address, @@ -278,4 +287,22 @@ contract FilterModel01 is InitializableOwnable, IERC721Receiver { } newBase = base; } + + function removeTokenId(uint256 id) internal returns(bool){ + uint256[] memory tokenIds = _TOKEN_IDS_; + uint256 i; + for (; i < tokenIds.length; i++) { + if (tokenIds[i] == id) { + tokenIds[i] = tokenIds[tokenIds.length - 1]; + break; + } + } + if(i < tokenIds.length) { + _TOKEN_IDS_ = tokenIds; + _TOKEN_IDS_.pop(); + return true; + }else { + return false; + } + } } \ No newline at end of file diff --git a/contracts/NFTPool/impl/NFTPoolFeeModel.sol b/contracts/NFTPool/impl/NFTPoolFeeModel.sol deleted file mode 100644 index c5818a2..0000000 --- a/contracts/NFTPool/impl/NFTPoolFeeModel.sol +++ /dev/null @@ -1,85 +0,0 @@ -/* - - 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 {IERC20} from "../../intf/IERC20.sol"; -import {SafeMath} from "../../lib/SafeMath.sol"; - -contract NFTPoolFeeModel is InitializableOwnable { - using SafeMath for uint256; - - uint256 public _GLOBAL_NFT_IN_FEE_ = 0; - uint256 public _GLOBAL_NFT_RANDOM_OUT_FEE_ = 0; - uint256 public _GLOBAL_NFT_TARGET_OUT_FEE_ = 50000000000000000;//0.05 - - struct FilterAdminInfo { - uint256 nftInFee; - uint256 nftRandomOutFee; - uint256 nftTargetOutFee; - bool isSet; - } - - mapping(address => FilterAdminInfo) filterAdmins; - - function addFilterAdminInfo(address filterAdminAddr, uint256 nftInFee, uint256 nftRandomOutFee, uint256 nftTargetOutFee) external onlyOwner { - FilterAdminInfo memory filterAdmin = FilterAdminInfo({ - nftInFee: nftInFee, - nftRandomOutFee: nftRandomOutFee, - nftTargetOutFee: nftTargetOutFee, - isSet: true - }); - filterAdmins[filterAdminAddr] = filterAdmin; - } - - function setFilterAdminInfo(address filterAdminAddr, uint256 nftInFee, uint256 nftRandomOutFee, uint256 nftTargetOutFee) external onlyOwner { - filterAdmins[filterAdminAddr].nftInFee = nftInFee; - filterAdmins[filterAdminAddr].nftRandomOutFee = nftRandomOutFee; - filterAdmins[filterAdminAddr].nftTargetOutFee = nftTargetOutFee; - } - - function setGlobalParam(uint256 nftInFee, uint256 nftRandomOutFee, uint256 nftTargetOutFee) external onlyOwner { - _GLOBAL_NFT_IN_FEE_ = nftInFee; - _GLOBAL_NFT_RANDOM_OUT_FEE_ = nftRandomOutFee; - _GLOBAL_NFT_TARGET_OUT_FEE_ = nftTargetOutFee; - } - - - function getNFTInFee(address filterAdminAddr, address) external view returns(uint256) { - FilterAdminInfo memory filterAdminInfo = filterAdmins[filterAdminAddr]; - - if(filterAdminInfo.isSet) { - return filterAdminInfo.nftInFee; - }else { - return _GLOBAL_NFT_IN_FEE_; - } - } - - - function getNFTRandomOutFee(address filterAdminAddr, address) external view returns(uint256) { - FilterAdminInfo memory filterAdminInfo = filterAdmins[filterAdminAddr]; - - if(filterAdminInfo.isSet) { - return filterAdminInfo.nftRandomOutFee; - }else { - return _GLOBAL_NFT_RANDOM_OUT_FEE_; - } - } - - function getNFTTargetOutFee(address filterAdminAddr, address) external view returns(uint256) { - FilterAdminInfo memory filterAdminInfo = filterAdmins[filterAdminAddr]; - - if(filterAdminInfo.isSet) { - return filterAdminInfo.nftTargetOutFee; - }else { - return _GLOBAL_NFT_TARGET_OUT_FEE_; - } - } - -} diff --git a/contracts/NFTPool/intf/IFeeModel.sol b/contracts/NFTPool/intf/IControllerModel.sol similarity index 78% rename from contracts/NFTPool/intf/IFeeModel.sol rename to contracts/NFTPool/intf/IControllerModel.sol index 79a745b..e8bfd1f 100644 --- a/contracts/NFTPool/intf/IFeeModel.sol +++ b/contracts/NFTPool/intf/IControllerModel.sol @@ -7,10 +7,12 @@ pragma solidity 0.6.9; -interface IFeeModel { +interface IControllerModel { function getNFTInFee(address filterAdminAddr, address user) external view returns(uint256); function getNFTRandomOutFee(address filterAdminAddr, address user) external view returns(uint256); function getNFTTargetOutFee(address filterAdminAddr, address user) external view returns(uint256); + + function getEmergencySwitch(address filter) external view returns(bool); } \ No newline at end of file diff --git a/contracts/NFTPool/intf/IFilterAdmin.sol b/contracts/NFTPool/intf/IFilterAdmin.sol index cf1cb37..6dc4f5a 100644 --- a/contracts/NFTPool/intf/IFilterAdmin.sol +++ b/contracts/NFTPool/intf/IFilterAdmin.sol @@ -10,6 +10,8 @@ pragma solidity 0.6.9; interface IFilterAdmin { function _OWNER_() external returns (address); + function _CONTROLLER_MODEL_() external returns (address); + function init( address _owner, string memory _name, @@ -19,4 +21,11 @@ interface IFilterAdmin { address defaultMaintainer, address[] memory filters ) external; + + function ERC721In( + address filter, + address nftContract, + uint256[] memory tokenIds, + uint256 minMintAmount + ) external; } \ No newline at end of file diff --git a/contracts/SmartRoute/proxies/DODONFTPoolProxy.sol b/contracts/SmartRoute/proxies/DODONFTPoolProxy.sol index 4d2a367..359570f 100644 --- a/contracts/SmartRoute/proxies/DODONFTPoolProxy.sol +++ b/contracts/SmartRoute/proxies/DODONFTPoolProxy.sol @@ -10,6 +10,7 @@ import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; import {ICloneFactory} from "../../lib/CloneFactory.sol"; import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; import {IFilterAdmin} from "../../NFTPool/intf/IFilterAdmin.sol"; +import {IERC721} from "../../intf/IERC721.sol"; interface IFilter01 { function init( @@ -99,6 +100,31 @@ contract DODONFTPoolProxy is ReentrancyGuard, InitializableOwnable { IFilter01(newFilter01).init(filterAdmin, nftCollection, switches, tokenRanges, nftAmounts, priceRules, spreadIds); } + function erc721ToErc20( + address filterAdmin, + address filter, + address nftContract, + uint256 tokenId, + address toToken, + address dodoApprove, + address dodoProxy, + bytes memory dodoSwapData + ) + external + preventReentrant + { + IERC721(nftContract).safeTransferFrom(msg.sender, address(this), tokenId); + IERC721(nftContract).approve(filter, tokenId); + + uint256[] memory tokenIds = new uint256[1]; + tokenIds[0] = tokenId; + IFilterAdmin(filterAdmin).ERC721In(filter, nftContract, tokenIds, 0); + + + + } + + //====================== Ownable ======================== function changeDefaultMaintainer(address newMaintainer) external onlyOwner { _DEFAULT_MAINTAINER_ = newMaintainer; @@ -116,4 +142,20 @@ contract DODONFTPoolProxy is ReentrancyGuard, InitializableOwnable { _FILTER_TEMPLATES_[idx] = newFilterTemplate; emit SetFilterTemplate(idx, newFilterTemplate); } + + + //======================= 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)); + } + } } \ No newline at end of file