diff --git a/config/kovan-config.js b/config/kovan-config.js index 4088a47..b314202 100644 --- a/config/kovan-config.js +++ b/config/kovan-config.js @@ -62,7 +62,8 @@ module.exports = { //================== NFT ==================== - Fragment: "0x96d07E96F703B2De722a8671638776924ab81E80", + BuyoutModel:"", + Fragment: "", NFTCollateralVault: "0xD25278cd387e54E77C5490F5220b551fe2feb772", DODONFTRouteHelper: "0xDD1511f2Bcdb0E6F916F9740BF83f31dF0fb63b4", @@ -74,7 +75,7 @@ module.exports = { DodoNftErc1155: "0xE9C572287936dB1B0a951ca0768C1b0d26b62A04", DODONFTRegistry: "0x579eBcC668b5517F733587091C35D495FE8d6b68", - DODONFTProxy: "0xe121c6C90735e2Ca12e21708F2F379A55Ce61426", + DODONFTProxy: "", //================= DropsV1 ================= // MysteryBoxV1: "0x47d2b27525b93A9c9E03001E1D19310A08748D55",//波老师 diff --git a/contracts/GeneralizedFragment/impl/BuyoutModel.sol b/contracts/GeneralizedFragment/impl/BuyoutModel.sol new file mode 100644 index 0000000..cae308e --- /dev/null +++ b/contracts/GeneralizedFragment/impl/BuyoutModel.sol @@ -0,0 +1,87 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; + +interface IBuyout { + function getBuyoutQualification(address user) external view returns (bool); +} + +contract BuyoutModel is InitializableOwnable { + using SafeMath for uint256; + + uint256 public _MIN_FRAG_ = 100; //0.1 + uint256 public _MAX_FRAG_ = 1000; //1 + int public _BUYOUT_FEE_ = 0; + + struct FragInfo { + uint256 minFrag; + uint256 maxFrag; + address buyoutAddr; + bool isSet; + } + + mapping(address => FragInfo) frags; + + function addFragInfo(address fragAddr, uint256 minFrag, uint256 maxFrag, address buyoutAddr) external onlyOwner { + FragInfo memory fragInfo = FragInfo({ + minFrag: minFrag, + maxFrag: maxFrag, + buyoutAddr: buyoutAddr, + isSet: true + }); + frags[fragAddr] = fragInfo; + } + + function setFragInfo(address fragAddr, uint256 minFrag, uint256 maxFrag, address buyoutAddr) external onlyOwner { + frags[fragAddr].minFrag = minFrag; + frags[fragAddr].maxFrag = maxFrag; + frags[fragAddr].buyoutAddr = buyoutAddr; + } + + function setGlobalParam(uint256 minFrag, uint256 maxFrag, uint256 buyoutFee) external onlyOwner { + require(minFrag <= 1000 && maxFrag <= 1000, "PARAM_INVALID"); + _MIN_FRAG_ = minFrag; + _MAX_FRAG_ = maxFrag; + _BUYOUT_FEE_ = int(buyoutFee); + } + + function getBuyoutStatus(address fragAddr, address user) external view returns (int) { + FragInfo memory fragInfo = frags[fragAddr]; + + uint256 userBalance = IERC20(fragAddr).balanceOf(user); + uint256 totalSupply = IERC20(fragAddr).totalSupply(); + uint256 minFrag = _MIN_FRAG_; + uint256 maxFrag = _MAX_FRAG_; + + if(fragInfo.isSet) { + address buyoutAddr = fragInfo.buyoutAddr; + if(buyoutAddr != address(0)) { + bool isQualified = IBuyout(buyoutAddr).getBuyoutQualification(user); + if(isQualified) { + return _BUYOUT_FEE_; + }else { + return -1; + } + } + + minFrag = fragInfo.minFrag; + maxFrag = fragInfo.maxFrag; + } + + if(userBalance >= totalSupply.mul(minFrag).div(1000) && userBalance <= totalSupply.mul(maxFrag).div(1000)) { + return _BUYOUT_FEE_; + }else { + return -1; + } + } +} diff --git a/contracts/GeneralizedFragment/impl/Fragment.sol b/contracts/GeneralizedFragment/impl/Fragment.sol index 87dbf74..c963cdd 100644 --- a/contracts/GeneralizedFragment/impl/Fragment.sol +++ b/contracts/GeneralizedFragment/impl/Fragment.sol @@ -16,6 +16,10 @@ import {IERC20} from "../../intf/IERC20.sol"; import {InitializableERC20} from "../../external/ERC20/InitializableERC20.sol"; import {ICollateralVault} from "../../CollateralVault/intf/ICollateralVault.sol"; +interface IBuyoutModel { + function getBuyoutStatus(address fragAddr, address user) external view returns (int); +} + contract Fragment is InitializableERC20 { using SafeMath for uint256; using SafeERC20 for IERC20; @@ -25,7 +29,6 @@ contract Fragment is InitializableERC20 { bool public _IS_BUYOUT_; uint256 public _BUYOUT_TIMESTAMP_; uint256 public _BUYOUT_PRICE_; - uint256 public _DEFAULT_BUYOUT_FEE_; uint256 public _DISTRIBUTION_RATIO_; address public _COLLATERAL_VAULT_; @@ -33,6 +36,7 @@ contract Fragment is InitializableERC20 { address public _QUOTE_; address public _DVM_; address public _DEFAULT_MAINTAINER_; + address public _BUYOUT_MODEL_; bool internal _FRAG_INITIALIZED_; @@ -53,7 +57,7 @@ contract Fragment is InitializableERC20 { uint256 ownerRatio, uint256 buyoutTimestamp, address defaultMaintainer, - uint256 defaultBuyoutFee, + address buyoutModel, uint256 distributionRatio, string memory _symbol ) external { @@ -67,7 +71,7 @@ contract Fragment is InitializableERC20 { _COLLATERAL_VAULT_ = collateralVault; _BUYOUT_TIMESTAMP_ = buyoutTimestamp; _DEFAULT_MAINTAINER_ = defaultMaintainer; - _DEFAULT_BUYOUT_FEE_ = defaultBuyoutFee; + _BUYOUT_MODEL_ = buyoutModel; _DISTRIBUTION_RATIO_ = distributionRatio; // init FRAG meta data @@ -93,6 +97,10 @@ contract Fragment is InitializableERC20 { require(_BUYOUT_TIMESTAMP_ != 0, "DODOFragment: NOT_SUPPORT_BUYOUT"); require(block.timestamp > _BUYOUT_TIMESTAMP_, "DODOFragment: BUYOUT_NOT_START"); require(!_IS_BUYOUT_, "DODOFragment: ALREADY_BUYOUT"); + + int buyoutFee = IBuyoutModel(_BUYOUT_MODEL_).getBuyoutStatus(address(this), newVaultOwner); + require(buyoutFee != -1, "DODOFragment: USER_UNABLE_BUYOUT"); + _IS_BUYOUT_ = true; _BUYOUT_PRICE_ = IDVM(_DVM_).getMidPrice(); @@ -114,10 +122,10 @@ contract Fragment is InitializableERC20 { _clearBalance(address(this)); _clearBalance(_VAULT_PRE_OWNER_); - uint256 buyoutFee = DecimalMath.mulFloor(ownerQuoteWithoutFee, _DEFAULT_BUYOUT_FEE_); + uint256 buyoutFeeAmount = DecimalMath.mulFloor(ownerQuoteWithoutFee, uint256(buyoutFee)); - IERC20(_QUOTE_).safeTransfer(_DEFAULT_MAINTAINER_, buyoutFee); - IERC20(_QUOTE_).safeTransfer(_VAULT_PRE_OWNER_, ownerQuoteWithoutFee.sub(buyoutFee)); + IERC20(_QUOTE_).safeTransfer(_DEFAULT_MAINTAINER_, buyoutFeeAmount); + IERC20(_QUOTE_).safeTransfer(_VAULT_PRE_OWNER_, ownerQuoteWithoutFee.sub(buyoutFeeAmount)); ICollateralVault(_COLLATERAL_VAULT_).directTransferOwnership(newVaultOwner); diff --git a/contracts/GeneralizedFragment/intf/IFragment.sol b/contracts/GeneralizedFragment/intf/IFragment.sol index 669a81f..53c31cf 100644 --- a/contracts/GeneralizedFragment/intf/IFragment.sol +++ b/contracts/GeneralizedFragment/intf/IFragment.sol @@ -18,7 +18,7 @@ interface IFragment { uint256 ownerRatio, uint256 buyoutTimestamp, address defaultMaintainer, - uint256 defaultBuyoutFee, + address buyoutModel, uint256 distributionRatio, string memory fragSymbol ) external; diff --git a/contracts/SmartRoute/proxies/DODONFTProxy.sol b/contracts/SmartRoute/proxies/DODONFTProxy.sol index 025ba1d..1453103 100644 --- a/contracts/SmartRoute/proxies/DODONFTProxy.sol +++ b/contracts/SmartRoute/proxies/DODONFTProxy.sol @@ -45,15 +45,14 @@ contract DODONFTProxy is ReentrancyGuard, InitializableOwnable { address public _VAULT_TEMPLATE_; address public _FRAG_TEMPLATE_; address public _DVM_TEMPLATE_; - - uint256 public _DEFAULT_BUYOUT_FEE_; + address public _BUYOUT_MODEL_; // ============ Events ============ event ChangeVaultTemplate(address newVaultTemplate); event ChangeFragTemplate(address newFragTemplate); event ChangeDvmTemplate(address newDvmTemplate); event ChangeMtFeeRateTemplate(address newMtFeeRateTemplate); - event ChangeBuyoutFee(uint256 newBuyoutFee); + event ChangeBuyoutModel(address newBuyoutModel); event CreateNFTCollateralVault(address creator, address vault, string name, string baseURI); event CreateFragment(address vault, address fragment, address dvm); event Buyout(address from, address fragment, uint256 amount); @@ -74,6 +73,7 @@ contract DODONFTProxy is ReentrancyGuard, InitializableOwnable { address payable weth, address dodoApproveProxy, address defaultMaintainer, + address buyoutModel, address mtFeeRateModel, address vaultTemplate, address fragTemplate, @@ -85,6 +85,7 @@ contract DODONFTProxy is ReentrancyGuard, InitializableOwnable { _DODO_APPROVE_PROXY_ = dodoApproveProxy; _DEFAULT_MAINTAINER_ = defaultMaintainer; _MT_FEE_RATE_MODEL_ = mtFeeRateModel; + _BUYOUT_MODEL_ = buyoutModel; _VAULT_TEMPLATE_ = vaultTemplate; _FRAG_TEMPLATE_ = fragTemplate; _DVM_TEMPLATE_ = dvmTemplate; @@ -128,7 +129,7 @@ contract DODONFTProxy is ReentrancyGuard, InitializableOwnable { _params[4], _params[5], _DEFAULT_MAINTAINER_, - _DEFAULT_BUYOUT_FEE_, + _BUYOUT_MODEL_, _params[6], fragSymbol ); @@ -190,9 +191,9 @@ contract DODONFTProxy is ReentrancyGuard, InitializableOwnable { emit ChangeDvmTemplate(newDvmTemplate); } - function updateBuyoutFee(uint256 buyoutFee) external onlyOwner { - _DEFAULT_BUYOUT_FEE_ = buyoutFee; - emit ChangeBuyoutFee(buyoutFee); + function updateBuyoutModel(address newBuyoutModel) external onlyOwner { + _BUYOUT_MODEL_ = newBuyoutModel; + emit ChangeBuyoutModel(newBuyoutModel); } diff --git a/migrations/5_deploy_nft.js b/migrations/5_deploy_nft.js index 48f88bd..d730402 100644 --- a/migrations/5_deploy_nft.js +++ b/migrations/5_deploy_nft.js @@ -6,6 +6,7 @@ const { GetConfig } = require("../configAdapter.js") const DODOApproveProxy = artifacts.require("DODOApproveProxy"); const NFTCollateralVault = artifacts.require("NFTCollateralVault"); +const BuyoutModel = artifacts.require("BuyoutModel"); const Fragment = artifacts.require("Fragment"); const DODONFTRegistry = artifacts.require("DODONFTRegistry"); const DODONFTProxy = artifacts.require("DODONFTProxy"); @@ -34,6 +35,7 @@ module.exports = async (deployer, network, accounts) => { let MtFeeRateModelAddress = CONFIG.FeeRateModel; let FragmentAddress = CONFIG.Fragment; + let BuyoutModelAddress = CONFIG.BuyoutModel; let NFTCollateralVaultAddress = CONFIG.NFTCollateralVault; let DODONFTRouteHelperAddress = CONFIG.DODONFTRouteHelper; @@ -161,6 +163,12 @@ module.exports = async (deployer, network, accounts) => { logger.log("DODONFTRouteHelperAddress: ", DODONFTRouteHelperAddress); } + //BuyoutModel + if(BuyoutModelAddress == "") { + await deployer.deploy(BuyoutModel); + BuyoutModelAddress = BuyoutModel.address; + logger.log("BuyoutModelAddress: ", BuyoutModelAddress); + } //DODONFTRouteHelper if (DODONFTRouteHelperAddress == "") { @@ -193,6 +201,7 @@ module.exports = async (deployer, network, accounts) => { WETHAddress, DODOApproveProxyAddress, defaultMaintainer, + BuyoutModelAddress, MtFeeRateModelAddress, NFTCollateralVaultAddress, FragmentAddress, diff --git a/test/DODONFT/nftMainFlow.test.ts b/test/DODONFT/nftMainFlow.test.ts index 1a2d8dc..0e39bf1 100644 --- a/test/DODONFT/nftMainFlow.test.ts +++ b/test/DODONFT/nftMainFlow.test.ts @@ -276,6 +276,8 @@ describe("DODONFT", () => { await mockTrade(ctx, dvmAddress, dvmInstance, fragInstance); + await fragInstance.methods.transfer(buyer, decimalStr("1002")).send(ctx.sendParam(author)); + await getUserBalance(author, fragInstance, ctx.USDT, "Author Before"); await getUserBalance(buyer, fragInstance, ctx.USDT, "Buyer Before"); await getUserBalance(dvmAddress, fragInstance, ctx.USDT, "DVM Before"); diff --git a/test/utils/Contracts.ts b/test/utils/Contracts.ts index 8c14e0c..20190e9 100644 --- a/test/utils/Contracts.ts +++ b/test/utils/Contracts.ts @@ -63,6 +63,7 @@ export const CONST_FEE_RATE_MODEL_NAME = "ConstFeeRateModel" export const NFT_TOKEN_FACTORY = "NFTTokenFactory" export const NFT_REGISTER = "DODONFTRegistry" export const NFT_PROXY = "DODONFTProxy" +export const BUYOUT_MODEL = "BuyoutModel" export const RANDOM_GENERATOR = "RandomGenerator" export const MYSTERY_BOX_V1 = "DODODropsV1" @@ -73,6 +74,7 @@ export const DROPS_ERC1155 = "DropsERC1155" export const DROPS_FEE_MODEL = "DropsFeeModel" export const DROPS_PROXY = "DODODropsProxy" + interface ContractJson { abi: any; networks: { [network: number]: any }; diff --git a/test/utils/NFTContext.ts b/test/utils/NFTContext.ts index 485f370..287c20f 100644 --- a/test/utils/NFTContext.ts +++ b/test/utils/NFTContext.ts @@ -28,6 +28,7 @@ export class NFTContext { NFTRegister: Contract; CollatteralVault: Contract; Fragment: Contract; + BuyoutModel: Contract; NFTProxy: Contract; DODOApprove: Contract; @@ -102,7 +103,9 @@ export class NFTContext { contracts.NFT_VAULT ); - this.Fragment = await contracts.newContract(contracts.NFT_FRAG) + this.Fragment = await contracts.newContract(contracts.NFT_FRAG); + + this.BuyoutModel = await contracts.newContract(contracts.BUYOUT_MODEL); this.NFTProxy = await contracts.newContract(contracts.NFT_PROXY, [ @@ -110,6 +113,7 @@ export class NFTContext { this.WETH.options.address, this.DODOApproveProxy.options.address, this.Deployer, + this.BuyoutModel.options.address, this.mtFeeRateModel.options.address, this.CollatteralVault.options.address, this.Fragment.options.address, diff --git a/truffle-config.js b/truffle-config.js index 60c4bdf..3670a8c 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -158,15 +158,13 @@ module.exports = { skipDryRun: true }, - arb: { - provider: function () { - return wrapProvider( - new HDWalletProvider(privKey, "https://arb1.arbitrum.io/rpc") - ) + omgTest: { + networkCheckTimeout: 100000, + provider: () => { + return new HDWalletProvider(privKey, 'https://rinkeby.omgx.network') }, - network_id: 42161, - gas: 200000000, - gasPrice: 400000000, + network_id: 28, + gasPrice: 0, }, arbtest: { diff --git a/truffle-test.sh b/truffle-test.sh index fbcdc45..d17e2ec 100644 --- a/truffle-test.sh +++ b/truffle-test.sh @@ -1,5 +1,5 @@ #!/bin/bash -truffle compile --all +# truffle compile --all if [ "$1"x = "proxy-dpp"x ] then