diff --git a/.gitignore b/.gitignore index 25e0aa973..810ed2241 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,9 @@ vendor *.swp .vscode/*.json +# Test generate tmp files +app/data +app/apptest/data +app_test/data +plugins/param/data +plugins/tokens/data diff --git a/app/app.go b/app/app.go index 8e73e5a09..63e433bd2 100644 --- a/app/app.go +++ b/app/app.go @@ -95,6 +95,8 @@ type BinanceChain struct { publicationConfig *config.PublicationConfig publisher pub.MarketDataPublisher + dexConfig *config.DexConfig + // Unlike tendermint, we don't need implement a no-op metrics, usage of this field should // check nil-ness to know whether metrics collection is turn on // TODO(#246): make it an aggregated wrapper of all component metrics (i.e. DexKeeper, StakeKeeper) @@ -119,6 +121,7 @@ func NewBinanceChain(logger log.Logger, db dbm.DB, traceStore io.Writer, baseApp upgradeConfig: ServerContext.UpgradeConfig, abciQueryBlackList: getABCIQueryBlackList(ServerContext.QueryConfig), publicationConfig: ServerContext.PublicationConfig, + dexConfig: ServerContext.DexConfig, } // set upgrade config SetUpgradeConfig(app.upgradeConfig) @@ -264,6 +267,8 @@ func SetUpgradeConfig(upgradeConfig *config.UpgradeConfig) { upgrade.Mgr.AddUpgradeHeight(upgrade.ListingRuleUpgrade, upgradeConfig.ListingRuleUpgradeHeight) upgrade.Mgr.AddUpgradeHeight(upgrade.FixZeroBalance, upgradeConfig.FixZeroBalanceHeight) upgrade.Mgr.AddUpgradeHeight(upgrade.BEP67, upgradeConfig.BEP67Height) + upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, upgradeConfig.BEP70Height) + // register store keys of upgrade upgrade.Mgr.RegisterStoreKeys(upgrade.BEP9, common.TimeLockStoreKey.Name()) @@ -303,9 +308,10 @@ func (app *BinanceChain) initRunningMode() { func (app *BinanceChain) initDex(pairMapper dex.TradingPairMapper) { app.DexKeeper = dex.NewOrderKeeper(common.DexStoreKey, app.AccountKeeper, pairMapper, - app.RegisterCodespace(dex.DefaultCodespace), app.baseConfig.OrderKeeperConcurrency, app.Codec, - app.publicationConfig.ShouldPublishAny()) + app.RegisterCodespace(dex.DefaultCodespace), app.baseConfig.OrderKeeperConcurrency, + app.Codec, app.publicationConfig.ShouldPublishAny()) app.DexKeeper.SubscribeParamChange(app.ParamHub) + app.DexKeeper.SetBUSDSymbol(app.dexConfig.BUSDSymbol) // do not proceed if we are in a unit test and `CheckState` is unset. if app.CheckState == nil { diff --git a/app/config/config.go b/app/config/config.go index d1866f220..461f82b6f 100644 --- a/app/config/config.go +++ b/app/config/config.go @@ -53,7 +53,7 @@ BEP6Height = {{ .UpgradeConfig.BEP6Height }} BEP9Height = {{ .UpgradeConfig.BEP9Height }} # Block height of BEP10 upgrade BEP10Height = {{ .UpgradeConfig.BEP10Height }} -# Block height of BEP19Height upgrade +# Block height of BEP19 upgrade BEP19Height = {{ .UpgradeConfig.BEP19Height }} # Block height of BEP12 upgrade BEP12Height = {{ .UpgradeConfig.BEP12Height }} @@ -69,6 +69,8 @@ ListingRuleUpgradeHeight = {{ .UpgradeConfig.ListingRuleUpgradeHeight }} FixZeroBalanceHeight = {{ .UpgradeConfig.FixZeroBalanceHeight }} # Block height of BEP67 upgrade BEP67Height = {{ .UpgradeConfig.BEP67Height }} +# Block height of BEP70 upgrade +BEP70Height = {{ .UpgradeConfig.BEP70Height }} [query] # ABCI query interface black list, suggested value: ["custom/gov/proposals", "custom/timelock/timelocks", "custom/atomicSwap/swapcreator", "custom/atomicSwap/swaprecipient"] @@ -154,6 +156,10 @@ logFileRoot = "{{ .LogConfig.LogFileRoot }}" logFilePath = "{{ .LogConfig.LogFilePath }}" # Number of logs keep in memory before writing to file logBuffSize = {{ .LogConfig.LogBuffSize }} + +[dex] +# The suffixed symbol of BUSD +BUSDSymbol = "{{ .DexConfig.BUSDSymbol }}" ` type BinanceChainContext struct { @@ -180,6 +186,7 @@ type BinanceChainConfig struct { *BaseConfig `mapstructure:"base"` *UpgradeConfig `mapstructure:"upgrade"` *QueryConfig `mapstructure:"query"` + *DexConfig `mapstructure:"dex"` } func DefaultBinanceChainConfig() *BinanceChainConfig { @@ -190,6 +197,7 @@ func DefaultBinanceChainConfig() *BinanceChainConfig { BaseConfig: defaultBaseConfig(), UpgradeConfig: defaultUpgradeConfig(), QueryConfig: defaultQueryConfig(), + DexConfig: defaultGovConfig(), } } @@ -360,7 +368,8 @@ type UpgradeConfig struct { // Hubble Upgrade BEP12Height int64 `mapstructure:"BEP12Height"` // Archimedes Upgrade - BEP3Height int64 `mapstructure:"BEP3Height"` + BEP3Height int64 `mapstructure:"BEP3Height"` + // TODO: add upgrade name FixSignBytesOverflowHeight int64 `mapstructure:"FixSignBytesOverflowHeight"` LotSizeUpgradeHeight int64 `mapstructure:"LotSizeUpgradeHeight"` @@ -368,6 +377,7 @@ type UpgradeConfig struct { FixZeroBalanceHeight int64 `mapstructure:"FixZeroBalanceHeight"` BEP67Height int64 `mapstructure:"BEP67Height"` + BEP70Height int64 `mapstructure:"BEP70Height"` } func defaultUpgradeConfig() *UpgradeConfig { @@ -384,6 +394,7 @@ func defaultUpgradeConfig() *UpgradeConfig { ListingRuleUpgradeHeight: math.MaxInt64, FixZeroBalanceHeight: math.MaxInt64, BEP67Height: 1, + BEP70Height: 1, } } @@ -397,6 +408,16 @@ func defaultQueryConfig() *QueryConfig { } } +type DexConfig struct { + BUSDSymbol string `mapstructure:"BUSDSymbol"` +} + +func defaultGovConfig() *DexConfig { + return &DexConfig{ + BUSDSymbol: "", + } +} + func (context *BinanceChainContext) ParseAppConfigInPlace() error { // this piece of code should be consistent with bindFlagsLoadViper // vendor/github.com/tendermint/tendermint/libs/cli/setup.go:125 diff --git a/common/upgrade/upgrade.go b/common/upgrade/upgrade.go index 43107dd13..d18a3e8b2 100644 --- a/common/upgrade/upgrade.go +++ b/common/upgrade/upgrade.go @@ -17,6 +17,8 @@ const ( BEP12 = "BEP12" // https://github.com/binance-chain/BEPs/pull/17 // Archimedes Upgrade BEP3 = "BEP3" // https://github.com/binance-chain/BEPs/pull/30 + + // TODO: add upgrade name FixSignBytesOverflow = sdk.FixSignBytesOverflow LotSizeOptimization = "LotSizeOptimization" @@ -24,6 +26,8 @@ const ( FixZeroBalance = "FixZeroBalance" BEP67 = "BEP67" // https://github.com/binance-chain/BEPs/pull/67 + // BUSD Pair Upgrade + BEP70 = "BEP70" // https://github.com/binance-chain/BEPs/pull/70 ) func UpgradeBEP10(before func(), after func()) { diff --git a/plugins/dex/order/fee.go b/plugins/dex/order/fee.go index 807363a65..826c86ae0 100644 --- a/plugins/dex/order/fee.go +++ b/plugins/dex/order/fee.go @@ -6,6 +6,8 @@ import ( "math" "math/big" + "github.com/binance-chain/node/common/upgrade" + sdk "github.com/cosmos/cosmos-sdk/types" tmlog "github.com/tendermint/tendermint/libs/log" @@ -75,7 +77,7 @@ func (m *FeeManager) CalcTradesFee(balances sdk.Coins, tradeTransfers TradeTrans } tradeTransfers.Sort() for _, tran := range tradeTransfers { - fee := m.calcTradeFeeForSingleTransfer(balances, tran, engines) + fee := m.calcTradeFeeFromTransfer(balances, tran, engines) tran.Fee = fee if tran.IsBuyer() { tran.Trade.BuyerFee = &fee @@ -106,62 +108,79 @@ func (m *FeeManager) CalcExpiresFee(balances sdk.Coins, expireType transferEvent return fees } -func (m *FeeManager) calcTradeFeeForSingleTransfer(balances sdk.Coins, tran *Transfer, engines map[string]*matcheng.MatchEng) types.Fee { +func (m *FeeManager) calcTradeFeeFromTransfer(balances sdk.Coins, tran *Transfer, engines map[string]*matcheng.MatchEng) types.Fee { var feeToken sdk.Coin - var nativeFee int64 - var isOverflow bool + nativeFee, isOverflow := m.calcNativeFee(tran, engines) if tran.IsNativeIn() { - // always have enough balance to pay the fee. - nativeFee = m.calcTradeFee(big.NewInt(tran.in), FeeByNativeToken).Int64() - return types.NewFee(sdk.Coins{sdk.NewCoin(types.NativeTokenSymbol, nativeFee)}, types.FeeForProposer) - } else if tran.IsNativeOut() { - nativeFee, isOverflow = m.calcNativeFee(types.NativeTokenSymbol, tran.out, engines) - } else { - nativeFee, isOverflow = m.calcNativeFee(tran.inAsset, tran.in, engines) + // special case, in this case, we always have + // 1. the fee is paid by native token + // 2. the balance is enough to pay the fee. + // 3. never have int64 overflow + return dexFeeWrap(sdk.NewCoin(types.NativeTokenSymbol, nativeFee)) } if isOverflow || nativeFee == 0 || nativeFee > balances.AmountOf(types.NativeTokenSymbol) { // 1. if the fee is too low and round to 0, we charge by inAsset // 2. no enough NativeToken, use the received tokens as fee - feeToken = sdk.NewCoin(tran.inAsset, m.calcTradeFee(big.NewInt(tran.in), FeeByTradeToken).Int64()) + feeToken = sdk.NewCoin(tran.inAsset, m.TradeFee(big.NewInt(tran.in), FeeByTradeToken).Int64()) m.logger.Debug("No enough native token to pay trade fee", "feeToken", feeToken) } else { // have sufficient native token to pay the fees feeToken = sdk.NewCoin(types.NativeTokenSymbol, nativeFee) } - return types.NewFee(sdk.Coins{feeToken}, types.FeeForProposer) + return dexFeeWrap(feeToken) } -func (m *FeeManager) calcNativeFee(inSymbol string, inQty int64, engines map[string]*matcheng.MatchEng) (fee int64, isOverflow bool) { - var nativeNotional *big.Int - if isNativeToken(inSymbol) { - nativeNotional = big.NewInt(inQty) +func (m *FeeManager) calcNativeFee(tran *Transfer, engines map[string]*matcheng.MatchEng) (fee int64, isOverflow bool) { + var nativeFee *big.Int + if tran.IsNativeIn() { + nativeFee = m.TradeFee(big.NewInt(tran.in), FeeByNativeToken) + } else if tran.IsNativeOut() { + nativeFee = m.TradeFee(big.NewInt(tran.out), FeeByNativeToken) } else { - // price against native token, - // both `nativeNotional` and `feeByNativeToken` may overflow when it's a non-BNB pair like ABC_XYZ - if engine, ok := engines[utils.Assets2TradingPair(inSymbol, types.NativeTokenSymbol)]; ok { - // XYZ_BNB - nativeNotional = utils.CalBigNotional(engine.LastTradePrice, inQty) - } else { - // BNB_XYZ - engine := engines[utils.Assets2TradingPair(types.NativeTokenSymbol, inSymbol)] - var amount big.Int - nativeNotional = amount.Div( - amount.Mul( - big.NewInt(inQty), - big.NewInt(cmnUtils.Fixed8One.ToInt64())), - big.NewInt(engine.LastTradePrice)) + // pair pattern: ABC_XYZ/XYZ_ABC, inAsset: ABC + // must exist ABC/BNB. or ABC/BUSD after upgrade + notional, pairExist := m.calcNotional(tran.inAsset, tran.in, types.NativeTokenSymbol, engines) + if !pairExist { + if sdk.IsUpgrade(upgrade.BEP70) && len(BUSDSymbol) > 0 { + // must be ABC_BUSD pair, we just use BUSD_BNB price to get the notional + var qty int64 + if tran.inAsset == BUSDSymbol { + qty = tran.in + } else { + // outAsset is BUSD + qty = tran.out + } + + notional, pairExist = m.calcNotional(BUSDSymbol, qty, types.NativeTokenSymbol, engines) + if pairExist { + // must not happen + m.logger.Error(BUSDSymbol + " must be listed against " + types.NativeTokenSymbol) + } + } } + nativeFee = m.TradeFee(notional, FeeByNativeToken) } - - nativeFee := m.calcTradeFee(nativeNotional, FeeByNativeToken) if nativeFee.IsInt64() { return nativeFee.Int64(), false } return 0, true } +func (m *FeeManager) calcNotional(asset string, qty int64, quoteAsset string, engines map[string]*matcheng.MatchEng) (notional *big.Int, engineFound bool) { + if engine, ok := m.getEngine(engines, asset, quoteAsset); ok { + notional = utils.CalBigNotional(engine.LastTradePrice, qty) + } else if engine, ok = m.getEngine(engines, quoteAsset, asset); ok { + var amt big.Int + notional = amt.Div(amt.Mul(big.NewInt(qty), big.NewInt(cmnUtils.Fixed8One.ToInt64())), big.NewInt(engine.LastTradePrice)) + } else { + return notional, false + } + + return notional, true +} + // DEPRECATED // Note1: the result of `CalcTradeFeeDeprecated` depends on the balances of the acc, // so the right way of allocation is: @@ -177,7 +196,7 @@ func (m *FeeManager) CalcTradeFee(balances sdk.Coins, tradeIn sdk.Coin, engines inSymbol := tradeIn.Denom inAmt := tradeIn.Amount if inSymbol == types.NativeTokenSymbol { - feeToken = sdk.NewCoin(types.NativeTokenSymbol, m.calcTradeFee(big.NewInt(inAmt), FeeByNativeToken).Int64()) + feeToken = sdk.NewCoin(types.NativeTokenSymbol, m.TradeFee(big.NewInt(inAmt), FeeByNativeToken).Int64()) } else { // price against native token, // both `amountOfNativeToken` and `feeByNativeToken` may overflow when it's a non-BNB pair like ABC_XYZ @@ -195,7 +214,7 @@ func (m *FeeManager) CalcTradeFee(balances sdk.Coins, tradeIn sdk.Coin, engines big.NewInt(cmnUtils.Fixed8One.ToInt64())), big.NewInt(market.LastTradePrice)) } - feeByNativeToken := m.calcTradeFee(amountOfNativeToken, FeeByNativeToken) + feeByNativeToken := m.TradeFee(amountOfNativeToken, FeeByNativeToken) if feeByNativeToken.IsInt64() && feeByNativeToken.Int64() != 0 && feeByNativeToken.Int64() <= balances.AmountOf(types.NativeTokenSymbol) { // 1. if the fee is too low and round to 0, we charge by inAsset @@ -203,12 +222,12 @@ func (m *FeeManager) CalcTradeFee(balances sdk.Coins, tradeIn sdk.Coin, engines feeToken = sdk.NewCoin(types.NativeTokenSymbol, feeByNativeToken.Int64()) } else { // no enough NativeToken, use the received tokens as fee - feeToken = sdk.NewCoin(inSymbol, m.calcTradeFee(big.NewInt(inAmt), FeeByTradeToken).Int64()) + feeToken = sdk.NewCoin(inSymbol, m.TradeFee(big.NewInt(inAmt), FeeByTradeToken).Int64()) m.logger.Debug("No enough native token to pay trade fee", "feeToken", feeToken) } } - return types.NewFee(sdk.Coins{feeToken}, types.FeeForProposer) + return dexFeeWrap(feeToken) } // Note: the result of `CalcFixedFee` depends on the balances of the acc, @@ -231,50 +250,51 @@ func (m *FeeManager) CalcFixedFee(balances sdk.Coins, eventType transferEventTyp return types.Fee{} } - var feeToken sdk.Coin nativeTokenBalance := balances.AmountOf(types.NativeTokenSymbol) if nativeTokenBalance >= feeAmountNative || inAsset == types.NativeTokenSymbol { - feeToken = sdk.NewCoin(types.NativeTokenSymbol, cmnUtils.MinInt(feeAmountNative, nativeTokenBalance)) - } else { - // the amount may overflow int64, so use big.Int instead. - // TODO: (perf) may remove the big.Int use to improve the performance - var amount *big.Int - if market, ok := engines[utils.Assets2TradingPair(inAsset, types.NativeTokenSymbol)]; ok { - // XYZ_BNB - var tmp big.Int - amount = tmp.Div(tmp.Mul( - big.NewInt(feeAmount), - big.NewInt(cmnUtils.Fixed8One.ToInt64())), - big.NewInt(market.LastTradePrice)) - } else { - // BNB_XYZ - market = engines[utils.Assets2TradingPair(types.NativeTokenSymbol, inAsset)] - amount = utils.CalBigNotional(market.LastTradePrice, feeAmount) - } + return dexFeeWrap(sdk.NewCoin(types.NativeTokenSymbol, cmnUtils.MinInt(feeAmountNative, nativeTokenBalance))) + } - if amount.IsInt64() { - feeAmount = amount.Int64() - } else { - feeAmount = math.MaxInt64 + // the amount may overflow int64, so use big.Int instead. + // TODO: (perf) may remove the big.Int use to improve the performance + amount, nativePairExist := m.calcNotional(types.NativeTokenSymbol, feeAmount, inAsset, engines) + if !nativePairExist { + // for BUSD pairs, it is possible that there is no trading pair between BNB and inAsset, e.g., BUSD -> XYZ + if sdk.IsUpgrade(upgrade.BEP70) && len(BUSDSymbol) > 0 { + busdAmount, busdPairExist := m.calcNotional(types.NativeTokenSymbol, feeAmount, BUSDSymbol, engines) + if busdPairExist { + var busdAmountInt64 int64 + if !busdAmount.IsInt64() { + m.logger.Error("fixed fee is too high", "eventType", eventType, "fee", feeAmount) + busdAmountInt64 = math.MaxInt64 + } else { + busdAmountInt64 = busdAmount.Int64() + } + + var pairExist bool + amount, pairExist = m.calcNotional(BUSDSymbol, busdAmountInt64, inAsset, engines) + if !pairExist { + // must exist + m.logger.Error(inAsset + " must be listed against " + BUSDSymbol) + } + } else { + m.logger.Error(BUSDSymbol + " must be listed against " + types.NativeTokenSymbol) + } } - feeAmount = cmnUtils.MinInt(feeAmount, balances.AmountOf(inAsset)) - feeToken = sdk.NewCoin(inAsset, feeAmount) } - return types.NewFee(sdk.Coins{feeToken}, types.FeeForProposer) -} - -func (m *FeeManager) calcTradeFee(amount *big.Int, feeType FeeType) *big.Int { - var feeRate int64 - if feeType == FeeByNativeToken { - feeRate = m.FeeConfig.FeeRateNative - } else if feeType == FeeByTradeToken { - feeRate = m.FeeConfig.FeeRate + if amount.IsInt64() { + feeAmount = amount.Int64() + } else { + feeAmount = math.MaxInt64 } + feeAmount = cmnUtils.MinInt(feeAmount, balances.AmountOf(inAsset)) + return dexFeeWrap(sdk.NewCoin(inAsset, feeAmount)) +} - // TODO: (Perf) find a more efficient way to replace the big.Int solution. - var fee big.Int - return fee.Div(fee.Mul(amount, big.NewInt(feeRate)), FeeRateMultiplier) +// for each trade, the fee only contains one kind of token. And distribution type is always FeeForProposer +func dexFeeWrap(fee sdk.Coin) types.Fee { + return types.NewFee(sdk.Coins{fee}, types.FeeForProposer) } func isNativeToken(symbol string) bool { @@ -293,6 +313,19 @@ func (m *FeeManager) CancelFees() (int64, int64) { return m.FeeConfig.CancelFeeNative, m.FeeConfig.CancelFee } +func (m *FeeManager) TradeFee(amount *big.Int, feeType FeeType) *big.Int { + var feeRate int64 + if feeType == FeeByNativeToken { + feeRate = m.FeeConfig.FeeRateNative + } else if feeType == FeeByTradeToken { + feeRate = m.FeeConfig.FeeRate + } + + // TODO: (Perf) find a more efficient way to replace the big.Int solution. + var fee big.Int + return fee.Div(fee.Mul(amount, big.NewInt(feeRate)), FeeRateMultiplier) +} + func (m *FeeManager) ExpireFee(feeType FeeType) int64 { if feeType == FeeByNativeToken { return m.FeeConfig.ExpireFeeNative @@ -391,3 +424,9 @@ func ParamToFeeConfig(feeParams []param.FeeParam) *FeeConfig { } return nil } + +// Get engine for trading pair baseAsset_quoteAsset +func (m *FeeManager) getEngine(engines map[string]*matcheng.MatchEng, baseAsset, quoteAsset string) (engine *matcheng.MatchEng, ok bool) { + engine, ok = engines[utils.Assets2TradingPair(baseAsset, quoteAsset)] + return +} diff --git a/plugins/dex/order/fee_test.go b/plugins/dex/order/fee_test.go index dd6f3eea6..4e40b26e6 100644 --- a/plugins/dex/order/fee_test.go +++ b/plugins/dex/order/fee_test.go @@ -3,6 +3,8 @@ package order import ( "testing" + "github.com/binance-chain/node/common/upgrade" + "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" @@ -39,10 +41,10 @@ func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) { out: 100, } // no enough bnb or native fee rounding to 0 - fee := keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee := keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"ABC-000", 1}}, fee.Tokens) _, acc = testutils.NewAccount(ctx, am, 100) - fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"ABC-000", 1}}, fee.Tokens) tran = Transfer{ @@ -52,10 +54,10 @@ func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) { out: 10000, } _, acc = testutils.NewAccount(ctx, am, 1) - fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"ABC-000", 1000}}, fee.Tokens) _, acc = testutils.NewAccount(ctx, am, 100) - fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens) tran = Transfer{ @@ -65,7 +67,7 @@ func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) { out: 1000, } _, acc = testutils.NewAccount(ctx, am, 100) - fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"BNB", 0}}, fee.Tokens) tran = Transfer{ @@ -74,7 +76,7 @@ func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) { outAsset: "ABC-000", out: 100000, } - fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens) tran = Transfer{ @@ -84,7 +86,7 @@ func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) { out: 100000, } acc.SetCoins(sdk.Coins{{"ABC-000", 1000000}, {"BNB", 100}}) - fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens) tran = Transfer{ inAsset: "XYZ-111", @@ -93,7 +95,7 @@ func TestFeeManager_calcTradeFeeForSingleTransfer(t *testing.T) { out: 100000, } acc.SetCoins(sdk.Coins{{"XYZ-111", 1000000}, {"BNB", 1000}}) - fee = keeper.FeeManager.calcTradeFeeForSingleTransfer(acc.GetCoins(), &tran, keeper.engines) + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) require.Equal(t, sdk.Coins{{"BNB", 500}}, fee.Tokens) } @@ -286,3 +288,149 @@ func TestFeeManager_CalcFixedFee(t *testing.T) { fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "BTC-000", keeper.engines) require.Equal(t, sdk.Coins{sdk.NewCoin("BTC-000", 1e13)}, fee.Tokens) } + +func TestFeeManager_calcTradeFeeForSingleTransfer_SupportBUSD(t *testing.T) { + upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1) + ctx, am, keeper := setup() + keeper.FeeManager.UpdateConfig(NewTestFeeConfig()) + keeper.SetBUSDSymbol("BUSD-BD1") + + // existing BNB -> BUSD trading pair + keeper.AddEngine(dextype.NewTradingPair("BNB", "BUSD-BD1", 1e5)) + keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7)) + keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6)) + + // enough BNB, BNB will be collected + _, acc := testutils.NewAccount(ctx, am, 1e5) + + // transferred in BNB + tran := Transfer{ + inAsset: "BNB", + in: 2e3, + } + fee := keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) + require.Equal(t, sdk.Coins{{"BNB", 1}}, fee.Tokens) + + // transferred in BUSD-BD1 + tran = Transfer{ + inAsset: "BUSD-BD1", + in: 1e3, + outAsset: "ABC-000", + out: 1e4, + } + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) + require.Equal(t, sdk.Coins{{"BNB", 5e2}}, fee.Tokens) + + // transferred in ABC-000 + tran = Transfer{ + inAsset: "ABC-000", + in: 1e3, + outAsset: "BUSD-BD1", + out: 100, + } + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) + require.Equal(t, sdk.Coins{{"BNB", 50}}, fee.Tokens) + + // transferred in XYZ-999 + tran = Transfer{ + inAsset: "XYZ-999", + in: 1e3, + outAsset: "BUSD-BD1", + out: 1e5, + } + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) + require.Equal(t, sdk.Coins{{"BNB", 5e4}}, fee.Tokens) + + // existing BUSD -> BNB trading pair + ctx, am, keeper = setup() + keeper.FeeManager.UpdateConfig(NewTestFeeConfig()) + keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "BNB", 1e8)) + keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7)) + keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6)) + + // enough BNB, BNB will be collected + _, acc = testutils.NewAccount(ctx, am, 1e10) + + // transferred in BUSD-BD1 + tran = Transfer{ + inAsset: "BUSD-BD1", + in: 1e4, + outAsset: "ABC-000", + out: 1e5, + } + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) + require.Equal(t, sdk.Coins{{"BNB", 5}}, fee.Tokens) + + // transferred in ABC-000 + tran = Transfer{ + inAsset: "ABC-000", + in: 1e6, + outAsset: "BUSD-BD1", + out: 1e5, + } + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) + require.Equal(t, sdk.Coins{{"BNB", 50}}, fee.Tokens) + + // transferred in XYZ-999 + tran = Transfer{ + inAsset: "XYZ-999", + in: 1e3, + outAsset: "BUSD-BD1", + out: 1e5, + } + fee = keeper.FeeManager.calcTradeFeeFromTransfer(acc.GetCoins(), &tran, keeper.engines) + require.Equal(t, sdk.Coins{{"BNB", 50}}, fee.Tokens) +} + +func TestFeeManager_CalcFixedFee_SupportBUSD(t *testing.T) { + upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1) + ctx, am, keeper := setup() + keeper.FeeManager.UpdateConfig(NewTestFeeConfig()) + keeper.SetBUSDSymbol("BUSD-BD1") + + // existing BNB -> BUSD trading pair + _, acc := testutils.NewAccount(ctx, am, 0) + keeper.AddEngine(dextype.NewTradingPair("BNB", "BUSD-BD1", 1e5)) + keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7)) + keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6)) + + // no enough BNB, the transferred-in asset will be collected + // buy BUSD-BD1 + acc.SetCoins(sdk.Coins{{Denom: "BUSD-BD1", Amount: 1e4}}) + fee := keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "BUSD-BD1", keeper.engines) + require.Equal(t, sdk.Coins{sdk.NewCoin("BUSD-BD1", 1e2)}, fee.Tokens) + + // buy ABC-000 + acc.SetCoins(sdk.Coins{{Denom: "ABC-000", Amount: 1e4}}) + fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "ABC-000", keeper.engines) + require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e3)}, fee.Tokens) + + // buy XYZ-999 + acc.SetCoins(sdk.Coins{{Denom: "XYZ-999", Amount: 1e4}}) + fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "XYZ-999", keeper.engines) + require.Equal(t, sdk.Coins{sdk.NewCoin("XYZ-999", 1)}, fee.Tokens) + + // existing BUSD -> BNB trading pair + ctx, am, keeper = setup() + keeper.FeeManager.UpdateConfig(NewTestFeeConfig()) + _, acc = testutils.NewAccount(ctx, am, 0) + keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "BNB", 1e9)) + keeper.AddEngine(dextype.NewTradingPair("ABC-000", "BUSD-BD1", 1e7)) + keeper.AddEngine(dextype.NewTradingPair("BUSD-BD1", "XYZ-999", 1e6)) + + // no enough BNB, the transferred-in asset will be collected + // buy BUSD-BD1 + acc.SetCoins(sdk.Coins{{Denom: "BUSD-BD1", Amount: 1e11}}) + fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "BUSD-BD1", keeper.engines) + require.Equal(t, sdk.Coins{sdk.NewCoin("BUSD-BD1", 1e4)}, fee.Tokens) + + // buy ABC-000 + acc.SetCoins(sdk.Coins{{Denom: "ABC-000", Amount: 1e10}}) + fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "ABC-000", keeper.engines) + require.Equal(t, sdk.Coins{sdk.NewCoin("ABC-000", 1e5)}, fee.Tokens) + + // buy XYZ-999 + acc.SetCoins(sdk.Coins{{Denom: "XYZ-999", Amount: 1e10}}) + fee = keeper.FeeManager.CalcFixedFee(acc.GetCoins(), eventFullyExpire, "XYZ-999", keeper.engines) + require.Equal(t, sdk.Coins{sdk.NewCoin("XYZ-999", 1e2)}, fee.Tokens) +} diff --git a/plugins/dex/order/keeper.go b/plugins/dex/order/keeper.go index 6c1205bea..1ce76127d 100644 --- a/plugins/dex/order/keeper.go +++ b/plugins/dex/order/keeper.go @@ -36,6 +36,8 @@ const ( preferencePriceLevel = 500 ) +var BUSDSymbol string + type FeeHandler func(map[string]*types.Fee) type TransferHandler func(Transfer) @@ -91,6 +93,10 @@ func NewKeeper(key sdk.StoreKey, am auth.AccountKeeper, tradingPairMapper store. } } +func (kp *Keeper) SetBUSDSymbol(symbol string) { + BUSDSymbol = symbol +} + func (kp *Keeper) Init(ctx sdk.Context, blockInterval, daysBack int, blockStore *tmstore.BlockStore, stateDB dbm.DB, lastHeight int64, txDecoder sdk.TxDecoder) { kp.initOrderBook(ctx, blockInterval, daysBack, blockStore, stateDB, lastHeight, txDecoder) kp.InitRecentPrices(ctx) @@ -1075,21 +1081,28 @@ func (kp *Keeper) CanListTradingPair(ctx sdk.Context, baseAsset, quoteAsset stri return fmt.Errorf("base asset symbol should not be identical to quote asset symbol") } - if kp.PairMapper.Exists(ctx, baseAsset, quoteAsset) || kp.PairMapper.Exists(ctx, quoteAsset, baseAsset) { + if kp.pairExistsBetween(ctx, baseAsset, quoteAsset) { return errors.New("trading pair exists") } if baseAsset != types.NativeTokenSymbol && quoteAsset != types.NativeTokenSymbol { - if !kp.PairMapper.Exists(ctx, baseAsset, types.NativeTokenSymbol) && - !kp.PairMapper.Exists(ctx, types.NativeTokenSymbol, baseAsset) { + // support busd pair listing + if sdk.IsUpgrade(upgrade.BEP70) && len(BUSDSymbol) > 0 { + if baseAsset == BUSDSymbol || quoteAsset == BUSDSymbol { + if kp.pairExistsBetween(ctx, types.NativeTokenSymbol, BUSDSymbol) { + return nil + } + } + } + + if !kp.pairExistsBetween(ctx, types.NativeTokenSymbol, baseAsset) { return fmt.Errorf("token %s should be listed against BNB before against %s", baseAsset, quoteAsset) } - if !kp.PairMapper.Exists(ctx, quoteAsset, types.NativeTokenSymbol) && - !kp.PairMapper.Exists(ctx, types.NativeTokenSymbol, quoteAsset) { + if !kp.pairExistsBetween(ctx, types.NativeTokenSymbol, quoteAsset) { return fmt.Errorf("token %s should be listed against BNB before listing %s against %s", quoteAsset, baseAsset, quoteAsset) } @@ -1132,3 +1145,8 @@ func (kp *Keeper) CanDelistTradingPair(ctx sdk.Context, baseAsset, quoteAsset st return nil } + +// Check whether there is trading pair between two symbols +func (kp *Keeper) pairExistsBetween(ctx sdk.Context, symbolA, symbolB string) bool { + return kp.PairMapper.Exists(ctx, symbolA, symbolB) || kp.PairMapper.Exists(ctx, symbolB, symbolA) +} diff --git a/plugins/dex/order/keeper_test.go b/plugins/dex/order/keeper_test.go index d7d81a12a..2800ce4e1 100644 --- a/plugins/dex/order/keeper_test.go +++ b/plugins/dex/order/keeper_test.go @@ -936,3 +936,77 @@ func TestKeeper_CanDelistTradingPair(t *testing.T) { require.NotNil(t, err) require.Contains(t, err.Error(), "trading pair BBB-000_AAA-000 should not exist before delisting BNB_BBB-000") } + +func TestKeeper_CanListTradingPair_SupportBUSD(t *testing.T) { + ctx, _, keeper := setup() + // before upgrade + err := keeper.CanListTradingPair(ctx, "AAA-000", "BUSD-BD1") + require.NotNil(t, err) + require.Contains(t, err.Error(), "token AAA-000 should be listed against BNB before against BUSD-BD1") + + err = keeper.CanListTradingPair(ctx, "BUSD-BD1", "AAA-000") + require.NotNil(t, err) + require.Contains(t, err.Error(), "token BUSD-BD1 should be listed against BNB before against AAA-000") + + // upgraded, but BNB-BUSD pair does not exist + upgrade.Mgr.AddUpgradeHeight(upgrade.BEP70, -1) + err = keeper.CanListTradingPair(ctx, "AAA-000", "BUSD-BD1") + require.NotNil(t, err) + require.Contains(t, err.Error(), "token AAA-000 should be listed against BNB before against BUSD-BD1") + + err = keeper.CanListTradingPair(ctx, "BUSD-BD1", "AAA-000") + require.NotNil(t, err) + require.Contains(t, err.Error(), "token BUSD-BD1 should be listed against BNB before against AAA-000") + + //upgraded, BNB-BUSD pair does exist + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", types.NativeTokenSymbol, 1e8)) + require.Nil(t, err) + + err = keeper.CanListTradingPair(ctx, "AAA-000", "BUSD-BD1") + require.Nil(t, err) + + err = keeper.CanListTradingPair(ctx, "BUSD-BD1", "AAA-000") + require.Nil(t, err) + + //upgraded, ABC-XYZ pair listing is still dependent on ABC-BNB and XYZ-BNB pairs + //BUSD-ABC or XYZ-BUSD pairs will be no help at this case + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", "AAA-000", 1e8)) + require.Nil(t, err) + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", "XYZ-000", 1e8)) + require.Nil(t, err) + + err = keeper.CanListTradingPair(ctx, "AAA-000", "XYZ-000") + require.NotNil(t, err) + require.Contains(t, err.Error(), "token AAA-000 should be listed against BNB before against XYZ-000") + + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "AAA-000", 1e8)) + require.Nil(t, err) + + err = keeper.CanListTradingPair(ctx, "AAA-000", "XYZ-000") + require.NotNil(t, err) + require.Contains(t, err.Error(), "token XYZ-000 should be listed against BNB before listing AAA-000 against XYZ-000") + + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "XYZ-000", 1e8)) + require.Nil(t, err) + + err = keeper.CanListTradingPair(ctx, "AAA-000", "XYZ-000") + require.Nil(t, err) +} + +func TestKeeper_CanDelistTradingPair_SupportBUSD(t *testing.T) { + ctx, _, keeper := setup() + err := keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000", "BUSD-BD1", 1e8)) + err = keeper.CanDelistTradingPair(ctx, "AAA-000", "BUSD-BD1") + require.Nil(t, err) + + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("BUSD-BD1", "AAA-000", 1e8)) + err = keeper.CanDelistTradingPair(ctx, "BUSD-BD1", "AAA-000") + require.Nil(t, err) + + // delisting AAA-XYZ will not depends on BUSD-AAA or BUSD-XYZ + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "AAA-000", 1e8)) + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair(types.NativeTokenSymbol, "XYZ-000", 1e8)) + err = keeper.PairMapper.AddTradingPair(ctx, dextypes.NewTradingPair("AAA-000", "XYZ-000", 1e8)) + err = keeper.CanDelistTradingPair(ctx, "AAA-000", "XYZ-000") + require.Nil(t, err) +}