diff --git a/contracts/CrowdPooling/impl/CP.sol b/contracts/CrowdPooling/impl/CP.sol index c85120e..65ed870 100644 --- a/contracts/CrowdPooling/impl/CP.sol +++ b/contracts/CrowdPooling/impl/CP.sol @@ -82,15 +82,17 @@ contract CP is CPVesting { 2. i 3. lp cliff rate 4. base token cliff rate + 5. lp fee rate */ - require(valueList.length == 5, "LIST_LENGTH_WRONG"); + require(valueList.length == 6, "LIST_LENGTH_WRONG"); _POOL_QUOTE_CAP_ = valueList[0]; _K_ = valueList[1]; _I_ = valueList[2]; _CLIFF_RATE_ = valueList[3]; _TOKEN_CLIFF_RATE_ = valueList[4]; + _POOL_FEE_RATE_ = valueList[5]; require(_I_ > 0 && _I_ <= 1e36, "I_VALUE_WRONG"); require(_K_ <= 1e18, "K_VALUE_WRONG"); diff --git a/contracts/CrowdPooling/impl/CPFunding.sol b/contracts/CrowdPooling/impl/CPFunding.sol index df232d3..e5596eb 100644 --- a/contracts/CrowdPooling/impl/CPFunding.sol +++ b/contracts/CrowdPooling/impl/CPFunding.sol @@ -37,7 +37,7 @@ contract CPFunding is CPStorage { _; } - function bid(address to) external phaseBid preventReentrant isBidderAllow(to) { + function bid(address to) external isForceStop phaseBid preventReentrant isBidderAllow(to) { uint256 input = _getQuoteInput(); uint256 mtFee = DecimalMath.mulFloor(input, _MT_FEE_RATE_MODEL_.getFeeRate(to)); _transferQuoteOut(_MAINTAINER_, mtFee); @@ -71,7 +71,7 @@ contract CPFunding is CPStorage { // ============ SETTLEMENT ============ - function settle() external phaseSettlement preventReentrant { + function settle() external isForceStop phaseSettlement preventReentrant { _settle(); (uint256 poolBase, uint256 poolQuote, uint256 poolI, uint256 unUsedBase, uint256 unUsedQuote) = getSettleResult(); @@ -92,7 +92,7 @@ contract CPFunding is CPStorage { _POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( _poolBaseToken, _poolQuoteToken, - 3e15, // 0.3% lp feeRate + _POOL_FEE_RATE_, poolI, DecimalMath.ONE, _IS_OPEN_TWAP_ @@ -112,7 +112,7 @@ contract CPFunding is CPStorage { } // in case something wrong with base token contract - function emergencySettle() external phaseSettlement preventReentrant { + function emergencySettle() external isForceStop phaseSettlement preventReentrant { require(block.timestamp >= _PHASE_CALM_ENDTIME_.add(_SETTLEMENT_EXPIRE_), "NOT_EMERGENCY"); _settle(); _UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)); diff --git a/contracts/CrowdPooling/impl/CPStorage.sol b/contracts/CrowdPooling/impl/CPStorage.sol index f1131fb..ff646e1 100644 --- a/contracts/CrowdPooling/impl/CPStorage.sol +++ b/contracts/CrowdPooling/impl/CPStorage.sol @@ -25,6 +25,8 @@ contract CPStorage is InitializableOwnable, ReentrancyGuard { bool public _IS_OPEN_TWAP_ = false; bool public _IS_OVERCAP_STOP = false; + bool public _FORCE_STOP_ = false; + // ============ Timeline ============ uint256 public _PHASE_BID_STARTTIME_; @@ -56,6 +58,7 @@ contract CPStorage is InitializableOwnable, ReentrancyGuard { address public _POOL_FACTORY_; address public _POOL_; + uint256 public _POOL_FEE_RATE_; uint256 public _AVG_SETTLED_PRICE_; // ============ Advanced Control ============ @@ -82,6 +85,10 @@ contract CPStorage is InitializableOwnable, ReentrancyGuard { mapping(address => uint256) _CLAIMED_BASE_TOKEN_; // ============ Modifiers ============ + modifier isForceStop() { + require(!_FORCE_STOP_, "FORCE_STOP"); + _; + } modifier phaseBid() { require( @@ -116,4 +123,12 @@ contract CPStorage is InitializableOwnable, ReentrancyGuard { require(_SETTLED_, "NOT_VESTING"); _; } + + function forceStop() external onlyOwner { + require(block.timestamp < _PHASE_BID_STARTTIME_, "CP_ALREADY_STARTED"); + _FORCE_STOP_ = true; + _TOTAL_BASE_ = 0; + uint256 baseAmount = _BASE_TOKEN_.balanceOf(address(this)); + _BASE_TOKEN_.transfer(_OWNER_, baseAmount); + } } diff --git a/contracts/DODOPrivatePool/impl/DPPOracle/DPPStorage.sol b/contracts/DODOPrivatePool/impl/DPPOracle/DPPStorage.sol index 03ae490..3396579 100644 --- a/contracts/DODOPrivatePool/impl/DPPOracle/DPPStorage.sol +++ b/contracts/DODOPrivatePool/impl/DPPOracle/DPPStorage.sol @@ -50,7 +50,7 @@ contract DPPStorage is InitializableOwnable, ReentrancyGuard { // ============ Helper Functions ============ function getPMMState() public view returns (PMMPricing.PMMState memory state) { - (state.i,,,) = IOracle(_I_).getPrice(address(_BASE_TOKEN_)); + state.i = IOracle(_I_).prices(address(_BASE_TOKEN_)); state.K = _K_; state.B = _BASE_RESERVE_; state.Q = _QUOTE_RESERVE_; diff --git a/contracts/DODOPrivatePool/impl/DPPOracle/DPPTrader.sol b/contracts/DODOPrivatePool/impl/DPPOracle/DPPTrader.sol index 6f224a4..e379a94 100644 --- a/contracts/DODOPrivatePool/impl/DPPOracle/DPPTrader.sol +++ b/contracts/DODOPrivatePool/impl/DPPOracle/DPPTrader.sol @@ -39,9 +39,8 @@ contract DPPTrader is DPPVault { event RChange(PMMPricing.RState newRState); modifier isOraclePriceValid() { - (, bool isValid, bool isStale,) = IOracle(_I_).getPrice(address(_BASE_TOKEN_)); - require(isValid, "ORACLE_PRICE_INVALID"); - require(!isStale, "ORACLE_PRICE_STALE"); + bool isFeasible = IOracle(_I_).isFeasible(address(_BASE_TOKEN_)); + require(isFeasible, "ORACLE_PRICE_INVALID"); _; } diff --git a/contracts/DODOPrivatePool/intf/IOracle.sol b/contracts/DODOPrivatePool/intf/IOracle.sol index 10271d2..c2d6dbe 100644 --- a/contracts/DODOPrivatePool/intf/IOracle.sol +++ b/contracts/DODOPrivatePool/intf/IOracle.sol @@ -9,4 +9,8 @@ pragma solidity 0.6.9; interface IOracle { function getPrice(address base) external view returns (uint256 latestPrice,bool isValid,bool isStale,uint256 timestamp); + + function prices(address base) external view returns (uint256); + + function isFeasible(address base) external view returns (bool); } diff --git a/contracts/DODOStarter/impl/FairFunding.sol b/contracts/DODOStarter/impl/FairFunding.sol index 248cde7..377c40b 100644 --- a/contracts/DODOStarter/impl/FairFunding.sol +++ b/contracts/DODOStarter/impl/FairFunding.sol @@ -8,43 +8,112 @@ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; -import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; -import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; import {IQuota} from "../../DODOFee/UserQuota.sol"; import {SafeMath} from "../../lib/SafeMath.sol"; import {DecimalMath} from "../../lib/DecimalMath.sol"; import {IERC20} from "../../intf/IERC20.sol"; import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {Vesting} from "./Vesting.sol"; -contract FairFunding is InitializableOwnable, ReentrancyGuard { +contract FairFunding is Vesting { using SafeMath for uint256; using SafeERC20 for IERC20; - // ============ Token & Balance ============ - - uint256 public _FUNDS_RESERVE_; - address public _FUNDS_ADDRESS_; - address public _TOKEN_ADDRESS_; - uint256 public _TOTAL_TOKEN_AMOUNT_; - // ============ Fair Mode ============ - - uint256 public _START_TIME_; - uint256 public _BIDDING_DURATION_; uint256 public _COOLING_DURATION_; - address _QUOTA_; mapping(address => uint256) _FUNDS_DEPOSITED_; mapping(address => bool) _FUNDS_CLAIMED_; - uint256 public _TOTAL_RAISED_FUNDS_; uint256 public _USED_FUND_RATIO_; uint256 public _FINAL_PRICE_; - // ============ Parameters ============ - uint256 public _LOWER_LIMIT_PRICE_; uint256 public _UPPER_LIMIT_PRICE_; + // ============ Init ============ + function init( + address[] calldata addressList, + uint256[] calldata timeLine, + uint256[] calldata valueList + ) external { + /* + Address List + 0. owner + 1. sellToken + 2. fundToken + 3. quotaManager + 4. poolFactory + */ + + require(addressList.length == 5, "ADDR_LENGTH_WRONG"); + + initOwner(addressList[0]); + _TOKEN_ADDRESS_ = addressList[1]; + _FUNDS_ADDRESS_ = addressList[2]; + _QUOTA_ = addressList[3]; + _POOL_FACTORY_ = addressList[4]; + + /* + Time Line + 0. starttime + 1. bid duration + 2. calm duration + 3. token vesting starttime + 4. token vesting duration + 5. fund vesting starttime + 6. fund vesting duration + 7. lp vesting starttime + 8. lp vesting duration + */ + + require(timeLine.length == 9, "TIME_LENGTH_WRONG"); + + _START_TIME_ = timeLine[0]; + _BIDDING_DURATION_ = timeLine[1]; + _COOLING_DURATION_ = timeLine[2]; + + _TOKEN_VESTING_START_ = timeLine[3]; + _TOKEN_VESTING_DURATION_ = timeLine[4]; + + _FUNDS_VESTING_START_ = timeLine[5]; + _FUNDS_VESTING_DURATION_ = timeLine[6]; + + _LP_VESTING_START_ = timeLine[7]; + _LP_VESTING_DURATION_ = timeLine[8]; + + require(block.timestamp <= _START_TIME_, "START_TIME_WRONG"); + require(_START_TIME_.add(_BIDDING_DURATION_).add(_COOLING_DURATION_) <= _TOKEN_VESTING_START_, "TOKEN_VESTING_TIME_WRONG"); + require(_START_TIME_.add(_BIDDING_DURATION_).add(_COOLING_DURATION_) <= _FUNDS_VESTING_START_, "FUND_VESTING_TIME_WRONG"); + + /* + Value List + 0. lower price + 1. upper price + 2. token cliffRate + 3. fund cliffRate + 4. lp cliffRate + 5. initial liquidity + */ + + require(valueList.length == 6, "VALUE_LENGTH_WRONG"); + + _LOWER_LIMIT_PRICE_ = valueList[0]; + _UPPER_LIMIT_PRICE_ = valueList[1]; + + _TOKEN_CLIFF_RATE_ = valueList[2]; + _FUNDS_CLIFF_RATE_ = valueList[3]; + _LP_CLIFF_RATE_ = valueList[4]; + + _INITIAL_FUND_LIQUIDITY_ = valueList[5]; + + require(_LOWER_LIMIT_PRICE_ <= _UPPER_LIMIT_PRICE_, "PRICE_WRONG"); + require(_TOKEN_CLIFF_RATE_ <= 1e18, "TOKEN_CLIFF_RATE_WRONG"); + require(_FUNDS_CLIFF_RATE_ <= 1e18, "FUND_CLIFF_RATE_WRONG"); + require(_LP_CLIFF_RATE_ <= 1e18, "LP_CLIFF_RATE_WRONG"); + + _TOTAL_TOKEN_AMOUNT_ = IERC20(_TOKEN_ADDRESS_).balanceOf(address(this)); + } + // ============ View Functions ============ function getCurrentPrice() public view returns (uint256) { @@ -84,7 +153,7 @@ contract FairFunding is InitializableOwnable, ReentrancyGuard { // ============ Settle Functions ============ - function settle() public { + function settle() public isForceStop { require(_FINAL_PRICE_ == 0 && isFundingEnd(), "CAN_NOT_SETTLE"); _FINAL_PRICE_ = getCurrentPrice(); _USED_FUND_RATIO_ = DecimalMath.divFloor( @@ -98,13 +167,7 @@ contract FairFunding is InitializableOwnable, ReentrancyGuard { // ============ Funding Functions ============ - function depositToken(uint256 amount) external preventReentrant onlyOwner { - require(block.timestamp < _START_TIME_, "FUNDING_ALREADY_STARTED"); - IERC20(_TOKEN_ADDRESS_).safeTransferFrom(msg.sender, address(this), amount); - _TOTAL_TOKEN_AMOUNT_ = _TOTAL_TOKEN_AMOUNT_.add(amount); - } - - function depositFunds(address to) external preventReentrant { + function depositFunds(address to) external preventReentrant isForceStop { require(isDepositOpen(), "DEPOSIT_NOT_OPEN"); // input fund check uint256 inputFund = IERC20(_FUNDS_ADDRESS_).balanceOf(address(this)).sub(_FUNDS_RESERVE_); @@ -141,6 +204,11 @@ contract FairFunding is InitializableOwnable, ReentrancyGuard { _TOTAL_TOKEN_AMOUNT_ = allocatedToken; } + function claimToken(address to) external { + uint256 totalAllocation = getUserTokenAllocation(msg.sender); + _claimToken(to, totalAllocation); + } + // ============ Timeline Control Functions ============ function isDepositOpen() public view returns (bool) { diff --git a/contracts/DODOStarter/impl/InstantFunding.sol b/contracts/DODOStarter/impl/InstantFunding.sol index 6d57954..29cce4b 100644 --- a/contracts/DODOStarter/impl/InstantFunding.sol +++ b/contracts/DODOStarter/impl/InstantFunding.sol @@ -8,49 +8,118 @@ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; -import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; -import {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; import {IQuota} from "../../DODOFee/UserQuota.sol"; import {SafeMath} from "../../lib/SafeMath.sol"; import {DecimalMath} from "../../lib/DecimalMath.sol"; import {IERC20} from "../../intf/IERC20.sol"; import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {Vesting} from "./Vesting.sol"; -contract InstantFunding is InitializableOwnable, ReentrancyGuard { +contract InstantFunding is Vesting { using SafeMath for uint256; using SafeERC20 for IERC20; - // ============ Token & Balance ============ - - uint256 public _FUNDS_RESERVE_; - address public _FUNDS_ADDRESS_; - address public _TOKEN_ADDRESS_; - uint256 public _TOTAL_TOKEN_AMOUNT_; - // ============ Instant Commit Mode ============ - - uint256 public _START_TIME_; - uint256 public _DURATION_; uint256 public _START_PRICE_; uint256 public _END_PRICE_; - address _QUOTA_; mapping(address => uint256) _FUNDS_USED_; mapping(address => uint256) _FUNDS_UNUSED_; mapping(address => uint256) _TOKEN_ALLOCATION_; uint256 public _TOTAL_ALLOCATED_TOKEN_; - uint256 public _TOTAL_RAISED_FUNDS_; + + + // ============ Init ============ + function init( + address[] calldata addressList, + uint256[] calldata timeLine, + uint256[] calldata valueList + ) external { + /* + Address List + 0. owner + 1. sellToken + 2. fundToken + 3. quotaManager + 4. poolFactory + */ + + require(addressList.length == 5, "ADDR_LENGTH_WRONG"); + + initOwner(addressList[0]); + _TOKEN_ADDRESS_ = addressList[1]; + _FUNDS_ADDRESS_ = addressList[2]; + _QUOTA_ = addressList[3]; + _POOL_FACTORY_ = addressList[4]; + + /* + Time Line + 0. starttime + 1. bid duration + 2. token vesting starttime + 3. token vesting duration + 4. fund vesting starttime + 5. fund vesting duration + 6. lp vesting starttime + 7. lp vesting duration + */ + + require(timeLine.length == 8, "TIME_LENGTH_WRONG"); + + _START_TIME_ = timeLine[0]; + _BIDDING_DURATION_ = timeLine[1]; + + _TOKEN_VESTING_START_ = timeLine[2]; + _TOKEN_VESTING_DURATION_ = timeLine[3]; + + _FUNDS_VESTING_START_ = timeLine[4]; + _FUNDS_VESTING_DURATION_ = timeLine[5]; + + _LP_VESTING_START_ = timeLine[6]; + _LP_VESTING_DURATION_ = timeLine[7]; + + require(block.timestamp <= _START_TIME_, "START_TIME_WRONG"); + require(_START_TIME_.add(_BIDDING_DURATION_) <= _TOKEN_VESTING_START_, "TOKEN_VESTING_TIME_WRONG"); + require(_START_TIME_.add(_BIDDING_DURATION_) <= _FUNDS_VESTING_START_, "FUND_VESTING_TIME_WRONG"); + + /* + Value List + 0. start price + 1. end price + 2. token cliffRate + 3. fund cliffRate + 4. lp cliffRate + 5. initial liquidity + */ + + require(valueList.length == 6, "VALUE_LENGTH_WRONG"); + + _START_PRICE_ = valueList[0]; + _END_PRICE_ = valueList[1]; + + _TOKEN_CLIFF_RATE_ = valueList[2]; + _FUNDS_CLIFF_RATE_ = valueList[3]; + _LP_CLIFF_RATE_ = valueList[4]; + + _INITIAL_FUND_LIQUIDITY_ = valueList[5]; + + require(_TOKEN_CLIFF_RATE_ <= 1e18, "TOKEN_CLIFF_RATE_WRONG"); + require(_FUNDS_CLIFF_RATE_ <= 1e18, "FUND_CLIFF_RATE_WRONG"); + require(_LP_CLIFF_RATE_ <= 1e18, "LP_CLIFF_RATE_WRONG"); + + _TOTAL_TOKEN_AMOUNT_ = IERC20(_TOKEN_ADDRESS_).balanceOf(address(this)); + } // ============ View Functions ============ function getCurrentPrice() public view returns (uint256 price) { if (block.timestamp <= _START_TIME_) { price = _START_PRICE_; - } else if (block.timestamp >= _START_TIME_.add(_DURATION_)) { + } else if (block.timestamp >= _START_TIME_.add(_BIDDING_DURATION_)) { price = _END_PRICE_; } else { uint256 timePast = block.timestamp.sub(_START_TIME_); - price = _START_PRICE_.mul(_DURATION_.sub(timePast)).div(_DURATION_).add( - _END_PRICE_.mul(timePast).div(_DURATION_) + price = _START_PRICE_.mul(_BIDDING_DURATION_.sub(timePast)).div(_BIDDING_DURATION_).add( + _END_PRICE_.mul(timePast).div(_BIDDING_DURATION_) ); } } @@ -68,16 +137,11 @@ contract InstantFunding is InitializableOwnable, ReentrancyGuard { } // ============ Funding Functions ============ - //TODO:强制转入,适配通缩代币 - function depositToken(uint256 amount) external preventReentrant onlyOwner { - require(block.timestamp < _START_TIME_, "FUNDING_ALREADY_STARTED"); - IERC20(_TOKEN_ADDRESS_).safeTransferFrom(msg.sender, address(this), amount); - _TOTAL_TOKEN_AMOUNT_ = _TOTAL_TOKEN_AMOUNT_.add(amount); - } function depositFunds(address to) external preventReentrant + isForceStop returns (uint256 newTokenAllocation) { require(isDepositOpen(), "DEPOSIT_NOT_OPEN"); @@ -124,15 +188,20 @@ contract InstantFunding is InitializableOwnable, ReentrancyGuard { _TOTAL_TOKEN_AMOUNT_ = _TOTAL_ALLOCATED_TOKEN_; } + function claimToken(address to) external { + uint256 totalAllocation = getUserTokenAllocation(msg.sender); + _claimToken(to, totalAllocation); + } + // ============ Timeline Control Functions ============ function isDepositOpen() public view returns (bool) { return block.timestamp >= _START_TIME_ && - block.timestamp < _START_TIME_.add(_DURATION_); + block.timestamp < _START_TIME_.add(_BIDDING_DURATION_); } function isFundingEnd() public view returns (bool) { - return block.timestamp > _START_TIME_.add(_DURATION_); + return block.timestamp > _START_TIME_.add(_BIDDING_DURATION_); } } diff --git a/contracts/DODOStarter/impl/Storage.sol b/contracts/DODOStarter/impl/Storage.sol new file mode 100644 index 0000000..4e7c192 --- /dev/null +++ b/contracts/DODOStarter/impl/Storage.sol @@ -0,0 +1,73 @@ +/* + + 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 {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {IERC20} from "../../intf/IERC20.sol"; + +contract Storage is InitializableOwnable, ReentrancyGuard { + using SafeMath for uint256; + + bool public _FORCE_STOP_ = false; + address public _QUOTA_; + + // ============ Token & Balance ============ + + uint256 public _FUNDS_RESERVE_; + address public _FUNDS_ADDRESS_; + address public _TOKEN_ADDRESS_; + uint256 public _TOTAL_TOKEN_AMOUNT_; + + uint256 public _TOTAL_RAISED_FUNDS_; + + // ============ Vesting Timeline ============ + + uint256 public _TOKEN_VESTING_START_; + uint256 public _TOKEN_VESTING_DURATION_; + uint256 public _TOKEN_CLIFF_RATE_; + mapping(address => uint256) _CLAIMED_TOKEN_; + + uint256 public _FUNDS_VESTING_START_; + uint256 public _FUNDS_VESTING_DURATION_; + uint256 public _FUNDS_CLIFF_RATE_; + uint256 _CLAIMED_FUNDS_; + + uint256 public _LP_VESTING_START_; + uint256 public _LP_VESTING_DURATION_; + uint256 public _LP_CLIFF_RATE_; + uint256 _CLAIMED_LP_; + + // ============ Liquidity Params ============ + + address public _POOL_FACTORY_; + address public _INITIAL_POOL_; + uint256 public _INITIAL_FUND_LIQUIDITY_; + uint256 public _TOTAL_LP_; + + // ============ Timeline ============== + uint256 public _START_TIME_; + uint256 public _BIDDING_DURATION_; + + + // ============ Modifiers ============ + modifier isForceStop() { + require(!_FORCE_STOP_, "FORCE_STOP"); + _; + } + + function forceStop() external onlyOwner { + require(block.timestamp < _START_TIME_, "FUNDING_ALREADY_STARTED"); + _FORCE_STOP_ = true; + _TOTAL_TOKEN_AMOUNT_ = 0; + uint256 tokenAmount = IERC20(_TOKEN_ADDRESS_).balanceOf(address(this)); + IERC20(_TOKEN_ADDRESS_).transfer(_OWNER_, tokenAmount); + } +} \ No newline at end of file diff --git a/contracts/DODOStarter/impl/Vesting.sol b/contracts/DODOStarter/impl/Vesting.sol index 2a6846a..6ea4406 100644 --- a/contracts/DODOStarter/impl/Vesting.sol +++ b/contracts/DODOStarter/impl/Vesting.sol @@ -8,7 +8,7 @@ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; -import {InstantFunding} from "./InstantFunding.sol"; +import {Storage} from "./Storage.sol"; import {IDVM} from "../../DODOVendingMachine/intf/IDVM.sol"; import {IDVMFactory} from "../../Factory/DVMFactory.sol"; import {SafeMath} from "../../lib/SafeMath.sol"; @@ -16,69 +16,82 @@ import {DecimalMath} from "../../lib/DecimalMath.sol"; import {IERC20} from "../../intf/IERC20.sol"; import {SafeERC20} from "../../lib/SafeERC20.sol"; -contract Vesting is InstantFunding { +contract Vesting is Storage { using SafeMath for uint256; using SafeERC20 for IERC20; - // ============ Timeline ============ - uint256 public _TOKEN_VESTING_START_; - uint256 public _TOKEN_VESTING_DURATION_; - mapping(address => uint256) _CLAIMED_TOKEN_; - - uint256 public _FUNDS_VESTING_START_; - uint256 public _FUNDS_VESTING_DURATION_; - uint256 _CLAIMED_FUNDS_; - - uint256 public _LP_VESTING_START_; - uint256 public _LP_VESTING_DURATION_; - uint256 _CLAIMED_LP_; - - // ============ Liquidity Params ============ - - address public _POOL_FACTORY_; - address public _INITIAL_POOL_; - uint256 public _INITIAL_FUND_LIQUIDITY_; - uint256 public _TOTAL_LP_; - - function claimToken(address to) external { - uint256 totalAllocation = getUserTokenAllocation(msg.sender); - uint256 unlockedAllocation = totalAllocation - .mul(block.timestamp.sub(_TOKEN_VESTING_START_)) - .div(_TOKEN_VESTING_DURATION_); - IERC20(_TOKEN_ADDRESS_).safeTransfer( - to, - unlockedAllocation.sub(_CLAIMED_TOKEN_[msg.sender]) + function _claimToken(address to, uint256 totalAllocation) internal { + uint256 remainingToken = DecimalMath.mulFloor( + getRemainingRatio(block.timestamp,0), + totalAllocation ); - _CLAIMED_TOKEN_[msg.sender] = unlockedAllocation; + uint256 claimableTokenAmount = totalAllocation.sub(remainingToken).sub(_CLAIMED_TOKEN_[msg.sender]); + IERC20(_TOKEN_ADDRESS_).safeTransfer(to,claimableTokenAmount); + _CLAIMED_TOKEN_[msg.sender] = _CLAIMED_TOKEN_[msg.sender].add(claimableTokenAmount); } function claimFunds(address to) external preventReentrant onlyOwner { uint256 vestingFunds = _TOTAL_RAISED_FUNDS_.sub(_INITIAL_FUND_LIQUIDITY_); - uint256 unlockedFunds = vestingFunds.mul(block.timestamp.sub(_FUNDS_VESTING_START_)).div( - _FUNDS_VESTING_DURATION_ + uint256 remainingFund = DecimalMath.mulFloor( + getRemainingRatio(block.timestamp,1), + vestingFunds ); - IERC20(_TOKEN_ADDRESS_).safeTransfer(to, unlockedFunds.sub(_CLAIMED_FUNDS_)); - _CLAIMED_FUNDS_ = unlockedFunds; + uint256 claimableFund = vestingFunds.sub(remainingFund).sub(_CLAIMED_FUNDS_); + IERC20(_FUNDS_ADDRESS_).safeTransfer(to, claimableFund); + _CLAIMED_FUNDS_ = _CLAIMED_FUNDS_.add(claimableFund); } function claimLp(address to) external preventReentrant onlyOwner { require(_INITIAL_POOL_ != address(0), "LIQUIDITY_NOT_ESTABLISHED"); - uint256 unlockedLp = _TOTAL_LP_.mul(block.timestamp.sub(_LP_VESTING_START_)).div( - _LP_VESTING_DURATION_ + uint256 remainingLp = DecimalMath.mulFloor( + getRemainingRatio(block.timestamp,2), + _TOTAL_LP_ ); - IERC20(_TOKEN_ADDRESS_).safeTransfer(to, unlockedLp.sub(_CLAIMED_LP_)); - _CLAIMED_LP_ = unlockedLp; + uint256 claimableLp = _TOTAL_LP_.sub(remainingLp).sub(_CLAIMED_LP_); + + IERC20(_INITIAL_POOL_).safeTransfer(to, claimableLp); + _CLAIMED_LP_ = _CLAIMED_LP_.add(claimableLp); } - function initializeLiquidity(uint256 initialTokenAmount) external preventReentrant onlyOwner { + + //tokenType 0: BaseToken, 1: Fund, 2: LpToken + function getRemainingRatio(uint256 timestamp, uint256 tokenType) public view returns (uint256) { + uint256 vestingStart; + uint256 vestingDuration; + uint256 cliffRate; + + if(tokenType == 0) { + vestingStart = _TOKEN_VESTING_START_; + vestingDuration = _TOKEN_VESTING_DURATION_; + cliffRate = _TOKEN_CLIFF_RATE_; + } else if(tokenType == 1) { + vestingStart = _FUNDS_VESTING_START_; + vestingDuration = _FUNDS_VESTING_DURATION_; + cliffRate = _FUNDS_CLIFF_RATE_; + } else { + vestingStart = _LP_VESTING_START_; + vestingDuration = _LP_VESTING_DURATION_; + cliffRate = _LP_CLIFF_RATE_; + } + + uint256 timePast = timestamp.sub(vestingStart); + if (timePast < vestingDuration) { + uint256 remainingTime = vestingDuration.sub(timePast); + return DecimalMath.ONE.sub(cliffRate).mul(remainingTime).div(vestingDuration); + } else { + return 0; + } + } + + function initializeLiquidity(uint256 initialTokenAmount, uint256 lpFeeRate, bool isOpenTWAP) external preventReentrant onlyOwner { _INITIAL_POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( _TOKEN_ADDRESS_, _FUNDS_ADDRESS_, - 3e15, // 0.3% lp feeRate DIP3 + lpFeeRate, 1, DecimalMath.ONE, - true //TODO:是否开启 + isOpenTWAP ); IERC20(_TOKEN_ADDRESS_).transferFrom(msg.sender, _INITIAL_POOL_, initialTokenAmount); IERC20(_FUNDS_ADDRESS_).transfer(_INITIAL_POOL_, _INITIAL_FUND_LIQUIDITY_); diff --git a/contracts/DODOStarter/intf/IDODOStarter.sol b/contracts/DODOStarter/intf/IDODOStarter.sol new file mode 100644 index 0000000..58aa3b3 --- /dev/null +++ b/contracts/DODOStarter/intf/IDODOStarter.sol @@ -0,0 +1,17 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +interface IDODOStarter { + function init( + address[] calldata addressList, + uint256[] calldata timeLine, + uint256[] calldata valueList + ) external; +} diff --git a/contracts/Factory/CrowdPoolingFactory.sol b/contracts/Factory/CrowdPoolingFactory.sol index bb2c3fb..6850bc7 100644 --- a/contracts/Factory/CrowdPoolingFactory.sol +++ b/contracts/Factory/CrowdPoolingFactory.sol @@ -41,8 +41,6 @@ contract CrowdPoolingFactory is InitializableOwnable { uint256 public _K_ = 0; uint256 public _CLIFF_RATE_ = 10**18; - mapping(address => address) liquidityProtectWhitelist; - // ============ Registry ============ // base -> quote -> CP address list @@ -66,9 +64,6 @@ contract CrowdPoolingFactory is InitializableOwnable { uint256 baseTokenBalance = IERC20(baseToken).balanceOf(cpAddress); require(valueList[0].mul(100) <= baseTokenBalance.mul(valueList[2]).div(10**18).mul(_CAP_RATIO_),"CP_FACTORY : QUOTE_CAP_INVALID"); - if(liquidityProtectWhitelist[creator] != baseToken) { - require(timeLine[3]>= _FREEZE_DURATION_, "CP_FACTORY : FREEZE_DURATION_INVALID"); - } _; } @@ -81,6 +76,8 @@ contract CrowdPoolingFactory is InitializableOwnable { address cp ); + event RemoveCP(address cp); + constructor( address cloneFactory, address cpTemplate, @@ -162,10 +159,6 @@ contract CrowdPoolingFactory is InitializableOwnable { } // ============ Owner Functions ============ - - function setLiquidityProtectWhitelist(address creator, address baseToken) external onlyOwner { - liquidityProtectWhitelist[creator] = baseToken; - } function updateCPTemplate(address _newCPTemplate) external onlyOwner { _CP_TEMPLATE_ = _newCPTemplate; @@ -201,4 +194,31 @@ contract CrowdPoolingFactory is InitializableOwnable { require(_newCliffRate <= 10**18, "CP_FACTORY : INVALID"); _CLIFF_RATE_ = _newCliffRate; } + + 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 RemoveCP(pool); + } } diff --git a/contracts/Factory/DODOStarterFactory.sol b/contracts/Factory/DODOStarterFactory.sol new file mode 100644 index 0000000..ff01665 --- /dev/null +++ b/contracts/Factory/DODOStarterFactory.sol @@ -0,0 +1,164 @@ +/* + + 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 {ICloneFactory} from "../lib/CloneFactory.sol"; +import {SafeMath} from "../lib/SafeMath.sol"; +import {IERC20} from "../intf/IERC20.sol"; +import {DecimalMath} from "../lib/DecimalMath.sol"; +import {IDODOStarter} from "../DODOStarter/intf/IDODOStarter.sol"; + +/** + * @title DODOStarterFactory + * @author DODO Breeder + * + * @notice Create And Register DODOStarter Pools + */ +contract DODOStarterFactory is InitializableOwnable { + using SafeMath for uint256; + // ============ Templates ============ + + address public immutable _CLONE_FACTORY_; + address public _FAIR_FUND_TEMPLATE_; + address public _INSTANT_FUND_TEMPLATE_; + + mapping(address => address) fundingWhitelist; + + // ============ Registry ============ + // baseToken -> fundToken -> fair Pool list + mapping(address => mapping(address => address[])) public _FAIR_REGISTRY_; + // baseToken -> fundToken -> Instant Pool list + mapping(address => mapping(address => address[])) public _INSTANT_REGISTRY_; + + // ============ Events ============ + event NewFairFund( + address baseToken, + address fundToken, + address creator, + address fairFundPool + ); + + event NewInstantFund( + address baseToken, + address fundToken, + address creator, + address instantFundPool + ); + + // ============ modifiers =========== + + modifier permissionCheck(address creator, address baseToken) { + require(fundingWhitelist[creator] == baseToken || msg.sender == _OWNER_, "NO_PERMISSION"); + _; + } + + constructor( + address cloneFactory, + address fairFundTemplate, + address instantFundTemplate + ) public { + _CLONE_FACTORY_ = cloneFactory; + _FAIR_FUND_TEMPLATE_ = fairFundTemplate; + _INSTANT_FUND_TEMPLATE_ = instantFundTemplate; + } + + // ============ Functions ============ + function createFairFund( + address[] memory addressList, + uint256[] memory timeLine, + uint256[] memory valueList, + uint256 sellTokenAmount + ) external permissionCheck(addressList[0],addressList[1]) returns(address newFairFundPool){ + newFairFundPool = ICloneFactory(_CLONE_FACTORY_).clone(_FAIR_FUND_TEMPLATE_); + + IERC20(addressList[1]).transferFrom(msg.sender, newFairFundPool,sellTokenAmount); + + IDODOStarter(newFairFundPool).init( + addressList, + timeLine, + valueList + ); + + _FAIR_REGISTRY_[addressList[1]][addressList[2]].push(newFairFundPool); + + emit NewFairFund(addressList[1], addressList[2], addressList[0], newFairFundPool); + } + + function createInstantFund( + address[] memory addressList, + uint256[] memory timeLine, + uint256[] memory valueList, + uint256 sellTokenAmount + ) external permissionCheck(addressList[0],addressList[1]) returns(address newInstantFundPool){ + newInstantFundPool = ICloneFactory(_CLONE_FACTORY_).clone(_INSTANT_FUND_TEMPLATE_); + + IERC20(addressList[1]).transferFrom(msg.sender, newInstantFundPool,sellTokenAmount); + + IDODOStarter(newInstantFundPool).init( + addressList, + timeLine, + valueList + ); + + _INSTANT_REGISTRY_[addressList[1]][addressList[2]].push(newInstantFundPool); + + emit NewInstantFund(addressList[1], addressList[2], addressList[0], newInstantFundPool); + } + + // ============ View Functions ============ + + function getFairFundPools(address baseToken, address fundToken) + external + view + returns (address[] memory pools) + { + return _FAIR_REGISTRY_[baseToken][fundToken]; + } + + function getFairFundPoolsBidirection(address token0, address token1) + external + view + returns (address[] memory baseToken0Pools, address[] memory baseToken1Pools) + { + return (_FAIR_REGISTRY_[token0][token1], _FAIR_REGISTRY_[token1][token0]); + } + + function getInstantFundPools(address baseToken, address fundToken) + external + view + returns (address[] memory pools) + { + return _INSTANT_REGISTRY_[baseToken][fundToken]; + } + + function getInstantFundPoolsBidirection(address token0, address token1) + external + view + returns (address[] memory baseToken0Pools, address[] memory baseToken1Pools) + { + return (_INSTANT_REGISTRY_[token0][token1], _INSTANT_REGISTRY_[token1][token0]); + } + + + // ============ Owner Functions ============ + + function setWhitelist(address creator, address baseToken) external onlyOwner { + fundingWhitelist[creator] = baseToken; + } + + function updateFairFundTemplate(address _newFairFundTemplate) external onlyOwner { + _FAIR_FUND_TEMPLATE_ = _newFairFundTemplate; + } + + function updateInstantFundTemplate(address _newInstantFundTemplate) external onlyOwner { + _INSTANT_FUND_TEMPLATE_ = _newInstantFundTemplate; + } + +}