restruct contract repo

This commit is contained in:
mingda
2020-10-23 01:16:52 +08:00
parent 5f10f065e4
commit e8182dd1a1
53 changed files with 775 additions and 2575 deletions

View File

@@ -1,303 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {ReentrancyGuard} from "./lib/ReentrancyGuard.sol";
import {SafeERC20} from "./lib/SafeERC20.sol";
import {SafeMath} from "./lib/SafeMath.sol";
import {IDODO} from "./intf/IDODO.sol";
import {IERC20} from "./intf/IERC20.sol";
import {IWETH} from "./intf/IWETH.sol";
interface IDODOZoo {
function getDODO(address baseToken, address quoteToken) external view returns (address);
}
/**
* @title DODO Eth Proxy
* @author DODO Breeder
*
* @notice Handle ETH-WETH converting for users.
*/
contract DODOEthProxy is ReentrancyGuard {
using SafeERC20 for IERC20;
using SafeMath for uint256;
address public _DODO_ZOO_;
address payable public _WETH_;
// ============ Events ============
event ProxySellEthToToken(
address indexed seller,
address indexed quoteToken,
uint256 payEth,
uint256 receiveToken
);
event ProxyBuyEthWithToken(
address indexed buyer,
address indexed quoteToken,
uint256 receiveEth,
uint256 payToken
);
event ProxySellTokenToEth(
address indexed seller,
address indexed baseToken,
uint256 payToken,
uint256 receiveEth
);
event ProxyBuyTokenWithEth(
address indexed buyer,
address indexed baseToken,
uint256 receiveToken,
uint256 payEth
);
event ProxyDepositEthAsBase(address indexed lp, address indexed DODO, uint256 ethAmount);
event ProxyWithdrawEthAsBase(address indexed lp, address indexed DODO, uint256 ethAmount);
event ProxyDepositEthAsQuote(address indexed lp, address indexed DODO, uint256 ethAmount);
event ProxyWithdrawEthAsQuote(address indexed lp, address indexed DODO, uint256 ethAmount);
// ============ Functions ============
constructor(address dodoZoo, address payable weth) public {
_DODO_ZOO_ = dodoZoo;
_WETH_ = weth;
}
fallback() external payable {
require(msg.sender == _WETH_, "WE_SAVED_YOUR_ETH_:)");
}
receive() external payable {
require(msg.sender == _WETH_, "WE_SAVED_YOUR_ETH_:)");
}
function sellEthToToken(
address quoteTokenAddress,
uint256 ethAmount,
uint256 minReceiveTokenAmount
) external payable preventReentrant returns (uint256 receiveTokenAmount) {
require(msg.value == ethAmount, "ETH_AMOUNT_NOT_MATCH");
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress);
require(DODO != address(0), "DODO_NOT_EXIST");
IWETH(_WETH_).deposit{value: ethAmount}();
IWETH(_WETH_).approve(DODO, ethAmount);
receiveTokenAmount = IDODO(DODO).sellBaseToken(ethAmount, minReceiveTokenAmount, "");
_transferOut(quoteTokenAddress, msg.sender, receiveTokenAmount);
emit ProxySellEthToToken(msg.sender, quoteTokenAddress, ethAmount, receiveTokenAmount);
return receiveTokenAmount;
}
function buyEthWithToken(
address quoteTokenAddress,
uint256 ethAmount,
uint256 maxPayTokenAmount
) external preventReentrant returns (uint256 payTokenAmount) {
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress);
require(DODO != address(0), "DODO_NOT_EXIST");
payTokenAmount = IDODO(DODO).queryBuyBaseToken(ethAmount);
_transferIn(quoteTokenAddress, msg.sender, payTokenAmount);
IERC20(quoteTokenAddress).safeApprove(DODO, payTokenAmount);
IDODO(DODO).buyBaseToken(ethAmount, maxPayTokenAmount, "");
IWETH(_WETH_).withdraw(ethAmount);
msg.sender.transfer(ethAmount);
emit ProxyBuyEthWithToken(msg.sender, quoteTokenAddress, ethAmount, payTokenAmount);
return payTokenAmount;
}
function sellTokenToEth(
address baseTokenAddress,
uint256 tokenAmount,
uint256 minReceiveEthAmount
) external preventReentrant returns (uint256 receiveEthAmount) {
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_);
require(DODO != address(0), "DODO_NOT_EXIST");
IERC20(baseTokenAddress).safeApprove(DODO, tokenAmount);
_transferIn(baseTokenAddress, msg.sender, tokenAmount);
receiveEthAmount = IDODO(DODO).sellBaseToken(tokenAmount, minReceiveEthAmount, "");
IWETH(_WETH_).withdraw(receiveEthAmount);
msg.sender.transfer(receiveEthAmount);
emit ProxySellTokenToEth(msg.sender, baseTokenAddress, tokenAmount, receiveEthAmount);
return receiveEthAmount;
}
function buyTokenWithEth(
address baseTokenAddress,
uint256 tokenAmount,
uint256 maxPayEthAmount
) external payable preventReentrant returns (uint256 payEthAmount) {
require(msg.value == maxPayEthAmount, "ETH_AMOUNT_NOT_MATCH");
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_);
require(DODO != address(0), "DODO_NOT_EXIST");
payEthAmount = IDODO(DODO).queryBuyBaseToken(tokenAmount);
IWETH(_WETH_).deposit{value: payEthAmount}();
IWETH(_WETH_).approve(DODO, payEthAmount);
IDODO(DODO).buyBaseToken(tokenAmount, maxPayEthAmount, "");
_transferOut(baseTokenAddress, msg.sender, tokenAmount);
uint256 refund = maxPayEthAmount.sub(payEthAmount);
if (refund > 0) {
msg.sender.transfer(refund);
}
emit ProxyBuyTokenWithEth(msg.sender, baseTokenAddress, tokenAmount, payEthAmount);
return payEthAmount;
}
function depositEthAsBase(uint256 ethAmount, address quoteTokenAddress)
external
payable
preventReentrant
{
require(msg.value == ethAmount, "ETH_AMOUNT_NOT_MATCH");
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress);
require(DODO != address(0), "DODO_NOT_EXIST");
IWETH(_WETH_).deposit{value: ethAmount}();
IWETH(_WETH_).approve(DODO, ethAmount);
IDODO(DODO).depositBaseTo(msg.sender, ethAmount);
emit ProxyDepositEthAsBase(msg.sender, DODO, ethAmount);
}
function withdrawEthAsBase(uint256 ethAmount, address quoteTokenAddress)
external
preventReentrant
returns (uint256 withdrawAmount)
{
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress);
require(DODO != address(0), "DODO_NOT_EXIST");
address ethLpToken = IDODO(DODO)._BASE_CAPITAL_TOKEN_();
// transfer all pool shares to proxy
uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender);
IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance);
IDODO(DODO).withdrawBase(ethAmount);
// transfer remain shares back to msg.sender
lpBalance = IERC20(ethLpToken).balanceOf(address(this));
IERC20(ethLpToken).transfer(msg.sender, lpBalance);
// because of withdraw penalty, withdrawAmount may not equal to ethAmount
// query weth amount first and than transfer ETH to msg.sender
uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this));
IWETH(_WETH_).withdraw(wethAmount);
msg.sender.transfer(wethAmount);
emit ProxyWithdrawEthAsBase(msg.sender, DODO, wethAmount);
return wethAmount;
}
function withdrawAllEthAsBase(address quoteTokenAddress)
external
preventReentrant
returns (uint256 withdrawAmount)
{
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(_WETH_, quoteTokenAddress);
require(DODO != address(0), "DODO_NOT_EXIST");
address ethLpToken = IDODO(DODO)._BASE_CAPITAL_TOKEN_();
// transfer all pool shares to proxy
uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender);
IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance);
IDODO(DODO).withdrawAllBase();
// because of withdraw penalty, withdrawAmount may not equal to ethAmount
// query weth amount first and than transfer ETH to msg.sender
uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this));
IWETH(_WETH_).withdraw(wethAmount);
msg.sender.transfer(wethAmount);
emit ProxyWithdrawEthAsBase(msg.sender, DODO, wethAmount);
return wethAmount;
}
function depositEthAsQuote(uint256 ethAmount, address baseTokenAddress)
external
payable
preventReentrant
{
require(msg.value == ethAmount, "ETH_AMOUNT_NOT_MATCH");
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_);
require(DODO != address(0), "DODO_NOT_EXIST");
IWETH(_WETH_).deposit{value: ethAmount}();
IWETH(_WETH_).approve(DODO, ethAmount);
IDODO(DODO).depositQuoteTo(msg.sender, ethAmount);
emit ProxyDepositEthAsQuote(msg.sender, DODO, ethAmount);
}
function withdrawEthAsQuote(uint256 ethAmount, address baseTokenAddress)
external
preventReentrant
returns (uint256 withdrawAmount)
{
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_);
require(DODO != address(0), "DODO_NOT_EXIST");
address ethLpToken = IDODO(DODO)._QUOTE_CAPITAL_TOKEN_();
// transfer all pool shares to proxy
uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender);
IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance);
IDODO(DODO).withdrawQuote(ethAmount);
// transfer remain shares back to msg.sender
lpBalance = IERC20(ethLpToken).balanceOf(address(this));
IERC20(ethLpToken).transfer(msg.sender, lpBalance);
// because of withdraw penalty, withdrawAmount may not equal to ethAmount
// query weth amount first and than transfer ETH to msg.sender
uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this));
IWETH(_WETH_).withdraw(wethAmount);
msg.sender.transfer(wethAmount);
emit ProxyWithdrawEthAsQuote(msg.sender, DODO, wethAmount);
return wethAmount;
}
function withdrawAllEthAsQuote(address baseTokenAddress)
external
preventReentrant
returns (uint256 withdrawAmount)
{
address DODO = IDODOZoo(_DODO_ZOO_).getDODO(baseTokenAddress, _WETH_);
require(DODO != address(0), "DODO_NOT_EXIST");
address ethLpToken = IDODO(DODO)._QUOTE_CAPITAL_TOKEN_();
// transfer all pool shares to proxy
uint256 lpBalance = IERC20(ethLpToken).balanceOf(msg.sender);
IERC20(ethLpToken).transferFrom(msg.sender, address(this), lpBalance);
IDODO(DODO).withdrawAllQuote();
// because of withdraw penalty, withdrawAmount may not equal to ethAmount
// query weth amount first and than transfer ETH to msg.sender
uint256 wethAmount = IERC20(_WETH_).balanceOf(address(this));
IWETH(_WETH_).withdraw(wethAmount);
msg.sender.transfer(wethAmount);
emit ProxyWithdrawEthAsQuote(msg.sender, DODO, wethAmount);
return wethAmount;
}
// ============ Helper Functions ============
function _transferIn(
address tokenAddress,
address from,
uint256 amount
) internal {
IERC20(tokenAddress).safeTransferFrom(from, address(this), amount);
}
function _transferOut(
address tokenAddress,
address to,
uint256 amount
) internal {
IERC20(tokenAddress).safeTransfer(to, amount);
}
}

View File

@@ -0,0 +1,51 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {Ownable} from "../lib/Ownable.sol";
import {ICloneFactory} from "../lib/CloneFactory.sol";
import {DVMVault} from "./impl/DVMVault.sol";
import {DVMController} from "./impl/DVMController.sol";
contract DVMFactory is Ownable {
address public _CLONE_FACTORY_;
address public _VAULT_TEMPLATE_;
address public _CONTROLLER_TEMPLATE_;
function createDODOVenderMachine(
address maintainer,
address baseToken,
address quoteToken,
address lpFeeRateModel,
address mtFeeRateModel,
uint256 i,
uint256 k,
uint256 gasPriceLimit
) external returns (address newVenderMachine) {
DVMController controller = DVMController(
ICloneFactory(_CLONE_FACTORY_).clone(_CONTROLLER_TEMPLATE_)
);
DVMVault vault = DVMVault(ICloneFactory(_CLONE_FACTORY_).clone(_VAULT_TEMPLATE_));
vault.init(address(controller), baseToken, quoteToken); // vault owner is controller
controller.init(
msg.sender,
maintainer,
address(vault),
lpFeeRateModel,
mtFeeRateModel,
i,
k,
gasPriceLimit
);
newVenderMachine = address(controller);
return newVenderMachine;
}
}

View File

@@ -0,0 +1,19 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {DVMStorage} from "./DVMStorage.sol";
contract DVMAdmin is DVMStorage{
function setI(uint256 newI) external onlyOwner{}
function setK(uint256 newK) external onlyOwner{}
}

View File

