From 7eda244ae124126622902191920f3e37fc01b316 Mon Sep 17 00:00:00 2001 From: mingda Date: Sun, 6 Dec 2020 11:25:03 +0800 Subject: [PATCH] DODO Call Auction --- README.md | 6 +- contracts/CallAuction/impl/CAFunding.sol | 132 +++++++++++++++++++++++ contracts/CallAuction/impl/CAStorage.sol | 100 +++++++++++++++++ contracts/CallAuction/impl/CAVesting.sol | 69 ++++++++++++ contracts/DODOToken/LockedTokenVault.sol | 2 +- contracts/intf/IDODOCallee.sol | 6 ++ 6 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 contracts/CallAuction/impl/CAFunding.sol create mode 100644 contracts/CallAuction/impl/CAStorage.sol create mode 100644 contracts/CallAuction/impl/CAVesting.sol diff --git a/README.md b/README.md index 2f0a08d..60d9b85 100644 --- a/README.md +++ b/README.md @@ -18,4 +18,8 @@ DPP 是 DODO Private Pool 的缩写,”私有池“ 1. 参数i和k可以任意改变 2. Target和reserve可以任意设置 - 3. 只有creator可以充提,且充提不受限制 \ No newline at end of file + 3. 只有creator可以充提,且充提不受限制 + +## 文档 + +https://dodoex.github.io/docs/docs/coreConcept \ No newline at end of file diff --git a/contracts/CallAuction/impl/CAFunding.sol b/contracts/CallAuction/impl/CAFunding.sol new file mode 100644 index 0000000..511748c --- /dev/null +++ b/contracts/CallAuction/impl/CAFunding.sol @@ -0,0 +1,132 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {IDODOCallee} from "../../intf/IDODOCallee.sol"; +import {CAStorage} from "./CAStorage.sol"; +import {PMMPricing} from "../../lib/PMMPricing.sol"; + +contract CAFunding is CAStorage { + using SafeERC20 for IERC20; + + // ============ BID ============ + + function bid(address to) external phaseBid preventReentrant { + uint256 input = _getQuoteInput(); + _QUOTE_SHARES_[to] = _QUOTE_SHARES_[to].add(input); + _TOTAL_QUOTE_SHARES_ = _TOTAL_QUOTE_SHARES_.add(input); + _sync(); + } + + // ============ CALM ============ + + function cancel( + address to, + uint256 amount, + bytes memory data + ) external phaseBidOrCalm preventReentrant { + require(_QUOTE_SHARES_[msg.sender] >= amount, "SHARES_NOT_ENOUGH"); + _QUOTE_SHARES_[msg.sender] = _QUOTE_SHARES_[msg.sender].sub(amount); + _transferQuoteOut(to, amount); + _sync(); + if (data.length > 0) { + IDODOCallee(to).CACancelCall(msg.sender, amount, data); + } + } + + // ============ SETTLEMENT ============ + + function settle() external phaseSettlement preventReentrant { + require(!_SETTLED_, "ALREADY_SETTLED"); + _SETTLED_ = true; + + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 mtFee; + (_TOTAL_SOLD_BASE_, mtFee) = getBaseSold(); + + // 1. maintainer quote + _transferQuoteOut(_MAINTAINER_, mtFee); + // 2. remaining quote + _transferQuoteOut(_QUOTE_PAY_BACK_, quoteBalance.sub(mtFee)); + // 3. base token pay back + _transferBaseOut(_BASE_PAY_BACK_, baseBalance.sub(_TOTAL_SOLD_BASE_)); + // 4. left base in contract + // 5. external call + if (_BASE_PAY_BACK_CALL_DATA_.length > 0) { + (bool success, ) = _BASE_PAY_BACK_.call(_BASE_PAY_BACK_CALL_DATA_); + require(success, "BASE_PAY_BACK_CALL_FAILED"); + } + if (_QUOTE_PAY_BACK_CALL_DATA_.length > 0) { + (bool success, ) = _QUOTE_PAY_BACK_.call(_QUOTE_PAY_BACK_CALL_DATA_); + require(success, "QUOTE_PAY_BACK_CALL_FAILED"); + } + } + + // ============ Pricing ============ + + function getAvgPrice() public view returns (uint256 avgPrice) { + (uint256 baseSold, ) = getBaseSold(); + avgPrice = DecimalMath.divFloor(_QUOTE_TOKEN_.balanceOf(address(this)), baseSold); + } + + function getBaseByUser(address user) public view returns (uint256 baseAmount) { + (uint256 baseSold, ) = getBaseSold(); + baseAmount = baseSold.mul(_QUOTE_SHARES_[user]).div(_TOTAL_QUOTE_SHARES_); + } + + function getBaseSold() public view returns (uint256 baseSold, uint256 mtFee) { + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + mtFee = DecimalMath.mulFloor(quoteBalance, _QUOTE_MAINTAINER_FEE_RATE_); + (baseSold, ) = PMMPricing.sellQuoteToken(_getPMMState(), quoteBalance); + } + + function _getPMMState() internal view returns (PMMPricing.PMMState memory state) { + state.i = _I_; + state.K = _K_; + state.B = _BASE_TOKEN_.balanceOf(address(this)); + state.Q = 0; + state.B0 = state.B; + state.Q0 = 0; + state.R = PMMPricing.RState.ONE; + } + + // ============ Asset In ============ + + function _getQuoteInput() internal view returns (uint256 input) { + return _QUOTE_TOKEN_.balanceOf(address(this)).sub(_QUOTE_RESERVE_); + } + + // ============ Set States ============ + + function _sync() internal { + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + if (quoteBalance != _QUOTE_RESERVE_) { + _QUOTE_RESERVE_ = quoteBalance; + } + } + + // ============ Asset Out ============ + + function _transferBaseOut(address to, uint256 amount) internal { + if (amount > 0) { + _BASE_TOKEN_.safeTransfer(to, amount); + } + } + + function _transferQuoteOut(address to, uint256 amount) internal { + if (amount > 0) { + _QUOTE_TOKEN_.safeTransfer(to, amount); + } + } +} diff --git a/contracts/CallAuction/impl/CAStorage.sol b/contracts/CallAuction/impl/CAStorage.sol new file mode 100644 index 0000000..8d6e844 --- /dev/null +++ b/contracts/CallAuction/impl/CAStorage.sol @@ -0,0 +1,100 @@ +/* + + 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 {ReentrancyGuard} from "../../lib/ReentrancyGuard.sol"; +import {SafeMath} from "../../lib/SafeMath.sol"; +import {IERC20} from "../../intf/IERC20.sol"; + +contract CAStorage is InitializableOwnable, ReentrancyGuard { + using SafeMath for uint256; + + // ============ Timeline ============ + + uint256 _PAHSE_SETTING_ENDTIME_; + uint256 _PHASE_BID_ENDTIME_; + uint256 _PHASE_CALM_ENDTIME_; + bool _SETTLED_; + + // ============ Core Address ============ + + IERC20 public _BASE_TOKEN_; + IERC20 public _QUOTE_TOKEN_; + address public _MAINTAINER_; + address public _BASE_PAY_BACK_; + address public _QUOTE_PAY_BACK_; + + // ============ Distribution Parameters ============ + + uint256 _QUOTE_MAINTAINER_FEE_RATE_; + bytes _BASE_PAY_BACK_CALL_DATA_; + bytes _QUOTE_PAY_BACK_CALL_DATA_; + + // ============ Balances ============ + + uint256 public _QUOTE_RESERVE_; + uint256 public _BASE_RESERVE_; + uint256 public _TOTAL_SOLD_BASE_; + uint256 public _TOTAL_QUOTE_SHARES_; + mapping(address => uint256) internal _QUOTE_SHARES_; + mapping(address => uint256) internal _CLAIMED_BALANCES_; + + // ============ Time Lock ============ + + uint256 public _START_VESTING_TIME_; + uint256 public _VESTING_DURATION_; + uint256 public _CLIFF_RATE_; + + // ============ PMM Parameters ============ + + uint256 public _K_; + uint256 public _I_; + + // ============ Modifiers ============ + + modifier phaseSetting() { + require(block.timestamp <= _PAHSE_SETTING_ENDTIME_, "NOT_PHASE_SETTING"); + _; + } + + modifier phaseBid() { + require( + block.timestamp > _PAHSE_SETTING_ENDTIME_ && block.timestamp <= _PHASE_BID_ENDTIME_, + "NOT_PHASE_BID" + ); + _; + } + + modifier phaseCalm() { + require( + block.timestamp > _PHASE_BID_ENDTIME_ && block.timestamp <= _PHASE_CALM_ENDTIME_, + "NOT_PHASE_CALM" + ); + _; + } + + modifier phaseBidOrCalm() { + require( + block.timestamp > _PAHSE_SETTING_ENDTIME_ && block.timestamp <= _PHASE_CALM_ENDTIME_, + "NOT_PHASE_BID_OR_CALM" + ); + _; + } + + modifier phaseSettlement() { + require(block.timestamp > _PHASE_CALM_ENDTIME_, "NOT_PHASE_EXE"); + _; + } + + modifier phaseVesting() { + require(_SETTLED_, "NOT_VESTING"); + _; + } +} diff --git a/contracts/CallAuction/impl/CAVesting.sol b/contracts/CallAuction/impl/CAVesting.sol new file mode 100644 index 0000000..1fb7546 --- /dev/null +++ b/contracts/CallAuction/impl/CAVesting.sol @@ -0,0 +1,69 @@ +/* + + Copyright 2020 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {SafeMath} from "../../lib/SafeMath.sol"; +import {DecimalMath} from "../../lib/DecimalMath.sol"; +import {Ownable} from "../../lib/Ownable.sol"; +import {SafeERC20} from "../../lib/SafeERC20.sol"; +import {IERC20} from "../../intf/IERC20.sol"; +import {CAFunding} from "./CAFunding.sol"; + +/** + * @title LockedTokenVault + * @author DODO Breeder + * + * @notice Lock Token and release it linearly + */ + +contract LockedTokenVault is CAFunding { + using SafeMath for uint256; + using SafeERC20 for IERC20; + + // ============ Functions ============ + + function claim() external { + uint256 claimableToken = getClaimableBalance(msg.sender); + _transferBaseOut(msg.sender, claimableToken); + _CLAIMED_BALANCES_[msg.sender] = _CLAIMED_BALANCES_[msg.sender].add(claimableToken); + } + + // ============ View ============ + + function getOriginBaseBalance(address user) public view returns (uint256) { + return _TOTAL_SOLD_BASE_.mul(_QUOTE_SHARES_[user]).div(_TOTAL_QUOTE_SHARES_); + } + + function getClaimedBaseBalance(address holder) public view returns (uint256) { + return _CLAIMED_BALANCES_[holder]; + } + + function getClaimableBalance(address holder) public view returns (uint256) { + uint256 remainingToken = getRemainingBalance(holder); + return getOriginBaseBalance(holder).sub(remainingToken).sub(_CLAIMED_BALANCES_[holder]); + } + + function getRemainingBalance(address holder) public view returns (uint256) { + uint256 remainingRatio = getRemainingRatio(block.timestamp); + return DecimalMath.mulFloor(getOriginBaseBalance(holder), remainingRatio); + } + + function getRemainingRatio(uint256 timestamp) public view returns (uint256) { + if (timestamp < _START_VESTING_TIME_) { + return DecimalMath.ONE; + } + uint256 timePast = timestamp.sub(_START_VESTING_TIME_); + if (timePast < _VESTING_DURATION_) { + uint256 remainingTime = _VESTING_DURATION_.sub(timePast); + return DecimalMath.ONE.sub(_CLIFF_RATE_).mul(remainingTime).div(_VESTING_DURATION_); + } else { + return 0; + } + } +} diff --git a/contracts/DODOToken/LockedTokenVault.sol b/contracts/DODOToken/LockedTokenVault.sol index 396b9f1..72d0975 100644 --- a/contracts/DODOToken/LockedTokenVault.sol +++ b/contracts/DODOToken/LockedTokenVault.sol @@ -37,7 +37,7 @@ contract LockedTokenVault is Ownable { bool public _DISTRIBUTE_FINISHED_; - // ============ Modifiers ============ + // ============ Events ============ event Claim(address indexed holder, uint256 origin, uint256 claimed, uint256 amount); diff --git a/contracts/intf/IDODOCallee.sol b/contracts/intf/IDODOCallee.sol index c366ca7..d506c0c 100644 --- a/contracts/intf/IDODOCallee.sol +++ b/contracts/intf/IDODOCallee.sol @@ -30,4 +30,10 @@ interface IDODOCallee { uint256 quoteAmount, bytes calldata data ) external; + + function CACancelCall( + address sender, + uint256 amount, + bytes calldata data + ) external; }