/* 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 {IDVM} from "../../DODOVendingMachine/intf/IDVM.sol"; import {IDVMFactory} from "../../Factory/DVMFactory.sol"; import {CPStorage} from "./CPStorage.sol"; import {PMMPricing} from "../../lib/PMMPricing.sol"; contract CPFunding is CPStorage { using SafeERC20 for IERC20; // ============ BID & CALM PHASE ============ modifier isBidderAllow(address bidder) { require(_BIDDER_PERMISSION_.isAllowed(bidder), "BIDDER_NOT_ALLOWED"); _; } function bid(address to) external phaseBid preventReentrant isBidderAllow(to) { uint256 input = _getQuoteInput(); uint256 mtFee = DecimalMath.mulFloor(input, _MT_FEE_RATE_MODEL_.getFeeRate(to)); _transferQuoteOut(_MAINTAINER_, mtFee); _mintShares(to, input.sub(mtFee)); _sync(); } function cancel(address assetTo, uint256 amount) external phaseBidOrCalm preventReentrant { require(_SHARES_[msg.sender] >= amount, "SHARES_NOT_ENOUGH"); _burnShares(msg.sender, amount); _transferQuoteOut(assetTo, amount); _sync(); } function _mintShares(address to, uint256 amount) internal { _SHARES_[to] = _SHARES_[to].add(amount); _TOTAL_SHARES_ = _TOTAL_SHARES_.add(amount); } function _burnShares(address from, uint256 amount) internal { _SHARES_[from] = _SHARES_[from].sub(amount); _TOTAL_SHARES_ = _TOTAL_SHARES_.sub(amount); } // ============ SETTLEMENT ============ function settle() external phaseSettlement preventReentrant { _settle(); (uint256 poolBase, uint256 poolQuote) = getSettleResult(); _UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)).sub(poolQuote); _UNUSED_BASE_ = _BASE_TOKEN_.balanceOf(address(this)).sub(poolBase); // 这里的目的是让midPrice尽量等于avgPrice // 我们统一设定k=1,如果quote和base不平衡,就必然要截断一边 // DVM截断了quote,所以如果进入池子的quote很多,就要把quote设置成DVM的base // m = avgPrice // i = m (1-quote/(m*base)) // if quote = m*base i = 1 // if quote > m*base reverse { address _poolBaseToken; address _poolQuoteToken; uint256 _poolI; uint256 avgPrice = _UNUSED_BASE_ == 0 ? _I_ : DecimalMath.divCeil(poolQuote, _UNUSED_BASE_); uint256 baseDepth = DecimalMath.mulFloor(avgPrice, poolBase); if (poolQuote == 0) { // ask side only DVM _poolBaseToken = address(_BASE_TOKEN_); _poolQuoteToken = address(_QUOTE_TOKEN_); _poolI = _I_; } else if (poolQuote.mul(_UNUSED_BASE_) == poolQuote.mul(poolBase)) { // standard bonding curve _poolBaseToken = address(_BASE_TOKEN_); _poolQuoteToken = address(_QUOTE_TOKEN_); _poolI = 1; } else if (poolQuote.mul(_UNUSED_BASE_) < poolQuote.mul(poolBase)) { // poolI up round _poolBaseToken = address(_BASE_TOKEN_); _poolQuoteToken = address(_QUOTE_TOKEN_); uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divFloor(poolQuote, baseDepth)); _poolI = avgPrice.mul(ratio).mul(ratio).divCeil(DecimalMath.ONE2); } else if (poolQuote.mul(_UNUSED_BASE_) > poolQuote.mul(poolBase)) { // poolI down round _poolBaseToken = address(_QUOTE_TOKEN_); _poolQuoteToken = address(_BASE_TOKEN_); uint256 ratio = DecimalMath.ONE.sub(DecimalMath.divCeil(baseDepth, poolQuote)); _poolI = ratio.mul(ratio).div(avgPrice); } _POOL_ = IDVMFactory(_POOL_FACTORY_).createDODOVendingMachine( _poolBaseToken, _poolQuoteToken, 3e15, // 0.3% lp feeRate _poolI, DecimalMath.ONE ); _AVG_SETTLED_PRICE_ = avgPrice; } _transferBaseOut(_POOL_, poolBase); _transferQuoteOut(_POOL_, poolQuote); _TOTAL_LP_AMOUNT_ = IDVM(_POOL_).buyShares(address(this)); } // in case something wrong with base token contract function emergencySettle() external phaseSettlement preventReentrant { require(block.timestamp >= _PHASE_CALM_ENDTIME_.add(_SETTLEMENT_EXPIRE_), "NOT_EMERGENCY"); _settle(); _UNUSED_QUOTE_ = _QUOTE_TOKEN_.balanceOf(address(this)); _UNUSED_BASE_ = _BASE_TOKEN_.balanceOf(address(this)); } function _settle() internal { require(!_SETTLED_, "ALREADY_SETTLED"); _SETTLED_ = true; _SETTLED_TIME_ = block.timestamp; } // ============ Pricing ============ function getSettleResult() public view returns (uint256 poolBase, uint256 poolQuote) { poolQuote = _QUOTE_TOKEN_.balanceOf(address(this)); if (poolQuote > _POOL_QUOTE_CAP_) { poolQuote = _POOL_QUOTE_CAP_; } (uint256 soldBase, ) = PMMPricing.sellQuoteToken(_getPMMState(), poolQuote); poolBase = _TOTAL_BASE_.sub(soldBase); } function _getPMMState() internal view returns (PMMPricing.PMMState memory state) { state.i = _I_; state.K = _K_; state.B = _TOTAL_BASE_; state.Q = 0; state.B0 = state.B; state.Q0 = 0; state.R = PMMPricing.RState.ONE; } function getExpectedAvgPrice() external view returns (uint256) { require(!_SETTLED_, "ALREADY_SETTLED"); (uint256 poolBase, uint256 poolQuote) = getSettleResult(); return DecimalMath.divCeil(poolQuote, _BASE_TOKEN_.balanceOf(address(this)).sub(poolBase)); } // ============ 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); } } // ============ Asset Out ============ function getShares(address user) external view returns (uint256) { return _SHARES_[user]; } }