diff --git a/cn b/cn new file mode 160000 index 0000000..dde6d47 --- /dev/null +++ b/cn @@ -0,0 +1 @@ +Subproject commit dde6d47d3b4adb3e187957074596b809f43a462e diff --git a/contracts/CollateralVault/intf/ICollateralVault.sol b/contracts/CollateralVault/intf/ICollateralVault.sol new file mode 100644 index 0000000..582673b --- /dev/null +++ b/contracts/CollateralVault/intf/ICollateralVault.sol @@ -0,0 +1,15 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +interface ICollateralVault { + function _OWNER_() external returns (address); + + function transferOwner(address to) external; +} diff --git a/contracts/DODOFee/FeeDistributer.sol b/contracts/DODOFee/FeeDistributer.sol new file mode 100644 index 0000000..ba94ce0 --- /dev/null +++ b/contracts/DODOFee/FeeDistributer.sol @@ -0,0 +1,102 @@ +/* + + Copyright 2020 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 FeeDistributor is InitializableOwnable { + + address public _BASE_TOKEN_; + address public _QUOTE_TOKEN_; + uint256 public _BASE_RESERVE_; + uint256 public _QUOTE_RESERVE_; + uint256 public _BASE_REWARD_RATIO_; + uint256 public _QUOTE_REWARD_RATIO_; + + address public _STAKE_VAULT_; + address public _STAKE_TOKEN_; + uint256 public _STAKE_RESERVE_; + mapping(address => uint256) internal _BASE_DEBT_; + mapping(address => uint256) internal _QUOTE_DEBT_; + + function init() external { + _BASE_TOKEN_ = baseToken; + _QUOTE_TOKEN_ = quoteToken; + _STAKE_TOKEN_ = stakeToken; + _BASE_REWARD_RATIO_ = DecimalMath.ONE; + _QUOTE_REWARD_RATIO_ = DecimalMath.ONE; + _STAKE_VAULT_ = new DistributorStakeVault(); + } + + function stake(address to) external { + _accuReward(); + uint256 stakeInput = IERC20(_STAKE_TOKEN_).balanceOf(_STAKE_VAULT_).sub(_STAKE_RESERVE_); + _addShares(stakeInput, to); + } + + function claim(address to) external { + _accuReward(); + _claim(); + } + + function unstake(uint256 amount, address to, bool withClaim) external { + require(_SHARES_[msg.sender]>=amount, "STAKE BALANCE ONT ENOUGH"); + _accuReward(); + + if (withClaim) { + _claim(msg.sender, to); + } + + _removeShares(amount, msg.sender); + DistributorStakeVault(_STAKE_VAULT_).transferOut(_STAKE_TOKEN_, amount, to); + } + + function _claim(address sender, address to) internal { + uint256 allBase = DecimalMath.mulFloor(_SHARES_[sender], _BASE_REWARD_RATIO_); + uint256 allQuote = DecimalMath.mulFloor(_SHARES_[sender], _QUOTE_REWARD_RATIO_); + IERC20(_BASE_TOKEN_).safeTransfer(allBase.sub(_BASE_DEBT_[sender]), to); + IERC20(_QUOTE_TOKEN_).safeTransfer(allQuote.sub(_QUOTE_DEBT_[sender]), to); + _BASE_DEBT_[sender] = allBase; + _QUOTE_DEBT_[sender] = allQuote; + } + + function _addShares(uint256 amount, address to) internal { + _SHARES_[to] = _SHARES_[to].add(amount) + _BASE_DEBT_[to] = _BASE_DEBT_[to].add(DecimalMath.mulCeil(amount, _BASE_REWARD_RATIO_)); + _QUOTE_DEBT_[to] = _QUOTE_DEBT_[to].add(DecimalMath.mulCeil(amount, _QUOTE_REWARD_RATIO_)); + _STAKE_RESERVE_ = IERC20(_STAKE_TOKEN_).balanceOf(_STAKE_VAULT_); + } + + function _removeShares(uint256 amount, address from) internal { + _SHARES_[from] = _SHARES_[from].sub(amount) + _BASE_DEBT_[from] = _BASE_DEBT_[from].sub(DecimalMath.mulFloor(amount, _BASE_REWARD_RATIO_)); + _QUOTE_DEBT_[from] = _QUOTE_DEBT_[from].sub(DecimalMath.mulFloor(amount, _QUOTE_REWARD_RATIO_)); + _STAKE_RESERVE_ = IERC20(_STAKE_TOKEN_).balanceOf(_STAKE_VAULT_); + } + + function _accuReward() internal { + uint256 baseInput = IERC20(_BASE_TOKEN_).balanceOf(address(this)).sub(_BASE_RESERVE_); + uint256 quoteInput = IERC20(_QUOTE_TOKEN_).balanceOf(address(this)).sub(_QUOTE_RESERVE_); + _BASE_REWARD_RATIO_ = _BASE_REWARD_RATIO_.add(DecimalMath.divFloor(baseInput, _STAKE_RESERVE_)); + _QUOTE_REWARD_RATIO_ = _QUOTE_REWARD_RATIO_.add(DecimalMath.divFloor(quoteInput, _STAKE_RESERVE_)); + _BASE_RESERVE_ = _BASE_RESERVE_.add(baseInput); + _QUOTE_RESERVE_ = _QUOTE_RESERVE_.add(quoteInput); + } + +} + +contract DistributorStakeVault is Ownable { + function transferOut( + address token, + uint256 amount, + address to + ) onlyOwner { + IERC20(token).SafeTransfer(amount, to); + } +} diff --git a/contracts/Factory/FragmentFactory.sol b/contracts/Factory/FragmentFactory.sol new file mode 100644 index 0000000..ad26344 --- /dev/null +++ b/contracts/Factory/FragmentFactory.sol @@ -0,0 +1,158 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; +import {ICloneFactory} from "../lib/CloneFactory.sol"; +import {IDVM} from "../DODOVendingMachine/intf/IDVM.sol"; + +interface IFragmentFactory { + function createFragment( + + ) external returns (address newVendingMachine); +} + + +contract FragmentFactory is InitializableOwnable { + // ============ Templates ============ + + address public immutable _CLONE_FACTORY_; + address public immutable _DEFAULT_MAINTAINER_; + address public immutable _DEFAULT_MT_FEE_RATE_MODEL_; + address public _DVM_TEMPLATE_; + address public _FEE_DISTRIBUTOR_TEMPLATE_ + + // ============ Registry ============ + + // base -> quote -> DVM address list + mapping(address => mapping(address => address[])) public _REGISTRY_; + // creator -> DVM address list + mapping(address => address[]) public _USER_REGISTRY_; + + // ============ Events ============ + + event NewDVM( + address baseToken, + address quoteToken, + address creator, + address dvm + ); + + event RemoveDVM(address dvm); + + // ============ Functions ============ + + constructor( + address cloneFactory, + address dvmTemplate, + address defaultMaintainer, + address defaultMtFeeRateModel + ) public { + _CLONE_FACTORY_ = cloneFactory; + _DVM_TEMPLATE_ = dvmTemplate; + _DEFAULT_MAINTAINER_ = defaultMaintainer; + _DEFAULT_MT_FEE_RATE_MODEL_ = defaultMtFeeRateModel; + } + + function createDODOVendingMachine( + address baseToken, + address quoteToken, + uint256 lpFeeRate, + uint256 i, + uint256 k, + bool isOpenTWAP + ) external returns (address newVendingMachine) { + newVendingMachine = ICloneFactory(_CLONE_FACTORY_).clone(_DVM_TEMPLATE_); + { + IDVM(newVendingMachine).init( + _DEFAULT_MAINTAINER_, + baseToken, + quoteToken, + lpFeeRate, + _DEFAULT_MT_FEE_RATE_MODEL_, + i, + k, + isOpenTWAP + ); + } + _REGISTRY_[baseToken][quoteToken].push(newVendingMachine); + _USER_REGISTRY_[tx.origin].push(newVendingMachine); + emit NewDVM(baseToken, quoteToken, tx.origin, newVendingMachine); + } + + // ============ Admin Operation Functions ============ + + function updateDvmTemplate(address _newDVMTemplate) external onlyOwner { + _DVM_TEMPLATE_ = _newDVMTemplate; + } + + function addPoolByAdmin( + address creator, + address baseToken, + address quoteToken, + address pool + ) external onlyOwner { + _REGISTRY_[baseToken][quoteToken].push(pool); + _USER_REGISTRY_[creator].push(pool); + emit NewDVM(baseToken, quoteToken, creator, pool); + } + + function removePoolByAdmin( + address creator, + address baseToken, + address quoteToken, + address pool + ) external onlyOwner { + address[] memory registryList = _REGISTRY_[baseToken][quoteToken]; + for (uint256 i = 0; i < registryList.length; i++) { + if (registryList[i] == pool) { + registryList[i] = registryList[registryList.length - 1]; + break; + } + } + _REGISTRY_[baseToken][quoteToken] = registryList; + _REGISTRY_[baseToken][quoteToken].pop(); + address[] memory userRegistryList = _USER_REGISTRY_[creator]; + for (uint256 i = 0; i < userRegistryList.length; i++) { + if (userRegistryList[i] == pool) { + userRegistryList[i] = userRegistryList[userRegistryList.length - 1]; + break; + } + } + _USER_REGISTRY_[creator] = userRegistryList; + _USER_REGISTRY_[creator].pop(); + emit RemoveDVM(pool); + } + + // ============ View Functions ============ + + function getDODOPool(address baseToken, address quoteToken) + external + view + returns (address[] memory machines) + { + return _REGISTRY_[baseToken][quoteToken]; + } + + function getDODOPoolBidirection(address token0, address token1) + external + view + returns (address[] memory baseToken0Machines, address[] memory baseToken1Machines) + { + return (_REGISTRY_[token0][token1], _REGISTRY_[token1][token0]); + } + + function getDODOPoolByUser(address user) + external + view + returns (address[] memory machines) + { + return _USER_REGISTRY_[user]; + } +} diff --git a/contracts/GeneralizedFragment/Fragment.sol b/contracts/GeneralizedFragment/Fragment.sol new file mode 100644 index 0000000..0c60c15 --- /dev/null +++ b/contracts/GeneralizedFragment/Fragment.sol @@ -0,0 +1,96 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +import {SafeMath} from "../lib/SafeMath.sol"; +import {SafeERC20} from "../lib/SafeERC20.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {InitializableOwnable} from "../lib/InitializableOwnable.sol"; +import {IDVM} from "../DODOVendingMachine/intf/IDVM.sol"; +import {ICloneFactory} from "../lib/CloneFactory.sol"; +import {IERC20} from "../intf/IERC20.sol"; + +contract Fragment is InitializableMintableERC20 { + using SafeMath for uint256; + using SafeTransfer for IERC20; + + uint256 _BUYOUT_TIMESTAMP_; + + address _COLLATERAL_VAULT_; + address _QUOTE_; + address _DVM_; + bool _IS_BUYOUT_; + uint256 _BUYOUT_PRICE_; + + function init( + address owner, + address dvm, + address collateralVault, + uint256 supply, + uint256 ownerRatio + ) external { + // init local variables + initOwner(owner); + _DVM_ = dvm; + _COLLATERAL_VAULT_ = collateralVault; + _QUOTE_ = IDVM(DVM)._QUOTE_TOKEN_(); + + // init FRAM meta data + string memory suffix = "FRAG_"; + name = string(abi.encodePacked(suffix, IDVM(_DVM_).addressToShortString(_COLLATERAL_VAULT_)); + symbol = "FRAG"; + decimals = 18; + + // init FRAG distribution + totalSupply = supply; + balances[owner] = DecimalMath.mulFloor(supply, ownerRatio); + balances[dvm] = supply.sub( balances[owner]); + emit Transfer(address(0), owner, balances[owner]); + emit Transfer(address(0), dvm, balances[dvm]); + + // init DVM liquidity + IDVM(DVM).buyShares(address(this)); + } + + function buyout() external { + _IS_BUYOUT_ = true; + _BUYOUT_PRICE_ = IDVM(_DVM_).getMidPrice(); + uint256 requireQuote = DecimalMath.mulCeil(_BUYOUT_PRICE_, totalSupply); + require(IERC20(_QUOTE_).balanceOf(address(this))>=requireQuote, "QUOTE NOT ENOUGH"); + + IDVM(_DVM_).sellShares( + IERC20(_DVM_).balanceOf(address(this)), + address(this), + 0, + 0, + "", + uint256(-1) + ); + + uint256 ownerQuote = DecimalMath.mulFloor(_BUYOUT_PRICE_, balances[address(this)]) + _clearSelfBalance(); + + IERC20(_QUOTE_).safeTransfer(ownerQuote, _OWNER_); + } + + // buyout之后的恒定兑换 + function redeem(address to) external { + IERC20(_QUOTE_).safeTransfer(DecimalMath.mulFloor(_BUYOUT_PRICE_, balances[address(this)]), to); + _clearSelfBalance(); + } + + function getBuyoutRequire() view return (uint256 requireQuote){ + uint256 price = IDVM(_DVM_).getMidPrice(); + requireQuote = DecimalMath.mulCeil(price, totalSupply); + } + + function _clearSelfBalance() internal { + emit Transfer(address(this), address(0), balances[address(this)]); + balances[address(this)] = 0; + } +}