From 6a7b6487536b199a9f62e07105499df68aa8a07e Mon Sep 17 00:00:00 2001 From: mingda Date: Wed, 18 Nov 2020 00:51:45 +0800 Subject: [PATCH] PMMPricing library --- contracts/lib/PMMPricing.sol | 237 +++++++++++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 contracts/lib/PMMPricing.sol diff --git a/contracts/lib/PMMPricing.sol b/contracts/lib/PMMPricing.sol new file mode 100644 index 0000000..b2a1f81 --- /dev/null +++ b/contracts/lib/PMMPricing.sol @@ -0,0 +1,237 @@ +/* + + 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 + */ + +enum RState {ONE, ABOVE_ONE, BELOW_ONE} + +struct PMMState { + uint256 i; + uint256 K; + uint256 B; + uint256 Q; + uint256 B0; + uint256 Q0; + RState R; +} + +library PMMPricing { + using SafeMath for uint256; + + function sellBaseToken(PMMState memory state, uint256 payBaseAmount) + public + 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; + } + + return (receiveQuoteAmount, newR); + } + + function sellQuoteToken(PMMState memory state, uint256 payQuoteAmount) + public + 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; + } + } + + return (receiveBaseAmount, newR); + } + + // ============ 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, + DecimalMath.mulFloor(state.i, payBaseAmount), + false, + state.K + ); + } + + function _ROneSellQuoteToken(PMMState memory state, uint256 payQuoteAmount) + internal + pure + returns (uint256 receiveBaseToken) + { + return + DODOMath._SolveQuadraticFunctionForTrade( + state.B0, + state.B0, + DecimalMath.divFloor(payQuoteAmount, state.i), + false, + 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.divFloor(DecimalMath.ONE, state.i), + state.K + ); + } + + function _RBelowSellBaseToken(PMMState memory state, uint256 payBaseAmount) + internal + pure + returns (uint256 receiveQuoteToken) + { + return + DODOMath._SolveQuadraticFunctionForTrade( + state.Q0, + state.Q, + DecimalMath.mul(state.i, payBaseAmount), + false, + 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, + DecimalMath.divFloor(payQuoteAmount, state.i), + false, + state.i + ); + } + + // ============ Helper functions ============ + + function adjustedTarget(PMMState memory state) public pure { + if (state.R == RState.BELOW_ONE) { + uint256 fairAmount = DecimalMath.mulFloor(state.B.sub(state.B0), state.i); + state.Q0 = DODOMath._SolveQuadraticFunctionForTarget(state.B, state.K, fairAmount); + } else if (state.R == RState.ABOVE_ONE) { + uint256 fairAmount = DecimalMath.divFloor(state.Q.sub(state.Q0), state.i); + state.B0 = DODOMath._SolveQuadraticFunctionForTarget(state.Q, state.K, fairAmount); + } + } + + function getMidPrice(PMMState memory state) public pure returns (uint256 midPrice) { + 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.mul(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.mul(state.K, R)); + return DecimalMath.mul(state.i, R); + } + } +}