restruct contract repo
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
51
contracts/DODOVenderMachine/DVMFactory.sol
Normal file
51
contracts/DODOVenderMachine/DVMFactory.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
19
contracts/DODOVenderMachine/impl/DVMAdmin.sol
Normal file
19
contracts/DODOVenderMachine/impl/DVMAdmin.sol
Normal 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{}
|
||||
|
||||
}
|
||||
38
contracts/DODOVenderMachine/impl/DVMController.sol
Normal file
38
contracts/DODOVenderMachine/impl/DVMController.sol
Normal 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;
|
||||
}
|
||||
}
|
||||
69
contracts/DODOVenderMachine/impl/DVMFunding.sol
Normal file
69
contracts/DODOVenderMachine/impl/DVMFunding.sol
Normal 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();
|
||||
}
|
||||
}
|
||||
75
contracts/DODOVenderMachine/impl/DVMStorage.sol
Normal file
75
contracts/DODOVenderMachine/impl/DVMStorage.sol
Normal 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
|
||||
}
|
||||
}
|
||||
69
contracts/DODOVenderMachine/impl/DVMTrader.sol
Normal file
69
contracts/DODOVenderMachine/impl/DVMTrader.sol
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
205
contracts/DODOVenderMachine/impl/DVMVault.sol
Normal file
205
contracts/DODOVenderMachine/impl/DVMVault.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
23
contracts/helper/ConstFeeRateModel.sol
Normal file
23
contracts/helper/ConstFeeRateModel.sol
Normal 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_;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
28
contracts/helper/NaiveFeeRateModel.sol
Normal file
28
contracts/helper/NaiveFeeRateModel.sol
Normal 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_;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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_);
|
||||
// }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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`.
|
||||
*/
|
||||
|
||||
@@ -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
15
contracts/intf/IVault.sol
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_;
|
||||
|
||||
53
contracts/lib/PermissionManager.sol
Normal file
53
contracts/lib/PermissionManager.sol
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
101
test/Rebalance.test.ts
Normal 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")
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user