diff --git a/contracts/DODOPrivatePool/impl/DPPAdvanced.sol b/contracts/DODOPrivatePool/impl/DPPAdvanced/DPPAdvanced.sol similarity index 98% rename from contracts/DODOPrivatePool/impl/DPPAdvanced.sol rename to contracts/DODOPrivatePool/impl/DPPAdvanced/DPPAdvanced.sol index 2f4aaf8..b8a64b7 100644 --- a/contracts/DODOPrivatePool/impl/DPPAdvanced.sol +++ b/contracts/DODOPrivatePool/impl/DPPAdvanced/DPPAdvanced.sol @@ -8,7 +8,7 @@ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; -import {DPP} from "./DPP.sol"; +import {DPP} from "../DPP.sol"; /** * @title DODO PrivatePool diff --git a/contracts/DODOPrivatePool/impl/DPPAdvancedAmin.sol b/contracts/DODOPrivatePool/impl/DPPAdvanced/DPPAdvancedAmin.sol similarity index 94% rename from contracts/DODOPrivatePool/impl/DPPAdvancedAmin.sol rename to contracts/DODOPrivatePool/impl/DPPAdvanced/DPPAdvancedAmin.sol index 31e3ddf..c9a084b 100644 --- a/contracts/DODOPrivatePool/impl/DPPAdvancedAmin.sol +++ b/contracts/DODOPrivatePool/impl/DPPAdvanced/DPPAdvancedAmin.sol @@ -8,9 +8,9 @@ pragma solidity 0.6.9; pragma experimental ABIEncoderV2; -import {IDPP} from "../intf/IDPP.sol"; -import {IDODOApproveProxy} from "../../SmartRoute/DODOApproveProxy.sol"; -import {InitializableOwnable} from "../../lib/InitializableOwnable.sol"; +import {IDPP} from "../../intf/IDPP.sol"; +import {IDODOApproveProxy} from "../../../SmartRoute/DODOApproveProxy.sol"; +import {InitializableOwnable} from "../../../lib/InitializableOwnable.sol"; /** * @title DPPAdmin diff --git a/contracts/DODOPrivatePool/impl/DPPOracle/DPPOracle.sol b/contracts/DODOPrivatePool/impl/DPPOracle/DPPOracle.sol new file mode 100644 index 0000000..086e9dd --- /dev/null +++ b/contracts/DODOPrivatePool/impl/DPPOracle/DPPOracle.sol @@ -0,0 +1,84 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {IFeeRateModel} from "../../../lib/FeeRateModel.sol"; +import {IERC20} from "../../../intf/IERC20.sol"; +import {DPPTrader} from "./DPPTrader.sol"; + +/** + * @title DODO PrivatePool + * @author DODO Breeder + * + * @notice DODOPrivatePool with oracle price + */ +contract DPPOracle is DPPTrader { + + function init( + address owner, + address maintainer, + address baseTokenAddress, + address quoteTokenAddress, + uint256 lpFeeRate, + address mtFeeRateModel, + uint256 k, + address i, + bool isOpenTWAP + ) external { + initOwner(owner); + + require(baseTokenAddress != quoteTokenAddress, "BASE_QUOTE_CAN_NOT_BE_SAME"); + _BASE_TOKEN_ = IERC20(baseTokenAddress); + _QUOTE_TOKEN_ = IERC20(quoteTokenAddress); + + _MAINTAINER_ = maintainer; + _MT_FEE_RATE_MODEL_ = IFeeRateModel(mtFeeRateModel); + + require(lpFeeRate <= 1e18, "LP_FEE_RATE_OUT_OF_RANGE"); + require(k <= 1e18, "K_OUT_OF_RANGE"); + require(i != address(0), "INVALID_ORACLE"); + + _LP_FEE_RATE_ = uint64(lpFeeRate); + _K_ = uint64(k); + _I_ = i; + + _IS_OPEN_TWAP_ = isOpenTWAP; + if(isOpenTWAP) _BLOCK_TIMESTAMP_LAST_ = uint32(block.timestamp % 2**32); + + _resetTargetAndReserve(); + } + + + function tuneParameters( + uint256 newLpFeeRate, + uint256 newK, + uint256 minBaseReserve, + uint256 minQuoteReserve + ) public preventReentrant onlyOwner returns (bool) { + require( + _BASE_RESERVE_ >= minBaseReserve && _QUOTE_RESERVE_ >= minQuoteReserve, + "RESERVE_AMOUNT_IS_NOT_ENOUGH" + ); + require(newLpFeeRate <= 1e18, "LP_FEE_RATE_OUT_OF_RANGE"); + require(newK <= 1e18, "K_OUT_OF_RANGE"); + + _LP_FEE_RATE_ = uint64(newLpFeeRate); + _K_ = uint64(newK); + + emit LpFeeRateChange(newLpFeeRate); + return true; + } + + + // ============ Version Control ============ + + function version() external pure returns (string memory) { + return "DPP Oracle 1.0.0"; + } +} diff --git a/contracts/DODOPrivatePool/impl/DPPOracle/DPPOracleAdmin.sol b/contracts/DODOPrivatePool/impl/DPPOracle/DPPOracleAdmin.sol new file mode 100644 index 0000000..94042d9 --- /dev/null +++ b/contracts/DODOPrivatePool/impl/DPPOracle/DPPOracleAdmin.sol @@ -0,0 +1,120 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {IDPPOracle} from "../../intf/IDPPOracle.sol"; +import {IDODOApproveProxy} from "../../../SmartRoute/DODOApproveProxy.sol"; +import {InitializableOwnable} from "../../../lib/InitializableOwnable.sol"; + +/** + * @title DPPOracleAdmin + * @author DODO Breeder + * + * @notice Admin of Oracle DODOPrivatePool + */ +contract DPPOracleAdmin is InitializableOwnable { + address public _DPP_; + address public _OPERATOR_; + address public _DODO_APPROVE_PROXY_; + uint256 public _FREEZE_TIMESTAMP_; + + + modifier notFreezed() { + require(block.timestamp >= _FREEZE_TIMESTAMP_, "ADMIN_FREEZED"); + _; + } + + function init( + address owner, + address dpp, + address operator, + address dodoApproveProxy + ) external { + initOwner(owner); + _DPP_ = dpp; + _OPERATOR_ = operator; + _DODO_APPROVE_PROXY_ = dodoApproveProxy; + } + + function sync() external notFreezed onlyOwner { + IDPPOracle(_DPP_).ratioSync(); + } + + function setFreezeTimestamp(uint256 timestamp) external notFreezed onlyOwner { + _FREEZE_TIMESTAMP_ = timestamp; + } + + function setOperator(address newOperator) external notFreezed onlyOwner { + _OPERATOR_ = newOperator; + } + + function retrieve( + address payable to, + address token, + uint256 amount + ) external notFreezed onlyOwner { + IDPPOracle(_DPP_).retrieve(to, token, amount); + } + + function tuneParameters( + address operator, + uint256 newLpFeeRate, + uint256 newK, + uint256 minBaseReserve, + uint256 minQuoteReserve + ) external notFreezed returns (bool) { + require( + msg.sender == _OWNER_ || + (IDODOApproveProxy(_DODO_APPROVE_PROXY_).isAllowedProxy(msg.sender) && + operator == _OPERATOR_), + "TUNEPARAMS FORBIDDEN!" + ); + return + IDPPOracle(_DPP_).tuneParameters( + newLpFeeRate, + newK, + minBaseReserve, + minQuoteReserve + ); + } + + + function reset( + address operator, + uint256 newLpFeeRate, + uint256 newK, + uint256 baseOutAmount, + uint256 quoteOutAmount, + uint256 minBaseReserve, + uint256 minQuoteReserve + ) external notFreezed returns (bool) { + require( + msg.sender == _OWNER_ || + (IDODOApproveProxy(_DODO_APPROVE_PROXY_).isAllowedProxy(msg.sender) && + operator == _OPERATOR_), + "RESET FORBIDDEN!" + ); + return + IDPPOracle(_DPP_).reset( + _OWNER_, //only support asset transfer out to owner + newLpFeeRate, + newK, + baseOutAmount, + quoteOutAmount, + minBaseReserve, + minQuoteReserve + ); + } + + // ============ Admin Version Control ============ + + function version() external pure returns (string memory) { + return "DPPOracle Admin 1.0.0"; // 1.0.0 + } +} diff --git a/contracts/DODOPrivatePool/impl/DPPOracle/DPPStorage.sol b/contracts/DODOPrivatePool/impl/DPPOracle/DPPStorage.sol new file mode 100644 index 0000000..03ae490 --- /dev/null +++ b/contracts/DODOPrivatePool/impl/DPPOracle/DPPStorage.sol @@ -0,0 +1,89 @@ +/* + + 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 {SafeMath} from "../../../lib/SafeMath.sol"; +import {DecimalMath} from "../../../lib/DecimalMath.sol"; +import {ReentrancyGuard} from "../../../lib/ReentrancyGuard.sol"; +import {IFeeRateModel} from "../../../lib/FeeRateModel.sol"; +import {IERC20} from "../../../intf/IERC20.sol"; +import {PMMPricing} from "../../../lib/PMMPricing.sol"; +import {IOracle} from "../../intf/IOracle.sol"; + +contract DPPStorage is InitializableOwnable, ReentrancyGuard { + using SafeMath for uint256; + + bool public _IS_OPEN_TWAP_ = false; + + // ============ Core Address ============ + + address public _MAINTAINER_; + + IERC20 public _BASE_TOKEN_; + IERC20 public _QUOTE_TOKEN_; + + uint112 public _BASE_RESERVE_; + uint112 public _QUOTE_RESERVE_; + uint32 public _BLOCK_TIMESTAMP_LAST_; + + uint112 public _BASE_TARGET_; + uint112 public _QUOTE_TARGET_; + uint32 public _RState_; + + uint256 public _BASE_PRICE_CUMULATIVE_LAST_; + + // ============ Variables for Pricing ============ + + IFeeRateModel public _MT_FEE_RATE_MODEL_; + + uint64 public _LP_FEE_RATE_; + uint64 public _K_; + address public _I_; + + // ============ Helper Functions ============ + + function getPMMState() public view returns (PMMPricing.PMMState memory state) { + (state.i,,,) = IOracle(_I_).getPrice(address(_BASE_TOKEN_)); + state.K = _K_; + state.B = _BASE_RESERVE_; + state.Q = _QUOTE_RESERVE_; + state.B0 = _BASE_TARGET_; + state.Q0 = _QUOTE_TARGET_; + state.R = PMMPricing.RState(_RState_); + PMMPricing.adjustedTarget(state); + } + + function getPMMStateForCall() + external + view + returns ( + uint256 i, + uint256 K, + uint256 B, + uint256 Q, + uint256 B0, + uint256 Q0, + uint256 R + ) + { + PMMPricing.PMMState memory state = getPMMState(); + i = state.i; + K = state.K; + B = state.B; + Q = state.Q; + B0 = state.B0; + Q0 = state.Q0; + R = uint256(state.R); + } + + function getMidPrice() public view returns (uint256 midPrice) { + return PMMPricing.getMidPrice(getPMMState()); + } +} diff --git a/contracts/DODOPrivatePool/impl/DPPOracle/DPPTrader.sol b/contracts/DODOPrivatePool/impl/DPPOracle/DPPTrader.sol new file mode 100644 index 0000000..6f224a4 --- /dev/null +++ b/contracts/DODOPrivatePool/impl/DPPOracle/DPPTrader.sol @@ -0,0 +1,255 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +import {DPPVault} from "./DPPVault.sol"; +import {SafeMath} from "../../../lib/SafeMath.sol"; +import {DecimalMath} from "../../../lib/DecimalMath.sol"; +import {PMMPricing} from "../../../lib/PMMPricing.sol"; +import {IDODOCallee} from "../../../intf/IDODOCallee.sol"; +import {IOracle} from "../../intf/IOracle.sol"; + +contract DPPTrader is DPPVault { + using SafeMath for uint256; + + // ============ Events ============ + + event DODOSwap( + address fromToken, + address toToken, + uint256 fromAmount, + uint256 toAmount, + address trader, + address receiver + ); + + event DODOFlashLoan( + address borrower, + address assetTo, + uint256 baseAmount, + uint256 quoteAmount + ); + + 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"); + _; + } + + // ============ Trade Functions ============ + + function sellBase(address to) + external + preventReentrant + isOraclePriceValid + returns (uint256 receiveQuoteAmount) + { + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 baseInput = baseBalance.sub(uint256(_BASE_RESERVE_)); + uint256 mtFee; + uint256 newBaseTarget; + PMMPricing.RState newRState; + (receiveQuoteAmount, mtFee, newRState, newBaseTarget) = querySellBase(tx.origin, baseInput); + + _transferQuoteOut(to, receiveQuoteAmount); + _transferQuoteOut(_MAINTAINER_, mtFee); + + // update TARGET + if (_RState_ != uint32(newRState)) { + require(newBaseTarget <= uint112(-1),"OVERFLOW"); + _BASE_TARGET_ = uint112(newBaseTarget); + _RState_ = uint32(newRState); + emit RChange(newRState); + } + + _setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this))); + + emit DODOSwap( + address(_BASE_TOKEN_), + address(_QUOTE_TOKEN_), + baseInput, + receiveQuoteAmount, + msg.sender, + to + ); + } + + function sellQuote(address to) + external + preventReentrant + isOraclePriceValid + returns (uint256 receiveBaseAmount) + { + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_)); + uint256 mtFee; + uint256 newQuoteTarget; + PMMPricing.RState newRState; + (receiveBaseAmount, mtFee, newRState, newQuoteTarget) = querySellQuote( + tx.origin, + quoteInput + ); + + _transferBaseOut(to, receiveBaseAmount); + _transferBaseOut(_MAINTAINER_, mtFee); + + // update TARGET + if (_RState_ != uint32(newRState)) { + require(newQuoteTarget <= uint112(-1),"OVERFLOW"); + _QUOTE_TARGET_ = uint112(newQuoteTarget); + _RState_ = uint32(newRState); + emit RChange(newRState); + } + + _setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance); + + emit DODOSwap( + address(_QUOTE_TOKEN_), + address(_BASE_TOKEN_), + quoteInput, + receiveBaseAmount, + msg.sender, + to + ); + } + + function flashLoan( + uint256 baseAmount, + uint256 quoteAmount, + address _assetTo, + bytes calldata data + ) external isOraclePriceValid preventReentrant { + address assetTo = _assetTo; + _transferBaseOut(assetTo, baseAmount); + _transferQuoteOut(assetTo, quoteAmount); + + if (data.length > 0) + IDODOCallee(assetTo).DPPFlashLoanCall(msg.sender, baseAmount, quoteAmount, data); + + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + + // no input -> pure loss + require( + baseBalance >= _BASE_RESERVE_ || quoteBalance >= _QUOTE_RESERVE_, + "FLASH_LOAN_FAILED" + ); + + // sell quote case + // quote input + base output + if (baseBalance < _BASE_RESERVE_) { + uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_)); + ( + uint256 receiveBaseAmount, + uint256 mtFee, + PMMPricing.RState newRState, + uint256 newQuoteTarget + ) = querySellQuote(tx.origin, quoteInput); // revert if quoteBalance 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) { + _BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed; + } + _BLOCK_TIMESTAMP_LAST_ = blockTimestamp; + } + + // ============ Set Status ============ + + function _setReserve(uint256 baseReserve, uint256 quoteReserve) internal { + require(baseReserve <= uint112(-1) && quoteReserve <= uint112(-1), "OVERFLOW"); + _BASE_RESERVE_ = uint112(baseReserve); + _QUOTE_RESERVE_ = uint112(quoteReserve); + + if(_IS_OPEN_TWAP_) _twapUpdate(); + } + + function _sync() internal { + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + + require(baseBalance <= uint112(-1) && quoteBalance <= uint112(-1), "OVERFLOW"); + + if (baseBalance != _BASE_RESERVE_) { + _BASE_RESERVE_ = uint112(baseBalance); + } + if (quoteBalance != _QUOTE_RESERVE_) { + _QUOTE_RESERVE_ = uint112(quoteBalance); + } + + if(_IS_OPEN_TWAP_) _twapUpdate(); + } + + function _resetTargetAndReserve() internal { + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + + require(baseBalance <= uint112(-1) && quoteBalance <= uint112(-1), "OVERFLOW"); + + _BASE_RESERVE_ = uint112(baseBalance); + _QUOTE_RESERVE_ = uint112(quoteBalance); + _BASE_TARGET_ = uint112(baseBalance); + _QUOTE_TARGET_ = uint112(quoteBalance); + _RState_ = uint32(PMMPricing.RState.ONE); + + if(_IS_OPEN_TWAP_) _twapUpdate(); + } + + function ratioSync() external preventReentrant onlyOwner { + uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this)); + uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this)); + + require(baseBalance <= uint112(-1) && quoteBalance <= uint112(-1), "OVERFLOW"); + + if (baseBalance != _BASE_RESERVE_) { + _BASE_TARGET_ = uint112(uint256(_BASE_TARGET_).mul(baseBalance).div(uint256(_BASE_RESERVE_))); + _BASE_RESERVE_ = uint112(baseBalance); + } + if (quoteBalance != _QUOTE_RESERVE_) { + _QUOTE_TARGET_ = uint112(uint256(_QUOTE_TARGET_).mul(quoteBalance).div(uint256(_QUOTE_RESERVE_))); + _QUOTE_RESERVE_ = uint112(quoteBalance); + } + + if(_IS_OPEN_TWAP_) _twapUpdate(); + } + + function reset( + address assetTo, + uint256 newLpFeeRate, + uint256 newK, + uint256 baseOutAmount, + uint256 quoteOutAmount, + uint256 minBaseReserve, + uint256 minQuoteReserve + ) public preventReentrant onlyOwner returns (bool) { + require( + _BASE_RESERVE_ >= minBaseReserve && _QUOTE_RESERVE_ >= minQuoteReserve, + "RESERVE_AMOUNT_IS_NOT_ENOUGH" + ); + require(newLpFeeRate <= 1e18, "LP_FEE_RATE_OUT_OF_RANGE"); + require(newK <= 1e18, "K_OUT_OF_RANGE"); + + _LP_FEE_RATE_ = uint64(newLpFeeRate); + _K_ = uint64(newK); + + _transferBaseOut(assetTo, baseOutAmount); + _transferQuoteOut(assetTo, quoteOutAmount); + _resetTargetAndReserve(); + emit LpFeeRateChange(newLpFeeRate); + return true; + } + + // ============ 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); + } + } + + function retrieve( + address to, + address token, + uint256 amount + ) external preventReentrant onlyOwner { + require(token != address(_BASE_TOKEN_) && token != address(_QUOTE_TOKEN_), "USE_RESET"); + IERC20(token).safeTransfer(to, amount); + } +} diff --git a/contracts/DODOPrivatePool/intf/IDPPOracle.sol b/contracts/DODOPrivatePool/intf/IDPPOracle.sol new file mode 100644 index 0000000..fc542b4 --- /dev/null +++ b/contracts/DODOPrivatePool/intf/IDPPOracle.sol @@ -0,0 +1,52 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; +pragma experimental ABIEncoderV2; + +interface IDPPOracle { + function init( + address owner, + address maintainer, + address baseTokenAddress, + address quoteTokenAddress, + uint256 lpFeeRate, + address mtFeeRateModel, + uint256 k, + address i, //oracle address + bool isOpenTWAP + ) external; + + function _MT_FEE_RATE_MODEL_() external returns (address); + + //=========== admin ========== + function ratioSync() external; + + function retrieve( + address payable to, + address token, + uint256 amount + ) external; + + function reset( + address assetTo, + uint256 newLpFeeRate, + uint256 newK, + uint256 baseOutAmount, + uint256 quoteOutAmount, + uint256 minBaseReserve, + uint256 minQuoteReserve + ) external returns (bool); + + + function tuneParameters( + uint256 newLpFeeRate, + uint256 newK, + uint256 minBaseReserve, + uint256 minQuoteReserve + ) external returns (bool); +} diff --git a/contracts/DODOPrivatePool/intf/IOracle.sol b/contracts/DODOPrivatePool/intf/IOracle.sol new file mode 100644 index 0000000..10271d2 --- /dev/null +++ b/contracts/DODOPrivatePool/intf/IOracle.sol @@ -0,0 +1,12 @@ +/* + + Copyright 2021 DODO ZOO. + SPDX-License-Identifier: Apache-2.0 + +*/ + +pragma solidity 0.6.9; + +interface IOracle { + function getPrice(address base) external view returns (uint256 latestPrice,bool isValid,bool isStale,uint256 timestamp); +}