Contract Source Code:
// File: contracts/intf/IDODOCallee.sol
/*
Copyright 2020 DODO ZOO.
SPDX-License-Identifier: Apache-2.0
*/
pragma solidity 0.6.9;
pragma experimental ABIEncoderV2;
interface IDODOCallee {
function DVMSellShareCall(
address sender,
uint256 burnShareAmount,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function DVMFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function DPPFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function DSPFlashLoanCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function CPCancelCall(
address sender,
uint256 amount,
bytes calldata data
) external;
function CPClaimBidCall(
address sender,
uint256 baseAmount,
uint256 quoteAmount,
bytes calldata data
) external;
function NFTRedeemCall(
address payable assetTo,
uint256 quoteAmount,
bytes calldata
) external;
}
// 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/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);
}
function powFloor(uint256 target, uint256 e) internal pure returns (uint256) {
if (e == 0) {
return 10 ** 18;
} else if (e == 1) {
return target;
} else {
uint p = powFloor(target, e.div(2));
p = p.mul(p) / (10**18);
if (e % 2 == 1) {
p = p.mul(target) / (10**18);
}
return p;
}
}
}
// File: contracts/lib/DODOMath.sol
/**
* @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 from 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))
i is the price of V-res trading pair
support k=1 & k=0 case
[round down]
*/
function _GeneralIntegrate(
uint256 V0,
uint256 V1,
uint256 V2,
uint256 i,
uint256 k
) internal pure returns (uint256) {
require(V0 > 0, "TARGET_IS_ZERO");
uint256 fairAmount = i.mul(V1.sub(V2)); // i*delta
if (k == 0) {
return fairAmount.div(DecimalMath.ONE);
}
uint256 V0V0V1V2 = DecimalMath.divFloor(V0.mul(V0).div(V1), V2);
uint256 penalty = DecimalMath.mulFloor(k, V0V0V1V2); // k(V0^2/V1/V2)
return DecimalMath.ONE.sub(k).add(penalty).mul(fairAmount).div(DecimalMath.ONE2);
}
/*
Follow the integration function above
i*deltaB = (Q2-Q1)*(1-k+kQ0^2/Q1/Q2)
Assume Q2=Q0, Given Q1 and deltaB, solve Q0
i is the price of delta-V trading pair
give out target of V
support k=1 & k=0 case
[round down]
*/
function _SolveQuadraticFunctionForTarget(
uint256 V1,
uint256 delta,
uint256 i,
uint256 k
) internal pure returns (uint256) {
if (k == 0) {
return V1.add(DecimalMath.mulFloor(i, delta));
}
// V0 = V1*(1+(sqrt-1)/2k)
// sqrt = √(1+4kidelta/V1)
// premium = 1+(sqrt-1)/2k
// uint256 sqrt = (4 * k).mul(i).mul(delta).div(V1).add(DecimalMath.ONE2).sqrt();
if (V1 == 0) {
return 0;
}
uint256 sqrt;
uint256 ki = (4 * k).mul(i);
if (ki == 0) {
sqrt = DecimalMath.ONE;
} else if ((ki * delta) / ki == delta) {
sqrt = (ki * delta).div(V1).add(DecimalMath.ONE2).sqrt();
} else {
sqrt = ki.div(V1).mul(delta).add(DecimalMath.ONE2).sqrt();
}
uint256 premium =
DecimalMath.divFloor(sqrt.sub(DecimalMath.ONE), k * 2).add(DecimalMath.ONE);
// V0 is greater than or equal to V1 according to the solution
return DecimalMath.mulFloor(V1, premium);
}
/*
Follow the 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, user sell Q and receive B
if deltaBSig=false, then Q2<Q1, user sell B and receive Q
return |Q1-Q2|
as we only support sell amount as delta, the deltaB is always negative
the input ideltaB is actually -ideltaB in the equation
i is the price of delta-V trading pair
support k=1 & k=0 case
[round down]
*/
function _SolveQuadraticFunctionForTrade(
uint256 V0,
uint256 V1,
uint256 delta,
uint256 i,
uint256 k
) internal pure returns (uint256) {
require(V0 > 0, "TARGET_IS_ZERO");
if (delta == 0) {
return 0;
}
if (k == 0) {
return DecimalMath.mulFloor(i, delta) > V1 ? V1 : DecimalMath.mulFloor(i, delta);
}
if (k == DecimalMath.ONE) {
// if k==1
// Q2=Q1/(1+ideltaBQ1/Q0/Q0)
// temp = ideltaBQ1/Q0/Q0
// Q2 = Q1/(1+temp)
// Q1-Q2 = Q1*(1-1/(1+temp)) = Q1*(temp/(1+temp))
// uint256 temp = i.mul(delta).mul(V1).div(V0.mul(V0));
uint256 temp;
uint256 idelta = i.mul(delta);
if (idelta == 0) {
temp = 0;
} else if ((idelta * V1) / idelta == V1) {
temp = (idelta * V1).div(V0.mul(V0));
} else {
temp = delta.mul(V1).div(V0).mul(i).div(V0);
}
return V1.mul(temp).div(temp.add(DecimalMath.ONE));
}
// calculate -b value and sig
// b = kQ0^2/Q1-i*deltaB-(1-k)Q1
// part1 = (1-k)Q1 >=0
// part2 = kQ0^2/Q1-i*deltaB >=0
// bAbs = abs(part1-part2)
// if part1>part2 => b is negative => bSig is false
// if part2>part1 => b is positive => bSig is true
uint256 part2 = k.mul(V0).div(V1).mul(V0).add(i.mul(delta)); // kQ0^2/Q1-i*deltaB
uint256 bAbs = DecimalMath.ONE.sub(k).mul(V1); // (1-k)Q1
bool bSig;
if (bAbs >= part2) {
bAbs = bAbs - part2;
bSig = false;
} else {
bAbs = part2 - bAbs;
bSig = true;
}
bAbs = bAbs.div(DecimalMath.ONE);
// calculate sqrt
uint256 squareRoot =
DecimalMath.mulFloor(
DecimalMath.ONE.sub(k).mul(4),
DecimalMath.mulFloor(k, V0).mul(V0)
); // 4(1-k)kQ0^2
squareRoot = bAbs.mul(bAbs).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 (bSig) {
numerator = squareRoot.sub(bAbs);
if (numerator == 0) {
revert("DODOMath: should not be zero");
}
} else {
numerator = bAbs.add(squareRoot);
}
uint256 V2 = DecimalMath.divCeil(numerator, denominator);
if (V2 > V1) {
return 0;
} else {
return V1 - V2;
}
}
}
// File: contracts/lib/PMMPricing.sol
/**
* @title Pricing
* @author DODO Breeder
*
* @notice DODO Pricing model
*/
library PMMPricing {
using SafeMath for uint256;
enum RState {ONE, ABOVE_ONE, BELOW_ONE}
struct PMMState {
uint256 i;
uint256 K;
uint256 B;
uint256 Q;
uint256 B0;
uint256 Q0;
RState R;
}
// ============ buy & sell ============
function sellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (uint256 receiveQuoteAmount, RState newR)
{
if (state.R == RState.ONE) {
// case 1: R=1
// R falls below one
receiveQuoteAmount = _ROneSellBaseToken(state, payBaseAmount);
newR = RState.BELOW_ONE;
} else if (state.R == RState.ABOVE_ONE) {
uint256 backToOnePayBase = state.B0.sub(state.B);
uint256 backToOneReceiveQuote = state.Q.sub(state.Q0);
// case 2: R>1
// complex case, R status depends on trading amount
if (payBaseAmount < backToOnePayBase) {
// case 2.1: R status do not change
receiveQuoteAmount = _RAboveSellBaseToken(state, payBaseAmount);
newR = RState.ABOVE_ONE;
if (receiveQuoteAmount > 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
receiveQuoteAmount = backToOneReceiveQuote;
}
} else if (payBaseAmount == backToOnePayBase) {
// case 2.2: R status changes to ONE
receiveQuoteAmount = backToOneReceiveQuote;
newR = RState.ONE;
} else {
// case 2.3: R status changes to BELOW_ONE
receiveQuoteAmount = backToOneReceiveQuote.add(
_ROneSellBaseToken(state, payBaseAmount.sub(backToOnePayBase))
);
newR = RState.BELOW_ONE;
}
} else {
// state.R == RState.BELOW_ONE
// case 3: R<1
receiveQuoteAmount = _RBelowSellBaseToken(state, payBaseAmount);
newR = RState.BELOW_ONE;
}
}
function sellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (uint256 receiveBaseAmount, RState newR)
{
if (state.R == RState.ONE) {
receiveBaseAmount = _ROneSellQuoteToken(state, payQuoteAmount);
newR = RState.ABOVE_ONE;
} else if (state.R == RState.ABOVE_ONE) {
receiveBaseAmount = _RAboveSellQuoteToken(state, payQuoteAmount);
newR = RState.ABOVE_ONE;
} else {
uint256 backToOnePayQuote = state.Q0.sub(state.Q);
uint256 backToOneReceiveBase = state.B.sub(state.B0);
if (payQuoteAmount < backToOnePayQuote) {
receiveBaseAmount = _RBelowSellQuoteToken(state, payQuoteAmount);
newR = RState.BELOW_ONE;
if (receiveBaseAmount > backToOneReceiveBase) {
receiveBaseAmount = backToOneReceiveBase;
}
} else if (payQuoteAmount == backToOnePayQuote) {
receiveBaseAmount = backToOneReceiveBase;
newR = RState.ONE;
} else {
receiveBaseAmount = backToOneReceiveBase.add(
_ROneSellQuoteToken(state, payQuoteAmount.sub(backToOnePayQuote))
);
newR = RState.ABOVE_ONE;
}
}
}
// ============ R = 1 cases ============
function _ROneSellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (
uint256 // receiveQuoteToken
)
{
// in theory Q2 <= targetQuoteTokenAmount
// however when amount is close to 0, precision problems may cause Q2 > targetQuoteTokenAmount
return
DODOMath._SolveQuadraticFunctionForTrade(
state.Q0,
state.Q0,
payBaseAmount,
state.i,
state.K
);
}
function _ROneSellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (
uint256 // receiveBaseToken
)
{
return
DODOMath._SolveQuadraticFunctionForTrade(
state.B0,
state.B0,
payQuoteAmount,
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
// ============ R < 1 cases ============
function _RBelowSellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (
uint256 // receiveBaseToken
)
{
return
DODOMath._GeneralIntegrate(
state.Q0,
state.Q.add(payQuoteAmount),
state.Q,
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
function _RBelowSellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (
uint256 // receiveQuoteToken
)
{
return
DODOMath._SolveQuadraticFunctionForTrade(
state.Q0,
state.Q,
payBaseAmount,
state.i,
state.K
);
}
// ============ R > 1 cases ============
function _RAboveSellBaseToken(PMMState memory state, uint256 payBaseAmount)
internal
pure
returns (
uint256 // receiveQuoteToken
)
{
return
DODOMath._GeneralIntegrate(
state.B0,
state.B.add(payBaseAmount),
state.B,
state.i,
state.K
);
}
function _RAboveSellQuoteToken(PMMState memory state, uint256 payQuoteAmount)
internal
pure
returns (
uint256 // receiveBaseToken
)
{
return
DODOMath._SolveQuadraticFunctionForTrade(
state.B0,
state.B,
payQuoteAmount,
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
// ============ Helper functions ============
function adjustedTarget(PMMState memory state) internal pure {
if (state.R == RState.BELOW_ONE) {
state.Q0 = DODOMath._SolveQuadraticFunctionForTarget(
state.Q,
state.B.sub(state.B0),
state.i,
state.K
);
} else if (state.R == RState.ABOVE_ONE) {
state.B0 = DODOMath._SolveQuadraticFunctionForTarget(
state.B,
state.Q.sub(state.Q0),
DecimalMath.reciprocalFloor(state.i),
state.K
);
}
}
function getMidPrice(PMMState memory state) internal pure returns (uint256) {
if (state.R == RState.BELOW_ONE) {
uint256 R = DecimalMath.divFloor(state.Q0.mul(state.Q0).div(state.Q), state.Q);
R = DecimalMath.ONE.sub(state.K).add(DecimalMath.mulFloor(state.K, R));
return DecimalMath.divFloor(state.i, R);
} else {
uint256 R = DecimalMath.divFloor(state.B0.mul(state.B0).div(state.B), state.B);
R = DecimalMath.ONE.sub(state.K).add(DecimalMath.mulFloor(state.K, R));
return DecimalMath.mulFloor(state.i, R);
}
}
}
// 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);
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/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/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/lib/FeeRateModel.sol
interface IFeeRateImpl {
function getFeeRate(address pool, address trader) external view returns (uint256);
}
interface IFeeRateModel {
function getFeeRate(address trader) external view returns (uint256);
}
contract FeeRateModel is InitializableOwnable {
address public feeRateImpl;
function setFeeProxy(address _feeRateImpl) public onlyOwner {
feeRateImpl = _feeRateImpl;
}
function getFeeRate(address trader) external view returns (uint256) {
if(feeRateImpl == address(0))
return 0;
return IFeeRateImpl(feeRateImpl).getFeeRate(msg.sender,trader);
}
}
// File: contracts/DODOVendingMachine/impl/DVMStorage.sol
contract DVMStorage is ReentrancyGuard {
using SafeMath for uint256;
bool public _IS_OPEN_TWAP_ = false;
bool internal _DVM_INITIALIZED_;
// ============ Core Address ============
address public _MAINTAINER_;
IERC20 public _BASE_TOKEN_;
IERC20 public _QUOTE_TOKEN_;
uint112 public _BASE_RESERVE_;
uint112 public _QUOTE_RESERVE_;
uint32 public _BLOCK_TIMESTAMP_LAST_;
uint256 public _BASE_PRICE_CUMULATIVE_LAST_;
// ============ Shares (ERC20) ============
string public symbol;
uint8 public decimals;
string public name;
uint256 public totalSupply;
mapping(address => uint256) internal _SHARES_;
mapping(address => mapping(address => uint256)) internal _ALLOWED_;
// ================= Permit ======================
bytes32 public DOMAIN_SEPARATOR;
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
mapping(address => uint256) public nonces;
// ============ Variables for Pricing ============
uint256 public _LP_FEE_RATE_;
IFeeRateModel public _MT_FEE_RATE_MODEL_;
uint256 public _K_;
uint256 public _I_;
// ============ Helper Functions ============
function getPMMState() public view returns (PMMPricing.PMMState memory state) {
state.i = _I_;
state.K = _K_;
state.B = _BASE_RESERVE_;
state.Q = _QUOTE_RESERVE_;
state.B0 = 0; // will be calculated in adjustedTarget
state.Q0 = 0;
state.R = PMMPricing.RState.ABOVE_ONE;
PMMPricing.adjustedTarget(state);
}
function getPMMStateForCall()
external
view
returns (
uint256 i,
uint256 K,
uint256 B,
uint256 Q,
uint256 B0,
uint256 Q0,
uint256 R
)
{
PMMPricing.PMMState memory state = getPMMState();
i = state.i;
K = state.K;
B = state.B;
Q = state.Q;
B0 = state.B0;
Q0 = state.Q0;
R = uint256(state.R);
}
function getMidPrice() public view returns (uint256 midPrice) {
return PMMPricing.getMidPrice(getPMMState());
}
}
// File: contracts/DODOVendingMachine/impl/DVMVault.sol
contract DVMVault is DVMStorage {
using SafeMath for uint256;
using SafeERC20 for IERC20;
// ============ 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);
// ============ View Functions ============
function getVaultReserve() external view returns (uint256 baseReserve, uint256 quoteReserve) {
baseReserve = _BASE_RESERVE_;
quoteReserve = _QUOTE_RESERVE_;
}
function getUserFeeRate(address user) external view returns (uint256 lpFeeRate, uint256 mtFeeRate) {
lpFeeRate = _LP_FEE_RATE_;
mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(user);
}
// ============ Asset In ============
function getBaseInput() public view returns (uint256 input) {
return _BASE_TOKEN_.balanceOf(address(this)).sub(uint256(_BASE_RESERVE_));
}
function getQuoteInput() public view returns (uint256 input) {
return _QUOTE_TOKEN_.balanceOf(address(this)).sub(uint256(_QUOTE_RESERVE_));
}
// ============ TWAP UPDATE ===========
function _twapUpdate() internal {
uint32 blockTimestamp = uint32(block.timestamp % 2**32);
uint32 timeElapsed = blockTimestamp - _BLOCK_TIMESTAMP_LAST_;
if (timeElapsed > 0 && _BASE_RESERVE_ != 0 && _QUOTE_RESERVE_ != 0) {
_BASE_PRICE_CUMULATIVE_LAST_ += getMidPrice() * timeElapsed;
}
_BLOCK_TIMESTAMP_LAST_ = blockTimestamp;
}
// ============ Set States ============
function _setReserve(uint256 baseReserve, uint256 quoteReserve) internal {
require(baseReserve <= uint112(-1) && quoteReserve <= uint112(-1), "OVERFLOW");
_BASE_RESERVE_ = uint112(baseReserve);
_QUOTE_RESERVE_ = uint112(quoteReserve);
if(_IS_OPEN_TWAP_) _twapUpdate();
}
function _sync() internal {
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
require(baseBalance <= uint112(-1) && quoteBalance <= uint112(-1), "OVERFLOW");
if (baseBalance != _BASE_RESERVE_) {
_BASE_RESERVE_ = uint112(baseBalance);
}
if (quoteBalance != _QUOTE_RESERVE_) {
_QUOTE_RESERVE_ = uint112(quoteBalance);
}
if(_IS_OPEN_TWAP_) _twapUpdate();
}
function sync() external preventReentrant {
_sync();
}
// ============ Asset Out ============
function _transferBaseOut(address to, uint256 amount) internal {
if (amount > 0) {
_BASE_TOKEN_.safeTransfer(to, amount);
}
}
function _transferQuoteOut(address to, uint256 amount) internal {
if (amount > 0) {
_QUOTE_TOKEN_.safeTransfer(to, amount);
}
}
// ============ Shares (ERC20) ============
/**
* @dev transfer token for a specified address
* @param to The address to transfer to.
* @param amount The amount to be transferred.
*/
function transfer(address to, uint256 amount) public returns (bool) {
require(amount <= _SHARES_[msg.sender], "BALANCE_NOT_ENOUGH");
_SHARES_[msg.sender] = _SHARES_[msg.sender].sub(amount);
_SHARES_[to] = _SHARES_[to].add(amount);
emit Transfer(msg.sender, to, amount);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the the balance of.
* @return balance An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) external view returns (uint256 balance) {
return _SHARES_[owner];
}
/**
* @dev Transfer tokens from one address to another
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param amount uint256 the amount of tokens to be transferred
*/
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool) {
require(amount <= _SHARES_[from], "BALANCE_NOT_ENOUGH");
require(amount <= _ALLOWED_[from][msg.sender], "ALLOWANCE_NOT_ENOUGH");
_SHARES_[from] = _SHARES_[from].sub(amount);
_SHARES_[to] = _SHARES_[to].add(amount);
_ALLOWED_[from][msg.sender] = _ALLOWED_[from][msg.sender].sub(amount);
emit Transfer(from, to, amount);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param spender The address which will spend the funds.
* @param amount The amount of tokens to be spent.
*/
function approve(address spender, uint256 amount) public returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
function _approve(
address owner,
address spender,
uint256 amount
) private {
_ALLOWED_[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @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) internal {
require(value > 1000, "MINT_INVALID");
_SHARES_[user] = _SHARES_[user].add(value);
totalSupply = totalSupply.add(value);
emit Mint(user, value);
emit Transfer(address(0), user, value);
}
function _burn(address user, uint256 value) internal {
_SHARES_[user] = _SHARES_[user].sub(value);
totalSupply = totalSupply.sub(value);
emit Burn(user, value);
emit Transfer(user, address(0), value);
}
// ============================ Permit ======================================
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external {
require(deadline >= block.timestamp, "DODO_DVM_LP: EXPIRED");
bytes32 digest = keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR,
keccak256(
abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)
)
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(
recoveredAddress != address(0) && recoveredAddress == owner,
"DODO_DVM_LP: INVALID_SIGNATURE"
);
_approve(owner, spender, value);
}
}
// File: contracts/DODOVendingMachine/impl/DVMFunding.sol
contract DVMFunding is DVMVault {
// ============ Events ============
event BuyShares(address to, uint256 increaseShares, uint256 totalShares);
event SellShares(address payer, address to, uint256 decreaseShares, uint256 totalShares);
// ============ Buy & Sell Shares ============
// buy shares [round down]
function buyShares(address to)
external
preventReentrant
returns (
uint256 shares,
uint256 baseInput,
uint256 quoteInput
)
{
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
uint256 baseReserve = _BASE_RESERVE_;
uint256 quoteReserve = _QUOTE_RESERVE_;
baseInput = baseBalance.sub(baseReserve);
quoteInput = quoteBalance.sub(quoteReserve);
require(baseInput > 0, "NO_BASE_INPUT");
// Round down when withdrawing. Therefore, never be a situation occuring balance is 0 but totalsupply is not 0
// But May Happen,reserve >0 But totalSupply = 0
if (totalSupply == 0) {
// case 1. initial supply
shares = baseBalance; // 以免出现balance很大但shares很小的情况
require(shares > 2001, "MINT_AMOUNT_NOT_ENOUGH");
_mint(address(0), 1001);
shares -= 1001;
} else if (baseReserve > 0 && quoteReserve == 0) {
// case 2. supply when quote reserve is 0
shares = baseInput.mul(totalSupply).div(baseReserve);
} else if (baseReserve > 0 && quoteReserve > 0) {
// case 3. normal case
uint256 baseInputRatio = DecimalMath.divFloor(baseInput, baseReserve);
uint256 quoteInputRatio = DecimalMath.divFloor(quoteInput, quoteReserve);
uint256 mintRatio = quoteInputRatio < baseInputRatio ? quoteInputRatio : baseInputRatio;
shares = DecimalMath.mulFloor(totalSupply, mintRatio);
}
_mint(to, shares);
_setReserve(baseBalance, quoteBalance);
emit BuyShares(to, shares, _SHARES_[to]);
}
// sell shares [round down]
function sellShares(
uint256 shareAmount,
address to,
uint256 baseMinAmount,
uint256 quoteMinAmount,
bytes calldata data,
uint256 deadline
) external preventReentrant returns (uint256 baseAmount, uint256 quoteAmount) {
require(deadline >= block.timestamp, "TIME_EXPIRED");
require(shareAmount <= _SHARES_[msg.sender], "DLP_NOT_ENOUGH");
require(to != address(this), "SELL_BACK_NOT_ALLOWED");
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
uint256 totalShares = totalSupply;
baseAmount = baseBalance.mul(shareAmount).div(totalShares);
quoteAmount = quoteBalance.mul(shareAmount).div(totalShares);
require(
baseAmount >= baseMinAmount && quoteAmount >= quoteMinAmount,
"WITHDRAW_NOT_ENOUGH"
);
_burn(msg.sender, shareAmount);
_transferBaseOut(to, baseAmount);
_transferQuoteOut(to, quoteAmount);
_sync();
if (data.length > 0) {
IDODOCallee(to).DVMSellShareCall(
msg.sender,
shareAmount,
baseAmount,
quoteAmount,
data
);
}
emit SellShares(msg.sender, to, shareAmount, _SHARES_[msg.sender]);
}
}
// File: contracts/DODOVendingMachine/impl/DVMTrader.sol
contract DVMTrader is DVMVault {
using SafeMath for uint256;
// ============ Events ============
event DODOSwap(
address fromToken,
address toToken,
uint256 fromAmount,
uint256 toAmount,
address trader,
address receiver
);
event DODOFlashLoan(
address borrower,
address assetTo,
uint256 baseAmount,
uint256 quoteAmount
);
// ============ Trade Functions ============
function sellBase(address to)
external
preventReentrant
returns (uint256 receiveQuoteAmount)
{
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
uint256 baseInput = baseBalance.sub(uint256(_BASE_RESERVE_));
uint256 mtFee;
(receiveQuoteAmount, mtFee) = querySellBase(tx.origin, baseInput);
_transferQuoteOut(to, receiveQuoteAmount);
_transferQuoteOut(_MAINTAINER_, mtFee);
_setReserve(baseBalance, _QUOTE_TOKEN_.balanceOf(address(this)));
emit DODOSwap(
address(_BASE_TOKEN_),
address(_QUOTE_TOKEN_),
baseInput,
receiveQuoteAmount,
msg.sender,
to
);
}
function sellQuote(address to)
external
preventReentrant
returns (uint256 receiveBaseAmount)
{
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_));
uint256 mtFee;
(receiveBaseAmount, mtFee) = querySellQuote(tx.origin, quoteInput);
_transferBaseOut(to, receiveBaseAmount);
_transferBaseOut(_MAINTAINER_, mtFee);
_setReserve(_BASE_TOKEN_.balanceOf(address(this)), quoteBalance);
emit DODOSwap(
address(_QUOTE_TOKEN_),
address(_BASE_TOKEN_),
quoteInput,
receiveBaseAmount,
msg.sender,
to
);
}
function flashLoan(
uint256 baseAmount,
uint256 quoteAmount,
address assetTo,
bytes calldata data
) external preventReentrant {
_transferBaseOut(assetTo, baseAmount);
_transferQuoteOut(assetTo, quoteAmount);
if (data.length > 0)
IDODOCallee(assetTo).DVMFlashLoanCall(msg.sender, baseAmount, quoteAmount, data);
uint256 baseBalance = _BASE_TOKEN_.balanceOf(address(this));
uint256 quoteBalance = _QUOTE_TOKEN_.balanceOf(address(this));
// no input -> pure loss
require(
baseBalance >= _BASE_RESERVE_ || quoteBalance >= _QUOTE_RESERVE_,
"FLASH_LOAN_FAILED"
);
// sell quote
if (baseBalance < _BASE_RESERVE_) {
uint256 quoteInput = quoteBalance.sub(uint256(_QUOTE_RESERVE_));
(uint256 receiveBaseAmount, uint256 mtFee) = querySellQuote(tx.origin, quoteInput);
require(uint256(_BASE_RESERVE_).sub(baseBalance) <= receiveBaseAmount, "FLASH_LOAN_FAILED");
_transferBaseOut(_MAINTAINER_, mtFee);
emit DODOSwap(
address(_QUOTE_TOKEN_),
address(_BASE_TOKEN_),
quoteInput,
receiveBaseAmount,
msg.sender,
assetTo
);
}
// sell base
if (quoteBalance < _QUOTE_RESERVE_) {
uint256 baseInput = baseBalance.sub(uint256(_BASE_RESERVE_));
(uint256 receiveQuoteAmount, uint256 mtFee) = querySellBase(tx.origin, baseInput);
require(uint256(_QUOTE_RESERVE_).sub(quoteBalance) <= receiveQuoteAmount, "FLASH_LOAN_FAILED");
_transferQuoteOut(_MAINTAINER_, mtFee);
emit DODOSwap(
address(_BASE_TOKEN_),
address(_QUOTE_TOKEN_),
baseInput,
receiveQuoteAmount,
msg.sender,
assetTo
);
}
_sync();
emit DODOFlashLoan(msg.sender, assetTo, baseAmount, quoteAmount);
}
// ============ Query Functions ============
function querySellBase(address trader, uint256 payBaseAmount)
public
view
returns (uint256 receiveQuoteAmount, uint256 mtFee)
{
(receiveQuoteAmount, ) = PMMPricing.sellBaseToken(getPMMState(), payBaseAmount);
uint256 lpFeeRate = _LP_FEE_RATE_;
uint256 mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(trader);
mtFee = DecimalMath.mulFloor(receiveQuoteAmount, mtFeeRate);
receiveQuoteAmount = receiveQuoteAmount
.sub(DecimalMath.mulFloor(receiveQuoteAmount, lpFeeRate))
.sub(mtFee);
}
function querySellQuote(address trader, uint256 payQuoteAmount)
public
view
returns (uint256 receiveBaseAmount, uint256 mtFee)
{
(receiveBaseAmount, ) = PMMPricing.sellQuoteToken(getPMMState(), payQuoteAmount);
uint256 lpFeeRate = _LP_FEE_RATE_;
uint256 mtFeeRate = _MT_FEE_RATE_MODEL_.getFeeRate(trader);
mtFee = DecimalMath.mulFloor(receiveBaseAmount, mtFeeRate);
receiveBaseAmount = receiveBaseAmount
.sub(DecimalMath.mulFloor(receiveBaseAmount, lpFeeRate))
.sub(mtFee);
}
}
// File: contracts/DODOVendingMachine/impl/DVM.sol
/**
* @title DODO VendingMachine
* @author DODO Breeder
*
* @notice DODOVendingMachine initialization
*/
contract DVM is DVMTrader, DVMFunding {
function init(
address maintainer,
address baseTokenAddress,
address quoteTokenAddress,
uint256 lpFeeRate,
address mtFeeRateModel,
uint256 i,
uint256 k,
bool isOpenTWAP
) external {
require(!_DVM_INITIALIZED_, "DVM_INITIALIZED");
_DVM_INITIALIZED_ = true;
require(baseTokenAddress != quoteTokenAddress, "BASE_QUOTE_CAN_NOT_BE_SAME");
_BASE_TOKEN_ = IERC20(baseTokenAddress);
_QUOTE_TOKEN_ = IERC20(quoteTokenAddress);
require(i > 0 && i <= 10**36);
_I_ = i;
require(k <= 10**18);
_K_ = k;
_LP_FEE_RATE_ = lpFeeRate;
_MT_FEE_RATE_MODEL_ = IFeeRateModel(mtFeeRateModel);
_MAINTAINER_ = maintainer;
_IS_OPEN_TWAP_ = isOpenTWAP;
if(isOpenTWAP) _BLOCK_TIMESTAMP_LAST_ = uint32(block.timestamp % 2**32);
string memory connect = "_";
string memory suffix = "DLP";
name = string(abi.encodePacked(suffix, connect, addressToShortString(address(this))));
symbol = "DLP";
decimals = _BASE_TOKEN_.decimals();
// ============================== Permit ====================================
uint256 chainId;
assembly {
chainId := chainid()
}
DOMAIN_SEPARATOR = keccak256(
abi.encode(
// keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f,
keccak256(bytes(name)),
keccak256(bytes("1")),
chainId,
address(this)
)
);
// ==========================================================================
}
function addressToShortString(address _addr) public pure returns (string memory) {
bytes32 value = bytes32(uint256(_addr));
bytes memory alphabet = "0123456789abcdef";
bytes memory str = new bytes(8);
for (uint256 i = 0; i < 4; i++) {
str[i * 2] = alphabet[uint8(value[i + 12] >> 4)];
str[1 + i * 2] = alphabet[uint8(value[i + 12] & 0x0f)];
}
return string(str);
}
// ============ Version Control ============
function version() external pure returns (string memory) {
return "DVM 1.0.3";
}
}