@@ -0,0 +1,38 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {DVMTrader} from "./DVMTrader.sol";
import {DVMFunding} from "./DVMFunding.sol";
import {DVMAdmin} from "./DVMAdmin.sol";
import {DVMVault} from "./DVMVault.sol";
import {IFeeRateModel} from "../../intf/IFeeRateModel.sol";
contract DVMController is DVMTrader, DVMFunding, DVMAdmin {
function init(
address owner,
address maintainer,
address vault,
address lpFeeRateModel,
address mtFeeRateModel,
uint256 i,
uint256 k,
uint256 gasPriceLimit
) external {
initOwner(owner);
_MAINTAINER_ = maintainer;
_BASE_TOKEN_ = DVMVault(vault)._BASE_TOKEN_();
_QUOTE_TOKEN_ = DVMVault(vault)._QUOTE_TOKEN_();
_LP_FEE_RATE_MODEL_ = IFeeRateModel(lpFeeRateModel);
_MT_FEE_RATE_MODEL_ = IFeeRateModel(mtFeeRateModel);
_I_ = i;
_K_ = k;
_GAS_PRICE_LIMIT_ = gasPriceLimit;
}
}

View File

@@ -0,0 +1,69 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {DVMStorage} from "./DVMStorage.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
contract DVMFunding is DVMStorage {
function buyShares(address account) external returns (uint256) {
uint256 baseInput = _VAULT_.getBaseInput();
uint256 quoteInput = _VAULT_.getQuoteInput();
require(baseInput > 0, "NO_BASE_INPUT");
uint256 baseReserve = _VAULT_._BASE_RESERVE_();
uint256 quoteReserve = _VAULT_._QUOTE_RESERVE_();
uint256 mintAmount;
// case 1. initial supply
if (baseReserve == 0 && quoteReserve == 0) {
mintAmount = baseInput;
}
// case 2. supply when quote reserve is 0
if (baseReserve > 0 && quoteReserve == 0) {
uint256 mintRatio = DecimalMath.divFloor(baseInput, baseReserve);
mintAmount = DecimalMath.mulFloor(_VAULT_.totalSupply(), mintRatio);
}
// case 3. normal case
if (baseReserve > 0 && quoteReserve > 0) {
uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve);
uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve);
uint256 mintRatio = baseInputRatio > quoteInputRatio ? quoteInputRatio : baseInputRatio;
// 在提币的时候向下取整。因此永远不会出现balance为0但totalsupply不为0的情况
// 但有可能出现reserve>0但totalSupply=0的场景
uint256 totalShare = _VAULT_.totalSupply();
if (totalShare > 0) {
mintAmount = DecimalMath.mulFloor(totalShare, mintRatio);
} else {
mintAmount = baseInput;
}
}
_VAULT_.mint(account, mintAmount);
_VAULT_.sync();
}
function sellShares(
address account,
address to,
uint256 amount
) external returns (uint256) {
require(msg.sender == account, "PERMISSION_DENY");
require(_VAULT_.balanceOf(account) >= amount, "SHARES_NOT_ENOUGH");
(uint256 baseBalance, uint256 quoteBalance) = _VAULT_.getVaultBalance();
uint256 totalShares = _VAULT_.totalSupply();
_VAULT_.burn(account, amount);
_VAULT_.transferBaseOut(to, baseBalance.mul(amount).div(totalShares));
_VAULT_.transferQuoteOut(to, quoteBalance.mul(amount).div(totalShares));
_VAULT_.sync();
}
}

View File

