diff --git a/contracts/NFTPool/impl/FilterAdmin.sol b/contracts/NFTPool/impl/FilterAdmin.sol new file mode 100644 index 0000000..8a3250e --- /dev/null +++ b/contracts/NFTPool/impl/FilterAdmin.sol @@ -0,0 +1,146 @@ +/* + + 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 {IFilterERC721Model} from "../intf/IFilterERC721Model.sol"; +import {IFilterERC1155Model} from "../intf/IFilterERC1155Model.sol"; +import {IERC721} from "../../intf/IERC721.sol"; +import {IERC721Receiver} from "../../intf/IERC721Receiver.sol"; +import {IERC1155} from "../../intf/IERC1155.sol"; +import {IERC1155Receiver} from "../../intf/IERC1155Receiver.sol"; + +contract FilterAdmin is InitializableInternalMintableERC20, IERC721Receiver, IERC1155Receiver { + using SafeMath for uint256; + + // ============ Storage ============ + address public _ERC721_FILTER_MODEL_; + address public _ERC1155_FILTER_MODEL_; + + function init( + address _owner, + uint256 _initSupply, + string memory _name, + string memory _symbol, + uint8 _decimals, + address _erc721FilterModel, + address _erc1155FilterModel + ) external { + super.init(_owner, _initSupply, _name, _symbol, _decimals); + _ERC721_FILTER_MODEL_ = _erc721FilterModel; + _ERC1155_FILTER_MODEL_ = _erc1155FilterModel; + } + + // ============ Event ============ + event RemoveNftToken(address nftContract, uint256 tokenId, uint256 amount); + event AddNftToken(address nftContract, uint256 tokenId, uint256 amount); + + + function depositERC721(address nftContract, uint256[] memory tokenIds) public { + require(nftContract != address(0), "ZERO_ADDRESS"); + for(uint256 i = 0; i < tokenIds.length; i++) { + uint256 price = IFilterERC721Model(_ERC721_FILTER_MODEL_).saveNFTPrice(nftContract, tokenIds[i]); + _mint(msg.sender, price); + + IERC721(nftContract).safeTransferFrom(msg.sender, address(this), tokenIds[i]); + emit AddNftToken(nftContract, tokenIds[i], 1); + } + } + + function depoistERC1155(address nftContract, uint256[] memory tokenIds, uint256[] memory amounts) public { + require(nftContract != address(0), "ZERO_ADDRESS"); + require(tokenIds.length == amounts.length, "PARAMS_NOT_MATCH"); + for(uint256 i = 0; i < tokenIds.length; i++) { + uint256 price = IFilterERC1155Model(_ERC1155_FILTER_MODEL_).saveNFTPrice(nftContract, tokenIds[i], amounts[i]); + _mint(msg.sender, price); + + emit AddNftToken(nftContract, tokenIds[i], amounts[i]); + } + + IERC1155(nftContract).safeBatchTransferFrom(msg.sender, address(this), tokenIds, amounts, ""); + } + + function doLotteryERC721() external { + uint256 lotteryPrice = IFilterERC721Model(_ERC721_FILTER_MODEL_).buyLotteryNFTPrice(); + _burn(msg.sender, lotteryPrice); + (address nftContract, uint256 tokenId) = IFilterERC721Model(_ERC721_FILTER_MODEL_).lottery(); + + IERC721(nftContract).safeTransferFrom(address(this), msg.sender, tokenId); + emit RemoveNftToken(nftContract, tokenId, 1); + } + + function doLotteryERC1155() external { + uint256 lotteryPrice = IFilterERC1155Model(_ERC1155_FILTER_MODEL_).buyLotteryNFTPrice(); + _burn(msg.sender, lotteryPrice); + (address nftContract, uint256 tokenId) = IFilterERC1155Model(_ERC721_FILTER_MODEL_).lottery(); + + //TODO: amount + IERC1155(nftContract).safeTransferFrom(address(this), msg.sender, tokenId, 1, ""); + emit RemoveNftToken(nftContract, tokenId, 1); + } + + function buySpecERC721(address nftContract, uint256 tokenId) external { + uint256 price = IFilterERC721Model(_ERC721_FILTER_MODEL_).buySpecNFTPrice(nftContract, tokenId); + _burn(msg.sender, price); + + IERC721(nftContract).safeTransferFrom(address(this), msg.sender, tokenId); + emit RemoveNftToken(nftContract, tokenId, 1); + } + + function buySpecERC1155(address nftContract, uint256 tokenId, uint256 amount) external { + uint256 price = IFilterERC1155Model(_ERC1155_FILTER_MODEL_).buySpecNFTPrice(nftContract, tokenId, amount); + _burn(msg.sender, price); + IERC1155(nftContract).safeTransferFrom(address(this), msg.sender, tokenId, amount, ""); + + emit RemoveNftToken(nftContract, tokenId, amount); + } + + + function supportsInterface(bytes4 interfaceId) public override view returns (bool) { + return interfaceId == type(IERC1155Receiver).interfaceId + || interfaceId == type(IERC721Receiver).interfaceId; + } + + // ============ Callback ============ + function onERC721Received( + address, + address, + uint256 tokenId, + bytes calldata + ) external override returns (bytes4) { + emit AddNftToken(msg.sender, tokenId, 1); + return IERC721Receiver.onERC721Received.selector; + } + + function onERC1155Received( + address, + address, + uint256 id, + uint256 value, + bytes calldata + ) external override returns (bytes4){ + emit AddNftToken(msg.sender, id, value); + return IERC1155Receiver.onERC1155Received.selector; + } + + function onERC1155BatchReceived( + address, + address, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata + ) external override returns (bytes4){ + require(ids.length == values.length, "PARAMS_NOT_MATCH"); + for(uint256 i = 0; i < ids.length; i++) { + emit AddNftToken(msg.sender, ids[i], values[i]); + } + return IERC1155Receiver.onERC1155BatchReceived.selector; + } +} diff --git a/contracts/NFTPool/impl/FilterERC1155Model.sol b/contracts/NFTPool/impl/FilterERC1155Model.sol new file mode 100644 index 0000000..41f2224 --- /dev/null +++ b/contracts/NFTPool/impl/FilterERC1155Model.sol @@ -0,0 +1,130 @@ +/* + + 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 {IFilterERC1155Model} from "../intf/IFilterERC1155Model.sol"; + +contract FilterERC1155Model is InitializableOwnable, IFilterERC1155Model { + using SafeMath for uint256; + + //=================== Storage ===================== + // nftCollection -> nftId -> price(frag) + mapping(address => mapping(uint256 => uint256)) public _PRICES_; + // nftCollection -> nftId -> amount + mapping(address => mapping(uint256 => uint256)) public _ERC1155_AMOUNT_; + // nftCollection -> nftId -> specPrice(frag) + mapping(address => mapping(uint256 => uint256)) public _SPEC_PRICES_; + + uint256 public _SPEC_FACTOR_ = 200; + + // nftColletcion -> nftIds + mapping(address => uint256[]) public _NFT_COLLECTION_IDS_; + + address[] public _NFT_COLLECTIONS_; + + uint256 public _LOTTERY_THRESHOLD_; + + uint256 public _TOTAL_NFT_AMOUNT_; + + uint256 public _CURRENT_NFT_AMOUNT_; + + function init( + address owner + ) external { + //TODO: + initOwner(owner); + } + + //================== View ====================== + function isFilterERC1155Pass(address nftCollectionAddress, uint256 nftId, uint256 amount) override external view returns (bool) { + if(_PRICES_[nftCollectionAddress][nftId] == 0) + return false; + else { + if(_ERC1155_AMOUNT_[nftCollectionAddress][nftId] >= amount) + return true; + else + return false; + } + } + + + //TODO: nftInCap + //TODO: nftOutCap + + function saveNFTPrice(address nftCollectionAddress, uint256 nftId, uint256 amount) override external view returns(uint256) { + return _PRICES_[nftCollectionAddress][nftId].mul(amount); + } + + function buySpecNFTPrice(address nftCollectionAddress, uint256 nftId, uint256 amount) override external view returns(uint256) { + require(_ERC1155_AMOUNT_[nftCollectionAddress][nftId] >= amount, "BUY_OVERFLOW"); + return _SPEC_PRICES_[nftCollectionAddress][nftId].mul(amount); + } + + //TODO: amount = 1 + function buyLotteryNFTPrice() override external view returns(uint256) { + return _LOTTERY_THRESHOLD_; + } + + function lottery() override external view returns(address nftCollection, uint256 nftId) { + //random + } + + + //================== Owner ===================== + function setNFTFilter( + address[] memory nftCollections, + uint256[] memory nftIds, + uint256[] memory amounts, + uint256[] memory prices, + uint256[] memory specPrices + ) external onlyOwner { + require(nftCollections.length == nftIds.length, "PARAMS_INVALID"); + require(nftCollections.length == amounts.length, "PARAMS_INVALID"); + require(nftCollections.length == prices.length, "PARAMS_INVALID"); + require(nftCollections.length == specPrices.length, "PARAMS_INVALID"); + + for(uint256 i = 0; i < nftCollections.length; i++){ + _PRICES_[nftCollections[i]][nftIds[i]] = prices[i]; + _PRICES_[nftCollections[i]][nftIds[i]] = amounts[i]; + + if(specPrices[i] == 0){ + _SPEC_PRICES_[nftCollections[i]][nftIds[i]] = prices[i].mul(_SPEC_FACTOR_).div(100); + }else { + _SPEC_PRICES_[nftCollections[i]][nftIds[i]] = specPrices[i]; + } + + if(_NFT_COLLECTION_IDS_[nftCollections[i]].length == 0) { + _NFT_COLLECTIONS_.push(nftCollections[i]); + _NFT_COLLECTION_IDS_[nftCollections[i]] = [nftIds[i]]; + require(++_CURRENT_NFT_AMOUNT_ <= _TOTAL_NFT_AMOUNT_, "OVERFLOW_NFT_AMOUNT"); + } else { + uint256 j = 0; + for(; j < _NFT_COLLECTION_IDS_[nftCollections[i]].length; i++) { + if(_NFT_COLLECTION_IDS_[nftCollections[i]][j] == nftIds[i]) { + break; + } + } + if(j == _NFT_COLLECTION_IDS_[nftCollections[i]].length) { + _NFT_COLLECTION_IDS_[nftCollections[i]].push(nftIds[i]); + require(++_CURRENT_NFT_AMOUNT_ <= _TOTAL_NFT_AMOUNT_, "OVERFLOW_NFT_AMOUNT"); + } + } + } + } + + function setLotteryThreshold(uint256 newLotteryThreshold) external onlyOwner { + _LOTTERY_THRESHOLD_ = newLotteryThreshold; + } + + function setSpecFactor(uint256 newSpecFactor) external onlyOwner { + _SPEC_FACTOR_ = newSpecFactor; + } +} diff --git a/contracts/NFTPool/impl/FilterERC721Model.sol b/contracts/NFTPool/impl/FilterERC721Model.sol new file mode 100644 index 0000000..9a28b1a --- /dev/null +++ b/contracts/NFTPool/impl/FilterERC721Model.sol @@ -0,0 +1,129 @@ +/* + + 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 {IFilterERC721Model} from "../intf/IFilterERC721Model.sol"; + +contract FilterERC721Model is InitializableOwnable, IFilterERC721Model { + using SafeMath for uint256; + + //=================== Storage =================== + // nftCollection -> nftId -> price(frag) + mapping(address => mapping(uint256 => uint256)) public _PRICES_; + + // nftCollection -> nftId -> specPrice(frag) + mapping(address => mapping(uint256 => uint256)) public _SPEC_PRICES_; + + uint256 public _SPEC_FACTOR_ = 200; + + // nftColletcion -> nftIds + mapping(address => uint256[]) public _NFT_COLLECTION_IDS_; + + address[] public _NFT_COLLECTIONS_; + + uint256 public _LOTTERY_THRESHOLD_; + + uint256 public _TOTAL_NFT_AMOUNT_; + + uint256 public _CURRENT_NFT_AMOUNT_; + + function init( + address owner + ) external { + //TODO: + initOwner(owner); + } + + //==================== View ================== + function isFilterERC721Pass(address nftCollectionAddress, uint256 nftId) override external view returns (bool) { + if(_PRICES_[nftCollectionAddress][nftId] == 0) + return false; + else + return true; + } + + function saveNFTPrice(address nftCollectionAddress, uint256 nftId) override external view returns(uint256) { + return _PRICES_[nftCollectionAddress][nftId]; + } + + function buySpecNFTPrice(address nftCollectionAddress, uint256 nftId) override external view returns(uint256) { + return _SPEC_PRICES_[nftCollectionAddress][nftId]; + } + + function buyLotteryNFTPrice() override external view returns(uint256) { + return _LOTTERY_THRESHOLD_; + } + + function lottery() override external view returns(address nftCollection, uint256 nftId) { + //random + } + + + + //TODO: nftInCap + //TODO: nftOutCap + + + //============= Owner =============== + function setNFTFilter( + address[] memory nftCollections, + uint256[] memory nftIds, + uint256[] memory prices, + uint256[] memory specPrices + ) external onlyOwner { + require(nftCollections.length == nftIds.length, "PARAMS_INVALID"); + require(nftCollections.length == prices.length, "PARAMS_INVALID"); + require(nftCollections.length == specPrices.length, "PARAMS_INVALID"); + + for(uint256 i = 0; i < nftCollections.length; i++){ + _PRICES_[nftCollections[i]][nftIds[i]] = prices[i]; + + if(specPrices[i] == 0){ + _SPEC_PRICES_[nftCollections[i]][nftIds[i]] = prices[i].mul(_SPEC_FACTOR_).div(100); + }else { + _SPEC_PRICES_[nftCollections[i]][nftIds[i]] = specPrices[i]; + } + + if(_NFT_COLLECTION_IDS_[nftCollections[i]].length == 0) { + _NFT_COLLECTIONS_.push(nftCollections[i]); + _NFT_COLLECTION_IDS_[nftCollections[i]] = [nftIds[i]]; + + require(++_CURRENT_NFT_AMOUNT_ <= _TOTAL_NFT_AMOUNT_, "OVERFLOW_NFT_AMOUNT"); + + } else { + uint256 j = 0; + for(; j < _NFT_COLLECTION_IDS_[nftCollections[i]].length; i++) { + if(_NFT_COLLECTION_IDS_[nftCollections[i]][j] == nftIds[i]) { + break; + } + } + if(j == _NFT_COLLECTION_IDS_[nftCollections[i]].length) { + _NFT_COLLECTION_IDS_[nftCollections[i]].push(nftIds[i]); + require(++_CURRENT_NFT_AMOUNT_ <= _TOTAL_NFT_AMOUNT_, "OVERFLOW_NFT_AMOUNT"); + } + } + } + } + + + function setLotteryThreshold(uint256 newLotteryThreshold) external onlyOwner { + _LOTTERY_THRESHOLD_ = newLotteryThreshold; + } + + function setSpecFactor(uint256 newSpecFactor) external onlyOwner { + _SPEC_FACTOR_ = newSpecFactor; + } + + function setTotalNFTAmount(uint256 newTotalNFTAmount) external onlyOwner { + _TOTAL_NFT_AMOUNT_ = newTotalNFTAmount; + } + +} diff --git a/contracts/NFTPool/intf/IFilterERC1155Model.sol b/contracts/NFTPool/intf/IFilterERC1155Model.sol new file mode 100644 index 0000000..f4fae33 --- /dev/null +++ b/contracts/NFTPool/intf/IFilterERC1155Model.sol @@ -0,0 +1,20 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IFilterERC1155Model { + function isFilterERC1155Pass(address nftCollectionAddress, uint256 nftId, uint256 amount) external view returns (bool); + + function saveNFTPrice(address nftCollectionAddress, uint256 nftId, uint256 amount) external view returns(uint256); + + function buySpecNFTPrice(address nftCollectionAddress, uint256 nftId, uint256 amount) external view returns(uint256); + + function buyLotteryNFTPrice() external view returns(uint256); + + function lottery() external view returns(address nftCollection, uint256 nftId); +} \ No newline at end of file diff --git a/contracts/NFTPool/intf/IFilterERC721Model.sol b/contracts/NFTPool/intf/IFilterERC721Model.sol new file mode 100644 index 0000000..2e2cd42 --- /dev/null +++ b/contracts/NFTPool/intf/IFilterERC721Model.sol @@ -0,0 +1,20 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IFilterERC721Model { + function isFilterERC721Pass(address nftCollectionAddress, uint256 nftId) external view returns (bool); + + function saveNFTPrice(address nftCollectionAddress, uint256 nftId) external view returns(uint256); + + function buySpecNFTPrice(address nftCollectionAddress, uint256 nftId) external view returns(uint256); + + function buyLotteryNFTPrice() external view returns(uint256); + + function lottery() external view returns(address nftCollection, uint256 nftId); +} \ No newline at end of file diff --git a/contracts/SmartRoute/intf/IDODOV2Proxy01.sol b/contracts/SmartRoute/intf/IDODOV2Proxy01.sol index f9c1ff0..5821138 100644 --- a/contracts/SmartRoute/intf/IDODOV2Proxy01.sol +++ b/contracts/SmartRoute/intf/IDODOV2Proxy01.sol @@ -132,4 +132,16 @@ interface IDODOV2Proxy01 { uint256 deadLine ) external payable returns (uint256 returnAmount); + // function mixSwap( + // address fromToken, + // address toToken, + // uint256 fromTokenAmount, + // uint256 minReturnAmount, + // address[] memory mixAdapters, + // address[] memory mixPairs, + // address[] memory assetTo, + // uint256 directions, + // bool isIncentive, + // uint256 deadLine + // ) external payable returns (uint256 returnAmount); } diff --git a/contracts/external/ERC20/InitializableInternalMintableERC20.sol b/contracts/external/ERC20/InitializableInternalMintableERC20.sol new file mode 100644 index 0000000..ec3eb49 --- /dev/null +++ b/contracts/external/ERC20/InitializableInternalMintableERC20.sol @@ -0,0 +1,98 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; + +contract InitializableInternalMintableERC20 is InitializableOwnable { + using SafeMath for uint256; + + string public name; + uint8 public decimals; + string public symbol; + uint256 public totalSupply; + + mapping(address => uint256) internal balances; + mapping(address => mapping(address => uint256)) internal allowed; + + event Transfer(address indexed from, address indexed to, uint256 amount); + event Approval(address indexed owner, address indexed spender, uint256 amount); + event Mint(address indexed user, uint256 value); + event Burn(address indexed user, uint256 value); + + function init( + address _creator, + uint256 _initSupply, + string memory _name, + string memory _symbol, + uint8 _decimals + ) public { + initOwner(_creator); + name = _name; + symbol = _symbol; + decimals = _decimals; + totalSupply = _initSupply; + balances[_creator] = _initSupply; + emit Transfer(address(0), _creator, _initSupply); + } + + function transfer(address to, uint256 amount) public virtual returns (bool) { + require(to != address(0), "TO_ADDRESS_IS_EMPTY"); + require(amount <= balances[msg.sender], "BALANCE_NOT_ENOUGH"); + + balances[msg.sender] = balances[msg.sender].sub(amount); + balances[to] = balances[to].add(amount); + emit Transfer(msg.sender, to, amount); + return true; + } + + function balanceOf(address owner) public view returns (uint256 balance) { + return balances[owner]; + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public virtual returns (bool) { + require(to != address(0), "TO_ADDRESS_IS_EMPTY"); + require(amount <= balances[from], "BALANCE_NOT_ENOUGH"); + require(amount <= allowed[from][msg.sender], "ALLOWANCE_NOT_ENOUGH"); + + balances[from] = balances[from].sub(amount); + balances[to] = balances[to].add(amount); + allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount); + emit Transfer(from, to, amount); + return true; + } + + function approve(address spender, uint256 amount) public virtual returns (bool) { + allowed[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; + } + + function allowance(address owner, address spender) public view returns (uint256) { + return allowed[owner][spender]; + } + + function _mint(address user, uint256 value) internal { + balances[user] = balances[user].add(value); + totalSupply = totalSupply.add(value); + emit Mint(user, value); + emit Transfer(address(0), user, value); + } + + function _burn(address user, uint256 value) internal { + balances[user] = balances[user].sub(value); + totalSupply = totalSupply.sub(value); + emit Burn(user, value); + emit Transfer(user, address(0), value); + } +}