Transaction Hash:
Block:
13309408 at Sep-27-2021 06:21:08 PM +UTC
Transaction Fee:
0.014653047502962756 ETH
$32.13
Gas Used:
166,953 Gas / 87.767500452 Gwei
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x15dd416f...55EfBb398 |
0.094056253931150648 Eth
Nonce: 353
|
0.079403206428187892 Eth
Nonce: 354
| 0.014653047502962756 | ||
|
0xEA674fdD...16B898ec8
Miner
| (Ethermine) | 2,441.19432310144962986 Eth | 2,441.19455850517962986 Eth | 0.00023540373 |
Execution Trace
DODOV2Proxy02.dodoSwapV1( fromToken=0x43Dfc4159D86F3A37A5A4B3D4580b888ad7d4DDd, toToken=0xdAC17F958D2ee523a2206206994597C13D831ec7, fromTokenAmount=1162929896313260511552, minReturnAmount=1387794059, dodoPairs=[0x8876819535b48b551C9e97EBc07332C7482b4b2d], directions=0, isIncentive=False, deadLine=1632767818 ) => ( returnAmount=3963877391197344453575983046348115674221700746820753546331534351508065746944 )
DODOApproveProxy.claimTokens( token=0x43Dfc4159D86F3A37A5A4B3D4580b888ad7d4DDd, who=0x15dd416f5E624979981667c50b1518755EfBb398, dest=0xa356867fDCEa8e71AEaF87805808803806231FdC, amount=1162929896313260511552 )DODOApprove.claimTokens( token=0x43Dfc4159D86F3A37A5A4B3D4580b888ad7d4DDd, who=0x15dd416f5E624979981667c50b1518755EfBb398, dest=0xa356867fDCEa8e71AEaF87805808803806231FdC, amount=1162929896313260511552 )-
DODOToken.transferFrom( from=0x15dd416f5E624979981667c50b1518755EfBb398, to=0xa356867fDCEa8e71AEaF87805808803806231FdC, amount=1162929896313260511552 ) => ( True )
-
DODO.STATICCALL( )-
DODO.DELEGATECALL( )
-
-
DODOToken.balanceOf( owner=0xa356867fDCEa8e71AEaF87805808803806231FdC ) => ( balance=1162929896313260511552 )
-
DODOToken.allowance( owner=0xa356867fDCEa8e71AEaF87805808803806231FdC, spender=0x8876819535b48b551C9e97EBc07332C7482b4b2d ) => ( 115792089237316195423570985008687907853269984665640529331784581374961593500608 )
DODO.sellBaseToken( amount=1162929896313260511552, minReceiveQuote=0, data=0x ) => ( 3963877391197344453575983046348115674221700746820753546331534351508065746944 )DODO.sellBaseToken( amount=1162929896313260511552, minReceiveQuote=0, data=0x ) => ( 3963877391197344453575983046348115674221700746820753546331534351508065746944 )-
ConstOracle.STATICCALL( ) -
ConstOracle.STATICCALL( ) -
TetherToken.transfer( _to=0xa356867fDCEa8e71AEaF87805808803806231FdC, _value=1394767899 )
-
dodoSwapV1[DODOV2Proxy02 (ln:1764)]
gasleft[DODOV2Proxy02 (ln:1785)]_deposit[DODOV2Proxy02 (ln:1790)]deposit[DODOV2Proxy02 (ln:1980)]safeTransfer[DODOV2Proxy02 (ln:1981)]_callOptionalReturn[SafeERC20 (ln:937)]call[SafeERC20 (ln:985)]decode[SafeERC20 (ln:991)]
encodeWithSelector[SafeERC20 (ln:937)]
claimTokens[DODOV2Proxy02 (ln:1984)]
_BASE_TOKEN_[DODOV2Proxy02 (ln:1795)]balanceOf[DODOV2Proxy02 (ln:1797)]universalApproveMax[DODOV2Proxy02 (ln:1798)]sellBaseToken[DODOV2Proxy02 (ln:1799)]_QUOTE_TOKEN_[DODOV2Proxy02 (ln:1801)]balanceOf[DODOV2Proxy02 (ln:1803)]universalApproveMax[DODOV2Proxy02 (ln:1804)]querySellQuoteToken[DODOV2Proxy02 (ln:1805)]buyBaseToken[DODOV2Proxy02 (ln:1809)]balanceOf[DODOV2Proxy02 (ln:1816)]withdraw[DODOV2Proxy02 (ln:1817)]tokenBalanceOf[DODOV2Proxy02 (ln:1819)]universalTransfer[DODOV2Proxy02 (ln:1823)]_dodoGasReturn[DODOV2Proxy02 (ln:1825)]sub[DODOV2Proxy02 (ln:2009)]gasleft[DODOV2Proxy02 (ln:2009)]gasleft[DODOV2Proxy02 (ln:2011)]freeUpTo[DODOV2Proxy02 (ln:2012)]
_execIncentive[DODOV2Proxy02 (ln:1827)]gasleft[DODOV2Proxy02 (ln:2025)]triggerIncentive[DODOV2Proxy02 (ln:2026)]
OrderHistory[DODOV2Proxy02 (ln:1829)]
File 1 of 8: DODOV2Proxy02
File 2 of 8: DODOApproveProxy
File 3 of 8: DODOApprove
File 4 of 8: DODOToken
File 5 of 8: DODO
File 6 of 8: DODO
File 7 of 8: ConstOracle
File 8 of 8: TetherToken
// File: contracts/SmartRoute/intf/IDODOV2Proxy01.sol
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
interface IDODOV2Proxy01 {
function dodoSwapV2ETHToToken(
address toToken,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
) external payable returns (uint256 returnAmount);
function dodoSwapV2TokenToETH(
address fromToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
) external returns (uint256 returnAmount);
function dodoSwapV2TokenToToken(
address fromToken,
address toToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
) external returns (uint256 returnAmount);
function createDODOVendingMachine(
address baseToken,
address quoteToken,
uint256 baseInAmount,
uint256 quoteInAmount,
uint256 lpFeeRate,
uint256 i,
uint256 k,
bool isOpenTWAP,
uint256 deadLine
) external payable returns (address newVendingMachine, uint256 shares);
function addDVMLiquidity(
address dvmAddress,
uint256 baseInAmount,
uint256 quoteInAmount,
uint256 baseMinAmount,
uint256 quoteMinAmount,
uint8 flag, // 0 - ERC20, 1 - baseInETH, 2 - quoteInETH
uint256 deadLine
)
external
payable
returns (
uint256 shares,
uint256 baseAdjustedInAmount,
uint256 quoteAdjustedInAmount
);
function createDODOPrivatePool(
address baseToken,
address quoteToken,
uint256 baseInAmount,
uint256 quoteInAmount,
uint256 lpFeeRate,
uint256 i,
uint256 k,
bool isOpenTwap,
uint256 deadLine
) external payable returns (address newPrivatePool);
function resetDODOPrivatePool(
address dppAddress,
uint256[] memory paramList, //0 - newLpFeeRate, 1 - newI, 2 - newK
uint256[] memory amountList, //0 - baseInAmount, 1 - quoteInAmount, 2 - baseOutAmount, 3 - quoteOutAmount
uint8 flag, // 0 - ERC20, 1 - baseInETH, 2 - quoteInETH, 3 - baseOutETH, 4 - quoteOutETH
uint256 minBaseReserve,
uint256 minQuoteReserve,
uint256 deadLine
) external payable;
function createCrowdPooling(
address baseToken,
address quoteToken,
uint256 baseInAmount,
uint256[] memory timeLine,
uint256[] memory valueList,
bool isOpenTWAP,
uint256 deadLine
) external payable returns (address payable newCrowdPooling);
function bid(
address cpAddress,
uint256 quoteAmount,
uint8 flag, // 0 - ERC20, 1 - quoteInETH
uint256 deadLine
) external payable;
function addLiquidityToV1(
address pair,
uint256 baseAmount,
uint256 quoteAmount,
uint256 baseMinShares,
uint256 quoteMinShares,
uint8 flag, // 0 erc20 Out 1 baseInETH 2 quoteInETH
uint256 deadLine
) external payable returns(uint256, uint256);
function dodoSwapV1(
address fromToken,
address toToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
) external payable returns (uint256 returnAmount);
function externalSwap(
address fromToken,
address toToken,
address approveTarget,
address to,
uint256 fromTokenAmount,
uint256 minReturnAmount,
bytes memory callDataConcat,
bool isIncentive,
uint256 deadLine
) external payable returns (uint256 returnAmount);
function mixSwap(
address fromToken,
address toToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory mixAdapters,
address[] memory mixPairs,
address[] memory assetTo,
uint256 directions,
bool isIncentive,
uint256 deadLine
) external payable returns (uint256 returnAmount);
}
// File: contracts/SmartRoute/intf/IDODOV2.sol
interface IDODOV2 {
//========== Common ==================
function sellBase(address to) external returns (uint256 receiveQuoteAmount);
function sellQuote(address to) external returns (uint256 receiveBaseAmount);
function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve);
function _BASE_TOKEN_() external view returns (address);
function _QUOTE_TOKEN_() external view returns (address);
function getPMMStateForCall() external view returns (
uint256 i,
uint256 K,
uint256 B,
uint256 Q,
uint256 B0,
uint256 Q0,
uint256 R
);
function getUserFeeRate(address user) external view returns (uint256 lpFeeRate, uint256 mtFeeRate);
function getDODOPoolBidirection(address token0, address token1) external view returns (address[] memory, address[] memory);
//========== DODOVendingMachine ========
function createDODOVendingMachine(
address baseToken,
address quoteToken,
uint256 lpFeeRate,
uint256 i,
uint256 k,
bool isOpenTWAP
) external returns (address newVendingMachine);
function buyShares(address to) external returns (uint256,uint256,uint256);
//========== DODOPrivatePool ===========
function createDODOPrivatePool() external returns (address newPrivatePool);
function initDODOPrivatePool(
address dppAddress,
address creator,
address baseToken,
address quoteToken,
uint256 lpFeeRate,
uint256 k,
uint256 i,
bool isOpenTwap
) external;
function reset(
address operator,
uint256 newLpFeeRate,
uint256 newI,
uint256 newK,
uint256 baseOutAmount,
uint256 quoteOutAmount,
uint256 minBaseReserve,
uint256 minQuoteReserve
) external returns (bool);
function _OWNER_() external returns (address);
//========== CrowdPooling ===========
function createCrowdPooling() external returns (address payable newCrowdPooling);
function initCrowdPooling(
address cpAddress,
address creator,
address baseToken,
address quoteToken,
uint256[] memory timeLine,
uint256[] memory valueList,
bool isOpenTWAP
) external;
function bid(address to) external;
}
// File: contracts/SmartRoute/intf/IDODOV1.sol
interface IDODOV1 {
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 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 returns (address);
function _QUOTE_CAPITAL_TOKEN_() external returns (address);
function _BASE_TOKEN_() external view returns (address);
function _QUOTE_TOKEN_() external view returns (address);
function _R_STATUS_() external view returns (uint8);
function _QUOTE_BALANCE_() external view returns (uint256);
function _BASE_BALANCE_() external view returns (uint256);
function _K_() external view returns (uint256);
function _MT_FEE_RATE_() external view returns (uint256);
function _LP_FEE_RATE_() external view returns (uint256);
function getExpectedTarget() external view returns (uint256 baseTarget, uint256 quoteTarget);
function getOraclePrice() external view returns (uint256);
function getMidPrice() external view returns (uint256 midPrice);
}
// File: contracts/intf/IDODOApprove.sol
interface IDODOApprove {
function claimTokens(address token,address who,address dest,uint256 amount) external;
function getDODOProxy() external view returns (address);
}
// File: contracts/lib/InitializableOwnable.sol
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract InitializableOwnable {
address public _OWNER_;
address public _NEW_OWNER_;
bool internal _INITIALIZED_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier notInitialized() {
require(!_INITIALIZED_, "DODO_INITIALIZED");
_;
}
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
function initOwner(address newOwner) public notInitialized {
_INITIALIZED_ = true;
_OWNER_ = newOwner;
}
function transferOwnership(address newOwner) public onlyOwner {
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() public {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}
// File: contracts/SmartRoute/DODOApproveProxy.sol
interface IDODOApproveProxy {
function isAllowedProxy(address _proxy) external view returns (bool);
function claimTokens(address token,address who,address dest,uint256 amount) external;
}
/**
* @title DODOApproveProxy
* @author DODO Breeder
*
* @notice Allow different version dodoproxy to claim from DODOApprove
*/
contract DODOApproveProxy is InitializableOwnable {
// ============ Storage ============
uint256 private constant _TIMELOCK_DURATION_ = 3 days;
mapping (address => bool) public _IS_ALLOWED_PROXY_;
uint256 public _TIMELOCK_;
address public _PENDING_ADD_DODO_PROXY_;
address public immutable _DODO_APPROVE_;
// ============ Modifiers ============
modifier notLocked() {
require(
_TIMELOCK_ <= block.timestamp,
"SetProxy is timelocked"
);
_;
}
constructor(address dodoApporve) public {
_DODO_APPROVE_ = dodoApporve;
}
function init(address owner, address[] memory proxies) external {
initOwner(owner);
for(uint i = 0; i < proxies.length; i++)
_IS_ALLOWED_PROXY_[proxies[i]] = true;
}
function unlockAddProxy(address newDodoProxy) public onlyOwner {
_TIMELOCK_ = block.timestamp + _TIMELOCK_DURATION_;
_PENDING_ADD_DODO_PROXY_ = newDodoProxy;
}
function lockAddProxy() public onlyOwner {
_PENDING_ADD_DODO_PROXY_ = address(0);
_TIMELOCK_ = 0;
}
function addDODOProxy() external onlyOwner notLocked() {
_IS_ALLOWED_PROXY_[_PENDING_ADD_DODO_PROXY_] = true;
lockAddProxy();
}
function removeDODOProxy (address oldDodoProxy) public onlyOwner {
_IS_ALLOWED_PROXY_[oldDodoProxy] = false;
}
function claimTokens(
address token,
address who,
address dest,
uint256 amount
) external {
require(_IS_ALLOWED_PROXY_[msg.sender], "DODOApproveProxy:Access restricted");
IDODOApprove(_DODO_APPROVE_).claimTokens(
token,
who,
dest,
amount
);
}
function isAllowedProxy(address _proxy) external view returns (bool) {
return _IS_ALLOWED_PROXY_[_proxy];
}
}
// File: contracts/lib/SafeMath.sol
/**
* @title SafeMath
* @author DODO Breeder
*
* @notice Math operations with safety checks that revert on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "MUL_ERROR");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "DIVIDING_ERROR");
return a / b;
}
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 quotient = div(a, b);
uint256 remainder = a - quotient * b;
if (remainder > 0) {
return quotient + 1;
} else {
return quotient;
}
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SUB_ERROR");
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "ADD_ERROR");
return c;
}
function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = x / 2 + 1;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
// File: contracts/lib/DecimalMath.sol
/**
* @title DecimalMath
* @author DODO Breeder
*
* @notice Functions for fixed point number with 18 decimals
*/
library DecimalMath {
using SafeMath for uint256;
uint256 internal constant ONE = 10**18;
uint256 internal constant ONE2 = 10**36;
function mulFloor(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(d) / (10**18);
}
function mulCeil(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(d).divCeil(10**18);
}
function divFloor(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(10**18).div(d);
}
function divCeil(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(10**18).divCeil(d);
}
function reciprocalFloor(uint256 target) internal pure returns (uint256) {
return uint256(10**36).div(target);
}
function reciprocalCeil(uint256 target) internal pure returns (uint256) {
return uint256(10**36).divCeil(target);
}
}
// File: contracts/SmartRoute/helper/DODOSellHelper.sol
// import {DODOMath} from "../lib/DODOMath.sol";
interface IDODOSellHelper {
function querySellQuoteToken(address dodo, uint256 amount) external view returns (uint256);
function querySellBaseToken(address dodo, uint256 amount) external view returns (uint256);
}
library DODOMath {
using SafeMath for uint256;
/*
Integrate dodo curve fron V1 to V2
require V0>=V1>=V2>0
res = (1-k)i(V1-V2)+ikV0*V0(1/V2-1/V1)
let V1-V2=delta
res = i*delta*(1-k+k(V0^2/V1/V2))
*/
function _GeneralIntegrate(
uint256 V0,
uint256 V1,
uint256 V2,
uint256 i,
uint256 k
) internal pure returns (uint256) {
uint256 fairAmount = DecimalMath.mulFloor(i, V1.sub(V2)); // i*delta
uint256 V0V0V1V2 = DecimalMath.divCeil(V0.mul(V0).div(V1), V2);
uint256 penalty = DecimalMath.mulFloor(k, V0V0V1V2); // k(V0^2/V1/V2)
return DecimalMath.mulFloor(fairAmount, DecimalMath.ONE.sub(k).add(penalty));
}
/*
The same with integration expression above, we have:
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Given Q1 and deltaB, solve Q2
This is a quadratic function and the standard version is
aQ2^2 + bQ2 + c = 0, where
a=1-k
-b=(1-k)Q1-kQ0^2/Q1+i*deltaB
c=-kQ0^2
and Q2=(-b+sqrt(b^2+4(1-k)kQ0^2))/2(1-k)
note: another root is negative, abondan
if deltaBSig=true, then Q2>Q1
if deltaBSig=false, then Q2<Q1
*/
function _SolveQuadraticFunctionForTrade(
uint256 Q0,
uint256 Q1,
uint256 ideltaB,
bool deltaBSig,
uint256 k
) internal pure returns (uint256) {
// calculate -b value and sig
// -b = (1-k)Q1-kQ0^2/Q1+i*deltaB
uint256 kQ02Q1 = DecimalMath.mulFloor(k, Q0).mul(Q0).div(Q1); // kQ0^2/Q1
uint256 b = DecimalMath.mulFloor(DecimalMath.ONE.sub(k), Q1); // (1-k)Q1
bool minusbSig = true;
if (deltaBSig) {
b = b.add(ideltaB); // (1-k)Q1+i*deltaB
} else {
kQ02Q1 = kQ02Q1.add(ideltaB); // i*deltaB+kQ0^2/Q1
}
if (b >= kQ02Q1) {
b = b.sub(kQ02Q1);
minusbSig = true;
} else {
b = kQ02Q1.sub(b);
minusbSig = false;
}
// calculate sqrt
uint256 squareRoot = DecimalMath.mulFloor(
DecimalMath.ONE.sub(k).mul(4),
DecimalMath.mulFloor(k, Q0).mul(Q0)
); // 4(1-k)kQ0^2
squareRoot = b.mul(b).add(squareRoot).sqrt(); // sqrt(b*b+4(1-k)kQ0*Q0)
// final res
uint256 denominator = DecimalMath.ONE.sub(k).mul(2); // 2(1-k)
uint256 numerator;
if (minusbSig) {
numerator = b.add(squareRoot);
} else {
numerator = squareRoot.sub(b);
}
if (deltaBSig) {
return DecimalMath.divFloor(numerator, denominator);
} else {
return DecimalMath.divCeil(numerator, denominator);
}
}
/*
Start from the integration function
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Assume Q2=Q0, Given Q1 and deltaB, solve Q0
let fairAmount = i*deltaB
*/
function _SolveQuadraticFunctionForTarget(
uint256 V1,
uint256 k,
uint256 fairAmount
) internal pure returns (uint256 V0) {
// V0 = V1+V1*(sqrt-1)/2k
uint256 sqrt = DecimalMath.divCeil(DecimalMath.mulFloor(k, fairAmount).mul(4), V1);
sqrt = sqrt.add(DecimalMath.ONE).mul(DecimalMath.ONE).sqrt();
uint256 premium = DecimalMath.divCeil(sqrt.sub(DecimalMath.ONE), k.mul(2));
// V0 is greater than or equal to V1 according to the solution
return DecimalMath.mulFloor(V1, DecimalMath.ONE.add(premium));
}
}
contract DODOSellHelper {
using SafeMath for uint256;
enum RStatus {ONE, ABOVE_ONE, BELOW_ONE}
uint256 constant ONE = 10**18;
struct DODOState {
uint256 oraclePrice;
uint256 K;
uint256 B;
uint256 Q;
uint256 baseTarget;
uint256 quoteTarget;
RStatus rStatus;
}
function querySellBaseToken(address dodo, uint256 amount) public view returns (uint256) {
return IDODOV1(dodo).querySellBaseToken(amount);
}
function querySellQuoteToken(address dodo, uint256 amount) public view returns (uint256) {
DODOState memory state;
(state.baseTarget, state.quoteTarget) = IDODOV1(dodo).getExpectedTarget();
state.rStatus = RStatus(IDODOV1(dodo)._R_STATUS_());
state.oraclePrice = IDODOV1(dodo).getOraclePrice();
state.Q = IDODOV1(dodo)._QUOTE_BALANCE_();
state.B = IDODOV1(dodo)._BASE_BALANCE_();
state.K = IDODOV1(dodo)._K_();
uint256 boughtAmount;
// Determine the status (RStatus) and calculate the amount
// based on the state
if (state.rStatus == RStatus.ONE) {
boughtAmount = _ROneSellQuoteToken(amount, state);
} else if (state.rStatus == RStatus.ABOVE_ONE) {
boughtAmount = _RAboveSellQuoteToken(amount, state);
} else {
uint256 backOneBase = state.B.sub(state.baseTarget);
uint256 backOneQuote = state.quoteTarget.sub(state.Q);
if (amount <= backOneQuote) {
boughtAmount = _RBelowSellQuoteToken(amount, state);
} else {
boughtAmount = backOneBase.add(
_ROneSellQuoteToken(amount.sub(backOneQuote), state)
);
}
}
// Calculate fees
return
DecimalMath.divFloor(
boughtAmount,
DecimalMath.ONE.add(IDODOV1(dodo)._MT_FEE_RATE_()).add(
IDODOV1(dodo)._LP_FEE_RATE_()
)
);
}
function _ROneSellQuoteToken(uint256 amount, DODOState memory state)
internal
pure
returns (uint256 receiveBaseToken)
{
uint256 i = DecimalMath.divFloor(ONE, state.oraclePrice);
uint256 B2 = DODOMath._SolveQuadraticFunctionForTrade(
state.baseTarget,
state.baseTarget,
DecimalMath.mulFloor(i, amount),
false,
state.K
);
return state.baseTarget.sub(B2);
}
function _RAboveSellQuoteToken(uint256 amount, DODOState memory state)
internal
pure
returns (uint256 receieBaseToken)
{
uint256 i = DecimalMath.divFloor(ONE, state.oraclePrice);
uint256 B2 = DODOMath._SolveQuadraticFunctionForTrade(
state.baseTarget,
state.B,
DecimalMath.mulFloor(i, amount),
false,
state.K
);
return state.B.sub(B2);
}
function _RBelowSellQuoteToken(uint256 amount, DODOState memory state)
internal
pure
returns (uint256 receiveBaseToken)
{
uint256 Q1 = state.Q.add(amount);
uint256 i = DecimalMath.divFloor(ONE, state.oraclePrice);
return DODOMath._GeneralIntegrate(state.quoteTarget, Q1, state.Q, i, state.K);
}
}
// File: contracts/intf/IERC20.sol
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
}
// File: contracts/intf/IWETH.sol
interface IWETH {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address src,
address dst,
uint256 wad
) external returns (bool);
function deposit() external payable;
function withdraw(uint256 wad) external;
}
// File: contracts/SmartRoute/intf/IUni.sol
interface IUni {
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
function token0() external view returns (address);
function token1() external view returns (address);
}
// File: contracts/SmartRoute/intf/IChi.sol
interface IChi {
function freeUpTo(uint256 value) external returns (uint256);
}
// File: contracts/lib/SafeERC20.sol
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/SmartRoute/lib/UniversalERC20.sol
library UniversalERC20 {
using SafeMath for uint256;
using SafeERC20 for IERC20;
IERC20 private constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
function universalTransfer(
IERC20 token,
address payable to,
uint256 amount
) internal {
if (amount > 0) {
if (isETH(token)) {
to.transfer(amount);
} else {
token.safeTransfer(to, amount);
}
}
}
function universalApproveMax(
IERC20 token,
address to,
uint256 amount
) internal {
uint256 allowance = token.allowance(address(this), to);
if (allowance < amount) {
if (allowance > 0) {
token.safeApprove(to, 0);
}
token.safeApprove(to, uint256(-1));
}
}
function universalBalanceOf(IERC20 token, address who) internal view returns (uint256) {
if (isETH(token)) {
return who.balance;
} else {
return token.balanceOf(who);
}
}
function tokenBalanceOf(IERC20 token, address who) internal view returns (uint256) {
return token.balanceOf(who);
}
function isETH(IERC20 token) internal pure returns (bool) {
return token == ETH_ADDRESS;
}
}
// File: contracts/lib/ReentrancyGuard.sol
/**
* @title ReentrancyGuard
* @author DODO Breeder
*
* @notice Protect functions from Reentrancy Attack
*/
contract ReentrancyGuard {
// https://solidity.readthedocs.io/en/latest/control-structures.html?highlight=zero-state#scoping-and-declarations
// zero-state of _ENTERED_ is false
bool private _ENTERED_;
modifier preventReentrant() {
require(!_ENTERED_, "REENTRANT");
_ENTERED_ = true;
_;
_ENTERED_ = false;
}
}
// File: contracts/DODOToken/DODOIncentive.sol
interface IDODOIncentive {
function triggerIncentive(
address fromToken,
address toToken,
address assetTo
) external;
}
/**
* @title DODOIncentive
* @author DODO Breeder
*
* @notice Trade Incentive in DODO platform
*/
contract DODOIncentive is InitializableOwnable {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// ============ Storage ============
address public immutable _DODO_TOKEN_;
address public _DODO_PROXY_;
uint256 public dodoPerBlock;
uint256 public defaultRate = 10;
mapping(address => uint256) public boosts;
uint32 public lastRewardBlock;
uint112 public totalReward;
uint112 public totalDistribution;
// ============ Events ============
event SetBoost(address token, uint256 boostRate);
event SetNewProxy(address dodoProxy);
event SetPerReward(uint256 dodoPerBlock);
event SetDefaultRate(uint256 defaultRate);
event Incentive(address user, uint256 reward);
constructor(address _dodoToken) public {
_DODO_TOKEN_ = _dodoToken;
}
// ============ Ownable ============
function changeBoost(address _token, uint256 _boostRate) public onlyOwner {
require(_token != address(0));
require(_boostRate + defaultRate <= 1000);
boosts[_token] = _boostRate;
emit SetBoost(_token, _boostRate);
}
function changePerReward(uint256 _dodoPerBlock) public onlyOwner {
_updateTotalReward();
dodoPerBlock = _dodoPerBlock;
emit SetPerReward(dodoPerBlock);
}
function changeDefaultRate(uint256 _defaultRate) public onlyOwner {
defaultRate = _defaultRate;
emit SetDefaultRate(defaultRate);
}
function changeDODOProxy(address _dodoProxy) public onlyOwner {
_DODO_PROXY_ = _dodoProxy;
emit SetNewProxy(_DODO_PROXY_);
}
function emptyReward(address assetTo) public onlyOwner {
uint256 balance = IERC20(_DODO_TOKEN_).balanceOf(address(this));
IERC20(_DODO_TOKEN_).transfer(assetTo, balance);
}
// ============ Incentive function ============
function triggerIncentive(
address fromToken,
address toToken,
address assetTo
) external {
require(msg.sender == _DODO_PROXY_, "DODOIncentive:Access restricted");
uint256 curTotalDistribution = totalDistribution;
uint256 fromRate = boosts[fromToken];
uint256 toRate = boosts[toToken];
uint256 rate = (fromRate >= toRate ? fromRate : toRate) + defaultRate;
require(rate <= 1000, "RATE_INVALID");
uint256 _totalReward = _getTotalReward();
uint256 reward = ((_totalReward - curTotalDistribution) * rate) / 1000;
uint256 _totalDistribution = curTotalDistribution + reward;
_update(_totalReward, _totalDistribution);
if (reward != 0) {
IERC20(_DODO_TOKEN_).transfer(assetTo, reward);
emit Incentive(assetTo, reward);
}
}
function _updateTotalReward() internal {
uint256 _totalReward = _getTotalReward();
require(_totalReward < uint112(-1), "OVERFLOW");
totalReward = uint112(_totalReward);
lastRewardBlock = uint32(block.number);
}
function _update(uint256 _totalReward, uint256 _totalDistribution) internal {
require(
_totalReward < uint112(-1) && _totalDistribution < uint112(-1) && block.number < uint32(-1),
"OVERFLOW"
);
lastRewardBlock = uint32(block.number);
totalReward = uint112(_totalReward);
totalDistribution = uint112(_totalDistribution);
}
function _getTotalReward() internal view returns (uint256) {
if (lastRewardBlock == 0) {
return totalReward;
} else {
return totalReward + (block.number - lastRewardBlock) * dodoPerBlock;
}
}
// ============= Helper function ===============
function incentiveStatus(address fromToken, address toToken)
external
view
returns (
uint256 reward,
uint256 baseRate,
uint256 totalRate,
uint256 curTotalReward,
uint256 perBlockReward
)
{
baseRate = defaultRate;
uint256 fromRate = boosts[fromToken];
uint256 toRate = boosts[toToken];
totalRate = (fromRate >= toRate ? fromRate : toRate) + defaultRate;
uint256 _totalReward = _getTotalReward();
reward = ((_totalReward - totalDistribution) * totalRate) / 1000;
curTotalReward = _totalReward - totalDistribution;
perBlockReward = dodoPerBlock;
}
}
// File: contracts/SmartRoute/intf/IDODOAdapter.sol
interface IDODOAdapter {
function sellBase(address to, address pool) external;
function sellQuote(address to, address pool) external;
}
// File: contracts/SmartRoute/DODOV2Proxy02.sol
/**
* @title DODOV2Proxy02
* @author DODO Breeder
*
* @notice Entrance of trading in DODO platform
*/
contract DODOV2Proxy02 is IDODOV2Proxy01, ReentrancyGuard, InitializableOwnable {
using SafeMath for uint256;
using UniversalERC20 for IERC20;
// ============ Storage ============
address constant _ETH_ADDRESS_ = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
address public immutable _WETH_;
address public immutable _DODO_APPROVE_PROXY_;
address public immutable _DODO_SELL_HELPER_;
address public immutable _DVM_FACTORY_;
address public immutable _DPP_FACTORY_;
address public immutable _CP_FACTORY_;
address public immutable _DODO_INCENTIVE_;
address public immutable _CHI_TOKEN_;
uint256 public _GAS_DODO_MAX_RETURN_ = 0;
uint256 public _GAS_EXTERNAL_RETURN_ = 0;
mapping (address => bool) public isWhiteListed;
// ============ Events ============
event OrderHistory(
address fromToken,
address toToken,
address sender,
uint256 fromAmount,
uint256 returnAmount
);
// ============ Modifiers ============
modifier judgeExpired(uint256 deadLine) {
require(deadLine >= block.timestamp, "DODOV2Proxy02: EXPIRED");
_;
}
fallback() external payable {}
receive() external payable {}
constructor(
address dvmFactory,
address dppFactory,
address cpFactory,
address payable weth,
address dodoApproveProxy,
address dodoSellHelper,
address chiToken,
address dodoIncentive
) public {
_DVM_FACTORY_ = dvmFactory;
_DPP_FACTORY_ = dppFactory;
_CP_FACTORY_ = cpFactory;
_WETH_ = weth;
_DODO_APPROVE_PROXY_ = dodoApproveProxy;
_DODO_SELL_HELPER_ = dodoSellHelper;
_CHI_TOKEN_ = chiToken;
_DODO_INCENTIVE_ = dodoIncentive;
}
function addWhiteList (address contractAddr) public onlyOwner {
isWhiteListed[contractAddr] = true;
}
function removeWhiteList (address contractAddr) public onlyOwner {
isWhiteListed[contractAddr] = false;
}
function updateGasReturn(uint256 newDodoGasReturn, uint256 newExternalGasReturn) public onlyOwner {
_GAS_DODO_MAX_RETURN_ = newDodoGasReturn;
_GAS_EXTERNAL_RETURN_ = newExternalGasReturn;
}
// ============ DVM Functions (create & add liquidity) ============
function createDODOVendingMachine(
address baseToken,
address quoteToken,
uint256 baseInAmount,
uint256 quoteInAmount,
uint256 lpFeeRate,
uint256 i,
uint256 k,
bool isOpenTWAP,
uint256 deadLine
)
external
override
payable
preventReentrant
judgeExpired(deadLine)
returns (address newVendingMachine, uint256 shares)
{
{
address _baseToken = baseToken == _ETH_ADDRESS_ ? _WETH_ : baseToken;
address _quoteToken = quoteToken == _ETH_ADDRESS_ ? _WETH_ : quoteToken;
newVendingMachine = IDODOV2(_DVM_FACTORY_).createDODOVendingMachine(
_baseToken,
_quoteToken,
lpFeeRate,
i,
k,
isOpenTWAP
);
}
{
address _baseToken = baseToken;
address _quoteToken = quoteToken;
_deposit(
msg.sender,
newVendingMachine,
_baseToken,
baseInAmount,
_baseToken == _ETH_ADDRESS_
);
_deposit(
msg.sender,
newVendingMachine,
_quoteToken,
quoteInAmount,
_quoteToken == _ETH_ADDRESS_
);
}
(shares, , ) = IDODOV2(newVendingMachine).buyShares(msg.sender);
}
function addDVMLiquidity(
address dvmAddress,
uint256 baseInAmount,
uint256 quoteInAmount,
uint256 baseMinAmount,
uint256 quoteMinAmount,
uint8 flag, // 0 - ERC20, 1 - baseInETH, 2 - quoteInETH
uint256 deadLine
)
external
override
payable
preventReentrant
judgeExpired(deadLine)
returns (
uint256 shares,
uint256 baseAdjustedInAmount,
uint256 quoteAdjustedInAmount
)
{
address _dvm = dvmAddress;
(baseAdjustedInAmount, quoteAdjustedInAmount) = _addDVMLiquidity(
_dvm,
baseInAmount,
quoteInAmount
);
require(
baseAdjustedInAmount >= baseMinAmount && quoteAdjustedInAmount >= quoteMinAmount,
"DODOV2Proxy02: deposit amount is not enough"
);
_deposit(msg.sender, _dvm, IDODOV2(_dvm)._BASE_TOKEN_(), baseAdjustedInAmount, flag == 1);
_deposit(msg.sender, _dvm, IDODOV2(_dvm)._QUOTE_TOKEN_(), quoteAdjustedInAmount, flag == 2);
(shares, , ) = IDODOV2(_dvm).buyShares(msg.sender);
// refund dust eth
if (flag == 1 && msg.value > baseAdjustedInAmount) msg.sender.transfer(msg.value - baseAdjustedInAmount);
if (flag == 2 && msg.value > quoteAdjustedInAmount) msg.sender.transfer(msg.value - quoteAdjustedInAmount);
}
function _addDVMLiquidity(
address dvmAddress,
uint256 baseInAmount,
uint256 quoteInAmount
) internal view returns (uint256 baseAdjustedInAmount, uint256 quoteAdjustedInAmount) {
(uint256 baseReserve, uint256 quoteReserve) = IDODOV2(dvmAddress).getVaultReserve();
if (quoteReserve == 0 && baseReserve == 0) {
baseAdjustedInAmount = baseInAmount;
quoteAdjustedInAmount = quoteInAmount;
}
if (quoteReserve == 0 && baseReserve > 0) {
baseAdjustedInAmount = baseInAmount;
quoteAdjustedInAmount = 0;
}
if (quoteReserve > 0 && baseReserve > 0) {
uint256 baseIncreaseRatio = DecimalMath.divFloor(baseInAmount, baseReserve);
uint256 quoteIncreaseRatio = DecimalMath.divFloor(quoteInAmount, quoteReserve);
if (baseIncreaseRatio <= quoteIncreaseRatio) {
baseAdjustedInAmount = baseInAmount;
quoteAdjustedInAmount = DecimalMath.mulFloor(quoteReserve, baseIncreaseRatio);
} else {
quoteAdjustedInAmount = quoteInAmount;
baseAdjustedInAmount = DecimalMath.mulFloor(baseReserve, quoteIncreaseRatio);
}
}
}
// ============ DPP Functions (create & reset) ============
function createDODOPrivatePool(
address baseToken,
address quoteToken,
uint256 baseInAmount,
uint256 quoteInAmount,
uint256 lpFeeRate,
uint256 i,
uint256 k,
bool isOpenTwap,
uint256 deadLine
)
external
override
payable
preventReentrant
judgeExpired(deadLine)
returns (address newPrivatePool)
{
newPrivatePool = IDODOV2(_DPP_FACTORY_).createDODOPrivatePool();
address _baseToken = baseToken;
address _quoteToken = quoteToken;
_deposit(msg.sender, newPrivatePool, _baseToken, baseInAmount, _baseToken == _ETH_ADDRESS_);
_deposit(
msg.sender,
newPrivatePool,
_quoteToken,
quoteInAmount,
_quoteToken == _ETH_ADDRESS_
);
if (_baseToken == _ETH_ADDRESS_) _baseToken = _WETH_;
if (_quoteToken == _ETH_ADDRESS_) _quoteToken = _WETH_;
IDODOV2(_DPP_FACTORY_).initDODOPrivatePool(
newPrivatePool,
msg.sender,
_baseToken,
_quoteToken,
lpFeeRate,
k,
i,
isOpenTwap
);
}
function resetDODOPrivatePool(
address dppAddress,
uint256[] memory paramList, //0 - newLpFeeRate, 1 - newI, 2 - newK
uint256[] memory amountList, //0 - baseInAmount, 1 - quoteInAmount, 2 - baseOutAmount, 3- quoteOutAmount
uint8 flag, // 0 - ERC20, 1 - baseInETH, 2 - quoteInETH, 3 - baseOutETH, 4 - quoteOutETH
uint256 minBaseReserve,
uint256 minQuoteReserve,
uint256 deadLine
) external override payable preventReentrant judgeExpired(deadLine) {
_deposit(
msg.sender,
dppAddress,
IDODOV2(dppAddress)._BASE_TOKEN_(),
amountList[0],
flag == 1
);
_deposit(
msg.sender,
dppAddress,
IDODOV2(dppAddress)._QUOTE_TOKEN_(),
amountList[1],
flag == 2
);
require(IDODOV2(IDODOV2(dppAddress)._OWNER_()).reset(
msg.sender,
paramList[0],
paramList[1],
paramList[2],
amountList[2],
amountList[3],
minBaseReserve,
minQuoteReserve
), "Reset Failed");
_withdraw(msg.sender, IDODOV2(dppAddress)._BASE_TOKEN_(), amountList[2], flag == 3);
_withdraw(msg.sender, IDODOV2(dppAddress)._QUOTE_TOKEN_(), amountList[3], flag == 4);
}
// ============ Swap ============
function dodoSwapV2ETHToToken(
address toToken,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
)
external
override
payable
judgeExpired(deadLine)
returns (uint256 returnAmount)
{
require(dodoPairs.length > 0, "DODOV2Proxy02: PAIRS_EMPTY");
require(minReturnAmount > 0, "DODOV2Proxy02: RETURN_AMOUNT_ZERO");
uint256 originGas = gasleft();
uint256 originToTokenBalance = IERC20(toToken).balanceOf(msg.sender);
IWETH(_WETH_).deposit{value: msg.value}();
IWETH(_WETH_).transfer(dodoPairs[0], msg.value);
for (uint256 i = 0; i < dodoPairs.length; i++) {
if (i == dodoPairs.length - 1) {
if (directions & 1 == 0) {
IDODOV2(dodoPairs[i]).sellBase(msg.sender);
} else {
IDODOV2(dodoPairs[i]).sellQuote(msg.sender);
}
} else {
if (directions & 1 == 0) {
IDODOV2(dodoPairs[i]).sellBase(dodoPairs[i + 1]);
} else {
IDODOV2(dodoPairs[i]).sellQuote(dodoPairs[i + 1]);
}
}
directions = directions >> 1;
}
returnAmount = IERC20(toToken).balanceOf(msg.sender).sub(originToTokenBalance);
require(returnAmount >= minReturnAmount, "DODOV2Proxy02: Return amount is not enough");
_dodoGasReturn(originGas);
_execIncentive(isIncentive, _ETH_ADDRESS_, toToken);
emit OrderHistory(
_ETH_ADDRESS_,
toToken,
msg.sender,
msg.value,
returnAmount
);
}
function dodoSwapV2TokenToETH(
address fromToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
)
external
override
judgeExpired(deadLine)
returns (uint256 returnAmount)
{
require(dodoPairs.length > 0, "DODOV2Proxy02: PAIRS_EMPTY");
require(minReturnAmount > 0, "DODOV2Proxy02: RETURN_AMOUNT_ZERO");
uint256 originGas = gasleft();
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(fromToken, msg.sender, dodoPairs[0], fromTokenAmount);
for (uint256 i = 0; i < dodoPairs.length; i++) {
if (i == dodoPairs.length - 1) {
if (directions & 1 == 0) {
IDODOV2(dodoPairs[i]).sellBase(address(this));
} else {
IDODOV2(dodoPairs[i]).sellQuote(address(this));
}
} else {
if (directions & 1 == 0) {
IDODOV2(dodoPairs[i]).sellBase(dodoPairs[i + 1]);
} else {
IDODOV2(dodoPairs[i]).sellQuote(dodoPairs[i + 1]);
}
}
directions = directions >> 1;
}
returnAmount = IWETH(_WETH_).balanceOf(address(this));
require(returnAmount >= minReturnAmount, "DODOV2Proxy02: Return amount is not enough");
IWETH(_WETH_).withdraw(returnAmount);
msg.sender.transfer(returnAmount);
_dodoGasReturn(originGas);
_execIncentive(isIncentive, fromToken, _ETH_ADDRESS_);
emit OrderHistory(
fromToken,
_ETH_ADDRESS_,
msg.sender,
fromTokenAmount,
returnAmount
);
}
function dodoSwapV2TokenToToken(
address fromToken,
address toToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
)
external
override
judgeExpired(deadLine)
returns (uint256 returnAmount)
{
require(dodoPairs.length > 0, "DODOV2Proxy02: PAIRS_EMPTY");
require(minReturnAmount > 0, "DODOV2Proxy02: RETURN_AMOUNT_ZERO");
uint256 originGas = gasleft();
uint256 originToTokenBalance = IERC20(toToken).balanceOf(msg.sender);
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(fromToken, msg.sender, dodoPairs[0], fromTokenAmount);
for (uint256 i = 0; i < dodoPairs.length; i++) {
if (i == dodoPairs.length - 1) {
if (directions & 1 == 0) {
IDODOV2(dodoPairs[i]).sellBase(msg.sender);
} else {
IDODOV2(dodoPairs[i]).sellQuote(msg.sender);
}
} else {
if (directions& 1 == 0) {
IDODOV2(dodoPairs[i]).sellBase(dodoPairs[i + 1]);
} else {
IDODOV2(dodoPairs[i]).sellQuote(dodoPairs[i + 1]);
}
}
directions = directions >> 1;
}
returnAmount = IERC20(toToken).balanceOf(msg.sender).sub(originToTokenBalance);
require(returnAmount >= minReturnAmount, "DODOV2Proxy02: Return amount is not enough");
_dodoGasReturn(originGas);
_execIncentive(isIncentive, fromToken, toToken);
emit OrderHistory(
fromToken,
toToken,
msg.sender,
fromTokenAmount,
returnAmount
);
}
function externalSwap(
address fromToken,
address toToken,
address approveTarget,
address swapTarget,
uint256 fromTokenAmount,
uint256 minReturnAmount,
bytes memory callDataConcat,
bool isIncentive,
uint256 deadLine
)
external
override
payable
judgeExpired(deadLine)
returns (uint256 returnAmount)
{
require(minReturnAmount > 0, "DODOV2Proxy02: RETURN_AMOUNT_ZERO");
require(fromToken != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_SELL_CHI");
require(toToken != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_BUY_CHI");
uint256 toTokenOriginBalance = IERC20(toToken).universalBalanceOf(msg.sender);
if (fromToken != _ETH_ADDRESS_) {
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(
fromToken,
msg.sender,
address(this),
fromTokenAmount
);
IERC20(fromToken).universalApproveMax(approveTarget, fromTokenAmount);
}
require(isWhiteListed[swapTarget], "DODOV2Proxy02: Not Whitelist Contract");
(bool success, ) = swapTarget.call{value: fromToken == _ETH_ADDRESS_ ? msg.value : 0}(callDataConcat);
require(success, "DODOV2Proxy02: External Swap execution Failed");
IERC20(toToken).universalTransfer(
msg.sender,
IERC20(toToken).universalBalanceOf(address(this))
);
returnAmount = IERC20(toToken).universalBalanceOf(msg.sender).sub(toTokenOriginBalance);
require(returnAmount >= minReturnAmount, "DODOV2Proxy02: Return amount is not enough");
_externalGasReturn();
_execIncentive(isIncentive, fromToken, toToken);
emit OrderHistory(
fromToken,
toToken,
msg.sender,
fromTokenAmount,
returnAmount
);
}
function dodoSwapV1(
address fromToken,
address toToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory dodoPairs,
uint256 directions,
bool isIncentive,
uint256 deadLine
)
external
override
payable
judgeExpired(deadLine)
returns (uint256 returnAmount)
{
require(dodoPairs.length > 0, "DODOV2Proxy02: PAIRS_EMPTY");
require(minReturnAmount > 0, "DODOV2Proxy02: RETURN_AMOUNT_ZERO");
require(fromToken != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_SELL_CHI");
require(toToken != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_BUY_CHI");
uint256 originGas = gasleft();
address _fromToken = fromToken;
address _toToken = toToken;
_deposit(msg.sender, address(this), _fromToken, fromTokenAmount, _fromToken == _ETH_ADDRESS_);
for (uint256 i = 0; i < dodoPairs.length; i++) {
address curDodoPair = dodoPairs[i];
if (directions & 1 == 0) {
address curDodoBase = IDODOV1(curDodoPair)._BASE_TOKEN_();
require(curDodoBase != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_CHI");
uint256 curAmountIn = IERC20(curDodoBase).balanceOf(address(this));
IERC20(curDodoBase).universalApproveMax(curDodoPair, curAmountIn);
IDODOV1(curDodoPair).sellBaseToken(curAmountIn, 0, "");
} else {
address curDodoQuote = IDODOV1(curDodoPair)._QUOTE_TOKEN_();
require(curDodoQuote != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_CHI");
uint256 curAmountIn = IERC20(curDodoQuote).balanceOf(address(this));
IERC20(curDodoQuote).universalApproveMax(curDodoPair, curAmountIn);
uint256 canBuyBaseAmount = IDODOSellHelper(_DODO_SELL_HELPER_).querySellQuoteToken(
curDodoPair,
curAmountIn
);
IDODOV1(curDodoPair).buyBaseToken(canBuyBaseAmount, curAmountIn, "");
}
directions = directions >> 1;
}
if (_toToken == _ETH_ADDRESS_) {
returnAmount = IWETH(_WETH_).balanceOf(address(this));
IWETH(_WETH_).withdraw(returnAmount);
} else {
returnAmount = IERC20(_toToken).tokenBalanceOf(address(this));
}
require(returnAmount >= minReturnAmount, "DODOV2Proxy02: Return amount is not enough");
IERC20(_toToken).universalTransfer(msg.sender, returnAmount);
_dodoGasReturn(originGas);
_execIncentive(isIncentive, _fromToken, _toToken);
emit OrderHistory(_fromToken, _toToken, msg.sender, fromTokenAmount, returnAmount);
}
function mixSwap(
address fromToken,
address toToken,
uint256 fromTokenAmount,
uint256 minReturnAmount,
address[] memory mixAdapters,
address[] memory mixPairs,
address[] memory assetTo,
uint256 directions,
bool isIncentive,
uint256 deadLine
) external override payable judgeExpired(deadLine) returns (uint256 returnAmount) {
require(mixPairs.length > 0, "DODOV2Proxy02: PAIRS_EMPTY");
require(mixPairs.length == mixAdapters.length, "DODOV2Proxy02: PAIR_ADAPTER_NOT_MATCH");
require(mixPairs.length == assetTo.length - 1, "DODOV2Proxy02: PAIR_ASSETTO_NOT_MATCH");
require(minReturnAmount > 0, "DODOV2Proxy02: RETURN_AMOUNT_ZERO");
address _fromToken = fromToken;
address _toToken = toToken;
uint256 _fromTokenAmount = fromTokenAmount;
require(_fromToken != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_SELL_CHI");
require(_toToken != _CHI_TOKEN_, "DODOV2Proxy02: NOT_SUPPORT_BUY_CHI");
uint256 originGas = gasleft();
uint256 toTokenOriginBalance = IERC20(_toToken).universalBalanceOf(msg.sender);
_deposit(msg.sender, assetTo[0], _fromToken, _fromTokenAmount, _fromToken == _ETH_ADDRESS_);
for (uint256 i = 0; i < mixPairs.length; i++) {
if (directions & 1 == 0) {
IDODOAdapter(mixAdapters[i]).sellBase(assetTo[i + 1],mixPairs[i]);
} else {
IDODOAdapter(mixAdapters[i]).sellQuote(assetTo[i + 1],mixPairs[i]);
}
directions = directions >> 1;
}
if(_toToken == _ETH_ADDRESS_) {
returnAmount = IWETH(_WETH_).balanceOf(address(this));
IWETH(_WETH_).withdraw(returnAmount);
msg.sender.transfer(returnAmount);
}else {
returnAmount = IERC20(_toToken).tokenBalanceOf(msg.sender).sub(toTokenOriginBalance);
}
require(returnAmount >= minReturnAmount, "DODOV2Proxy02: Return amount is not enough");
_dodoGasReturn(originGas);
_execIncentive(isIncentive, _fromToken, _toToken);
emit OrderHistory(
_fromToken,
_toToken,
msg.sender,
_fromTokenAmount,
returnAmount
);
}
//============ CrowdPooling Functions (create & bid) ============
function createCrowdPooling(
address baseToken,
address quoteToken,
uint256 baseInAmount,
uint256[] memory timeLine,
uint256[] memory valueList,
bool isOpenTWAP,
uint256 deadLine
) external override payable preventReentrant judgeExpired(deadLine) returns (address payable newCrowdPooling) {
address _baseToken = baseToken;
address _quoteToken = quoteToken == _ETH_ADDRESS_ ? _WETH_ : quoteToken;
newCrowdPooling = IDODOV2(_CP_FACTORY_).createCrowdPooling();
_deposit(
msg.sender,
newCrowdPooling,
_baseToken,
baseInAmount,
false
);
newCrowdPooling.transfer(msg.value);
IDODOV2(_CP_FACTORY_).initCrowdPooling(
newCrowdPooling,
msg.sender,
_baseToken,
_quoteToken,
timeLine,
valueList,
isOpenTWAP
);
}
function bid(
address cpAddress,
uint256 quoteAmount,
uint8 flag, // 0 - ERC20, 1 - quoteInETH
uint256 deadLine
) external override payable preventReentrant judgeExpired(deadLine) {
_deposit(msg.sender, cpAddress, IDODOV2(cpAddress)._QUOTE_TOKEN_(), quoteAmount, flag == 1);
IDODOV2(cpAddress).bid(msg.sender);
}
function addLiquidityToV1(
address pair,
uint256 baseAmount,
uint256 quoteAmount,
uint256 baseMinShares,
uint256 quoteMinShares,
uint8 flag, // 0 erc20 In 1 baseInETH 2 quoteIn ETH
uint256 deadLine
) external override payable preventReentrant judgeExpired(deadLine) returns(uint256 baseShares, uint256 quoteShares) {
address _baseToken = IDODOV1(pair)._BASE_TOKEN_();
address _quoteToken = IDODOV1(pair)._QUOTE_TOKEN_();
_deposit(msg.sender, address(this), _baseToken, baseAmount, flag == 1);
_deposit(msg.sender, address(this), _quoteToken, quoteAmount, flag == 2);
if(baseAmount > 0) {
IERC20(_baseToken).universalApproveMax(pair, baseAmount);
baseShares = IDODOV1(pair).depositBaseTo(msg.sender, baseAmount);
}
if(quoteAmount > 0) {
IERC20(_quoteToken).universalApproveMax(pair, quoteAmount);
quoteShares = IDODOV1(pair).depositQuoteTo(msg.sender, quoteAmount);
}
require(baseShares >= baseMinShares && quoteShares >= quoteMinShares,"DODOV2Proxy02: Return DLP is not enough");
}
function _deposit(
address from,
address to,
address token,
uint256 amount,
bool isETH
) internal {
if (isETH) {
if (amount > 0) {
IWETH(_WETH_).deposit{value: amount}();
if (to != address(this)) SafeERC20.safeTransfer(IERC20(_WETH_), to, amount);
}
} else {
IDODOApproveProxy(_DODO_APPROVE_PROXY_).claimTokens(token, from, to, amount);
}
}
function _withdraw(
address payable to,
address token,
uint256 amount,
bool isETH
) internal {
if (isETH) {
if (amount > 0) {
IWETH(_WETH_).withdraw(amount);
to.transfer(amount);
}
} else {
if (amount > 0) {
SafeERC20.safeTransfer(IERC20(token), to, amount);
}
}
}
function _dodoGasReturn(uint256 originGas) internal {
uint256 _gasDodoMaxReturn = _GAS_DODO_MAX_RETURN_;
if(_gasDodoMaxReturn > 0) {
uint256 calcGasTokenBurn = originGas.sub(gasleft()) / 65000;
uint256 gasTokenBurn = calcGasTokenBurn > _gasDodoMaxReturn ? _gasDodoMaxReturn : calcGasTokenBurn;
if(gasTokenBurn >= 3 && gasleft() > 27710 + gasTokenBurn * 6080)
IChi(_CHI_TOKEN_).freeUpTo(gasTokenBurn);
}
}
function _externalGasReturn() internal {
uint256 _gasExternalReturn = _GAS_EXTERNAL_RETURN_;
if(_gasExternalReturn > 0) {
if(gasleft() > 27710 + _gasExternalReturn * 6080)
IChi(_CHI_TOKEN_).freeUpTo(_gasExternalReturn);
}
}
function _execIncentive(bool isIncentive, address fromToken,address toToken) internal {
if(isIncentive && gasleft() > 30000) {
IDODOIncentive(_DODO_INCENTIVE_).triggerIncentive(fromToken, toToken, msg.sender);
}
}
}File 2 of 8: DODOApproveProxy
// File: contracts/intf/IDODOApprove.sol
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IDODOApprove {
function claimTokens(address token,address who,address dest,uint256 amount) external;
function getDODOProxy() external view returns (address);
}
// File: contracts/lib/InitializableOwnable.sol
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract InitializableOwnable {
address public _OWNER_;
address public _NEW_OWNER_;
bool internal _INITIALIZED_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier notInitialized() {
require(!_INITIALIZED_, "DODO_INITIALIZED");
_;
}
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
function initOwner(address newOwner) public notInitialized {
_INITIALIZED_ = true;
_OWNER_ = newOwner;
}
function transferOwnership(address newOwner) public onlyOwner {
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() public {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}
// File: contracts/SmartRoute/DODOApproveProxy.sol
interface IDODOApproveProxy {
function isAllowedProxy(address _proxy) external view returns (bool);
function claimTokens(address token,address who,address dest,uint256 amount) external;
}
/**
* @title DODOApproveProxy
* @author DODO Breeder
*
* @notice Allow different version dodoproxy to claim from DODOApprove
*/
contract DODOApproveProxy is InitializableOwnable {
// ============ Storage ============
uint256 private constant _TIMELOCK_DURATION_ = 3 days;
mapping (address => bool) public _IS_ALLOWED_PROXY_;
uint256 public _TIMELOCK_;
address public _PENDING_ADD_DODO_PROXY_;
address public immutable _DODO_APPROVE_;
// ============ Modifiers ============
modifier notLocked() {
require(
_TIMELOCK_ <= block.timestamp,
"SetProxy is timelocked"
);
_;
}
constructor(address dodoApporve) public {
_DODO_APPROVE_ = dodoApporve;
}
function init(address owner, address[] memory proxies) external {
initOwner(owner);
for(uint i = 0; i < proxies.length; i++)
_IS_ALLOWED_PROXY_[proxies[i]] = true;
}
function unlockAddProxy(address newDodoProxy) public onlyOwner {
_TIMELOCK_ = block.timestamp + _TIMELOCK_DURATION_;
_PENDING_ADD_DODO_PROXY_ = newDodoProxy;
}
function lockAddProxy() public onlyOwner {
_PENDING_ADD_DODO_PROXY_ = address(0);
_TIMELOCK_ = 0;
}
function addDODOProxy() external onlyOwner notLocked() {
_IS_ALLOWED_PROXY_[_PENDING_ADD_DODO_PROXY_] = true;
lockAddProxy();
}
function removeDODOProxy (address oldDodoProxy) public onlyOwner {
_IS_ALLOWED_PROXY_[oldDodoProxy] = false;
}
function claimTokens(
address token,
address who,
address dest,
uint256 amount
) external {
require(_IS_ALLOWED_PROXY_[msg.sender], "DODOApproveProxy:Access restricted");
IDODOApprove(_DODO_APPROVE_).claimTokens(
token,
who,
dest,
amount
);
}
function isAllowedProxy(address _proxy) external view returns (bool) {
return _IS_ALLOWED_PROXY_[_proxy];
}
}File 3 of 8: DODOApprove
// File: contracts/intf/IERC20.sol
// This is a file copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
pragma solidity 0.6.9;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
}
// File: contracts/lib/SafeMath.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title SafeMath
* @author DODO Breeder
*
* @notice Math operations with safety checks that revert on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "MUL_ERROR");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "DIVIDING_ERROR");
return a / b;
}
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 quotient = div(a, b);
uint256 remainder = a - quotient * b;
if (remainder > 0) {
return quotient + 1;
} else {
return quotient;
}
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SUB_ERROR");
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "ADD_ERROR");
return c;
}
function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = x / 2 + 1;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
// File: contracts/lib/SafeERC20.sol
/*
Copyright 2020 DODO ZOO.
This is a simplified version of OpenZepplin's SafeERC20 library
*/
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
// solhint-disable-next-line max-line-length
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/lib/InitializableOwnable.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract InitializableOwnable {
address public _OWNER_;
address public _NEW_OWNER_;
bool internal _INITIALIZED_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier notInitialized() {
require(!_INITIALIZED_, "DODO_INITIALIZED");
_;
}
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
function initOwner(address newOwner) public notInitialized {
_INITIALIZED_ = true;
_OWNER_ = newOwner;
}
function transferOwnership(address newOwner) public onlyOwner {
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() public {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}
// File: contracts/SmartRoute/DODOApprove.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title DODOApprove
* @author DODO Breeder
*
* @notice Handle authorizations in DODO platform
*/
contract DODOApprove is InitializableOwnable {
using SafeERC20 for IERC20;
// ============ Storage ============
uint256 private constant _TIMELOCK_DURATION_ = 3 days;
uint256 private constant _TIMELOCK_EMERGENCY_DURATION_ = 24 hours;
uint256 public _TIMELOCK_;
address public _PENDING_DODO_PROXY_;
address public _DODO_PROXY_;
// ============ Events ============
event SetDODOProxy(address indexed oldProxy, address indexed newProxy);
// ============ Modifiers ============
modifier notLocked() {
require(
_TIMELOCK_ <= block.timestamp,
"SetProxy is timelocked"
);
_;
}
function init(address owner, address initProxyAddress) external {
initOwner(owner);
_DODO_PROXY_ = initProxyAddress;
}
function unlockSetProxy(address newDodoProxy) public onlyOwner {
if(_DODO_PROXY_ == address(0))
_TIMELOCK_ = block.timestamp + _TIMELOCK_EMERGENCY_DURATION_;
else
_TIMELOCK_ = block.timestamp + _TIMELOCK_DURATION_;
_PENDING_DODO_PROXY_ = newDodoProxy;
}
function lockSetProxy() public onlyOwner {
_PENDING_DODO_PROXY_ = address(0);
_TIMELOCK_ = 0;
}
function setDODOProxy() external onlyOwner notLocked() {
emit SetDODOProxy(_DODO_PROXY_, _PENDING_DODO_PROXY_);
_DODO_PROXY_ = _PENDING_DODO_PROXY_;
lockSetProxy();
}
function claimTokens(
address token,
address who,
address dest,
uint256 amount
) external {
require(msg.sender == _DODO_PROXY_, "DODOApprove:Access restricted");
if (amount > 0) {
IERC20(token).safeTransferFrom(who, dest, amount);
}
}
function getDODOProxy() public view returns (address) {
return _DODO_PROXY_;
}
}File 4 of 8: DODOToken
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
/**
* @title SafeMath
* @author DODO Breeder
*
* @notice Math operations with safety checks that revert on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "MUL_ERROR");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "DIVIDING_ERROR");
return a / b;
}
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 quotient = div(a, b);
uint256 remainder = a - quotient * b;
if (remainder > 0) {
return quotient + 1;
} else {
return quotient;
}
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SUB_ERROR");
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "ADD_ERROR");
return c;
}
function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = x / 2 + 1;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
// File: contracts/token/DODOToken.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title DODO Token
* @author DODO Breeder
*/
contract DODOToken {
using SafeMath for uint256;
string public symbol = "DODO";
string public name = "DODO bird";
uint256 public decimals = 18;
uint256 public totalSupply = 1000000000 * 10**18; // 1 Billion
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);
// ============ Functions ============
constructor() public {
balances[msg.sender] = totalSupply;
}
/**
* @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];
}
}File 5 of 8: DODO
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
library Types {
enum RStatus {ONE, ABOVE_ONE, BELOW_ONE}
}
// File: contracts/intf/IERC20.sol
// This is a file copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
function name() external view returns (string memory);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
}
// File: contracts/lib/InitializableOwnable.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract InitializableOwnable {
address public _OWNER_;
address public _NEW_OWNER_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "INVALID_OWNER");
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() external {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}
// File: contracts/lib/SafeMath.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title SafeMath
* @author DODO Breeder
*
* @notice Math operations with safety checks that revert on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "MUL_ERROR");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "DIVIDING_ERROR");
return a / b;
}
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 quotient = div(a, b);
uint256 remainder = a - quotient * b;
if (remainder > 0) {
return quotient + 1;
} else {
return quotient;
}
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SUB_ERROR");
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "ADD_ERROR");
return c;
}
function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = x / 2 + 1;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
// File: contracts/lib/DecimalMath.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title DecimalMath
* @author DODO Breeder
*
* @notice Functions for fixed point number with 18 decimals
*/
library DecimalMath {
using SafeMath for uint256;
uint256 constant ONE = 10**18;
function mul(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(d) / ONE;
}
function divFloor(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(ONE).div(d);
}
function divCeil(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(ONE).divCeil(d);
}
}
// File: contracts/lib/ReentrancyGuard.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title ReentrancyGuard
* @author DODO Breeder
*
* @notice Protect functions from Reentrancy Attack
*/
contract ReentrancyGuard {
// https://solidity.readthedocs.io/en/latest/control-structures.html?highlight=zero-state#scoping-and-declarations
// zero-state of _ENTERED_ is false
bool private _ENTERED_;
modifier preventReentrant() {
require(!_ENTERED_, "REENTRANT");
_ENTERED_ = true;
_;
_ENTERED_ = false;
}
}
// File: contracts/intf/IOracle.sol
/*
Copyright 2020 DODO ZOO.
*/
interface IOracle {
function getPrice() external view returns (uint256);
}
// File: contracts/intf/IDODOLpToken.sol
/*
Copyright 2020 DODO ZOO.
*/
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);
}
// File: contracts/impl/Storage.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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_;
// ============ 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 100; // 1.0.0
}
}
// File: contracts/intf/IDODOCallee.sol
/*
Copyright 2020 DODO ZOO.
*/
interface IDODOCallee {
function dodoCall(
bool isBuyBaseToken,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
}
// File: contracts/lib/DODOMath.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title DODOMath
* @author DODO Breeder
*
* @notice Functions for complex calculating. Including ONE Integration and TWO Quadratic solutions
*/
library DODOMath {
using SafeMath for uint256;
/*
Integrate dodo curve fron V1 to V2
require V0>=V1>=V2>0
res = (1-k)i(V1-V2)+ikV0*V0(1/V2-1/V1)
let V1-V2=delta
res = i*delta*(1-k+k(V0^2/V1/V2))
*/
function _GeneralIntegrate(
uint256 V0,
uint256 V1,
uint256 V2,
uint256 i,
uint256 k
) internal pure returns (uint256) {
uint256 fairAmount = DecimalMath.mul(i, V1.sub(V2)); // i*delta
uint256 V0V0V1V2 = DecimalMath.divCeil(V0.mul(V0).div(V1), V2);
uint256 penalty = DecimalMath.mul(k, V0V0V1V2); // k(V0^2/V1/V2)
return DecimalMath.mul(fairAmount, DecimalMath.ONE.sub(k).add(penalty));
}
/*
The same with integration expression above, we have:
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Given Q1 and deltaB, solve Q2
This is a quadratic function and the standard version is
aQ2^2 + bQ2 + c = 0, where
a=1-k
-b=(1-k)Q1-kQ0^2/Q1+i*deltaB
c=-kQ0^2
and Q2=(-b+sqrt(b^2+4(1-k)kQ0^2))/2(1-k)
note: another root is negative, abondan
if deltaBSig=true, then Q2>Q1
if deltaBSig=false, then Q2<Q1
*/
function _SolveQuadraticFunctionForTrade(
uint256 Q0,
uint256 Q1,
uint256 ideltaB,
bool deltaBSig,
uint256 k
) internal pure returns (uint256) {
// calculate -b value and sig
// -b = (1-k)Q1-kQ0^2/Q1+i*deltaB
uint256 kQ02Q1 = DecimalMath.mul(k, Q0).mul(Q0).div(Q1); // kQ0^2/Q1
uint256 b = DecimalMath.mul(DecimalMath.ONE.sub(k), Q1); // (1-k)Q1
bool minusbSig = true;
if (deltaBSig) {
b = b.add(ideltaB); // (1-k)Q1+i*deltaB
} else {
kQ02Q1 = kQ02Q1.add(ideltaB); // i*deltaB+kQ0^2/Q1
}
if (b >= kQ02Q1) {
b = b.sub(kQ02Q1);
minusbSig = true;
} else {
b = kQ02Q1.sub(b);
minusbSig = false;
}
// calculate sqrt
uint256 squareRoot = DecimalMath.mul(
DecimalMath.ONE.sub(k).mul(4),
DecimalMath.mul(k, Q0).mul(Q0)
); // 4(1-k)kQ0^2
squareRoot = b.mul(b).add(squareRoot).sqrt(); // sqrt(b*b+4(1-k)kQ0*Q0)
// final res
uint256 denominator = DecimalMath.ONE.sub(k).mul(2); // 2(1-k)
uint256 numerator;
if (minusbSig) {
numerator = b.add(squareRoot);
} else {
numerator = squareRoot.sub(b);
}
if (deltaBSig) {
return DecimalMath.divFloor(numerator, denominator);
} else {
return DecimalMath.divCeil(numerator, denominator);
}
}
/*
Start from the integration function
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Assume Q2=Q0, Given Q1 and deltaB, solve Q0
let fairAmount = i*deltaB
*/
function _SolveQuadraticFunctionForTarget(
uint256 V1,
uint256 k,
uint256 fairAmount
) internal pure returns (uint256 V0) {
// V0 = V1+V1*(sqrt-1)/2k
uint256 sqrt = DecimalMath.divCeil(DecimalMath.mul(k, fairAmount).mul(4), V1);
sqrt = sqrt.add(DecimalMath.ONE).mul(DecimalMath.ONE).sqrt();
uint256 premium = DecimalMath.divCeil(sqrt.sub(DecimalMath.ONE), k.mul(2));
// V0 is greater than or equal to V1 according to the solution
return DecimalMath.mul(V1, DecimalMath.ONE.add(premium));
}
}
// File: contracts/impl/Pricing.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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.mul(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_);
// }
}
// File: contracts/lib/SafeERC20.sol
/*
Copyright 2020 DODO ZOO.
This is a simplified version of OpenZepplin's SafeERC20 library
*/
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/impl/Settlement.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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 {
IERC20(_BASE_TOKEN_).safeTransferFrom(from, address(this), amount);
_BASE_BALANCE_ = _BASE_BALANCE_.add(amount);
}
function _quoteTokenTransferIn(address from, uint256 amount) internal {
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 quoteAmount = DecimalMath.mul(
getBaseCapitalBalanceOf(msg.sender),
_BASE_CAPITAL_RECEIVE_QUOTE_
);
uint256 baseAmount = DecimalMath.mul(
getQuoteCapitalBalanceOf(msg.sender),
_QUOTE_CAPITAL_RECEIVE_BASE_
);
_baseTokenTransferOut(msg.sender, baseAmount);
_quoteTokenTransferOut(msg.sender, quoteAmount);
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);
}
}
// File: contracts/impl/Trader.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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 gasPriceLimit() {
require(tx.gasprice <= _GAS_PRICE_LIMIT_, "GAS_PRICE_EXCEED");
_;
}
// ============ Trade Functions ============
function sellBaseToken(
uint256 amount,
uint256 minReceiveQuote,
bytes calldata data
) external tradeAllowed 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 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);
}
}
// File: contracts/impl/LiquidityProvider.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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");
_;
}
// ============ 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 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 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 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 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;
}
}
}
// File: contracts/impl/Admin.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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;
}
}
// File: contracts/lib/Ownable.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract Ownable {
address public _OWNER_;
address public _NEW_OWNER_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
constructor() internal {
_OWNER_ = msg.sender;
emit OwnershipTransferred(address(0), _OWNER_);
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "INVALID_OWNER");
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() external {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}
// File: contracts/impl/DODOLpToken.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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);
}
}
// File: contracts/dodo.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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_ = true;
_DEPOSIT_QUOTE_ALLOWED_ = true;
_TRADE_ALLOWED_ = true;
_GAS_PRICE_LIMIT_ = gasPriceLimit;
_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();
}
}File 6 of 8: DODO
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
library Types {
enum RStatus {ONE, ABOVE_ONE, BELOW_ONE}
}
// File: contracts/intf/IERC20.sol
// This is a file copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/IERC20.sol
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
function decimals() external view returns (uint8);
function name() external view returns (string memory);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
}
// File: contracts/lib/InitializableOwnable.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract InitializableOwnable {
address public _OWNER_;
address public _NEW_OWNER_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "INVALID_OWNER");
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() external {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}
// File: contracts/lib/SafeMath.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title SafeMath
* @author DODO Breeder
*
* @notice Math operations with safety checks that revert on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "MUL_ERROR");
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "DIVIDING_ERROR");
return a / b;
}
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 quotient = div(a, b);
uint256 remainder = a - quotient * b;
if (remainder > 0) {
return quotient + 1;
} else {
return quotient;
}
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SUB_ERROR");
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "ADD_ERROR");
return c;
}
function sqrt(uint256 x) internal pure returns (uint256 y) {
uint256 z = x / 2 + 1;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
// File: contracts/lib/DecimalMath.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title DecimalMath
* @author DODO Breeder
*
* @notice Functions for fixed point number with 18 decimals
*/
library DecimalMath {
using SafeMath for uint256;
uint256 constant ONE = 10**18;
function mul(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(d) / ONE;
}
function divFloor(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(ONE).div(d);
}
function divCeil(uint256 target, uint256 d) internal pure returns (uint256) {
return target.mul(ONE).divCeil(d);
}
}
// File: contracts/lib/ReentrancyGuard.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title ReentrancyGuard
* @author DODO Breeder
*
* @notice Protect functions from Reentrancy Attack
*/
contract ReentrancyGuard {
// https://solidity.readthedocs.io/en/latest/control-structures.html?highlight=zero-state#scoping-and-declarations
// zero-state of _ENTERED_ is false
bool private _ENTERED_;
modifier preventReentrant() {
require(!_ENTERED_, "REENTRANT");
_ENTERED_ = true;
_;
_ENTERED_ = false;
}
}
// File: contracts/intf/IOracle.sol
/*
Copyright 2020 DODO ZOO.
*/
interface IOracle {
function getPrice() external view returns (uint256);
}
// File: contracts/intf/IDODOLpToken.sol
/*
Copyright 2020 DODO ZOO.
*/
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);
}
// File: contracts/impl/Storage.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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_;
// ============ 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 100; // 1.0.0
}
}
// File: contracts/intf/IDODOCallee.sol
/*
Copyright 2020 DODO ZOO.
*/
interface IDODOCallee {
function dodoCall(
bool isBuyBaseToken,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
}
// File: contracts/lib/DODOMath.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title DODOMath
* @author DODO Breeder
*
* @notice Functions for complex calculating. Including ONE Integration and TWO Quadratic solutions
*/
library DODOMath {
using SafeMath for uint256;
/*
Integrate dodo curve fron V1 to V2
require V0>=V1>=V2>0
res = (1-k)i(V1-V2)+ikV0*V0(1/V2-1/V1)
let V1-V2=delta
res = i*delta*(1-k+k(V0^2/V1/V2))
*/
function _GeneralIntegrate(
uint256 V0,
uint256 V1,
uint256 V2,
uint256 i,
uint256 k
) internal pure returns (uint256) {
uint256 fairAmount = DecimalMath.mul(i, V1.sub(V2)); // i*delta
uint256 V0V0V1V2 = DecimalMath.divCeil(V0.mul(V0).div(V1), V2);
uint256 penalty = DecimalMath.mul(k, V0V0V1V2); // k(V0^2/V1/V2)
return DecimalMath.mul(fairAmount, DecimalMath.ONE.sub(k).add(penalty));
}
/*
The same with integration expression above, we have:
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Given Q1 and deltaB, solve Q2
This is a quadratic function and the standard version is
aQ2^2 + bQ2 + c = 0, where
a=1-k
-b=(1-k)Q1-kQ0^2/Q1+i*deltaB
c=-kQ0^2
and Q2=(-b+sqrt(b^2+4(1-k)kQ0^2))/2(1-k)
note: another root is negative, abondan
if deltaBSig=true, then Q2>Q1
if deltaBSig=false, then Q2<Q1
*/
function _SolveQuadraticFunctionForTrade(
uint256 Q0,
uint256 Q1,
uint256 ideltaB,
bool deltaBSig,
uint256 k
) internal pure returns (uint256) {
// calculate -b value and sig
// -b = (1-k)Q1-kQ0^2/Q1+i*deltaB
uint256 kQ02Q1 = DecimalMath.mul(k, Q0).mul(Q0).div(Q1); // kQ0^2/Q1
uint256 b = DecimalMath.mul(DecimalMath.ONE.sub(k), Q1); // (1-k)Q1
bool minusbSig = true;
if (deltaBSig) {
b = b.add(ideltaB); // (1-k)Q1+i*deltaB
} else {
kQ02Q1 = kQ02Q1.add(ideltaB); // i*deltaB+kQ0^2/Q1
}
if (b >= kQ02Q1) {
b = b.sub(kQ02Q1);
minusbSig = true;
} else {
b = kQ02Q1.sub(b);
minusbSig = false;
}
// calculate sqrt
uint256 squareRoot = DecimalMath.mul(
DecimalMath.ONE.sub(k).mul(4),
DecimalMath.mul(k, Q0).mul(Q0)
); // 4(1-k)kQ0^2
squareRoot = b.mul(b).add(squareRoot).sqrt(); // sqrt(b*b+4(1-k)kQ0*Q0)
// final res
uint256 denominator = DecimalMath.ONE.sub(k).mul(2); // 2(1-k)
uint256 numerator;
if (minusbSig) {
numerator = b.add(squareRoot);
} else {
numerator = squareRoot.sub(b);
}
if (deltaBSig) {
return DecimalMath.divFloor(numerator, denominator);
} else {
return DecimalMath.divCeil(numerator, denominator);
}
}
/*
Start from the integration function
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Assume Q2=Q0, Given Q1 and deltaB, solve Q0
let fairAmount = i*deltaB
*/
function _SolveQuadraticFunctionForTarget(
uint256 V1,
uint256 k,
uint256 fairAmount
) internal pure returns (uint256 V0) {
// V0 = V1+V1*(sqrt-1)/2k
uint256 sqrt = DecimalMath.divCeil(DecimalMath.mul(k, fairAmount).mul(4), V1);
sqrt = sqrt.add(DecimalMath.ONE).mul(DecimalMath.ONE).sqrt();
uint256 premium = DecimalMath.divCeil(sqrt.sub(DecimalMath.ONE), k.mul(2));
// V0 is greater than or equal to V1 according to the solution
return DecimalMath.mul(V1, DecimalMath.ONE.add(premium));
}
}
// File: contracts/impl/Pricing.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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.mul(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_);
// }
}
// File: contracts/lib/SafeERC20.sol
/*
Copyright 2020 DODO ZOO.
This is a simplified version of OpenZepplin's SafeERC20 library
*/
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using SafeMath for uint256;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(
token,
abi.encodeWithSelector(token.transferFrom.selector, from, to, value)
);
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves.
// A Solidity high level call has three parts:
// 1. The target address is checked to verify it contains contract code
// 2. The call itself is made, and success asserted
// 3. The return value is decoded, which in turn checks the size of the returned data.
// solhint-disable-next-line max-line-length
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory returndata) = address(token).call(data);
require(success, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
// solhint-disable-next-line max-line-length
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// File: contracts/impl/Settlement.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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 {
IERC20(_BASE_TOKEN_).safeTransferFrom(from, address(this), amount);
_BASE_BALANCE_ = _BASE_BALANCE_.add(amount);
}
function _quoteTokenTransferIn(address from, uint256 amount) internal {
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 quoteAmount = DecimalMath.mul(
getBaseCapitalBalanceOf(msg.sender),
_BASE_CAPITAL_RECEIVE_QUOTE_
);
uint256 baseAmount = DecimalMath.mul(
getQuoteCapitalBalanceOf(msg.sender),
_QUOTE_CAPITAL_RECEIVE_BASE_
);
_baseTokenTransferOut(msg.sender, baseAmount);
_quoteTokenTransferOut(msg.sender, quoteAmount);
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);
}
}
// File: contracts/impl/Trader.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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 gasPriceLimit() {
require(tx.gasprice <= _GAS_PRICE_LIMIT_, "GAS_PRICE_EXCEED");
_;
}
// ============ Trade Functions ============
function sellBaseToken(
uint256 amount,
uint256 minReceiveQuote,
bytes calldata data
) external tradeAllowed 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 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);
}
}
// File: contracts/impl/LiquidityProvider.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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");
_;
}
// ============ 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 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 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 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 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;
}
}
}
// File: contracts/impl/Admin.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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;
}
}
// File: contracts/lib/Ownable.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @title Ownable
* @author DODO Breeder
*
* @notice Ownership related functions
*/
contract Ownable {
address public _OWNER_;
address public _NEW_OWNER_;
// ============ Events ============
event OwnershipTransferPrepared(address indexed previousOwner, address indexed newOwner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
// ============ Modifiers ============
modifier onlyOwner() {
require(msg.sender == _OWNER_, "NOT_OWNER");
_;
}
// ============ Functions ============
constructor() internal {
_OWNER_ = msg.sender;
emit OwnershipTransferred(address(0), _OWNER_);
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "INVALID_OWNER");
emit OwnershipTransferPrepared(_OWNER_, newOwner);
_NEW_OWNER_ = newOwner;
}
function claimOwnership() external {
require(msg.sender == _NEW_OWNER_, "INVALID_CLAIM");
emit OwnershipTransferred(_OWNER_, _NEW_OWNER_);
_OWNER_ = _NEW_OWNER_;
_NEW_OWNER_ = address(0);
}
}
// File: contracts/impl/DODOLpToken.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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);
}
}
// File: contracts/dodo.sol
/*
Copyright 2020 DODO ZOO.
*/
/**
* @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_ = true;
_DEPOSIT_QUOTE_ALLOWED_ = true;
_TRADE_ALLOWED_ = true;
_GAS_PRICE_LIMIT_ = gasPriceLimit;
_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();
}
}File 7 of 8: ConstOracle
/*
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;
}
}File 8 of 8: TetherToken
pragma solidity ^0.4.17;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
if (newOwner != address(0)) {
owner = newOwner;
}
}
}
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20Basic {
uint public _totalSupply;
function totalSupply() public constant returns (uint);
function balanceOf(address who) public constant returns (uint);
function transfer(address to, uint value) public;
event Transfer(address indexed from, address indexed to, uint value);
}
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) public constant returns (uint);
function transferFrom(address from, address to, uint value) public;
function approve(address spender, uint value) public;
event Approval(address indexed owner, address indexed spender, uint value);
}
/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
contract BasicToken is Ownable, ERC20Basic {
using SafeMath for uint;
mapping(address => uint) public balances;
// additional variables for use if transaction fees ever became necessary
uint public basisPointsRate = 0;
uint public maximumFee = 0;
/**
* @dev Fix for the ERC20 short address attack.
*/
modifier onlyPayloadSize(uint size) {
require(!(msg.data.length < size + 4));
_;
}
/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) {
uint fee = (_value.mul(basisPointsRate)).div(10000);
if (fee > maximumFee) {
fee = maximumFee;
}
uint sendAmount = _value.sub(fee);
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(sendAmount);
if (fee > 0) {
balances[owner] = balances[owner].add(fee);
Transfer(msg.sender, owner, fee);
}
Transfer(msg.sender, _to, sendAmount);
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public constant returns (uint balance) {
return balances[_owner];
}
}
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* @dev https://github.com/ethereum/EIPs/issues/20
* @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract StandardToken is BasicToken, ERC20 {
mapping (address => mapping (address => uint)) public allowed;
uint public constant MAX_UINT = 2**256 - 1;
/**
* @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 _value uint the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
var _allowance = allowed[_from][msg.sender];
// Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
// if (_value > _allowance) throw;
uint fee = (_value.mul(basisPointsRate)).div(10000);
if (fee > maximumFee) {
fee = maximumFee;
}
if (_allowance < MAX_UINT) {
allowed[_from][msg.sender] = _allowance.sub(_value);
}
uint sendAmount = _value.sub(fee);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(sendAmount);
if (fee > 0) {
balances[owner] = balances[owner].add(fee);
Transfer(_from, owner, fee);
}
Transfer(_from, _to, sendAmount);
}
/**
* @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 _value The amount of tokens to be spent.
*/
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
// To change the approve amount you first have to reduce the addresses`
// allowance to zero by calling `approve(_spender, 0)` if it is not
// already 0 to mitigate the race condition described here:
// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}
/**
* @dev Function to check the amount of tokens than 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 uint specifying the amount of tokens still available for the spender.
*/
function allowance(address _owner, address _spender) public constant returns (uint remaining) {
return allowed[_owner][_spender];
}
}
/**
* @title Pausable
* @dev Base contract which allows children to implement an emergency stop mechanism.
*/
contract Pausable is Ownable {
event Pause();
event Unpause();
bool public paused = false;
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*/
modifier whenNotPaused() {
require(!paused);
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*/
modifier whenPaused() {
require(paused);
_;
}
/**
* @dev called by the owner to pause, triggers stopped state
*/
function pause() onlyOwner whenNotPaused public {
paused = true;
Pause();
}
/**
* @dev called by the owner to unpause, returns to normal state
*/
function unpause() onlyOwner whenPaused public {
paused = false;
Unpause();
}
}
contract BlackList is Ownable, BasicToken {
/////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
function getBlackListStatus(address _maker) external constant returns (bool) {
return isBlackListed[_maker];
}
function getOwner() external constant returns (address) {
return owner;
}
mapping (address => bool) public isBlackListed;
function addBlackList (address _evilUser) public onlyOwner {
isBlackListed[_evilUser] = true;
AddedBlackList(_evilUser);
}
function removeBlackList (address _clearedUser) public onlyOwner {
isBlackListed[_clearedUser] = false;
RemovedBlackList(_clearedUser);
}
function destroyBlackFunds (address _blackListedUser) public onlyOwner {
require(isBlackListed[_blackListedUser]);
uint dirtyFunds = balanceOf(_blackListedUser);
balances[_blackListedUser] = 0;
_totalSupply -= dirtyFunds;
DestroyedBlackFunds(_blackListedUser, dirtyFunds);
}
event DestroyedBlackFunds(address _blackListedUser, uint _balance);
event AddedBlackList(address _user);
event RemovedBlackList(address _user);
}
contract UpgradedStandardToken is StandardToken{
// those methods are called by the legacy contract
// and they must ensure msg.sender to be the contract address
function transferByLegacy(address from, address to, uint value) public;
function transferFromByLegacy(address sender, address from, address spender, uint value) public;
function approveByLegacy(address from, address spender, uint value) public;
}
contract TetherToken is Pausable, StandardToken, BlackList {
string public name;
string public symbol;
uint public decimals;
address public upgradedAddress;
bool public deprecated;
// The contract can be initialized with a number of tokens
// All the tokens are deposited to the owner address
//
// @param _balance Initial supply of the contract
// @param _name Token Name
// @param _symbol Token symbol
// @param _decimals Token decimals
function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
_totalSupply = _initialSupply;
name = _name;
symbol = _symbol;
decimals = _decimals;
balances[owner] = _initialSupply;
deprecated = false;
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function transfer(address _to, uint _value) public whenNotPaused {
require(!isBlackListed[msg.sender]);
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
} else {
return super.transfer(_to, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
require(!isBlackListed[_from]);
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
} else {
return super.transferFrom(_from, _to, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function balanceOf(address who) public constant returns (uint) {
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).balanceOf(who);
} else {
return super.balanceOf(who);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
if (deprecated) {
return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
} else {
return super.approve(_spender, _value);
}
}
// Forward ERC20 methods to upgraded contract if this one is deprecated
function allowance(address _owner, address _spender) public constant returns (uint remaining) {
if (deprecated) {
return StandardToken(upgradedAddress).allowance(_owner, _spender);
} else {
return super.allowance(_owner, _spender);
}
}
// deprecate current contract in favour of a new one
function deprecate(address _upgradedAddress) public onlyOwner {
deprecated = true;
upgradedAddress = _upgradedAddress;
Deprecate(_upgradedAddress);
}
// deprecate current contract if favour of a new one
function totalSupply() public constant returns (uint) {
if (deprecated) {
return StandardToken(upgradedAddress).totalSupply();
} else {
return _totalSupply;
}
}
// Issue a new amount of tokens
// these tokens are deposited into the owner address
//
// @param _amount Number of tokens to be issued
function issue(uint amount) public onlyOwner {
require(_totalSupply + amount > _totalSupply);
require(balances[owner] + amount > balances[owner]);
balances[owner] += amount;
_totalSupply += amount;
Issue(amount);
}
// Redeem tokens.
// These tokens are withdrawn from the owner address
// if the balance must be enough to cover the redeem
// or the call will fail.
// @param _amount Number of tokens to be issued
function redeem(uint amount) public onlyOwner {
require(_totalSupply >= amount);
require(balances[owner] >= amount);
_totalSupply -= amount;
balances[owner] -= amount;
Redeem(amount);
}
function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
// Ensure transparency by hardcoding limit beyond which fees can never be added
require(newBasisPoints < 20);
require(newMaxFee < 50);
basisPointsRate = newBasisPoints;
maximumFee = newMaxFee.mul(10**decimals);
Params(basisPointsRate, maximumFee);
}
// Called when new token are issued
event Issue(uint amount);
// Called when tokens are redeemed
event Redeem(uint amount);
// Called when contract is deprecated
event Deprecate(address newAddress);
// Called if contract ever adds fees
event Params(uint feeBasisPoints, uint maxFee);
}