426
contracts/DODOStarter/impl/FairFundingV2.sol
Normal file
426
contracts/DODOStarter/impl/FairFundingV2.sol
Normal file
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
|
||||
Copyright 2022 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
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 FairFundingV2 is Vesting {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
uint256 internal constant _SETTEL_FUND_ = 2e17;
|
||||
// ============ Fair Mode ============
|
||||
uint256 public _COOLING_DURATION_;
|
||||
|
||||
mapping(address => uint256) _FUNDS_DEPOSITED_;
|
||||
mapping(address => bool) _FUNDS_CLAIMED_;
|
||||
uint256 public _USED_FUND_RATIO_;
|
||||
uint256 public _FINAL_PRICE_;
|
||||
|
||||
uint256 public _LOWER_LIMIT_PRICE_;
|
||||
uint256 public _UPPER_LIMIT_PRICE_;
|
||||
|
||||
bool public _IS_OVERCAP_STOP = false;
|
||||
bool public _HAS_DEPOSIT_SELLTOKEN = false;
|
||||
|
||||
|
||||
// ============ Events ============
|
||||
event Settle(address indexed account);
|
||||
event DepositFund(address indexed account, uint256 fundAmount);
|
||||
event WithdrawFund(address indexed caller, address indexed to, uint256 fundAmount, bool isSettled);
|
||||
event ClaimToken(address indexed caller, address indexed to, uint256 tokenAmount, uint256 fundAmount);
|
||||
|
||||
event WithdrawUnallocatedToken(address indexed to, uint256 tokenAmount);
|
||||
event InitializeLiquidity(address pool, uint256 tokenAmount);
|
||||
event ClaimFund(address indexed to, uint256 fundAmount);
|
||||
|
||||
// ============ Init ============
|
||||
function init(
|
||||
address[] calldata addressList,
|
||||
uint256[] calldata timeLine,
|
||||
uint256[] calldata valueList,
|
||||
bool isOverCapStop
|
||||
) 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");
|
||||
require(_START_TIME_.add(_BIDDING_DURATION_).add(_COOLING_DURATION_) <= _LP_VESTING_START_, "LP_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_ > 0, "LOWER_PRICE_WRONG");
|
||||
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");
|
||||
|
||||
_IS_OVERCAP_STOP = isOverCapStop;
|
||||
}
|
||||
|
||||
function ownerDepositSellToken(uint256 sellTokenAmount) external payable onlyOwner {
|
||||
require(_HAS_DEPOSIT_SELLTOKEN == false, "ALREADY_DEPOSITED_TOKEN");
|
||||
IERC20(_TOKEN_ADDRESS_).safeTransferFrom(msg.sender, address(this), sellTokenAmount);
|
||||
_TOTAL_TOKEN_AMOUNT_ = IERC20(_TOKEN_ADDRESS_).balanceOf(address(this));
|
||||
|
||||
require(_TOTAL_TOKEN_AMOUNT_ > 0, "NO_TOKEN_TRANSFERED");
|
||||
require(msg.value == _SETTEL_FUND_, "SETTLE_FUND_NOT_MATCH");
|
||||
_HAS_DEPOSIT_SELLTOKEN = true;
|
||||
}
|
||||
|
||||
// ============ View Functions ============
|
||||
|
||||
function getCurrentPrice() public view returns (uint256) {
|
||||
return getPrice(_TOTAL_RAISED_FUNDS_);
|
||||
}
|
||||
|
||||
function getPrice(uint256 fundAmount) public view returns (uint256 price) {
|
||||
if(_FINAL_PRICE_ != 0)
|
||||
price = _FINAL_PRICE_;
|
||||
else {
|
||||
price = DecimalMath.divFloor(fundAmount, _TOTAL_TOKEN_AMOUNT_);
|
||||
if (price < _LOWER_LIMIT_PRICE_) {
|
||||
price = _LOWER_LIMIT_PRICE_;
|
||||
}
|
||||
if (price > _UPPER_LIMIT_PRICE_) {
|
||||
price = _UPPER_LIMIT_PRICE_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getUserTokenAllocation(address user) public view returns (uint256) {
|
||||
if (_FINAL_PRICE_ == 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return
|
||||
DecimalMath.divFloor(
|
||||
DecimalMath.mulFloor(_FUNDS_DEPOSITED_[user], _USED_FUND_RATIO_),
|
||||
_FINAL_PRICE_
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getUserFundsUnused(address user) public view returns (uint256) {
|
||||
return
|
||||
DecimalMath.mulFloor(_FUNDS_DEPOSITED_[user], DecimalMath.ONE.sub(_USED_FUND_RATIO_));
|
||||
}
|
||||
|
||||
function getUserFundsUsed(address user) public view returns (uint256) {
|
||||
return DecimalMath.mulFloor(_FUNDS_DEPOSITED_[user], _USED_FUND_RATIO_);
|
||||
}
|
||||
|
||||
// ============ Settle Functions ============
|
||||
|
||||
function settle() public isNotForceStop preventReentrant {
|
||||
require(_FINAL_PRICE_ == 0 && isFundingEnd(), "CAN_NOT_SETTLE");
|
||||
_FINAL_PRICE_ = getCurrentPrice();
|
||||
if(_TOTAL_RAISED_FUNDS_ == 0) {
|
||||
return;
|
||||
}
|
||||
_USED_FUND_RATIO_ = DecimalMath.divFloor(
|
||||
DecimalMath.mulFloor(_TOTAL_TOKEN_AMOUNT_, _FINAL_PRICE_),
|
||||
_TOTAL_RAISED_FUNDS_
|
||||
);
|
||||
if (_USED_FUND_RATIO_ > DecimalMath.ONE) {
|
||||
_USED_FUND_RATIO_ = DecimalMath.ONE;
|
||||
}
|
||||
|
||||
msg.sender.transfer(_SETTEL_FUND_);
|
||||
|
||||
emit Settle(msg.sender);
|
||||
}
|
||||
|
||||
// ============ Funding Functions ============
|
||||
|
||||
function depositFunds(address to) external preventReentrant isNotForceStop returns(uint256 inputFund) {
|
||||
require(_HAS_DEPOSIT_SELLTOKEN, "SELLTOKEN_NOT_DEPOSITED");
|
||||
require(isDepositOpen(), "DEPOSIT_NOT_OPEN");
|
||||
|
||||
uint256 currentFundBalance = IERC20(_FUNDS_ADDRESS_).balanceOf(address(this));
|
||||
|
||||
if(_IS_OVERCAP_STOP) {
|
||||
require(currentFundBalance <= DecimalMath.mulFloor(_TOTAL_TOKEN_AMOUNT_, _UPPER_LIMIT_PRICE_), "ALREADY_OVER_CAP");
|
||||
}
|
||||
|
||||
// input fund check
|
||||
inputFund = currentFundBalance.sub(_FUNDS_RESERVE_);
|
||||
|
||||
if (_QUOTA_ != address(0)) {
|
||||
require(
|
||||
inputFund.add(_FUNDS_DEPOSITED_[to]) <= uint256(IQuota(_QUOTA_).getUserQuota(to)),
|
||||
"QUOTA_EXCEED"
|
||||
);
|
||||
}
|
||||
|
||||
_FUNDS_RESERVE_ = _FUNDS_RESERVE_.add(inputFund);
|
||||
_FUNDS_DEPOSITED_[to] = _FUNDS_DEPOSITED_[to].add(inputFund);
|
||||
_TOTAL_RAISED_FUNDS_ = _TOTAL_RAISED_FUNDS_.add(inputFund);
|
||||
|
||||
emit DepositFund(to, inputFund);
|
||||
}
|
||||
|
||||
function withdrawFunds(address to, uint256 amount) external preventReentrant {
|
||||
uint256 fundAmount;
|
||||
bool isSettled = isSettled();
|
||||
if (!isSettled) {
|
||||
require(_FUNDS_DEPOSITED_[msg.sender] >= amount, "WITHDRAW_TOO_MUCH");
|
||||
_FUNDS_DEPOSITED_[msg.sender] = _FUNDS_DEPOSITED_[msg.sender].sub(amount);
|
||||
_TOTAL_RAISED_FUNDS_ = _TOTAL_RAISED_FUNDS_.sub(amount);
|
||||
_FUNDS_RESERVE_ = _FUNDS_RESERVE_.sub(amount);
|
||||
fundAmount = amount;
|
||||
IERC20(_FUNDS_ADDRESS_).safeTransfer(to, amount);
|
||||
} else {
|
||||
require(!_FUNDS_CLAIMED_[msg.sender], "ALREADY_CLAIMED");
|
||||
_FUNDS_CLAIMED_[msg.sender] = true;
|
||||
fundAmount = getUserFundsUnused(msg.sender);
|
||||
IERC20(_FUNDS_ADDRESS_).safeTransfer(to, fundAmount);
|
||||
}
|
||||
|
||||
emit WithdrawFund(msg.sender, to, fundAmount, isSettled);
|
||||
}
|
||||
|
||||
function claimToken(address to) external preventReentrant {
|
||||
require(isSettled(), "NOT_SETTLED");
|
||||
uint256 totalAllocation = getUserTokenAllocation(msg.sender);
|
||||
uint256 claimableTokenAmount = _claimToken(to, totalAllocation);
|
||||
|
||||
uint256 fundAmount = 0;
|
||||
if(!_FUNDS_CLAIMED_[msg.sender]) {
|
||||
_FUNDS_CLAIMED_[msg.sender] = true;
|
||||
fundAmount = getUserFundsUnused(msg.sender);
|
||||
IERC20(_FUNDS_ADDRESS_).safeTransfer(to, fundAmount);
|
||||
}
|
||||
|
||||
emit ClaimToken(msg.sender, to, claimableTokenAmount, fundAmount);
|
||||
}
|
||||
|
||||
// ============ Ownable Functions ============
|
||||
|
||||
function withdrawUnallocatedToken(address to) external preventReentrant onlyOwner {
|
||||
require(isSettled(), "NOT_SETTLED");
|
||||
require(_FINAL_PRICE_ == _LOWER_LIMIT_PRICE_, "NO_TOKEN_LEFT");
|
||||
uint256 allocatedToken = DecimalMath.divCeil(_TOTAL_RAISED_FUNDS_, _FINAL_PRICE_);
|
||||
uint256 unallocatedAmount = _TOTAL_TOKEN_AMOUNT_.sub(allocatedToken);
|
||||
IERC20(_TOKEN_ADDRESS_).safeTransfer(to, unallocatedAmount);
|
||||
_TOTAL_TOKEN_AMOUNT_ = allocatedToken;
|
||||
|
||||
emit WithdrawUnallocatedToken(to, unallocatedAmount);
|
||||
}
|
||||
|
||||
function initializeLiquidity(uint256 initialTokenAmount, uint256 lpFeeRate, bool isOpenTWAP) external preventReentrant onlyOwner {
|
||||
require(isSettled(), "NOT_SETTLED");
|
||||
uint256 totalUsedRaiseFunds = DecimalMath.mulFloor(_TOTAL_RAISED_FUNDS_, _USED_FUND_RATIO_);
|
||||
_initializeLiquidity(initialTokenAmount, totalUsedRaiseFunds, lpFeeRate, isOpenTWAP);
|
||||
|
||||
emit InitializeLiquidity(_INITIAL_POOL_, initialTokenAmount);
|
||||
}
|
||||
|
||||
function claimFund(address to) external preventReentrant onlyOwner {
|
||||
require(isSettled(), "NOT_SETTLED");
|
||||
uint256 totalUsedRaiseFunds = DecimalMath.mulFloor(_TOTAL_RAISED_FUNDS_, _USED_FUND_RATIO_);
|
||||
uint256 claimableFund = _claimFunds(to,totalUsedRaiseFunds);
|
||||
|
||||
emit ClaimFund(to, claimableFund);
|
||||
}
|
||||
|
||||
// ============ Timeline Control Functions ============
|
||||
|
||||
function isDepositOpen() public view returns (bool) {
|
||||
return
|
||||
block.timestamp >= _START_TIME_ &&
|
||||
block.timestamp < _START_TIME_.add(_BIDDING_DURATION_);
|
||||
}
|
||||
|
||||
function isFundingEnd() public view returns (bool) {
|
||||
return block.timestamp > _START_TIME_.add(_BIDDING_DURATION_).add(_COOLING_DURATION_);
|
||||
}
|
||||
|
||||
function isSettled() public view returns (bool) {
|
||||
return _FINAL_PRICE_ != 0;
|
||||
}
|
||||
|
||||
// ============ Version Control ============
|
||||
|
||||
function version() virtual public pure returns (string memory) {
|
||||
return "FairFunding 2.0.0";
|
||||
}
|
||||
|
||||
// ============ View Helper ==============
|
||||
function getCurrentFundingInfo(address user) external view returns(
|
||||
uint256 raiseFundAmount,
|
||||
uint256 userFundAmount,
|
||||
uint256 currentPrice,
|
||||
uint256 soldTokenAmount,
|
||||
uint256 totalClaimAmount,
|
||||
uint256 claimableTokenAmount,
|
||||
uint256 claimedTokenAmount,
|
||||
bool isHaveCap,
|
||||
uint256 userQuota,
|
||||
uint256 userCurrentQuota
|
||||
) {
|
||||
raiseFundAmount =_TOTAL_RAISED_FUNDS_;
|
||||
userFundAmount = _FUNDS_DEPOSITED_[user];
|
||||
currentPrice = getCurrentPrice();
|
||||
uint256 tmpSoldTokenAmount = DecimalMath.divCeil(_TOTAL_RAISED_FUNDS_, currentPrice);
|
||||
soldTokenAmount = tmpSoldTokenAmount > _TOTAL_TOKEN_AMOUNT_ ? _TOTAL_TOKEN_AMOUNT_ : tmpSoldTokenAmount;
|
||||
|
||||
if(block.timestamp > _TOKEN_VESTING_START_) {
|
||||
uint256 totalAllocation = getUserTokenAllocation(user);
|
||||
uint256 remainingToken = DecimalMath.mulFloor(
|
||||
getRemainingRatio(block.timestamp,0),
|
||||
totalAllocation
|
||||
);
|
||||
claimedTokenAmount = _CLAIMED_TOKEN_[user];
|
||||
claimableTokenAmount = totalAllocation.sub(remainingToken).sub(claimedTokenAmount);
|
||||
}else {
|
||||
claimableTokenAmount = 0;
|
||||
claimedTokenAmount =0;
|
||||
}
|
||||
|
||||
if(raiseFundAmount == 0) {
|
||||
totalClaimAmount = 0;
|
||||
} else {
|
||||
uint256 usedFundRatio = DecimalMath.divFloor(
|
||||
DecimalMath.mulFloor(_TOTAL_TOKEN_AMOUNT_, currentPrice),
|
||||
raiseFundAmount
|
||||
);
|
||||
|
||||
if (usedFundRatio > DecimalMath.ONE) {
|
||||
usedFundRatio = DecimalMath.ONE;
|
||||
}
|
||||
|
||||
totalClaimAmount = DecimalMath.divFloor(
|
||||
DecimalMath.mulFloor(userFundAmount, usedFundRatio),
|
||||
currentPrice
|
||||
);
|
||||
}
|
||||
|
||||
if(_QUOTA_ == address(0)) {
|
||||
isHaveCap = false;
|
||||
userQuota = uint256(-1);
|
||||
userCurrentQuota = uint256(-1);
|
||||
} else {
|
||||
isHaveCap = true;
|
||||
userQuota = uint256(IQuota(_QUOTA_).getUserQuota(user));
|
||||
if(userQuota > userFundAmount) {
|
||||
userCurrentQuota = userQuota - userFundAmount;
|
||||
} else {
|
||||
userCurrentQuota = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getBaseFundInfo() external view returns(
|
||||
address tokenAddress,
|
||||
address fundAddress,
|
||||
uint256 totalTokenAmount,
|
||||
uint256 price0, //_LOWER_LIMIT_PRICE_
|
||||
uint256 price1, //_UPPER_LIMIT_PRICE_
|
||||
string memory versionType,
|
||||
uint256 startTime,
|
||||
uint256 bidDuration,
|
||||
uint256 tokenVestingStart,
|
||||
uint256 tokenVestingDuration
|
||||
) {
|
||||
tokenAddress = _TOKEN_ADDRESS_;
|
||||
fundAddress = _FUNDS_ADDRESS_;
|
||||
totalTokenAmount = _TOTAL_TOKEN_AMOUNT_;
|
||||
price0 = _LOWER_LIMIT_PRICE_;
|
||||
price1 = _UPPER_LIMIT_PRICE_;
|
||||
|
||||
versionType = version();
|
||||
|
||||
startTime = _START_TIME_;
|
||||
bidDuration = _BIDDING_DURATION_;
|
||||
tokenVestingStart = _TOKEN_VESTING_START_;
|
||||
tokenVestingDuration = _TOKEN_VESTING_DURATION_;
|
||||
}
|
||||
|
||||
|
||||
function getFairFundInfo(address user) external view returns(
|
||||
bool isOverCapStop,
|
||||
uint256 finalPrice,
|
||||
uint256 userUnusedFundAmount,
|
||||
uint256 coolDuration
|
||||
) {
|
||||
isOverCapStop = _IS_OVERCAP_STOP;
|
||||
finalPrice = _FINAL_PRICE_;
|
||||
userUnusedFundAmount = getUserFundsUnused(user);
|
||||
coolDuration = _COOLING_DURATION_;
|
||||
}
|
||||
|
||||
}
|
||||
329
contracts/DODOStarter/impl/InstantFundingV2.sol
Normal file
329
contracts/DODOStarter/impl/InstantFundingV2.sol
Normal file
@@ -0,0 +1,329 @@
|
||||
/*
|
||||
|
||||
Copyright 2022 DODO ZOO.
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
*/
|
||||
|
||||
pragma solidity 0.6.9;
|
||||
pragma experimental ABIEncoderV2;
|
||||
|
||||
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 InstantFundingV2 is Vesting {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
// ============ Instant Commit Mode ============
|
||||
uint256 public _START_PRICE_;
|
||||
uint256 public _END_PRICE_;
|
||||
|
||||
mapping(address => uint256) _FUNDS_USED_;
|
||||
mapping(address => uint256) _TOKEN_ALLOCATION_;
|
||||
uint256 public _TOTAL_ALLOCATED_TOKEN_;
|
||||
|
||||
bool public _HAS_DEPOSIT_SELLTOKEN = false;
|
||||
|
||||
// ============ Events ============
|
||||
event DepositFund(address indexed account, uint256 fundAmount, uint256 allocationAmount);
|
||||
event ClaimToken(address indexed caller, address indexed to, uint256 tokenAmount);
|
||||
|
||||
event WithdrawUnallocatedToken(address indexed to, uint256 tokenAmount);
|
||||
event InitializeLiquidity(address pool, uint256 tokenAmount);
|
||||
event ClaimFund(address indexed to, uint256 fundAmount);
|
||||
|
||||
// ============ 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");
|
||||
require(_START_TIME_.add(_BIDDING_DURATION_) <= _LP_VESTING_START_, "LP_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(_START_PRICE_ > 0, "START_PRICE_INVALID");
|
||||
require(_END_PRICE_ > 0, "END_PRICE_INVALID");
|
||||
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));
|
||||
|
||||
require(_TOTAL_TOKEN_AMOUNT_ > 0, "NO_TOKEN_TRANSFERED");
|
||||
}
|
||||
|
||||
function ownerDepositSellToken(uint256 sellTokenAmount) external onlyOwner {
|
||||
require(_HAS_DEPOSIT_SELLTOKEN == false, "ALREADY_DEPOSITED_TOKEN");
|
||||
IERC20(_TOKEN_ADDRESS_).safeTransferFrom(msg.sender, address(this), sellTokenAmount);
|
||||
_TOTAL_TOKEN_AMOUNT_ = IERC20(_TOKEN_ADDRESS_).balanceOf(address(this));
|
||||
|
||||
require(_TOTAL_TOKEN_AMOUNT_ > 0, "NO_TOKEN_TRANSFERED");
|
||||
_HAS_DEPOSIT_SELLTOKEN = true;
|
||||
}
|
||||
|
||||
// ============ View Functions ============
|
||||
function getCurrentPrice() public view returns (uint256 price) {
|
||||
if (block.timestamp <= _START_TIME_) {
|
||||
price = _START_PRICE_;
|
||||
} else if (block.timestamp >= _START_TIME_.add(_BIDDING_DURATION_)) {
|
||||
price = _END_PRICE_;
|
||||
} else {
|
||||
uint256 timePast = block.timestamp.sub(_START_TIME_);
|
||||
price = _START_PRICE_.mul(_BIDDING_DURATION_.sub(timePast)).div(_BIDDING_DURATION_).add(
|
||||
_END_PRICE_.mul(timePast).div(_BIDDING_DURATION_)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getUserTokenAllocation(address user) public view returns (uint256) {
|
||||
return _TOKEN_ALLOCATION_[user];
|
||||
}
|
||||
|
||||
function getUserFundsUsed(address user) public view returns (uint256) {
|
||||
return _FUNDS_USED_[user];
|
||||
}
|
||||
|
||||
// ============ Funding Functions ============
|
||||
|
||||
function depositFunds(address to)
|
||||
external
|
||||
preventReentrant
|
||||
isNotForceStop
|
||||
returns (uint256 newTokenAllocation)
|
||||
{
|
||||
require(_HAS_DEPOSIT_SELLTOKEN, "SELLTOKEN_NOT_DEPOSITED");
|
||||
require(isDepositOpen(), "DEPOSIT_NOT_OPEN");
|
||||
// input fund check
|
||||
uint256 inputFund = IERC20(_FUNDS_ADDRESS_).balanceOf(address(this)).sub(_FUNDS_RESERVE_);
|
||||
|
||||
if (_QUOTA_ != address(0)) {
|
||||
require(
|
||||
inputFund.add(_FUNDS_USED_[to]) <= uint256(IQuota(_QUOTA_).getUserQuota(to)),
|
||||
"QUOTA_EXCEED"
|
||||
);
|
||||
}
|
||||
|
||||
// allocation calculation
|
||||
uint256 currentPrice = getCurrentPrice();
|
||||
newTokenAllocation = DecimalMath.divFloor(inputFund, currentPrice);
|
||||
|
||||
uint256 depositFundAmount = inputFund;
|
||||
if (newTokenAllocation.add(_TOTAL_ALLOCATED_TOKEN_) > _TOTAL_TOKEN_AMOUNT_) {
|
||||
newTokenAllocation = _TOTAL_TOKEN_AMOUNT_.sub(_TOTAL_ALLOCATED_TOKEN_);
|
||||
uint256 fundUsed = DecimalMath.mulFloor(newTokenAllocation, currentPrice);
|
||||
_FUNDS_USED_[to] = _FUNDS_USED_[to].add(fundUsed);
|
||||
_TOTAL_RAISED_FUNDS_ = _TOTAL_RAISED_FUNDS_.add(fundUsed);
|
||||
_FUNDS_RESERVE_ = _FUNDS_RESERVE_.add(fundUsed);
|
||||
depositFundAmount = fundUsed;
|
||||
|
||||
IERC20(_FUNDS_ADDRESS_).safeTransfer(to, inputFund.sub(fundUsed));
|
||||
} else {
|
||||
_FUNDS_USED_[to] = _FUNDS_USED_[to].add(inputFund);
|
||||
_TOTAL_RAISED_FUNDS_ = _TOTAL_RAISED_FUNDS_.add(inputFund);
|
||||
_FUNDS_RESERVE_ = _FUNDS_RESERVE_.add(inputFund);
|
||||
}
|
||||
|
||||
_TOKEN_ALLOCATION_[to] = _TOKEN_ALLOCATION_[to].add(newTokenAllocation);
|
||||
_TOTAL_ALLOCATED_TOKEN_ = _TOTAL_ALLOCATED_TOKEN_.add(newTokenAllocation);
|
||||
|
||||
emit DepositFund(to, depositFundAmount, newTokenAllocation);
|
||||
}
|
||||
|
||||
function claimToken(address to) external preventReentrant {
|
||||
uint256 totalAllocation = getUserTokenAllocation(msg.sender);
|
||||
uint256 claimableTokenAmount = _claimToken(to, totalAllocation);
|
||||
|
||||
emit ClaimToken(msg.sender, to, claimableTokenAmount);
|
||||
}
|
||||
|
||||
// ============ Ownable Functions ============
|
||||
|
||||
function withdrawUnallocatedToken(address to) external preventReentrant onlyOwner {
|
||||
require(isFundingEnd(), "CAN_NOT_WITHDRAW");
|
||||
uint256 unallocatedAmount = _TOTAL_TOKEN_AMOUNT_.sub(_TOTAL_ALLOCATED_TOKEN_);
|
||||
IERC20(_TOKEN_ADDRESS_).safeTransfer(to, unallocatedAmount);
|
||||
_TOTAL_TOKEN_AMOUNT_ = _TOTAL_ALLOCATED_TOKEN_;
|
||||
|
||||
emit WithdrawUnallocatedToken(to, unallocatedAmount);
|
||||
}
|
||||
|
||||
function initializeLiquidity(uint256 initialTokenAmount, uint256 lpFeeRate, bool isOpenTWAP) external preventReentrant onlyOwner {
|
||||
require(isFundingEnd(),"FUNDING_NOT_FINISHED");
|
||||
_initializeLiquidity(initialTokenAmount, _TOTAL_RAISED_FUNDS_, lpFeeRate, isOpenTWAP);
|
||||
|
||||
emit InitializeLiquidity(_INITIAL_POOL_, initialTokenAmount);
|
||||
}
|
||||
|
||||
function claimFund(address to) external preventReentrant onlyOwner {
|
||||
uint256 claimableFund = _claimFunds(to,_TOTAL_RAISED_FUNDS_);
|
||||
|
||||
emit ClaimFund(to, claimableFund);
|
||||
}
|
||||
|
||||
// ============ Timeline Control Functions ============
|
||||
|
||||
function isDepositOpen() public view returns (bool) {
|
||||
return
|
||||
block.timestamp >= _START_TIME_ &&
|
||||
block.timestamp < _START_TIME_.add(_BIDDING_DURATION_);
|
||||
}
|
||||
|
||||
function isFundingEnd() public view returns (bool) {
|
||||
return block.timestamp > _START_TIME_.add(_BIDDING_DURATION_);
|
||||
}
|
||||
|
||||
|
||||
// ============ Version Control ============
|
||||
|
||||
function version() virtual public pure returns (string memory) {
|
||||
return "InstantFunding 2.0.0";
|
||||
}
|
||||
|
||||
// ============ View Helper ==============
|
||||
function getCurrentFundingInfo(address user) external view returns(
|
||||
uint256 raiseFundAmount,
|
||||
uint256 userFundAmount,
|
||||
uint256 currentPrice,
|
||||
uint256 soldTokenAmount,
|
||||
uint256 totalClaimAmount,
|
||||
uint256 claimableTokenAmount,
|
||||
uint256 claimedTokenAmount,
|
||||
bool isHaveCap,
|
||||
uint256 userQuota,
|
||||
uint256 userCurrentQuota
|
||||
) {
|
||||
raiseFundAmount =_TOTAL_RAISED_FUNDS_;
|
||||
userFundAmount = _FUNDS_USED_[user];
|
||||
currentPrice = getCurrentPrice();
|
||||
soldTokenAmount = _TOTAL_ALLOCATED_TOKEN_;
|
||||
|
||||
if(block.timestamp > _TOKEN_VESTING_START_) {
|
||||
uint256 totalAllocation = getUserTokenAllocation(user);
|
||||
uint256 remainingToken = DecimalMath.mulFloor(
|
||||
getRemainingRatio(block.timestamp,0),
|
||||
totalAllocation
|
||||
);
|
||||
claimedTokenAmount = _CLAIMED_TOKEN_[user];
|
||||
claimableTokenAmount = totalAllocation.sub(remainingToken).sub(claimedTokenAmount);
|
||||
}else {
|
||||
claimableTokenAmount = 0;
|
||||
claimedTokenAmount = 0;
|
||||
}
|
||||
|
||||
totalClaimAmount = getUserTokenAllocation(user);
|
||||
|
||||
|
||||
if(_QUOTA_ == address(0)) {
|
||||
isHaveCap = false;
|
||||
userQuota = uint256(-1);
|
||||
userCurrentQuota = uint256(-1);
|
||||
} else {
|
||||
isHaveCap = true;
|
||||
userQuota = uint256(IQuota(_QUOTA_).getUserQuota(user));
|
||||
if(userQuota > userFundAmount) {
|
||||
userCurrentQuota = userQuota - userFundAmount;
|
||||
} else {
|
||||
userCurrentQuota = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getBaseFundInfo() external view returns(
|
||||
address tokenAddress,
|
||||
address fundAddress,
|
||||
uint256 totalTokenAmount,
|
||||
uint256 price0, //_START_PRICE_
|
||||
uint256 price1, //_END_PRICE_
|
||||
string memory versionType,
|
||||
uint256 startTime,
|
||||
uint256 bidDuration,
|
||||
uint256 tokenVestingStart,
|
||||
uint256 tokenVestingDuration
|
||||
) {
|
||||
tokenAddress = _TOKEN_ADDRESS_;
|
||||
fundAddress = _FUNDS_ADDRESS_;
|
||||
totalTokenAmount = _TOTAL_TOKEN_AMOUNT_;
|
||||
price0 = _START_PRICE_;
|
||||
price1 = _END_PRICE_;
|
||||
|
||||
versionType = version();
|
||||
|
||||
startTime = _START_TIME_;
|
||||
bidDuration = _BIDDING_DURATION_;
|
||||
tokenVestingStart = _TOKEN_VESTING_START_;
|
||||
tokenVestingDuration = _TOKEN_VESTING_DURATION_;
|
||||
}
|
||||
}
|
||||
170
contracts/Factory/DODOStarterFactoryV2.sol
Normal file
170
contracts/Factory/DODOStarterFactoryV2.sol
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
|
||||
Copyright 2022 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 {SafeERC20} from "../lib/SafeERC20.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 DODOStarterFactoryV2 is InitializableOwnable {
|
||||
using SafeMath for uint256;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
// ============ 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
|
||||
);
|
||||
|
||||
event SetWhitelist(address creator, address baseToken);
|
||||
event UpdateFairFundTempalte(address newTemplate);
|
||||
event UpdateInstantFundTempalte(address newTemplate);
|
||||
|
||||
// ============ 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,
|
||||
bool isOverCapStop
|
||||
) external permissionCheck(addressList[0],addressList[1]) returns(address newFairFundPool){
|
||||
newFairFundPool = ICloneFactory(_CLONE_FACTORY_).clone(_FAIR_FUND_TEMPLATE_);
|
||||
|
||||
IDODOStarter(newFairFundPool).init(
|
||||
addressList,
|
||||
timeLine,
|
||||
valueList,
|
||||
isOverCapStop
|
||||
);
|
||||
|
||||
_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
|
||||
) external permissionCheck(addressList[0],addressList[1]) returns(address newInstantFundPool){
|
||||
newInstantFundPool = ICloneFactory(_CLONE_FACTORY_).clone(_INSTANT_FUND_TEMPLATE_);
|
||||
|
||||
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;
|
||||
emit SetWhitelist(creator, baseToken);
|
||||
}
|
||||
|
||||
function updateFairFundTemplate(address _newFairFundTemplate) external onlyOwner {
|
||||
_FAIR_FUND_TEMPLATE_ = _newFairFundTemplate;
|
||||
emit UpdateFairFundTempalte(_newFairFundTemplate);
|
||||
}
|
||||
|
||||
function updateInstantFundTemplate(address _newInstantFundTemplate) external onlyOwner {
|
||||
_INSTANT_FUND_TEMPLATE_ = _newInstantFundTemplate;
|
||||
emit UpdateInstantFundTempalte(_newInstantFundTemplate);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user