@@ -0,0 +1,75 @@
/*
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 {DODOMath} from "../../lib/DODOMath.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {PermissionManager} from "../../lib/PermissionManager.sol";
import {IFeeRateModel} from "../../intf/IFeeRateModel.sol";
import {DVMVault} from "./DVMVault.sol";
contract DVMStorage is InitializableOwnable, ReentrancyGuard {
using SafeMath for uint256;
// ============ Variables for Control ============
bool public _CLOSED_;
uint256 public _GAS_PRICE_LIMIT_;
// ============ Advanced Controls ============
bool public _BUYING_ALLOWED_;
bool public _SELLING_ALLOWED_;
PermissionManager public _TRADE_PERMISSION_;
PermissionManager public _FUNDING_PERMISSION_;
// ============ Core Address ============
address public _MAINTAINER_; // collect maintainer fee
address public _BASE_TOKEN_;
address public _QUOTE_TOKEN_;
// ============ Variables for Pricing ============
IFeeRateModel public _LP_FEE_RATE_MODEL_;
IFeeRateModel public _MT_FEE_RATE_MODEL_;
uint256 public _K_;
uint256 public _I_;
uint256 public _BASE0_;
DVMVault public _VAULT_;
DVMVault public _PROTECTION_VAULT_;
// ============ Modifiers ============
modifier notClosed() {
require(!_CLOSED_, "DODO_CLOSED");
_;
}
// ============ Helper Functions ============
function _updateBase0() internal {
uint256 fairAmount = DecimalMath.divFloor(_VAULT_._QUOTE_RESERVE_(), _I_);
_BASE0_ = DODOMath._SolveQuadraticFunctionForTarget(
_VAULT_._BASE_RESERVE_(),
_K_,
fairAmount
);
}
// ============ Version Control ============
function version() external pure returns (uint256) {
return 101; // 1.0.1
}
}

View File

@@ -0,0 +1,69 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {DVMStorage} from "./DVMStorage.sol";
import {SafeMath} from "../../lib/SafeMath.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {DODOMath} from "../../lib/DODOMath.sol";
contract DVMTrader is DVMStorage {
using SafeMath for uint256;
function sellBase (address to) external returns(uint256 receiveQuoteAmount){
uint256 baseInput = _VAULT_.getBaseInput();
uint256 mtFee;
(receiveQuoteAmount, mtFee) = querySellBase(baseInput);
_VAULT_.transferQuoteOut(to, receiveQuoteAmount);
if (mtFee>0){
_VAULT_.transferQuoteOut(_MAINTAINER_, mtFee);
}
_VAULT_.sync();
_updateBase0(); // 这里需要想想原则上不需要update B0. 但精度问题或者用户往合约里充值可能导致需要updateBase0
return receiveQuoteAmount;
}
function sellQuote(address to) external returns(uint256 receiveBaseAmount){
uint256 quoteInput = _VAULT_.getQuoteInput();
uint256 mtFee;
(receiveBaseAmount, mtFee) = querySellQuote(quoteInput);
_VAULT_.transferBaseOut(to, receiveBaseAmount);
if (mtFee>0){
_VAULT_.transferBaseOut(_MAINTAINER_, mtFee);
}
_VAULT_.sync();
_updateBase0();
return receiveBaseAmount;
}
function querySellBase(uint256 payBaseAmount) public view returns(uint256 receiveQuoteAmount, uint256 mtFee){
uint256 B2 = _VAULT_._BASE_RESERVE_();
uint256 B1 = B2.add(payBaseAmount);
require(_BASE0_>=B1, "DODO_BASE_BALANCE_NOT_ENOUGH");
uint256 Q = DODOMath._GeneralIntegrate(_BASE0_, B1, B2, _I_, _K_);
uint256 lpFeeRate = _LP_FEE_RATE_MODEL_.getFeeRate(Q);
uint256 mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(Q);
mtFee = DecimalMath.mulCeil(Q, mtFeeRate);
receiveQuoteAmount = Q.sub(mtFee).sub(DecimalMath.mulCeil(Q, lpFeeRate));
return (receiveQuoteAmount, mtFee);
}
function querySellQuote(uint256 payQuoteAmount) public view returns(uint256 receiveBaseAmount, uint256 mtFee){
uint256 B1 = _VAULT_._BASE_RESERVE_();
uint256 fairAmount = DecimalMath.divFloor(payQuoteAmount, _I_);
uint256 newBaseReserve = DODOMath._SolveQuadraticFunctionForTrade(_BASE0_,B1,fairAmount,false, _K_);
uint256 deltaBase = B1.sub(newBaseReserve);
uint256 lpFeeRate = _LP_FEE_RATE_MODEL_.getFeeRate(payQuoteAmount);
uint256 mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(payQuoteAmount);
mtFee = DecimalMath.mulCeil(deltaBase, mtFeeRate);
receiveBaseAmount = deltaBase.sub(mtFee).sub(DecimalMath.mulCeil(deltaBase, lpFeeRate));
return (receiveBaseAmount, mtFee);
}
}

View File

@@ -0,0 +1,205 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {IERC20} from "../../intf/IERC20.sol";
import {SafeMath} from "../../lib/SafeMath.sol";
import {DecimalMath} from "../../lib/DecimalMath.sol";
import {SafeERC20} from "../../lib/SafeERC20.sol";
import {Ownable} from "../../lib/Ownable.sol";
import {InitializableOwnable} from "../../lib/InitializableOwnable.sol";
contract DVMVault is InitializableOwnable {
using SafeMath for uint256;
using SafeERC20 for IERC20;
address public _BASE_TOKEN_;
address public _QUOTE_TOKEN_;
uint256 public _BASE_RESERVE_;
uint256 public _QUOTE_RESERVE_;
string public symbol;
uint256 public decimals;
string public name;
uint256 public totalSupply;
mapping(address => uint256) internal _SHARES_;
mapping(address => mapping(address => uint256)) internal _ALLOWED_;
// ============ Events ============
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
event Mint(address indexed user, uint256 value);
event Burn(address indexed user, uint256 value);
// init functions
function init(
address owner,
address _baseToken,
address _quoteToken
) public notInitialized {
initOwner(owner);
string memory connect = "_";
string memory suffix = "DLP";
string memory uid = string(abi.encodePacked(address(this)));
name = string(
abi.encodePacked(
suffix,
connect,
IERC20(_baseToken).symbol(),
connect,
IERC20(_quoteToken).symbol(),
connect,
uid
)
);
symbol = "DLP";
decimals = IERC20(_baseToken).decimals();
}
// Vault related
function getVaultBalance() public view returns (uint256 baseBalance, uint256 quoteBalance) {
return (
IERC20(_BASE_TOKEN_).balanceOf(address(this)),
IERC20(_QUOTE_TOKEN_).balanceOf(address(this))
);
}
function getBaseBalance() public view returns (uint256 baseBalance) {
return IERC20(_BASE_TOKEN_).balanceOf(address(this));
}
function getQuoteBalance() public view returns (uint256 quoteBalance) {
return IERC20(_QUOTE_TOKEN_).balanceOf(address(this));
}
function getBaseInput() public view returns (uint256 input) {
return IERC20(_BASE_TOKEN_).balanceOf(address(this)).sub(_BASE_RESERVE_);
}
function getQuoteInput() public view returns (uint256 input) {
return IERC20(_QUOTE_TOKEN_).balanceOf(address(this)).sub(_QUOTE_RESERVE_);
}
function sync() public onlyOwner {
(uint256 baseBalance, uint256 quoteBalance) = getVaultBalance();
if (baseBalance != _BASE_RESERVE_) {
_BASE_RESERVE_ = baseBalance;
}
if (quoteBalance != _QUOTE_RESERVE_) {
_QUOTE_RESERVE_ = quoteBalance;
}
}
function transferOut(
address token,
address to,
uint256 amount
) public onlyOwner {
IERC20(token).safeTransfer(to, amount);
}
function transferBaseOut(address to, uint256 amount) public onlyOwner {
IERC20(_BASE_TOKEN_).safeTransfer(to, amount);
}
function transferQuoteOut(address to, uint256 amount) public onlyOwner {
IERC20(_QUOTE_TOKEN_).safeTransfer(to, amount);
}
// Shares related
/**
* @dev transfer token for a specified address
* @param to The address to transfer to.
* @param amount The amount to be transferred.
*/
function transfer(address to, uint256 amount) public returns (bool) {
require(amount <= _SHARES_[msg.sender], "BALANCE_NOT_ENOUGH");
_SHARES_[msg.sender] = _SHARES_[msg.sender].sub(amount);
_SHARES_[to] = _SHARES_[to].add(amount);
emit Transfer(msg.sender, to, amount);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the the balance of.
* @return balance An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) external view returns (uint256 balance) {
return _SHARES_[owner];
}
function shareRatioOf(address owner) external view returns (uint256 shareRatio) {
return DecimalMath.divFloor(_SHARES_[owner], totalSupply);
}
/**
* @dev Transfer tokens from one address to another
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param amount uint256 the amount of tokens to be transferred
*/
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
require(amount <= _SHARES_[from], "BALANCE_NOT_ENOUGH");
require(amount <= _ALLOWED_[from][msg.sender], "ALLOWANCE_NOT_ENOUGH");
_SHARES_[from] = _SHARES_[from].sub(amount);
_SHARES_[to] = _SHARES_[to].add(amount);
_ALLOWED_[from][msg.sender] = _ALLOWED_[from][msg.sender].sub(amount);
emit Transfer(from, to, amount);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param spender The address which will spend the funds.
* @param amount The amount of tokens to be spent.
*/
function approve(address spender, uint256 amount) public returns (bool) {
_ALLOWED_[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
/**
* @dev Function to check the amount of tokens that an owner _ALLOWED_ to a spender.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _ALLOWED_[owner][spender];
}
function mint(address user, uint256 value) external onlyOwner {
_SHARES_[user] = _SHARES_[user].add(value);
totalSupply = totalSupply.add(value);
emit Mint(user, value);
emit Transfer(address(0), user, value);
}
function burn(address user, uint256 value) external onlyOwner {
_SHARES_[user] = _SHARES_[user].sub(value);
totalSupply = totalSupply.sub(value);
emit Burn(user, value);
emit Transfer(user, address(0), value);
}
}

View File

@@ -1,134 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {Ownable} from "./lib/Ownable.sol";
import {IDODO} from "./intf/IDODO.sol";
import {ICloneFactory} from "./helper/CloneFactory.sol";
/**
* @title DODOZoo
* @author DODO Breeder
*
* @notice Register of All DODO
*/
contract DODOZoo is Ownable {
address public _DODO_LOGIC_;
address public _CLONE_FACTORY_;
address public _DEFAULT_SUPERVISOR_;
mapping(address => mapping(address => address)) internal _DODO_REGISTER_;
address[] public _DODOs;
// ============ Events ============
event DODOBirth(address newBorn, address baseToken, address quoteToken);
// ============ Constructor Function ============
constructor(
address _dodoLogic,
address _cloneFactory,
address _defaultSupervisor
) public {
_DODO_LOGIC_ = _dodoLogic;
_CLONE_FACTORY_ = _cloneFactory;
_DEFAULT_SUPERVISOR_ = _defaultSupervisor;
}
// ============ Admin Function ============
function setDODOLogic(address _dodoLogic) external onlyOwner {
_DODO_LOGIC_ = _dodoLogic;
}
function setCloneFactory(address _cloneFactory) external onlyOwner {
_CLONE_FACTORY_ = _cloneFactory;
}
function setDefaultSupervisor(address _defaultSupervisor) external onlyOwner {
_DEFAULT_SUPERVISOR_ = _defaultSupervisor;
}
function removeDODO(address dodo) external onlyOwner {
address baseToken = IDODO(dodo)._BASE_TOKEN_();
address quoteToken = IDODO(dodo)._QUOTE_TOKEN_();
require(isDODORegistered(baseToken, quoteToken), "DODO_NOT_REGISTERED");
_DODO_REGISTER_[baseToken][quoteToken] = address(0);
for (uint256 i = 0; i <= _DODOs.length - 1; i++) {
if (_DODOs[i] == dodo) {
_DODOs[i] = _DODOs[_DODOs.length - 1];
_DODOs.pop();
break;
}
}
}
function addDODO(address dodo) public onlyOwner {
address baseToken = IDODO(dodo)._BASE_TOKEN_();
address quoteToken = IDODO(dodo)._QUOTE_TOKEN_();
require(!isDODORegistered(baseToken, quoteToken), "DODO_REGISTERED");
_DODO_REGISTER_[baseToken][quoteToken] = dodo;
_DODOs.push(dodo);
}
// ============ Breed DODO Function ============
function breedDODO(
address maintainer,
address baseToken,
address quoteToken,
address oracle,
uint256 lpFeeRate,
uint256 mtFeeRate,
uint256 k,
uint256 gasPriceLimit
) external onlyOwner returns (address newBornDODO) {
require(!isDODORegistered(baseToken, quoteToken), "DODO_REGISTERED");
newBornDODO = ICloneFactory(_CLONE_FACTORY_).clone(_DODO_LOGIC_);
IDODO(newBornDODO).init(
_OWNER_,
_DEFAULT_SUPERVISOR_,
maintainer,
baseToken,
quoteToken,
oracle,
lpFeeRate,
mtFeeRate,
k,
gasPriceLimit
);
addDODO(newBornDODO);
emit DODOBirth(newBornDODO, baseToken, quoteToken);
return newBornDODO;
}
// ============ View Functions ============
function isDODORegistered(address baseToken, address quoteToken) public view returns (bool) {
if (
_DODO_REGISTER_[baseToken][quoteToken] == address(0) &&
_DODO_REGISTER_[quoteToken][baseToken] == address(0)
) {
return false;
} else {
return true;
}
}
function getDODO(address baseToken, address quoteToken) external view returns (address) {
return _DODO_REGISTER_[baseToken][quoteToken];
}
function getDODOs() external view returns (address[] memory) {
return _DODOs;
}
}

View File

@@ -1,74 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {Types} from "./lib/Types.sol";
import {IERC20} from "./intf/IERC20.sol";
import {Storage} from "./impl/Storage.sol";
import {Trader} from "./impl/Trader.sol";
import {LiquidityProvider} from "./impl/LiquidityProvider.sol";
import {Admin} from "./impl/Admin.sol";
import {DODOLpToken} from "./impl/DODOLpToken.sol";
/**
* @title DODO
* @author DODO Breeder
*
* @notice Entrance for users
*/
contract DODO is Admin, Trader, LiquidityProvider {
function init(
address owner,
address supervisor,
address maintainer,
address baseToken,
address quoteToken,
address oracle,
uint256 lpFeeRate,
uint256 mtFeeRate,
uint256 k,
uint256 gasPriceLimit
) external {
require(!_INITIALIZED_, "DODO_INITIALIZED");
_INITIALIZED_ = true;
// constructor
_OWNER_ = owner;
emit OwnershipTransferred(address(0), _OWNER_);
_SUPERVISOR_ = supervisor;
_MAINTAINER_ = maintainer;
_BASE_TOKEN_ = baseToken;
_QUOTE_TOKEN_ = quoteToken;
_ORACLE_ = oracle;
_DEPOSIT_BASE_ALLOWED_ = false;
_DEPOSIT_QUOTE_ALLOWED_ = false;
_TRADE_ALLOWED_ = false;
_GAS_PRICE_LIMIT_ = gasPriceLimit;
// Advanced controls are disabled by default
_BUYING_ALLOWED_ = true;
_SELLING_ALLOWED_ = true;
uint256 MAX_INT = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
_BASE_BALANCE_LIMIT_ = MAX_INT;
_QUOTE_BALANCE_LIMIT_ = MAX_INT;
_LP_FEE_RATE_ = lpFeeRate;
_MT_FEE_RATE_ = mtFeeRate;
_K_ = k;
_R_STATUS_ = Types.RStatus.ONE;
_BASE_CAPITAL_TOKEN_ = address(new DODOLpToken(_BASE_TOKEN_));
_QUOTE_CAPITAL_TOKEN_ = address(new DODOLpToken(_QUOTE_TOKEN_));
_checkDODOParameters();
}
}

View File

@@ -7,7 +7,7 @@
pragma solidity 0.6.9;
import {SafeMath} from "../lib/SafeMath.sol";
import {SafeMath} from "../../lib/SafeMath.sol";
contract TestERC20 {
using SafeMath for uint256;

View File

@@ -1,5 +1,6 @@
/**
*Submitted for verification at Etherscan.io on 2020-05-05
/*
Submitted for verification at Etherscan.io on 2020-05-05
SPDX-License-Identifier: Apache-2.0
*/
// File: contracts/interfaces/IUniswapV2Pair.sol
@@ -184,7 +185,8 @@ contract UniswapV2ERC20 {
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
bytes32
public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint256) public nonces;
event Approval(address indexed owner, address indexed spender, uint256 value);

View File

@@ -1,30 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IBandOracleAggregator {
function getReferenceData(string memory base, string memory quote)
external
view
returns (uint256);
}
contract BandBNBBUSDPriceOracleProxy {
IBandOracleAggregator public aggregator;
constructor(IBandOracleAggregator _aggregator) public {
aggregator = _aggregator;
}
function getPrice() public view returns (uint256) {
return aggregator.getReferenceData("BNB", "USD");
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for COMP-USDC(decimals=6) price convert
contract ChainlinkCOMPUSDCPriceOracleProxy {
address public chainlink = 0xdbd020CAeF83eFd542f4De03e3cF0C28A4428bd5;
function getPrice() external view returns (uint256) {
return IChainlink(chainlink).latestAnswer() / 100;
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for WETH-USDC(decimals=6) price convert
contract ChainlinkETHPriceOracleProxy {
address public chainlink = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;
function getPrice() external view returns (uint256) {
return IChainlink(chainlink).latestAnswer() / 100;
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for WETH-USDT(decimals=6) price convert
contract ChainlinkETHUSDTPriceOracleProxy {
address public chainlink = 0xEe9F2375b4bdF6387aa8265dD4FB8F16512A1d46;
function getPrice() external view returns (uint256) {
return 10**24 / IChainlink(chainlink).latestAnswer();
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for LEND-USDC(decimals=6) price convert
contract ChainlinkLENDUSDCPriceOracleProxy {
address public chainlink = 0x4aB81192BB75474Cf203B56c36D6a13623270A67;
function getPrice() external view returns (uint256) {
return IChainlink(chainlink).latestAnswer() / 100;
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for LINK-USDC(decimals=6) price convert
contract ChainlinkLINKUSDCPriceOracleProxy {
address public chainlink = 0x2c1d072e956AFFC0D435Cb7AC38EF18d24d9127c;
function getPrice() external view returns (uint256) {
return IChainlink(chainlink).latestAnswer() / 100;
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for SNX-USDC(decimals=6) price convert
contract ChainlinkSNXUSDCPriceOracleProxy {
address public chainlink = 0xDC3EA94CD0AC27d9A86C180091e7f78C683d3699;
function getPrice() external view returns (uint256) {
return IChainlink(chainlink).latestAnswer() / 100;
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for WBTC(decimals=8)-USDC(decimals=6) price convert
contract ChainlinkWBTCUSDCPriceOracleProxy {
address public chainlink = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
function getPrice() external view returns (uint256) {
return IChainlink(chainlink).latestAnswer() * (10**8);
}
}

View File

@@ -1,32 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {SafeMath} from "../lib/SafeMath.sol";
interface IChainlink {
function latestAnswer() external view returns (uint256);
}
// for YFI-USDC(decimals=6) price convert
contract ChainlinkYFIUSDCPriceOracleProxy {
using SafeMath for uint256;
address public yfiEth = 0x7c5d4F8345e66f68099581Db340cd65B078C41f4;
address public EthUsd = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;
function getPrice() external view returns (uint256) {
uint256 yfiEthPrice = IChainlink(yfiEth).latestAnswer();
uint256 EthUsdPrice = IChainlink(EthUsd).latestAnswer();
return yfiEthPrice.mul(EthUsdPrice).div(10**20);
}
}

View File

@@ -0,0 +1,23 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {IFeeRateModel} from "../intf/IFeeRateModel.sol";
contract ConstFeeRateModel is IFeeRateModel {
uint256 public _FEE_RATE_;
constructor(uint256 feeRate) public {
feeRate = _FEE_RATE_;
}
function getFeeRate(uint256) external override view returns (uint256) {
return _FEE_RATE_;
}
}

View File

@@ -1,22 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
contract ConstOracle {
uint256 public tokenPrice;
constructor(uint256 _price) public {
tokenPrice = _price;
}
function getPrice() external view returns (uint256) {
return tokenPrice;
}
}

View File

@@ -1,56 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IMinimumOracle {
function getPrice() external view returns (uint256);
function setPrice(uint256 newPrice) external;
function transferOwnership(address newOwner) external;
}
contract MinimumOracle {
address public _OWNER_;
uint256 public tokenPrice;
// ============ Events ============
event OwnershipTransfer(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
constructor() public {
_OWNER_ = msg.sender;
emit OwnershipTransfer(address(0), _OWNER_);
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "INVALID_OWNER");
emit OwnershipTransfer(_OWNER_, newOwner);
_OWNER_ = newOwner;
}
function setPrice(uint256 newPrice) external onlyOwner {
tokenPrice = newPrice;
}
function getPrice() external view returns (uint256) {
return tokenPrice;
}
}

View File

@@ -0,0 +1,28 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {IFeeRateModel} from "../intf/IFeeRateModel.sol";
import {Ownable} from "../lib/Ownable.sol";
contract ConstFeeRateModel is Ownable, IFeeRateModel {
uint256 public _FEE_RATE_;
constructor(uint256 feeRate) public {
feeRate = _FEE_RATE_;
}
function setFeeRate(uint256 newFeeRate) external {
_FEE_RATE_ = newFeeRate;
}
function getFeeRate(uint256) external override view returns (uint256) {
return _FEE_RATE_;
}
}

View File

@@ -1,25 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {Ownable} from "../lib/Ownable.sol";
// Oracle only for test
contract NaiveOracle is Ownable {
uint256 public tokenPrice;
function setPrice(uint256 newPrice) external onlyOwner {
tokenPrice = newPrice;
}
function getPrice() external view returns (uint256) {
return tokenPrice;
}
}

View File

@@ -1,165 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {Ownable} from "../lib/Ownable.sol";
import {IDODO} from "../intf/IDODO.sol";
import {IERC20} from "../intf/IERC20.sol";
import {SafeERC20} from "../lib/SafeERC20.sol";
import {SafeMath} from "../lib/SafeMath.sol";
interface IUniswapV2Pair {
function token0() external view returns (address);
function token1() external view returns (address);
function getReserves()
external
view
returns (
uint112 reserve0,
uint112 reserve1,
uint32 blockTimestampLast
);
function swap(
uint256 amount0Out,
uint256 amount1Out,
address to,
bytes calldata data
) external;
}
contract UniswapArbitrageur {
using SafeMath for uint256;
using SafeERC20 for IERC20;
address public _UNISWAP_;
address public _DODO_;
address public _BASE_;
address public _QUOTE_;
bool public _REVERSE_; // true if dodo.baseToken=uniswap.token0
constructor(address _uniswap, address _dodo) public {
_UNISWAP_ = _uniswap;
_DODO_ = _dodo;
_BASE_ = IDODO(_DODO_)._BASE_TOKEN_();
_QUOTE_ = IDODO(_DODO_)._QUOTE_TOKEN_();
address token0 = IUniswapV2Pair(_UNISWAP_).token0();
address token1 = IUniswapV2Pair(_UNISWAP_).token1();
if (token0 == _BASE_ && token1 == _QUOTE_) {
_REVERSE_ = false;
} else if (token0 == _QUOTE_ && token1 == _BASE_) {
_REVERSE_ = true;
} else {
require(true, "DODO_UNISWAP_NOT_MATCH");
}
IERC20(_BASE_).approve(_DODO_, uint256(-1));
IERC20(_QUOTE_).approve(_DODO_, uint256(-1));
}
function executeBuyArbitrage(uint256 baseAmount) external returns (uint256 quoteProfit) {
IDODO(_DODO_).buyBaseToken(baseAmount, uint256(-1), "0xd");
quoteProfit = IERC20(_QUOTE_).balanceOf(address(this));
IERC20(_QUOTE_).transfer(msg.sender, quoteProfit);
return quoteProfit;
}
function executeSellArbitrage(uint256 baseAmount) external returns (uint256 baseProfit) {
IDODO(_DODO_).sellBaseToken(baseAmount, 0, "0xd");
baseProfit = IERC20(_BASE_).balanceOf(address(this));
IERC20(_BASE_).transfer(msg.sender, baseProfit);
return baseProfit;
}
function dodoCall(
bool isDODOBuy,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata
) external {
require(msg.sender == _DODO_, "WRONG_DODO");
if (_REVERSE_) {
_inverseArbitrage(isDODOBuy, baseAmount, quoteAmount);
} else {
_arbitrage(isDODOBuy, baseAmount, quoteAmount);
}
}
function _inverseArbitrage(
bool isDODOBuy,
uint256 baseAmount,
uint256 quoteAmount
) internal {
(uint112 _reserve0, uint112 _reserve1, ) = IUniswapV2Pair(_UNISWAP_).getReserves();
uint256 token0Balance = uint256(_reserve0);
uint256 token1Balance = uint256(_reserve1);
uint256 token0Amount;
uint256 token1Amount;
if (isDODOBuy) {
IERC20(_BASE_).transfer(_UNISWAP_, baseAmount);
// transfer token1 into uniswap
uint256 newToken0Balance = token0Balance.mul(token1Balance).div(
token1Balance.add(baseAmount)
);
token0Amount = token0Balance.sub(newToken0Balance).mul(9969).div(10000); // mul 0.9969
require(token0Amount > quoteAmount, "NOT_PROFITABLE");
IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), "");
} else {
IERC20(_QUOTE_).transfer(_UNISWAP_, quoteAmount);
// transfer token0 into uniswap
uint256 newToken1Balance = token0Balance.mul(token1Balance).div(
token0Balance.add(quoteAmount)
);
token1Amount = token1Balance.sub(newToken1Balance).mul(9969).div(10000); // mul 0.9969
require(token1Amount > baseAmount, "NOT_PROFITABLE");
IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), "");
}
}
function _arbitrage(
bool isDODOBuy,
uint256 baseAmount,
uint256 quoteAmount
) internal {
(uint112 _reserve0, uint112 _reserve1, ) = IUniswapV2Pair(_UNISWAP_).getReserves();
uint256 token0Balance = uint256(_reserve0);
uint256 token1Balance = uint256(_reserve1);
uint256 token0Amount;
uint256 token1Amount;
if (isDODOBuy) {
IERC20(_BASE_).transfer(_UNISWAP_, baseAmount);
// transfer token0 into uniswap
uint256 newToken1Balance = token1Balance.mul(token0Balance).div(
token0Balance.add(baseAmount)
);
token1Amount = token1Balance.sub(newToken1Balance).mul(9969).div(10000); // mul 0.9969
require(token1Amount > quoteAmount, "NOT_PROFITABLE");
IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), "");
} else {
IERC20(_QUOTE_).transfer(_UNISWAP_, quoteAmount);
// transfer token1 into uniswap
uint256 newToken0Balance = token1Balance.mul(token0Balance).div(
token1Balance.add(quoteAmount)
);
token0Amount = token0Balance.sub(newToken0Balance).mul(9969).div(10000); // mul 0.9969
require(token0Amount > baseAmount, "NOT_PROFITABLE");
IUniswapV2Pair(_UNISWAP_).swap(token0Amount, token1Amount, address(this), "");
}
}
function retrieve(address token, uint256 amount) external {
IERC20(token).safeTransfer(msg.sender, amount);
}
}

View File

@@ -1,122 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {Storage} from "./Storage.sol";
/**
* @title Admin
* @author DODO Breeder
*
* @notice Functions for admin operations
*/
contract Admin is Storage {
// ============ Events ============
event UpdateGasPriceLimit(uint256 oldGasPriceLimit, uint256 newGasPriceLimit);
event UpdateLiquidityProviderFeeRate(
uint256 oldLiquidityProviderFeeRate,
uint256 newLiquidityProviderFeeRate
);
event UpdateMaintainerFeeRate(uint256 oldMaintainerFeeRate, uint256 newMaintainerFeeRate);
event UpdateK(uint256 oldK, uint256 newK);
// ============ Params Setting Functions ============
function setOracle(address newOracle) external onlyOwner {
_ORACLE_ = newOracle;
}
function setSupervisor(address newSupervisor) external onlyOwner {
_SUPERVISOR_ = newSupervisor;
}
function setMaintainer(address newMaintainer) external onlyOwner {
_MAINTAINER_ = newMaintainer;
}
function setLiquidityProviderFeeRate(uint256 newLiquidityPorviderFeeRate) external onlyOwner {
emit UpdateLiquidityProviderFeeRate(_LP_FEE_RATE_, newLiquidityPorviderFeeRate);
_LP_FEE_RATE_ = newLiquidityPorviderFeeRate;
_checkDODOParameters();
}
function setMaintainerFeeRate(uint256 newMaintainerFeeRate) external onlyOwner {
emit UpdateMaintainerFeeRate(_MT_FEE_RATE_, newMaintainerFeeRate);
_MT_FEE_RATE_ = newMaintainerFeeRate;
_checkDODOParameters();
}
function setK(uint256 newK) external onlyOwner {
emit UpdateK(_K_, newK);
_K_ = newK;
_checkDODOParameters();
}
function setGasPriceLimit(uint256 newGasPriceLimit) external onlySupervisorOrOwner {
emit UpdateGasPriceLimit(_GAS_PRICE_LIMIT_, newGasPriceLimit);
_GAS_PRICE_LIMIT_ = newGasPriceLimit;
}
// ============ System Control Functions ============
function disableTrading() external onlySupervisorOrOwner {
_TRADE_ALLOWED_ = false;
}
function enableTrading() external onlyOwner notClosed {
_TRADE_ALLOWED_ = true;
}
function disableQuoteDeposit() external onlySupervisorOrOwner {
_DEPOSIT_QUOTE_ALLOWED_ = false;
}
function enableQuoteDeposit() external onlyOwner notClosed {
_DEPOSIT_QUOTE_ALLOWED_ = true;
}
function disableBaseDeposit() external onlySupervisorOrOwner {
_DEPOSIT_BASE_ALLOWED_ = false;
}
function enableBaseDeposit() external onlyOwner notClosed {
_DEPOSIT_BASE_ALLOWED_ = true;
}
// ============ Advanced Control Functions ============
function disableBuying() external onlySupervisorOrOwner {
_BUYING_ALLOWED_ = false;
}
function enableBuying() external onlyOwner notClosed {
_BUYING_ALLOWED_ = true;
}
function disableSelling() external onlySupervisorOrOwner {
_SELLING_ALLOWED_ = false;
}
function enableSelling() external onlyOwner notClosed {
_SELLING_ALLOWED_ = true;
}
function setBaseBalanceLimit(uint256 newBaseBalanceLimit) external onlyOwner notClosed {
_BASE_BALANCE_LIMIT_ = newBaseBalanceLimit;
}
function setQuoteBalanceLimit(uint256 newQuoteBalanceLimit) external onlyOwner notClosed {
_QUOTE_BALANCE_LIMIT_ = newQuoteBalanceLimit;
}
}

View File

@@ -1,134 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {IERC20} from "../intf/IERC20.sol";
import {SafeMath} from "../lib/SafeMath.sol";
import {Ownable} from "../lib/Ownable.sol";
/**
* @title DODOLpToken
* @author DODO Breeder
*
* @notice Tokenize liquidity pool assets. An ordinary ERC20 contract with mint and burn functions
*/
contract DODOLpToken is Ownable {
using SafeMath for uint256;
string public symbol = "DLP";
address public originToken;
uint256 public totalSupply;
mapping(address => uint256) internal balances;
mapping(address => mapping(address => uint256)) internal allowed;
// ============ Events ============
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
event Mint(address indexed user, uint256 value);
event Burn(address indexed user, uint256 value);
// ============ Functions ============
constructor(address _originToken) public {
originToken = _originToken;
}
function name() public view returns (string memory) {
string memory lpTokenSuffix = "_DODO_LP_TOKEN_";
return string(abi.encodePacked(IERC20(originToken).name(), lpTokenSuffix));
}
function decimals() public view returns (uint8) {
return IERC20(originToken).decimals();
}
/**
* @dev transfer token for a specified address
* @param to The address to transfer to.
* @param amount The amount to be transferred.
*/
function transfer(address to, uint256 amount) public returns (bool) {
require(amount <= balances[msg.sender], "BALANCE_NOT_ENOUGH");
balances[msg.sender] = balances[msg.sender].sub(amount);
balances[to] = balances[to].add(amount);
emit Transfer(msg.sender, to, amount);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the the balance of.
* @return balance An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) external view returns (uint256 balance) {
return balances[owner];
}
/**
* @dev Transfer tokens from one address to another
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param amount uint256 the amount of tokens to be transferred
*/
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
require(amount <= balances[from], "BALANCE_NOT_ENOUGH");
require(amount <= allowed[from][msg.sender], "ALLOWANCE_NOT_ENOUGH");
balances[from] = balances[from].sub(amount);
balances[to] = balances[to].add(amount);
allowed[from][msg.sender] = allowed[from][msg.sender].sub(amount);
emit Transfer(from, to, amount);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param spender The address which will spend the funds.
* @param amount The amount of tokens to be spent.
*/
function approve(address spender, uint256 amount) public returns (bool) {
allowed[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return allowed[owner][spender];
}
function mint(address user, uint256 value) external onlyOwner {
balances[user] = balances[user].add(value);
totalSupply = totalSupply.add(value);
emit Mint(user, value);
emit Transfer(address(0), user, value);
}
function burn(address user, uint256 value) external onlyOwner {
balances[user] = balances[user].sub(value);
totalSupply = totalSupply.sub(value);
emit Burn(user, value);
emit Transfer(user, address(0), value);
}
}

View File

@@ -1,345 +0,0 @@
/*
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 {DODOMath} from "../lib/DODOMath.sol";
import {Types} from "../lib/Types.sol";
import {IDODOLpToken} from "../intf/IDODOLpToken.sol";
import {Storage} from "./Storage.sol";
import {Settlement} from "./Settlement.sol";
import {Pricing} from "./Pricing.sol";
/**
* @title LiquidityProvider
* @author DODO Breeder
*
* @notice Functions for liquidity provider operations
*/
contract LiquidityProvider is Storage, Pricing, Settlement {
using SafeMath for uint256;
// ============ Events ============
event Deposit(
address indexed payer,
address indexed receiver,
bool isBaseToken,
uint256 amount,
uint256 lpTokenAmount
);
event Withdraw(
address indexed payer,
address indexed receiver,
bool isBaseToken,
uint256 amount,
uint256 lpTokenAmount
);
event ChargePenalty(address indexed payer, bool isBaseToken, uint256 amount);
// ============ Modifiers ============
modifier depositQuoteAllowed() {
require(_DEPOSIT_QUOTE_ALLOWED_, "DEPOSIT_QUOTE_NOT_ALLOWED");
_;
}
modifier depositBaseAllowed() {
require(_DEPOSIT_BASE_ALLOWED_, "DEPOSIT_BASE_NOT_ALLOWED");
_;
}
modifier dodoNotClosed() {
require(!_CLOSED_, "DODO_CLOSED");
_;
}
// ============ Routine Functions ============
function withdrawBase(uint256 amount) external returns (uint256) {
return withdrawBaseTo(msg.sender, amount);
}
function depositBase(uint256 amount) external returns (uint256) {
return depositBaseTo(msg.sender, amount);
}
function withdrawQuote(uint256 amount) external returns (uint256) {
return withdrawQuoteTo(msg.sender, amount);
}
function depositQuote(uint256 amount) external returns (uint256) {
return depositQuoteTo(msg.sender, amount);
}
function withdrawAllBase() external returns (uint256) {
return withdrawAllBaseTo(msg.sender);
}
function withdrawAllQuote() external returns (uint256) {
return withdrawAllQuoteTo(msg.sender);
}
// ============ Deposit Functions ============
function depositQuoteTo(address to, uint256 amount)
public
preventReentrant
depositQuoteAllowed
returns (uint256)
{
(, uint256 quoteTarget) = getExpectedTarget();
uint256 totalQuoteCapital = getTotalQuoteCapital();
uint256 capital = amount;
if (totalQuoteCapital == 0) {
// give remaining quote token to lp as a gift
capital = amount.add(quoteTarget);
} else if (quoteTarget > 0) {
capital = amount.mul(totalQuoteCapital).div(quoteTarget);
}
// settlement
_quoteTokenTransferIn(msg.sender, amount);
_mintQuoteCapital(to, capital);
_TARGET_QUOTE_TOKEN_AMOUNT_ = _TARGET_QUOTE_TOKEN_AMOUNT_.add(amount);
emit Deposit(msg.sender, to, false, amount, capital);
return capital;
}
function depositBaseTo(address to, uint256 amount)
public
preventReentrant
depositBaseAllowed
returns (uint256)
{
(uint256 baseTarget, ) = getExpectedTarget();
uint256 totalBaseCapital = getTotalBaseCapital();
uint256 capital = amount;
if (totalBaseCapital == 0) {
// give remaining base token to lp as a gift
capital = amount.add(baseTarget);
} else if (baseTarget > 0) {
capital = amount.mul(totalBaseCapital).div(baseTarget);
}
// settlement
_baseTokenTransferIn(msg.sender, amount);
_mintBaseCapital(to, capital);
_TARGET_BASE_TOKEN_AMOUNT_ = _TARGET_BASE_TOKEN_AMOUNT_.add(amount);
emit Deposit(msg.sender, to, true, amount, capital);
return capital;
}
// ============ Withdraw Functions ============
function withdrawQuoteTo(address to, uint256 amount)
public
preventReentrant
dodoNotClosed
returns (uint256)
{
// calculate capital
(, uint256 quoteTarget) = getExpectedTarget();
uint256 totalQuoteCapital = getTotalQuoteCapital();
require(totalQuoteCapital > 0, "NO_QUOTE_LP");
uint256 requireQuoteCapital = amount.mul(totalQuoteCapital).divCeil(quoteTarget);
require(
requireQuoteCapital <= getQuoteCapitalBalanceOf(msg.sender),
"LP_QUOTE_CAPITAL_BALANCE_NOT_ENOUGH"
);
// handle penalty, penalty may exceed amount
uint256 penalty = getWithdrawQuotePenalty(amount);
require(penalty < amount, "PENALTY_EXCEED");
// settlement
_TARGET_QUOTE_TOKEN_AMOUNT_ = _TARGET_QUOTE_TOKEN_AMOUNT_.sub(amount);
_burnQuoteCapital(msg.sender, requireQuoteCapital);
_quoteTokenTransferOut(to, amount.sub(penalty));
_donateQuoteToken(penalty);
emit Withdraw(msg.sender, to, false, amount.sub(penalty), requireQuoteCapital);
emit ChargePenalty(msg.sender, false, penalty);
return amount.sub(penalty);
}
function withdrawBaseTo(address to, uint256 amount)
public
preventReentrant
dodoNotClosed
returns (uint256)
{
// calculate capital
(uint256 baseTarget, ) = getExpectedTarget();
uint256 totalBaseCapital = getTotalBaseCapital();
require(totalBaseCapital > 0, "NO_BASE_LP");
uint256 requireBaseCapital = amount.mul(totalBaseCapital).divCeil(baseTarget);
require(
requireBaseCapital <= getBaseCapitalBalanceOf(msg.sender),
"LP_BASE_CAPITAL_BALANCE_NOT_ENOUGH"
);
// handle penalty, penalty may exceed amount
uint256 penalty = getWithdrawBasePenalty(amount);
require(penalty <= amount, "PENALTY_EXCEED");
// settlement
_TARGET_BASE_TOKEN_AMOUNT_ = _TARGET_BASE_TOKEN_AMOUNT_.sub(amount);
_burnBaseCapital(msg.sender, requireBaseCapital);
_baseTokenTransferOut(to, amount.sub(penalty));
_donateBaseToken(penalty);
emit Withdraw(msg.sender, to, true, amount.sub(penalty), requireBaseCapital);
emit ChargePenalty(msg.sender, true, penalty);
return amount.sub(penalty);
}
// ============ Withdraw all Functions ============
function withdrawAllQuoteTo(address to)
public
preventReentrant
dodoNotClosed
returns (uint256)
{
uint256 withdrawAmount = getLpQuoteBalance(msg.sender);
uint256 capital = getQuoteCapitalBalanceOf(msg.sender);
// handle penalty, penalty may exceed amount
uint256 penalty = getWithdrawQuotePenalty(withdrawAmount);
require(penalty <= withdrawAmount, "PENALTY_EXCEED");
// settlement
_TARGET_QUOTE_TOKEN_AMOUNT_ = _TARGET_QUOTE_TOKEN_AMOUNT_.sub(withdrawAmount);
_burnQuoteCapital(msg.sender, capital);
_quoteTokenTransferOut(to, withdrawAmount.sub(penalty));
_donateQuoteToken(penalty);
emit Withdraw(msg.sender, to, false, withdrawAmount, capital);
emit ChargePenalty(msg.sender, false, penalty);
return withdrawAmount.sub(penalty);
}
function withdrawAllBaseTo(address to) public preventReentrant dodoNotClosed returns (uint256) {
uint256 withdrawAmount = getLpBaseBalance(msg.sender);
uint256 capital = getBaseCapitalBalanceOf(msg.sender);
// handle penalty, penalty may exceed amount
uint256 penalty = getWithdrawBasePenalty(withdrawAmount);
require(penalty <= withdrawAmount, "PENALTY_EXCEED");
// settlement
_TARGET_BASE_TOKEN_AMOUNT_ = _TARGET_BASE_TOKEN_AMOUNT_.sub(withdrawAmount);
_burnBaseCapital(msg.sender, capital);
_baseTokenTransferOut(to, withdrawAmount.sub(penalty));
_donateBaseToken(penalty);
emit Withdraw(msg.sender, to, true, withdrawAmount, capital);
emit ChargePenalty(msg.sender, true, penalty);
return withdrawAmount.sub(penalty);
}
// ============ Helper Functions ============
function _mintBaseCapital(address user, uint256 amount) internal {
IDODOLpToken(_BASE_CAPITAL_TOKEN_).mint(user, amount);
}
function _mintQuoteCapital(address user, uint256 amount) internal {
IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).mint(user, amount);
}
function _burnBaseCapital(address user, uint256 amount) internal {
IDODOLpToken(_BASE_CAPITAL_TOKEN_).burn(user, amount);
}
function _burnQuoteCapital(address user, uint256 amount) internal {
IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).burn(user, amount);
}
// ============ Getter Functions ============
function getLpBaseBalance(address lp) public view returns (uint256 lpBalance) {
uint256 totalBaseCapital = getTotalBaseCapital();
(uint256 baseTarget, ) = getExpectedTarget();
if (totalBaseCapital == 0) {
return 0;
}
lpBalance = getBaseCapitalBalanceOf(lp).mul(baseTarget).div(totalBaseCapital);
return lpBalance;
}
function getLpQuoteBalance(address lp) public view returns (uint256 lpBalance) {
uint256 totalQuoteCapital = getTotalQuoteCapital();
(, uint256 quoteTarget) = getExpectedTarget();
if (totalQuoteCapital == 0) {
return 0;
}
lpBalance = getQuoteCapitalBalanceOf(lp).mul(quoteTarget).div(totalQuoteCapital);
return lpBalance;
}
function getWithdrawQuotePenalty(uint256 amount) public view returns (uint256 penalty) {
require(amount <= _QUOTE_BALANCE_, "DODO_QUOTE_BALANCE_NOT_ENOUGH");
if (_R_STATUS_ == Types.RStatus.BELOW_ONE) {
uint256 spareBase = _BASE_BALANCE_.sub(_TARGET_BASE_TOKEN_AMOUNT_);
uint256 price = getOraclePrice();
uint256 fairAmount = DecimalMath.mul(spareBase, price);
uint256 targetQuote = DODOMath._SolveQuadraticFunctionForTarget(
_QUOTE_BALANCE_,
_K_,
fairAmount
);
// if amount = _QUOTE_BALANCE_, div error
uint256 targetQuoteWithWithdraw = DODOMath._SolveQuadraticFunctionForTarget(
_QUOTE_BALANCE_.sub(amount),
_K_,
fairAmount
);
return targetQuote.sub(targetQuoteWithWithdraw.add(amount));
} else {
return 0;
}
}
function getWithdrawBasePenalty(uint256 amount) public view returns (uint256 penalty) {
require(amount <= _BASE_BALANCE_, "DODO_BASE_BALANCE_NOT_ENOUGH");
if (_R_STATUS_ == Types.RStatus.ABOVE_ONE) {
uint256 spareQuote = _QUOTE_BALANCE_.sub(_TARGET_QUOTE_TOKEN_AMOUNT_);
uint256 price = getOraclePrice();
uint256 fairAmount = DecimalMath.divFloor(spareQuote, price);
uint256 targetBase = DODOMath._SolveQuadraticFunctionForTarget(
_BASE_BALANCE_,
_K_,
fairAmount
);
// if amount = _BASE_BALANCE_, div error
uint256 targetBaseWithWithdraw = DODOMath._SolveQuadraticFunctionForTarget(
_BASE_BALANCE_.sub(amount),
_K_,
fairAmount
);
return targetBase.sub(targetBaseWithWithdraw.add(amount));
} else {
return 0;
}
}
}

View File

@@ -1,198 +0,0 @@
/*
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 {DODOMath} from "../lib/DODOMath.sol";
import {Types} from "../lib/Types.sol";
import {Storage} from "./Storage.sol";
/**
* @title Pricing
* @author DODO Breeder
*
* @notice DODO Pricing model
*/
contract Pricing is Storage {
using SafeMath for uint256;
// ============ R = 1 cases ============
function _ROneSellBaseToken(uint256 amount, uint256 targetQuoteTokenAmount)
internal
view
returns (uint256 receiveQuoteToken)
{
uint256 i = getOraclePrice();
uint256 Q2 = DODOMath._SolveQuadraticFunctionForTrade(
targetQuoteTokenAmount,
targetQuoteTokenAmount,
DecimalMath.mul(i, amount),
false,
_K_
);
// in theory Q2 <= targetQuoteTokenAmount
// however when amount is close to 0, precision problems may cause Q2 > targetQuoteTokenAmount
return targetQuoteTokenAmount.sub(Q2);
}
function _ROneBuyBaseToken(uint256 amount, uint256 targetBaseTokenAmount)
internal
view
returns (uint256 payQuoteToken)
{
require(amount < targetBaseTokenAmount, "DODO_BASE_BALANCE_NOT_ENOUGH");
uint256 B2 = targetBaseTokenAmount.sub(amount);
payQuoteToken = _RAboveIntegrate(targetBaseTokenAmount, targetBaseTokenAmount, B2);
return payQuoteToken;
}
// ============ R < 1 cases ============
function _RBelowSellBaseToken(
uint256 amount,
uint256 quoteBalance,
uint256 targetQuoteAmount
) internal view returns (uint256 receieQuoteToken) {
uint256 i = getOraclePrice();
uint256 Q2 = DODOMath._SolveQuadraticFunctionForTrade(
targetQuoteAmount,
quoteBalance,
DecimalMath.mul(i, amount),
false,
_K_
);
return quoteBalance.sub(Q2);
}
function _RBelowBuyBaseToken(
uint256 amount,
uint256 quoteBalance,
uint256 targetQuoteAmount
) internal view returns (uint256 payQuoteToken) {
// Here we don't require amount less than some value
// Because it is limited at upper function
// See Trader.queryBuyBaseToken
uint256 i = getOraclePrice();
uint256 Q2 = DODOMath._SolveQuadraticFunctionForTrade(
targetQuoteAmount,
quoteBalance,
DecimalMath.mulCeil(i, amount),
true,
_K_
);
return Q2.sub(quoteBalance);
}
function _RBelowBackToOne() internal view returns (uint256 payQuoteToken) {
// important: carefully design the system to make sure spareBase always greater than or equal to 0
uint256 spareBase = _BASE_BALANCE_.sub(_TARGET_BASE_TOKEN_AMOUNT_);
uint256 price = getOraclePrice();
uint256 fairAmount = DecimalMath.mul(spareBase, price);
uint256 newTargetQuote = DODOMath._SolveQuadraticFunctionForTarget(
_QUOTE_BALANCE_,
_K_,
fairAmount
);
return newTargetQuote.sub(_QUOTE_BALANCE_);
}
// ============ R > 1 cases ============
function _RAboveBuyBaseToken(
uint256 amount,
uint256 baseBalance,
uint256 targetBaseAmount
) internal view returns (uint256 payQuoteToken) {
require(amount < baseBalance, "DODO_BASE_BALANCE_NOT_ENOUGH");
uint256 B2 = baseBalance.sub(amount);
return _RAboveIntegrate(targetBaseAmount, baseBalance, B2);
}
function _RAboveSellBaseToken(
uint256 amount,
uint256 baseBalance,
uint256 targetBaseAmount
) internal view returns (uint256 receiveQuoteToken) {
// here we don't require B1 <= targetBaseAmount
// Because it is limited at upper function
// See Trader.querySellBaseToken
uint256 B1 = baseBalance.add(amount);
return _RAboveIntegrate(targetBaseAmount, B1, baseBalance);
}
function _RAboveBackToOne() internal view returns (uint256 payBaseToken) {
// important: carefully design the system to make sure spareBase always greater than or equal to 0
uint256 spareQuote = _QUOTE_BALANCE_.sub(_TARGET_QUOTE_TOKEN_AMOUNT_);
uint256 price = getOraclePrice();
uint256 fairAmount = DecimalMath.divFloor(spareQuote, price);
uint256 newTargetBase = DODOMath._SolveQuadraticFunctionForTarget(
_BASE_BALANCE_,
_K_,
fairAmount
);
return newTargetBase.sub(_BASE_BALANCE_);
}
// ============ Helper functions ============
function getExpectedTarget() public view returns (uint256 baseTarget, uint256 quoteTarget) {
uint256 Q = _QUOTE_BALANCE_;
uint256 B = _BASE_BALANCE_;
if (_R_STATUS_ == Types.RStatus.ONE) {
return (_TARGET_BASE_TOKEN_AMOUNT_, _TARGET_QUOTE_TOKEN_AMOUNT_);
} else if (_R_STATUS_ == Types.RStatus.BELOW_ONE) {
uint256 payQuoteToken = _RBelowBackToOne();
return (_TARGET_BASE_TOKEN_AMOUNT_, Q.add(payQuoteToken));
} else if (_R_STATUS_ == Types.RStatus.ABOVE_ONE) {
uint256 payBaseToken = _RAboveBackToOne();
return (B.add(payBaseToken), _TARGET_QUOTE_TOKEN_AMOUNT_);
}
}
function getMidPrice() public view returns (uint256 midPrice) {
(uint256 baseTarget, uint256 quoteTarget) = getExpectedTarget();
if (_R_STATUS_ == Types.RStatus.BELOW_ONE) {
uint256 R = DecimalMath.divFloor(
quoteTarget.mul(quoteTarget).div(_QUOTE_BALANCE_),
_QUOTE_BALANCE_
);
R = DecimalMath.ONE.sub(_K_).add(DecimalMath.mul(_K_, R));
return DecimalMath.divFloor(getOraclePrice(), R);
} else {
uint256 R = DecimalMath.divFloor(
baseTarget.mul(baseTarget).div(_BASE_BALANCE_),
_BASE_BALANCE_
);
R = DecimalMath.ONE.sub(_K_).add(DecimalMath.mul(_K_, R));
return DecimalMath.mul(getOraclePrice(), R);
}
}
function _RAboveIntegrate(
uint256 B0,
uint256 B1,
uint256 B2
) internal view returns (uint256) {
uint256 i = getOraclePrice();
return DODOMath._GeneralIntegrate(B0, B1, B2, i, _K_);
}
// function _RBelowIntegrate(
// uint256 Q0,
// uint256 Q1,
// uint256 Q2
// ) internal view returns (uint256) {
// uint256 i = getOraclePrice();
// i = DecimalMath.divFloor(DecimalMath.ONE, i); // 1/i
// return DODOMath._GeneralIntegrate(Q0, Q1, Q2, i, _K_);
// }
}

View File

@@ -1,163 +0,0 @@
/*
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 {Types} from "../lib/Types.sol";
import {IDODOLpToken} from "../intf/IDODOLpToken.sol";
import {IERC20} from "../intf/IERC20.sol";
import {Storage} from "./Storage.sol";
/**
* @title Settlement
* @author DODO Breeder
*
* @notice Functions for assets settlement
*/
contract Settlement is Storage {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// ============ Events ============
event Donate(uint256 amount, bool isBaseToken);
event ClaimAssets(address indexed user, uint256 baseTokenAmount, uint256 quoteTokenAmount);
// ============ Assets IN/OUT Functions ============
function _baseTokenTransferIn(address from, uint256 amount) internal {
require(_BASE_BALANCE_.add(amount) <= _BASE_BALANCE_LIMIT_, "BASE_BALANCE_LIMIT_EXCEEDED");
IERC20(_BASE_TOKEN_).safeTransferFrom(from, address(this), amount);
_BASE_BALANCE_ = _BASE_BALANCE_.add(amount);
}
function _quoteTokenTransferIn(address from, uint256 amount) internal {
require(
_QUOTE_BALANCE_.add(amount) <= _QUOTE_BALANCE_LIMIT_,
"QUOTE_BALANCE_LIMIT_EXCEEDED"
);
IERC20(_QUOTE_TOKEN_).safeTransferFrom(from, address(this), amount);
_QUOTE_BALANCE_ = _QUOTE_BALANCE_.add(amount);
}
function _baseTokenTransferOut(address to, uint256 amount) internal {
IERC20(_BASE_TOKEN_).safeTransfer(to, amount);
_BASE_BALANCE_ = _BASE_BALANCE_.sub(amount);
}
function _quoteTokenTransferOut(address to, uint256 amount) internal {
IERC20(_QUOTE_TOKEN_).safeTransfer(to, amount);
_QUOTE_BALANCE_ = _QUOTE_BALANCE_.sub(amount);
}
// ============ Donate to Liquidity Pool Functions ============
function _donateBaseToken(uint256 amount) internal {
_TARGET_BASE_TOKEN_AMOUNT_ = _TARGET_BASE_TOKEN_AMOUNT_.add(amount);
emit Donate(amount, true);
}
function _donateQuoteToken(uint256 amount) internal {
_TARGET_QUOTE_TOKEN_AMOUNT_ = _TARGET_QUOTE_TOKEN_AMOUNT_.add(amount);
emit Donate(amount, false);
}
function donateBaseToken(uint256 amount) external preventReentrant {
_baseTokenTransferIn(msg.sender, amount);
_donateBaseToken(amount);
}
function donateQuoteToken(uint256 amount) external preventReentrant {
_quoteTokenTransferIn(msg.sender, amount);
_donateQuoteToken(amount);
}
// ============ Final Settlement Functions ============
// last step to shut down dodo
function finalSettlement() external onlyOwner notClosed {
_CLOSED_ = true;
_DEPOSIT_QUOTE_ALLOWED_ = false;
_DEPOSIT_BASE_ALLOWED_ = false;
_TRADE_ALLOWED_ = false;
uint256 totalBaseCapital = getTotalBaseCapital();
uint256 totalQuoteCapital = getTotalQuoteCapital();
if (_QUOTE_BALANCE_ > _TARGET_QUOTE_TOKEN_AMOUNT_) {
uint256 spareQuote = _QUOTE_BALANCE_.sub(_TARGET_QUOTE_TOKEN_AMOUNT_);
_BASE_CAPITAL_RECEIVE_QUOTE_ = DecimalMath.divFloor(spareQuote, totalBaseCapital);
} else {
_TARGET_QUOTE_TOKEN_AMOUNT_ = _QUOTE_BALANCE_;
}
if (_BASE_BALANCE_ > _TARGET_BASE_TOKEN_AMOUNT_) {
uint256 spareBase = _BASE_BALANCE_.sub(_TARGET_BASE_TOKEN_AMOUNT_);
_QUOTE_CAPITAL_RECEIVE_BASE_ = DecimalMath.divFloor(spareBase, totalQuoteCapital);
} else {
_TARGET_BASE_TOKEN_AMOUNT_ = _BASE_BALANCE_;
}
_R_STATUS_ = Types.RStatus.ONE;
}
// claim remaining assets after final settlement
function claimAssets() external preventReentrant {
require(_CLOSED_, "DODO_NOT_CLOSED");
require(!_CLAIMED_[msg.sender], "ALREADY_CLAIMED");
_CLAIMED_[msg.sender] = true;
uint256 quoteCapital = getQuoteCapitalBalanceOf(msg.sender);
uint256 baseCapital = getBaseCapitalBalanceOf(msg.sender);
uint256 quoteAmount = 0;
if (quoteCapital > 0) {
quoteAmount = _TARGET_QUOTE_TOKEN_AMOUNT_.mul(quoteCapital).div(getTotalQuoteCapital());
}
uint256 baseAmount = 0;
if (baseCapital > 0) {
baseAmount = _TARGET_BASE_TOKEN_AMOUNT_.mul(baseCapital).div(getTotalBaseCapital());
}
_TARGET_QUOTE_TOKEN_AMOUNT_ = _TARGET_QUOTE_TOKEN_AMOUNT_.sub(quoteAmount);
_TARGET_BASE_TOKEN_AMOUNT_ = _TARGET_BASE_TOKEN_AMOUNT_.sub(baseAmount);
quoteAmount = quoteAmount.add(DecimalMath.mul(baseCapital, _BASE_CAPITAL_RECEIVE_QUOTE_));
baseAmount = baseAmount.add(DecimalMath.mul(quoteCapital, _QUOTE_CAPITAL_RECEIVE_BASE_));
_baseTokenTransferOut(msg.sender, baseAmount);
_quoteTokenTransferOut(msg.sender, quoteAmount);
IDODOLpToken(_BASE_CAPITAL_TOKEN_).burn(msg.sender, baseCapital);
IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).burn(msg.sender, quoteCapital);
emit ClaimAssets(msg.sender, baseAmount, quoteAmount);
return;
}
// in case someone transfer to contract directly
function retrieve(address token, uint256 amount) external onlyOwner {
if (token == _BASE_TOKEN_) {
require(
IERC20(_BASE_TOKEN_).balanceOf(address(this)) >= _BASE_BALANCE_.add(amount),
"DODO_BASE_BALANCE_NOT_ENOUGH"
);
}
if (token == _QUOTE_TOKEN_) {
require(
IERC20(_QUOTE_TOKEN_).balanceOf(address(this)) >= _QUOTE_BALANCE_.add(amount),
"DODO_QUOTE_BALANCE_NOT_ENOUGH"
);
}
IERC20(token).safeTransfer(msg.sender, amount);
}
}

View File

@@ -1,118 +0,0 @@
/*
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 {SafeMath} from "../lib/SafeMath.sol";
import {DecimalMath} from "../lib/DecimalMath.sol";
import {ReentrancyGuard} from "../lib/ReentrancyGuard.sol";
import {IOracle} from "../intf/IOracle.sol";
import {IDODOLpToken} from "../intf/IDODOLpToken.sol";
import {Types} from "../lib/Types.sol";
/**
* @title Storage
* @author DODO Breeder
*
* @notice Local Variables
*/
contract Storage is InitializableOwnable, ReentrancyGuard {
using SafeMath for uint256;
// ============ Variables for Control ============
bool internal _INITIALIZED_;
bool public _CLOSED_;
bool public _DEPOSIT_QUOTE_ALLOWED_;
bool public _DEPOSIT_BASE_ALLOWED_;
bool public _TRADE_ALLOWED_;
uint256 public _GAS_PRICE_LIMIT_;
// ============ Advanced Controls ============
bool public _BUYING_ALLOWED_;
bool public _SELLING_ALLOWED_;
uint256 public _BASE_BALANCE_LIMIT_;
uint256 public _QUOTE_BALANCE_LIMIT_;
// ============ Core Address ============
address public _SUPERVISOR_; // could freeze system in emergency
address public _MAINTAINER_; // collect maintainer fee to buy food for DODO
address public _BASE_TOKEN_;
address public _QUOTE_TOKEN_;
address public _ORACLE_;
// ============ Variables for PMM Algorithm ============
uint256 public _LP_FEE_RATE_;
uint256 public _MT_FEE_RATE_;
uint256 public _K_;
Types.RStatus public _R_STATUS_;
uint256 public _TARGET_BASE_TOKEN_AMOUNT_;
uint256 public _TARGET_QUOTE_TOKEN_AMOUNT_;
uint256 public _BASE_BALANCE_;
uint256 public _QUOTE_BALANCE_;
address public _BASE_CAPITAL_TOKEN_;
address public _QUOTE_CAPITAL_TOKEN_;
// ============ Variables for Final Settlement ============
uint256 public _BASE_CAPITAL_RECEIVE_QUOTE_;
uint256 public _QUOTE_CAPITAL_RECEIVE_BASE_;
mapping(address => bool) public _CLAIMED_;
// ============ Modifiers ============
modifier onlySupervisorOrOwner() {
require(msg.sender == _SUPERVISOR_ || msg.sender == _OWNER_, "NOT_SUPERVISOR_OR_OWNER");
_;
}
modifier notClosed() {
require(!_CLOSED_, "DODO_CLOSED");
_;
}
// ============ Helper Functions ============
function _checkDODOParameters() internal view returns (uint256) {
require(_K_ < DecimalMath.ONE, "K>=1");
require(_K_ > 0, "K=0");
require(_LP_FEE_RATE_.add(_MT_FEE_RATE_) < DecimalMath.ONE, "FEE_RATE>=1");
}
function getOraclePrice() public view returns (uint256) {
return IOracle(_ORACLE_).getPrice();
}
function getBaseCapitalBalanceOf(address lp) public view returns (uint256) {
return IDODOLpToken(_BASE_CAPITAL_TOKEN_).balanceOf(lp);
}
function getTotalBaseCapital() public view returns (uint256) {
return IDODOLpToken(_BASE_CAPITAL_TOKEN_).totalSupply();
}
function getQuoteCapitalBalanceOf(address lp) public view returns (uint256) {
return IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).balanceOf(lp);
}
function getTotalQuoteCapital() public view returns (uint256) {
return IDODOLpToken(_QUOTE_CAPITAL_TOKEN_).totalSupply();
}
// ============ Version Control ============
function version() external pure returns (uint256) {
return 101; // 1.0.1
}
}

View File

@@ -1,274 +0,0 @@
/*
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 {Types} from "../lib/Types.sol";
import {IDODOCallee} from "../intf/IDODOCallee.sol";
import {Storage} from "./Storage.sol";
import {Pricing} from "./Pricing.sol";
import {Settlement} from "./Settlement.sol";
/**
* @title Trader
* @author DODO Breeder
*
* @notice Functions for trader operations
*/
contract Trader is Storage, Pricing, Settlement {
using SafeMath for uint256;
// ============ Events ============
event SellBaseToken(address indexed seller, uint256 payBase, uint256 receiveQuote);
event BuyBaseToken(address indexed buyer, uint256 receiveBase, uint256 payQuote);
event ChargeMaintainerFee(address indexed maintainer, bool isBaseToken, uint256 amount);
// ============ Modifiers ============
modifier tradeAllowed() {
require(_TRADE_ALLOWED_, "TRADE_NOT_ALLOWED");
_;
}
modifier buyingAllowed() {
require(_BUYING_ALLOWED_, "BUYING_NOT_ALLOWED");
_;
}
modifier sellingAllowed() {
require(_SELLING_ALLOWED_, "SELLING_NOT_ALLOWED");
_;
}
modifier gasPriceLimit() {
require(tx.gasprice <= _GAS_PRICE_LIMIT_, "GAS_PRICE_EXCEED");
_;
}
// ============ Trade Functions ============
function sellBaseToken(
uint256 amount,
uint256 minReceiveQuote,
bytes calldata data
) external tradeAllowed sellingAllowed gasPriceLimit preventReentrant returns (uint256) {
// query price
(
uint256 receiveQuote,
uint256 lpFeeQuote,
uint256 mtFeeQuote,
Types.RStatus newRStatus,
uint256 newQuoteTarget,
uint256 newBaseTarget
) = _querySellBaseToken(amount);
require(receiveQuote >= minReceiveQuote, "SELL_BASE_RECEIVE_NOT_ENOUGH");
// settle assets
_quoteTokenTransferOut(msg.sender, receiveQuote);
if (data.length > 0) {
IDODOCallee(msg.sender).dodoCall(false, amount, receiveQuote, data);
}
_baseTokenTransferIn(msg.sender, amount);
if (mtFeeQuote != 0) {
_quoteTokenTransferOut(_MAINTAINER_, mtFeeQuote);
emit ChargeMaintainerFee(_MAINTAINER_, false, mtFeeQuote);
}
// update TARGET
if (_TARGET_QUOTE_TOKEN_AMOUNT_ != newQuoteTarget) {
_TARGET_QUOTE_TOKEN_AMOUNT_ = newQuoteTarget;
}
if (_TARGET_BASE_TOKEN_AMOUNT_ != newBaseTarget) {
_TARGET_BASE_TOKEN_AMOUNT_ = newBaseTarget;
}
if (_R_STATUS_ != newRStatus) {
_R_STATUS_ = newRStatus;
}
_donateQuoteToken(lpFeeQuote);
emit SellBaseToken(msg.sender, amount, receiveQuote);
return receiveQuote;
}
function buyBaseToken(
uint256 amount,
uint256 maxPayQuote,
bytes calldata data
) external tradeAllowed buyingAllowed gasPriceLimit preventReentrant returns (uint256) {
// query price
(
uint256 payQuote,
uint256 lpFeeBase,
uint256 mtFeeBase,
Types.RStatus newRStatus,
uint256 newQuoteTarget,
uint256 newBaseTarget
) = _queryBuyBaseToken(amount);
require(payQuote <= maxPayQuote, "BUY_BASE_COST_TOO_MUCH");
// settle assets
_baseTokenTransferOut(msg.sender, amount);
if (data.length > 0) {
IDODOCallee(msg.sender).dodoCall(true, amount, payQuote, data);
}
_quoteTokenTransferIn(msg.sender, payQuote);
if (mtFeeBase != 0) {
_baseTokenTransferOut(_MAINTAINER_, mtFeeBase);
emit ChargeMaintainerFee(_MAINTAINER_, true, mtFeeBase);
}
// update TARGET
if (_TARGET_QUOTE_TOKEN_AMOUNT_ != newQuoteTarget) {
_TARGET_QUOTE_TOKEN_AMOUNT_ = newQuoteTarget;
}
if (_TARGET_BASE_TOKEN_AMOUNT_ != newBaseTarget) {
_TARGET_BASE_TOKEN_AMOUNT_ = newBaseTarget;
}
if (_R_STATUS_ != newRStatus) {
_R_STATUS_ = newRStatus;
}
_donateBaseToken(lpFeeBase);
emit BuyBaseToken(msg.sender, amount, payQuote);
return payQuote;
}
// ============ Query Functions ============
function querySellBaseToken(uint256 amount) external view returns (uint256 receiveQuote) {
(receiveQuote, , , , , ) = _querySellBaseToken(amount);
return receiveQuote;
}
function queryBuyBaseToken(uint256 amount) external view returns (uint256 payQuote) {
(payQuote, , , , , ) = _queryBuyBaseToken(amount);
return payQuote;
}
function _querySellBaseToken(uint256 amount)
internal
view
returns (
uint256 receiveQuote,
uint256 lpFeeQuote,
uint256 mtFeeQuote,
Types.RStatus newRStatus,
uint256 newQuoteTarget,
uint256 newBaseTarget
)
{
(newBaseTarget, newQuoteTarget) = getExpectedTarget();
uint256 sellBaseAmount = amount;
if (_R_STATUS_ == Types.RStatus.ONE) {
// case 1: R=1
// R falls below one
receiveQuote = _ROneSellBaseToken(sellBaseAmount, newQuoteTarget);
newRStatus = Types.RStatus.BELOW_ONE;
} else if (_R_STATUS_ == Types.RStatus.ABOVE_ONE) {
uint256 backToOnePayBase = newBaseTarget.sub(_BASE_BALANCE_);
uint256 backToOneReceiveQuote = _QUOTE_BALANCE_.sub(newQuoteTarget);
// case 2: R>1
// complex case, R status depends on trading amount
if (sellBaseAmount < backToOnePayBase) {
// case 2.1: R status do not change
receiveQuote = _RAboveSellBaseToken(sellBaseAmount, _BASE_BALANCE_, newBaseTarget);
newRStatus = Types.RStatus.ABOVE_ONE;
if (receiveQuote > backToOneReceiveQuote) {
// [Important corner case!] may enter this branch when some precision problem happens. And consequently contribute to negative spare quote amount
// to make sure spare quote>=0, mannually set receiveQuote=backToOneReceiveQuote
receiveQuote = backToOneReceiveQuote;
}
} else if (sellBaseAmount == backToOnePayBase) {
// case 2.2: R status changes to ONE
receiveQuote = backToOneReceiveQuote;
newRStatus = Types.RStatus.ONE;
} else {
// case 2.3: R status changes to BELOW_ONE
receiveQuote = backToOneReceiveQuote.add(
_ROneSellBaseToken(sellBaseAmount.sub(backToOnePayBase), newQuoteTarget)
);
newRStatus = Types.RStatus.BELOW_ONE;
}
} else {
// _R_STATUS_ == Types.RStatus.BELOW_ONE
// case 3: R<1
receiveQuote = _RBelowSellBaseToken(sellBaseAmount, _QUOTE_BALANCE_, newQuoteTarget);
newRStatus = Types.RStatus.BELOW_ONE;
}
// count fees
lpFeeQuote = DecimalMath.mul(receiveQuote, _LP_FEE_RATE_);
mtFeeQuote = DecimalMath.mul(receiveQuote, _MT_FEE_RATE_);
receiveQuote = receiveQuote.sub(lpFeeQuote).sub(mtFeeQuote);
return (receiveQuote, lpFeeQuote, mtFeeQuote, newRStatus, newQuoteTarget, newBaseTarget);
}
function _queryBuyBaseToken(uint256 amount)
internal
view
returns (
uint256 payQuote,
uint256 lpFeeBase,
uint256 mtFeeBase,
Types.RStatus newRStatus,
uint256 newQuoteTarget,
uint256 newBaseTarget
)
{
(newBaseTarget, newQuoteTarget) = getExpectedTarget();
// charge fee from user receive amount
lpFeeBase = DecimalMath.mul(amount, _LP_FEE_RATE_);
mtFeeBase = DecimalMath.mul(amount, _MT_FEE_RATE_);
uint256 buyBaseAmount = amount.add(lpFeeBase).add(mtFeeBase);
if (_R_STATUS_ == Types.RStatus.ONE) {
// case 1: R=1
payQuote = _ROneBuyBaseToken(buyBaseAmount, newBaseTarget);
newRStatus = Types.RStatus.ABOVE_ONE;
} else if (_R_STATUS_ == Types.RStatus.ABOVE_ONE) {
// case 2: R>1
payQuote = _RAboveBuyBaseToken(buyBaseAmount, _BASE_BALANCE_, newBaseTarget);
newRStatus = Types.RStatus.ABOVE_ONE;
} else if (_R_STATUS_ == Types.RStatus.BELOW_ONE) {
uint256 backToOnePayQuote = newQuoteTarget.sub(_QUOTE_BALANCE_);
uint256 backToOneReceiveBase = _BASE_BALANCE_.sub(newBaseTarget);
// case 3: R<1
// complex case, R status may change
if (buyBaseAmount < backToOneReceiveBase) {
// case 3.1: R status do not change
// no need to check payQuote because spare base token must be greater than zero
payQuote = _RBelowBuyBaseToken(buyBaseAmount, _QUOTE_BALANCE_, newQuoteTarget);
newRStatus = Types.RStatus.BELOW_ONE;
} else if (buyBaseAmount == backToOneReceiveBase) {
// case 3.2: R status changes to ONE
payQuote = backToOnePayQuote;
newRStatus = Types.RStatus.ONE;
} else {
// case 3.3: R status changes to ABOVE_ONE
payQuote = backToOnePayQuote.add(
_ROneBuyBaseToken(buyBaseAmount.sub(backToOneReceiveBase), newBaseTarget)
);
newRStatus = Types.RStatus.ABOVE_ONE;
}
}
return (payQuote, lpFeeBase, mtFeeBase, newRStatus, newQuoteTarget, newBaseTarget);
}
}

View File

@@ -1,67 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IDODO {
function init(
address owner,
address supervisor,
address maintainer,
address baseToken,
address quoteToken,
address oracle,
uint256 lpFeeRate,
uint256 mtFeeRate,
uint256 k,
uint256 gasPriceLimit
) external;
function transferOwnership(address newOwner) external;
function claimOwnership() external;
function sellBaseToken(
uint256 amount,
uint256 minReceiveQuote,
bytes calldata data
) external returns (uint256);
function buyBaseToken(
uint256 amount,
uint256 maxPayQuote,
bytes calldata data
) external returns (uint256);
function querySellBaseToken(uint256 amount) external view returns (uint256 receiveQuote);
function queryBuyBaseToken(uint256 amount) external view returns (uint256 payQuote);
function getExpectedTarget() external view returns (uint256 baseTarget, uint256 quoteTarget);
function depositBaseTo(address to, uint256 amount) external returns (uint256);
function withdrawBase(uint256 amount) external returns (uint256);
function withdrawAllBase() external returns (uint256);
function depositQuoteTo(address to, uint256 amount) external returns (uint256);
function withdrawQuote(uint256 amount) external returns (uint256);
function withdrawAllQuote() external returns (uint256);
function _BASE_CAPITAL_TOKEN_() external view returns (address);
function _QUOTE_CAPITAL_TOKEN_() external view returns (address);
function _BASE_TOKEN_() external returns (address);
function _QUOTE_TOKEN_() external returns (address);
}

View File

@@ -1,18 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IDODOCallee {
function dodoCall(
bool isBuyBaseToken,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
}

View File

@@ -1,20 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity ^0.6.9;
pragma experimental ABIEncoderV2;
interface IDODOLpToken {
function mint(address user, uint256 value) external;
function burn(address user, uint256 value) external;
function balanceOf(address owner) external view returns (uint256);
function totalSupply() external view returns (uint256);
}

View File

@@ -17,6 +17,8 @@ interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
/**
* @dev Returns the amount of tokens owned by `account`.
*/

View File

@@ -8,6 +8,7 @@
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
library Types {
enum RStatus {ONE, ABOVE_ONE, BELOW_ONE}
interface IFeeRateModel {
function getFeeRate(uint256 amount) external view returns (uint256);
}

15
contracts/intf/IVault.sol Normal file
View File

@@ -0,0 +1,15 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IBasicVault {
function controller() external view returns(address);
function getReserve(address token) external view returns(address);
function transferOut(address token, address to, uint256 amount) external;
}

View File

@@ -26,6 +26,10 @@ library DecimalMath {
return target.mul(d) / ONE;
}
function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(d) / ONE;
}
function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(d).divCeil(ONE);
}

View File

@@ -17,6 +17,7 @@ pragma experimental ABIEncoderV2;
contract InitializableOwnable {
address public _OWNER_;
address public _NEW_OWNER_;
bool internal _INITIALIZED_;
// ============ Events ============
@@ -26,6 +27,11 @@ contract InitializableOwnable {
// ============ Modifiers ============
modifier notInitialized() {
require(!_INITIALIZED_, "DODO_INITIALIZED");
_;
}
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
@@ -33,13 +39,18 @@ contract InitializableOwnable {
// ============ Functions ============
function transferOwnership(address newOwner) external onlyOwner {
function initOwner(address newOwner) public notInitialized{
_INITIALIZED_ = true;
_OWNER_ = newOwner;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "INVALID_OWNER");
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() external {
function claimOwnership() public {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;

View File

@@ -0,0 +1,53 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {InitializableOwnable} from "./InitializableOwnable.sol";
contract PermissionManager is InitializableOwnable {
bool public _BLACKLIST_MODE_ON_;
mapping(address => bool) internal _whitelist_;
mapping(address => bool) internal _blacklist_;
function isAllowed(address account) external view returns(bool){
if (_BLACKLIST_MODE_ON_) {
return !_blacklist_[account];
} else {
return _whitelist_[account];
}
}
function openBlacklist() external onlyOwner {
_BLACKLIST_MODE_ON_ = true;
}
function openWhitelist() external onlyOwner {
_BLACKLIST_MODE_ON_ = true;
}
function addToWhitelist(address account) external onlyOwner{
_whitelist_[account] = true;
}
function removeFromWhitelist(address account) external onlyOwner{
_whitelist_[account] = false;
}
function addToBlacklist(address account) external onlyOwner{
_blacklist_[account] = true;
}
function removeFromBlacklist(address account) external onlyOwner{
_blacklist_[account] = false;
}
}

View File

@@ -1,44 +0,0 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
import {IDODO} from "../intf/IDODO.sol";
import {IERC20} from "../intf/IERC20.sol";
import {SafeMath} from "../lib/SafeMath.sol";
interface IDODOMine {
function getUserLpBalance(address _lpToken, address _user) external view returns (uint256);
}
contract DODOMineReader {
using SafeMath for uint256;
function getUserStakedBalance(
address _dodoMine,
address _dodo,
address _user
) external view returns (uint256 baseBalance, uint256 quoteBalance) {
address baseLpToken = IDODO(_dodo)._BASE_CAPITAL_TOKEN_();
address quoteLpToken = IDODO(_dodo)._QUOTE_CAPITAL_TOKEN_();
uint256 baseLpBalance = IDODOMine(_dodoMine).getUserLpBalance(baseLpToken, _user);
uint256 quoteLpBalance = IDODOMine(_dodoMine).getUserLpBalance(quoteLpToken, _user);
uint256 baseLpTotalSupply = IERC20(baseLpToken).totalSupply();
uint256 quoteLpTotalSupply = IERC20(quoteLpToken).totalSupply();
(uint256 baseTarget, uint256 quoteTarget) = IDODO(_dodo).getExpectedTarget();
baseBalance = baseTarget.mul(baseLpBalance).div(baseLpTotalSupply);
quoteBalance = quoteTarget.mul(quoteLpBalance).div(quoteLpTotalSupply);
return (baseBalance, quoteBalance);
}
}

101
test/Rebalance.test.ts Normal file
View File

@@ -0,0 +1,101 @@
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
import * as assert from 'assert';
import { Contract } from 'web3-eth-contract';
import { DODOContext, getDODOContext } from './utils/Context';
import { DODO_REBALANCER_NAME, newContract } from './utils/Contracts';
import { decimalStr } from './utils/Converter';
let lp: string;
let trader: string;
let rebalancer: Contract;
async function init(ctx: DODOContext): Promise<void> {
await ctx.setOraclePrice(decimalStr("100"));
lp = ctx.spareAccounts[0];
trader = ctx.spareAccounts[1];
await ctx.approveDODO(lp);
await ctx.approveDODO(trader);
await ctx.mintTestToken(lp, decimalStr("10"), decimalStr("1000"));
await ctx.mintTestToken(trader, decimalStr("10"), decimalStr("1000"));
await ctx.DODO.methods
.depositBaseTo(lp, decimalStr("10"))
.send(ctx.sendParam(lp));
await ctx.DODO.methods
.depositQuoteTo(lp, decimalStr("1000"))
.send(ctx.sendParam(lp));
rebalancer = await newContract(DODO_REBALANCER_NAME)
}
describe("Trader", () => {
let snapshotId: string;
let ctx: DODOContext;
before(async () => {
ctx = await getDODOContext();
await init(ctx);
});
beforeEach(async () => {
snapshotId = await ctx.EVM.snapshot();
});
afterEach(async () => {
await ctx.EVM.reset(snapshotId);
});
describe("rebalance", () => {
it("R above ONE rebalance", async () => {
await ctx.DODO.methods.buyBaseToken(decimalStr("1"), decimalStr("110"), "0x").send(ctx.sendParam(trader))
await ctx.DODO.methods.disableTrading().send(ctx.sendParam(ctx.Deployer))
await ctx.DODO.methods.transferOwnership(rebalancer.options.address).send(ctx.sendParam(ctx.Deployer))
await rebalancer.methods.claimOwnership(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer))
await ctx.BASE.methods.transfer(rebalancer.options.address, decimalStr("2")).send(ctx.sendParam(trader))
await rebalancer.methods.rebalance(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer));
assert.equal(await ctx.DODO.methods.getMidPrice().call(), await ctx.DODO.methods.getOraclePrice().call())
await rebalancer.methods.transferOwnership(ctx.DODO.options.address, ctx.Deployer).send(ctx.sendParam(ctx.Deployer))
await ctx.DODO.methods.claimOwnership().send(ctx.sendParam(ctx.Deployer))
await rebalancer.methods.retrieve(ctx.BASE.options.address).send(ctx.sendParam(ctx.Deployer))
await rebalancer.methods.retrieve(ctx.QUOTE.options.address).send(ctx.sendParam(ctx.Deployer))
assert.equal(await ctx.BASE.methods.balanceOf(ctx.Deployer).call(), "996997569110682237")
assert.equal(await ctx.QUOTE.methods.balanceOf(ctx.Deployer).call(), "101113906016449927750")
});
it("R below ONE rebalance", async () => {
await ctx.DODO.methods.sellBaseToken(decimalStr("1"), decimalStr("90"), "0x").send(ctx.sendParam(trader))
await ctx.DODO.methods.disableTrading().send(ctx.sendParam(ctx.Deployer))
await ctx.DODO.methods.transferOwnership(rebalancer.options.address).send(ctx.sendParam(ctx.Deployer))
await rebalancer.methods.claimOwnership(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer))
await ctx.QUOTE.methods.transfer(rebalancer.options.address, decimalStr("200")).send(ctx.sendParam(trader))
await rebalancer.methods.rebalance(ctx.DODO.options.address).send(ctx.sendParam(ctx.Deployer));
assert.equal(await ctx.DODO.methods.getMidPrice().call(), await ctx.DODO.methods.getOraclePrice().call())
await rebalancer.methods.transferOwnership(ctx.DODO.options.address, ctx.Deployer).send(ctx.sendParam(ctx.Deployer))
await ctx.DODO.methods.claimOwnership().send(ctx.sendParam(ctx.Deployer))
await rebalancer.methods.retrieve(ctx.BASE.options.address).send(ctx.sendParam(ctx.Deployer))
await rebalancer.methods.retrieve(ctx.QUOTE.options.address).send(ctx.sendParam(ctx.Deployer))
assert.equal(await ctx.BASE.methods.balanceOf(ctx.Deployer).call(), "997008973080757726")
assert.equal(await ctx.QUOTE.methods.balanceOf(ctx.Deployer).call(), "101085569972088780856")
});
});
});

View File

@@ -10,56 +10,9 @@ if (process.env["COVERAGE"]) {
jsonPath = "../../.coverage_artifacts/contracts/"
}
const CloneFactory = require(`${jsonPath}CloneFactory.json`)
const DODO = require(`${jsonPath}DODO.json`)
const DODOZoo = require(`${jsonPath}DODOZoo.json`)
const DODOEthProxy = require(`${jsonPath}DODOEthProxy.json`)
const WETH = require(`${jsonPath}WETH9.json`)
const TestERC20 = require(`${jsonPath}TestERC20.json`)
const NaiveOracle = require(`${jsonPath}NaiveOracle.json`)
const DODOLpToken = require(`${jsonPath}DODOLpToken.json`)
const Uniswap = require(`${jsonPath}UniswapV2Pair.json`)
const UniswapArbitrageur = require(`${jsonPath}UniswapArbitrageur.json`)
const DODOToken = require(`${jsonPath}DODOToken.json`)
const DODOMine = require(`${jsonPath}DODOMine.json`)
const DODOMineReader = require(`${jsonPath}DODOMineReader.json`)
const LockedTokenVault = require(`${jsonPath}LockedTokenVault.json`)
import { getDefaultWeb3 } from './EVM';
import { Contract } from 'web3-eth-contract';
export const CLONE_FACTORY_CONTRACT_NAME = "CloneFactory"
export const DODO_CONTRACT_NAME = "DODO"
export const TEST_ERC20_CONTRACT_NAME = "TestERC20"
export const NAIVE_ORACLE_CONTRACT_NAME = "NaiveOracle"
export const DODO_LP_TOKEN_CONTRACT_NAME = "DODOLpToken"
export const DODO_ZOO_CONTRACT_NAME = "DOOZoo"
export const DODO_WILD_CONTRACT_NAME = "DOOWild"
export const DODO_ETH_PROXY_CONTRACT_NAME = "DODOEthProxy"
export const WETH_CONTRACT_NAME = "WETH"
export const UNISWAP_CONTRACT_NAME = "Uniswap"
export const UNISWAP_ARBITRAGEUR_CONTRACT_NAME = "UniswapArbitrageur"
export const DODO_TOKEN_CONTRACT_NAME = "DODOToken"
export const LOCKED_TOKEN_VAULT_CONTRACT_NAME = "LockedTokenVault"
export const DODO_MINE_NAME = "DODOMine"
export const DODO_MINE_READER_NAME = "DODOMineReader"
var contractMap: { [name: string]: any } = {}
contractMap[CLONE_FACTORY_CONTRACT_NAME] = CloneFactory
contractMap[DODO_CONTRACT_NAME] = DODO
contractMap[TEST_ERC20_CONTRACT_NAME] = TestERC20
contractMap[NAIVE_ORACLE_CONTRACT_NAME] = NaiveOracle
contractMap[DODO_LP_TOKEN_CONTRACT_NAME] = DODOLpToken
contractMap[DODO_ZOO_CONTRACT_NAME] = DODOZoo
contractMap[DODO_ETH_PROXY_CONTRACT_NAME] = DODOEthProxy
contractMap[WETH_CONTRACT_NAME] = WETH
contractMap[UNISWAP_CONTRACT_NAME] = Uniswap
contractMap[UNISWAP_ARBITRAGEUR_CONTRACT_NAME] = UniswapArbitrageur
contractMap[DODO_TOKEN_CONTRACT_NAME] = DODOToken
contractMap[LOCKED_TOKEN_VAULT_CONTRACT_NAME] = LockedTokenVault
contractMap[DODO_MINE_NAME] = DODOMine
contractMap[DODO_MINE_READER_NAME] = DODOMineReader
interface ContractJson {
abi: any;
networks: { [network: number]: any };
@@ -67,7 +20,7 @@ interface ContractJson {
}
export function getContractJSON(contractName: string): ContractJson {
var info = contractMap[contractName]
var info = require(`${jsonPath}${contractName}.json`)
return {
abi: info.abi,
networks: info.networks,