267 lines
8.5 KiB
Solidity
267 lines
8.5 KiB
Solidity
/*
|
|
|
|
Copyright 2020 DODO ZOO.
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
|
|
|
pragma solidity 0.8.16;
|
|
pragma experimental ABIEncoderV2;
|
|
|
|
import {DecimalMath} from "../lib/DecimalMath.sol";
|
|
import {DODOMath} from "../lib/DODOMath.sol";
|
|
|
|
/**
|
|
* @title Pricing
|
|
* @author DODO Breeder
|
|
*
|
|
* @notice DODO Pricing model
|
|
*/
|
|
|
|
library PMMPricing {
|
|
|
|
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 ============
|
|
/**
|
|
* @notice Inner calculation based on pmm algorithm, sell base
|
|
* @param state The current PMM state
|
|
* @param payBaseAmount The amount of base token user want to sell
|
|
* @return receiveQuoteAmount The amount of quote token user will receive
|
|
* @return newR The new R status after swap
|
|
*/
|
|
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 - state.B;
|
|
uint256 backToOneReceiveQuote = state.Q - 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 + (
|
|
_ROneSellBaseToken(state, (payBaseAmount - backToOnePayBase))
|
|
);
|
|
newR = RState.BELOW_ONE;
|
|
}
|
|
} else {
|
|
// state.R == RState.BELOW_ONE
|
|
// case 3: R<1
|
|
receiveQuoteAmount = _RBelowSellBaseToken(state, payBaseAmount);
|
|
newR = RState.BELOW_ONE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @notice Inner calculation based on pmm algorithm, sell quote
|
|
* @param state The current PMM state
|
|
* @param payQuoteAmount The amount of quote token user want to sell
|
|
* @return receiveBaseAmount The amount of base token user will receive
|
|
* @return newR The new R status after swap
|
|
*/
|
|
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 - state.Q;
|
|
uint256 backToOneReceiveBase = state.B - 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 + (
|
|
_ROneSellQuoteToken(state, payQuoteAmount - 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 + 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 + 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 - state.B0,
|
|
state.i,
|
|
state.K
|
|
);
|
|
} else if (state.R == RState.ABOVE_ONE) {
|
|
state.B0 = DODOMath._SolveQuadraticFunctionForTarget(
|
|
state.B,
|
|
state.Q - 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 * state.Q0 / state.Q, state.Q);
|
|
R = DecimalMath.ONE - state.K + (DecimalMath.mulFloor(state.K, R));
|
|
return DecimalMath.divFloor(state.i, R);
|
|
} else {
|
|
uint256 R = DecimalMath.divFloor(state.B0 * state.B0 / state.B, state.B);
|
|
R = DecimalMath.ONE - state.K + (DecimalMath.mulFloor(state.K, R));
|
|
return DecimalMath.mulFloor(state.i, R);
|
|
}
|
|
}
|
|
}
|