256 lines
7.9 KiB
Solidity
256 lines
7.9 KiB
Solidity
/*
|
|
|
|
Copyright 2020 DODO ZOO.
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
pragma solidity 0.6.9;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import {SafeMath} from "../lib/SafeMath.sol";
|
|
import {DecimalMath} from "../lib/DecimalMath.sol";
|
|
import {DODOMath} from "../lib/DODOMath.sol";
|
|
|
|
/**
|
|
* @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);
|
|
}
|
|
}
|
|
}
|