diff --git a/src/init.cpp b/src/init.cpp index 8e2178e0b6..490c136d3d 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -505,6 +506,7 @@ void SetupServerArgs() gArgs.AddArg("-greatworldheight", "Great World fork activation height (regtest only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); gArgs.AddArg("-jellyfish_regtest", "Configure the regtest network for jellyfish testing", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); gArgs.AddArg("-simulatemainnet", "Configure the regtest network to mainnet target timespan and spacing ", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::OPTIONS); + gArgs.AddArg("-dexstats", strprintf("Enable storing live dex data in DB (default: %u)", DEFAULT_DEXSTATS), ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS); #ifdef USE_UPNP #if USE_UPNP gArgs.AddArg("-upnp", "Use UPnP to map the listening port (default: 1 when listening and no -proxy)", ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION); @@ -1706,6 +1708,7 @@ bool AppInitMain(InitInterfaces& interfaces) pcustomcsDB = std::make_unique(GetDataDir() / "enhancedcs", nCustomCacheSize, false, fReset || fReindexChainState); pcustomcsview.reset(); pcustomcsview = std::make_unique(*pcustomcsDB.get()); + if (!fReset && !fReindexChainState) { if (!pcustomcsDB->IsEmpty() && pcustomcsview->GetDbVersion() != CCustomCSView::DbVersion) { strLoadError = _("Account database is unsuitable").translated; @@ -1760,6 +1763,24 @@ bool AppInitMain(InitInterfaces& interfaces) } assert(::ChainActive().Tip() != nullptr); } + + auto dexStats = gArgs.GetBoolArg("-dexstats", DEFAULT_DEXSTATS); + pcustomcsview->SetDexStatsEnabled(dexStats); + + if (!fReset && !fReindexChainState && !pcustomcsDB->IsEmpty() && dexStats) { + // force reindex if there is no dex data at the tip + PoolHeightKey anyPoolSwap{DCT_ID{}, ~0u}; + auto it = pcustomcsview->LowerBound(anyPoolSwap); + auto shouldReindex = it.Valid(); + auto lastHeight = pcustomcsview->GetDexStatsLastHeight(); + if (lastHeight.has_value()) + shouldReindex &= !(*lastHeight == ::ChainActive().Tip()->nHeight); + + if (shouldReindex) { + strLoadError = _("Live dex needs reindex").translated; + break; + } + } } catch (const std::exception& e) { LogPrintf("%s\n", e.what()); strLoadError = _("Error opening block database").translated; diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index a4ef320ccd..b7bb71f71b 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -206,6 +206,7 @@ const std::map>& ATTRIBUTES::displayKeys {EconomyKeys::DFIP2203Current, "dfip2203_current"}, {EconomyKeys::DFIP2203Burned, "dfip2203_burned"}, {EconomyKeys::DFIP2203Minted, "dfip2203_minted"}, + {EconomyKeys::DexTokens, "dex"}, {EconomyKeys::DFIP2206FCurrent, "dfip2206f_current"}, {EconomyKeys::DFIP2206FBurned, "dfip2206f_burned"}, {EconomyKeys::DFIP2206FMinted, "dfip2206f_minted"}, @@ -607,7 +608,7 @@ Res ATTRIBUTES::RefundFuturesContracts(CCustomCSView &mnview, const uint32_t hei } } - attributes[liveKey] = balances; + SetValue(liveKey, std::move(balances)); return Res::Ok(); } @@ -661,7 +662,7 @@ Res ATTRIBUTES::RefundFuturesDUSD(CCustomCSView &mnview, const uint32_t height) } } - attributes[liveKey] = balances; + SetValue(liveKey, std::move(balances)); return Res::Ok(); } @@ -696,7 +697,7 @@ Res ATTRIBUTES::Import(const UniValue & val) { const auto& [id, multiplier] = *(splitValue->begin()); tokenSplits.insert(id); - attributes[attribute] = *splitValue; + SetValue(attribute, *splitValue); return Res::Ok(); } @@ -708,11 +709,11 @@ Res ATTRIBUTES::Import(const UniValue & val) { } else { newAttr.key = TokenKeys::PaybackDFIFeePCT; } - attributes[newAttr] = value; + SetValue(newAttr, value); return Res::Ok(); } } - attributes[attribute] = value; + SetValue(attribute, value); return Res::Ok(); } ); @@ -800,6 +801,18 @@ UniValue ATTRIBUTES::ExportFiltered(GovVarsFilter filter, const std::string &pre result.pushKV("paybackfees", AmountsToJSON(paybacks->tokensFee.balances)); result.pushKV("paybacktokens", AmountsToJSON(paybacks->tokensPayback.balances)); ret.pushKV(key, result); + } else if (const auto balances = std::get_if(&attribute.second)) { + for (const auto& pool : *balances) { + auto& dexTokenA = pool.second.totalTokenA; + auto& dexTokenB = pool.second.totalTokenB; + auto poolkey = KeyBuilder(key, pool.first.v); + ret.pushKV(KeyBuilder(poolkey, "total_commission_a"), ValueFromUint(dexTokenA.commissions)); + ret.pushKV(KeyBuilder(poolkey, "total_commission_b"), ValueFromUint(dexTokenB.commissions)); + ret.pushKV(KeyBuilder(poolkey, "fee_burn_a"), ValueFromUint(dexTokenA.feeburn)); + ret.pushKV(KeyBuilder(poolkey, "fee_burn_b"), ValueFromUint(dexTokenB.feeburn)); + ret.pushKV(KeyBuilder(poolkey, "total_swap_a"), ValueFromUint(dexTokenA.swaps)); + ret.pushKV(KeyBuilder(poolkey, "total_swap_b"), ValueFromUint(dexTokenB.swaps)); + } } else if (const auto splitValues = std::get_if(&attribute.second)) { std::string keyValue; for (auto it{splitValues->begin()}; it != splitValues->end(); ++it) { diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 245c6c9a15..55102ea22d 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -37,14 +37,15 @@ enum OracleIDs : uint8_t { }; enum EconomyKeys : uint8_t { - PaybackDFITokens = 'a', - PaybackTokens = 'b', - DFIP2203Current = 'c', - DFIP2203Burned = 'd', - DFIP2203Minted = 'e', + PaybackDFITokens = 'a', + PaybackTokens = 'b', + DFIP2203Current = 'c', + DFIP2203Burned = 'd', + DFIP2203Minted = 'e', DFIP2206FCurrent = 'f', DFIP2206FBurned = 'g', DFIP2206FMinted = 'h', + DexTokens = 'i', }; enum DFIPKeys : uint8_t { @@ -150,17 +151,46 @@ struct CFeeDir { ResVal GetFutureSwapContractAddress(const std::string& contract); +struct CDexTokenInfo { + struct CTokenInfo { + uint64_t swaps; + uint64_t feeburn; + uint64_t commissions; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(swaps); + READWRITE(feeburn); + READWRITE(commissions); + } + }; + + CTokenInfo totalTokenA; + CTokenInfo totalTokenB; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(totalTokenA); + READWRITE(totalTokenB); + } +}; + enum FeeDirValues : uint8_t { Both, In, Out }; +using CDexBalances = std::map; using OracleSplits = std::map; using DescendantValue = std::pair; using AscendantValue = std::pair; using CAttributeType = std::variant; -using CAttributeValue = std::variant; +using CAttributeValue = std::variant; enum GovVarsFilter { All, @@ -260,6 +290,7 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator(var.GetName(), var) ? Res::Ok() : Res::Err("can't write to DB"); + auto WriteVar = [this](GovVariable const & var) { + return WriteBy(var.GetName(), var) ? Res::Ok() : Res::Err("can't write to DB"); + }; + if (var.GetName() != "ATTRIBUTES") { + return WriteVar(var); + } + auto attributes = GetAttributes(); + if (!attributes) { + return WriteVar(var); + } + auto& current = dynamic_cast(var); + if (current.changed.empty()) { + return Res::Ok(); + } + for (auto& key : current.changed) { + auto it = current.attributes.find(key); + if (it == current.attributes.end()) { + attributes->attributes.erase(key); + } else { + attributes->attributes[key] = it->second; + } + } + return WriteVar(*attributes); } std::shared_ptr CGovView::GetVariable(std::string const & name) const diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index ea9f65fd3b..a366c1ec07 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -702,6 +702,29 @@ std::vector CAnchorConfirmsView::GetAnchorConfirmData() return confirms; } +/* + * CSettingsView + */ + +void CSettingsView::SetDexStatsLastHeight(const int32_t height) +{ + WriteBy(DEX_STATS_LAST_HEIGHT, height); +} + +std::optional CSettingsView::GetDexStatsLastHeight() +{ + return ReadBy(DEX_STATS_LAST_HEIGHT); +} + +void CSettingsView::SetDexStatsEnabled(const bool enabled) +{ + WriteBy(DEX_STATS_ENABLED, enabled); +} + +std::optional CSettingsView::GetDexStatsEnabled() +{ + return ReadBy(DEX_STATS_ENABLED); +} /* * CCustomCSView */ @@ -1049,14 +1072,34 @@ Res CCustomCSView::PopulateCollateralData(CCollateralLoans& result, CVaultId con } uint256 CCustomCSView::MerkleRoot() { - auto& rawMap = GetStorage().GetRaw(); + auto rawMap = GetStorage().GetRaw(); if (rawMap.empty()) { return {}; } + auto isAttributes = [](const TBytes& key) { + MapKV map = {std::make_pair(key, TBytes{})}; + // Attributes should not be part of merkle root + static const std::string attributes("ATTRIBUTES"); + auto it = NewKVIterator(attributes, map); + return it.Valid() && it.Key() == attributes; + }; + + auto it = NewKVIterator(UndoKey{}, rawMap); + for (; it.Valid(); it.Next()) { + CUndo value = it.Value(); + auto& map = value.before; + for (auto it = map.begin(); it != map.end();) { + isAttributes(it->first) ? map.erase(it++) : ++it; + } + auto key = std::make_pair(CUndosView::ByUndoKey::prefix(), static_cast(it.Key())); + rawMap[DbTypeToBytes(key)] = DbTypeToBytes(value); + } + std::vector hashes; - for (const auto& it : rawMap) { - auto value = it.second ? *it.second : TBytes{}; - hashes.push_back(Hash2(it.first, value)); + for (const auto& [key, value] : rawMap) { + if (!isAttributes(key)) { + hashes.push_back(Hash2(key, value ? *value : TBytes{})); + } } return ComputeMerkleRoot(std::move(hashes)); } diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index f92beb73b6..456d698bc3 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -297,6 +297,21 @@ class CAnchorConfirmsView : public virtual CStorageView struct BtcTx { static constexpr uint8_t prefix() { return 'x'; } }; }; +class CSettingsView : public virtual CStorageView +{ + +public: + const std::string DEX_STATS_LAST_HEIGHT = "DexStatsLastHeight"; + const std::string DEX_STATS_ENABLED = "DexStatsEnabled"; + + void SetDexStatsLastHeight(int32_t height); + std::optional GetDexStatsLastHeight(); + void SetDexStatsEnabled(bool enabled); + std::optional GetDexStatsEnabled(); + + struct KVSettings { static constexpr uint8_t prefix() { return '0'; } }; +}; + class CCollateralLoans { // in USD double calcRatio(uint64_t maxRatio) const; @@ -351,6 +366,7 @@ class CCustomCSView , public CICXOrderView , public CLoanView , public CVaultView + , public CSettingsView { void CheckPrefixes() { @@ -379,7 +395,8 @@ class CCustomCSView CLoanView :: LoanSetCollateralTokenCreationTx, LoanSetCollateralTokenKey, LoanSetLoanTokenCreationTx, LoanSetLoanTokenKey, LoanSchemeKey, DefaultLoanSchemeKey, DelayedLoanSchemeKey, DestroyLoanSchemeKey, LoanInterestByVault, LoanTokenAmount, LoanLiquidationPenalty, LoanInterestV2ByVault, - CVaultView :: VaultKey, OwnerVaultKey, CollateralKey, AuctionBatchKey, AuctionHeightKey, AuctionBidKey + CVaultView :: VaultKey, OwnerVaultKey, CollateralKey, AuctionBatchKey, AuctionHeightKey, AuctionBidKey, + CSettingsView :: KVSettings >(); } private: diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index a0db063ef5..a0b8025aa5 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -3989,6 +3989,12 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo mnview.Flush(); } + auto attributes = view.GetAttributes(); + assert(attributes); + + CDataStructureV0 dexKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DexTokens}; + auto dexBalances = attributes->GetValue(dexKey, CDexBalances{}); + // Set amount to be swapped in pool CTokenAmount swapAmountResult{obj.idTokenFrom, obj.amountFrom}; @@ -4028,16 +4034,24 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo return Res::Err("Pool currently disabled due to locked token"); } - auto dexfeeInPct = view.GetDexFeeInPct(currentID, swapAmount.nTokenId); - - const auto attributes = view.GetAttributes(); - assert(attributes); - CDataStructureV0 dirAKey{AttributeTypes::Poolpairs, currentID.v, PoolKeys::TokenAFeeDir}; CDataStructureV0 dirBKey{AttributeTypes::Poolpairs, currentID.v, PoolKeys::TokenBFeeDir}; const auto dirA = attributes->GetValue(dirAKey, CFeeDir{FeeDirValues::Both}); const auto dirB = attributes->GetValue(dirBKey, CFeeDir{FeeDirValues::Both}); const auto asymmetricFee = std::make_pair(dirA, dirB); + + auto dexfeeInPct = view.GetDexFeeInPct(currentID, swapAmount.nTokenId); + auto& balances = dexBalances[currentID]; + auto forward = swapAmount.nTokenId == pool->idTokenA; + + auto& totalTokenA = forward ? balances.totalTokenA : balances.totalTokenB; + auto& totalTokenB = forward ? balances.totalTokenB : balances.totalTokenA; + + const auto& reserveAmount = forward ? pool->reserveA : pool->reserveB; + const auto& blockCommission = forward ? pool->blockCommissionA : pool->blockCommissionB; + + const auto initReserveAmount = reserveAmount; + const auto initBlockCommission = blockCommission; // Perform swap poolResult = pool->Swap(swapAmount, dexfeeInPct, poolPrice, asymmetricFee, [&] (const CTokenAmount& dexfeeInAmount, const CTokenAmount& tokenAmount) { @@ -4086,6 +4100,7 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo if (!res) { return res; } + totalTokenA.feeburn += dexfeeInAmount.nValue; } // burn the dex out amount @@ -4094,6 +4109,14 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo if (!res) { return res; } + totalTokenB.feeburn += dexfeeOutAmount.nValue; + } + + totalTokenA.swaps += (reserveAmount - initReserveAmount); + totalTokenA.commissions += (blockCommission - initBlockCommission); + + if (lastSwap && obj.to == Params().GetConsensus().burnAddress) { + totalTokenB.feeburn += swapAmountResult.nValue; } return res; @@ -4114,6 +4137,10 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo } } + if (!testOnly && view.GetDexStatsEnabled().value_or(false)) { + attributes->SetValue(dexKey, std::move(dexBalances)); + view.SetVariable(*attributes); + } // Assign to result for loop testing best pool swap result result = swapAmountResult.nValue; diff --git a/src/validation.cpp b/src/validation.cpp index 507fc66728..573cc654eb 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3024,6 +3024,10 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // Loan splits ProcessTokenSplits(block, pindex, cache, creationTxs, chainparams); + // Set height for live dex data + if (cache.GetDexStatsEnabled().value_or(false)) + cache.SetDexStatsLastHeight(pindex->nHeight); + // DFI-to-DUSD swaps ProcessFuturesDUSD(pindex, cache, chainparams); diff --git a/src/validation.h b/src/validation.h index 84a0336e6e..cc33e442fd 100644 --- a/src/validation.h +++ b/src/validation.h @@ -129,6 +129,9 @@ static const unsigned int DEFAULT_BANSCORE_THRESHOLD = 100; static const bool DEFAULT_PERSIST_MEMPOOL = false; /** Default for using fee filter */ static const bool DEFAULT_FEEFILTER = true; +/** Default for using live dex in attributes */ +static const bool DEFAULT_DEXSTATS = false; + /** Maximum number of headers to announce when relaying blocks with headers message.*/ static const unsigned int MAX_BLOCKS_TO_ANNOUNCE = 8; @@ -861,8 +864,8 @@ inline CAmount CalculateCoinbaseReward(const CAmount blockReward, const uint32_t Res AddNonTxToBurnIndex(const CScript& from, const CBalances& amounts); -void ConsolidateRewards(CCustomCSView& view, int height, - const std::vector> &items, +void ConsolidateRewards(CCustomCSView& view, int height, + const std::vector> &items, bool interruptOnShutdown, int numWorkers = 0); #endif // DEFI_VALIDATION_H diff --git a/test/functional/feature_poolswap.py b/test/functional/feature_poolswap.py index 7566a205ff..d212dea45c 100755 --- a/test/functional/feature_poolswap.py +++ b/test/functional/feature_poolswap.py @@ -29,509 +29,514 @@ def set_test_params(self): # node2: Non Foundation self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177', '-acindex=1'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177', '-acindex=1'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177']] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177', '-acindex=1', '-dexstats'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177', '-acindex=1', '-dexstats'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177', '-dexstats'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-fortcanningroadheight=177', '-dexstats']] - - def run_test(self): + def setup(self): assert_equal(len(self.nodes[0].listtokens()), 1) # only one token == DFI self.setup_tokens() # Stop node #3 for future revert self.stop_node(3) - # CREATION: - #======================== - # 1 Getting new addresses and checking coins - symbolGOLD = "GOLD#" + self.get_id_token("GOLD") - symbolSILVER = "SILVER#" + self.get_id_token("SILVER") - idGold = list(self.nodes[0].gettoken(symbolGOLD).keys())[0] - idSilver = list(self.nodes[0].gettoken(symbolSILVER).keys())[0] - accountGN0 = self.nodes[0].get_genesis_keys().ownerAuthAddress - accountSN1 = self.nodes[1].get_genesis_keys().ownerAuthAddress + self.symbolGOLD = "GOLD#" + self.get_id_token("GOLD") + self.symbolSILVER = "SILVER#" + self.get_id_token("SILVER") + self.idGold = list(self.nodes[0].gettoken(self.symbolGOLD).keys())[0] + self.idSilver = list(self.nodes[0].gettoken(self.symbolSILVER).keys())[0] + + self.init_accounts() + self.create_poolpair() - owner = self.nodes[0].getnewaddress("", "legacy") + def init_accounts(self): + self.accountGN0 = self.nodes[0].get_genesis_keys().ownerAuthAddress + self.accountSN1 = self.nodes[1].get_genesis_keys().ownerAuthAddress + self.owner = self.nodes[0].getnewaddress("", "legacy") # 2 Transferring SILVER from N1 Account to N0 Account - self.nodes[1].accounttoaccount(accountSN1, {accountGN0: "1000@" + symbolSILVER}) + self.nodes[1].accounttoaccount(self.accountSN1, {self.accountGN0: "1000@" + self.symbolSILVER}) self.nodes[1].generate(1) # Transferring GOLD from N0 Account to N1 Account - self.nodes[0].accounttoaccount(accountGN0, {accountSN1: "200@" + symbolGOLD}) + self.nodes[0].accounttoaccount(self.accountGN0, {self.accountSN1: "200@" + self.symbolGOLD}) self.nodes[0].generate(1) - silverCheckN0 = self.nodes[0].getaccount(accountGN0, {}, True)[idSilver] - silverCheckN1 = self.nodes[1].getaccount(accountSN1, {}, True)[idSilver] + self.silverCheckN0 = self.nodes[0].getaccount(self.accountGN0, {}, True)[self.idSilver] - # 3 Creating poolpair + def create_poolpair(self): self.nodes[0].createpoolpair({ - "tokenA": symbolGOLD, - "tokenB": symbolSILVER, + "tokenA": self.symbolGOLD, + "tokenB": self.symbolSILVER, "commission": 0.1, "status": True, - "ownerAddress": owner, + "ownerAddress": self.owner, "pairSymbol": "GS", }, []) self.nodes[0].generate(1) - # only 4 tokens = DFI, GOLD, SILVER, GS assert_equal(len(self.nodes[0].listtokens()), 4) # check tokens id pool = self.nodes[0].getpoolpair("GS") - idGS = list(self.nodes[0].gettoken("GS").keys())[0] - assert_equal(pool[idGS]['idTokenA'], idGold) - assert_equal(pool[idGS]['idTokenB'], idSilver) + self.idGS = list(self.nodes[0].gettoken("GS").keys())[0] + assert_equal(pool[self.idGS]['idTokenA'], self.idGold) + assert_equal(pool[self.idGS]['idTokenB'], self.idSilver) - # Fail swap: lack of liquidity + def test_swap_with_no_liquidity(self): assert_raises_rpc_error(-32600, "Lack of liquidity", self.nodes[0].poolswap, { - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 10, - "to": accountSN1, - "tokenTo": symbolGOLD, + "to": self.accountSN1, + "tokenTo": self.symbolGOLD, } ) - #list_pool = self.nodes[0].listpoolpairs() - #print (list_pool) - - # 4 Adding liquidity - #list_poolshares = self.nodes[0].listpoolshares() - #print (list_poolshares) - + def test_add_liquidity_from_different_nodes(self): self.nodes[0].addpoolliquidity({ - accountGN0: ["100@" + symbolGOLD, "500@" + symbolSILVER] - }, accountGN0, []) + self.accountGN0: ["100@" + self.symbolGOLD, "500@" + self.symbolSILVER] + }, self.accountGN0, []) self.nodes[0].generate(1) self.sync_blocks([self.nodes[0], self.nodes[1]]) - #list_poolshares = self.nodes[0].listpoolshares() - #print (list_poolshares) - self.nodes[1].addpoolliquidity({ - accountSN1: ["100@" + symbolGOLD, "500@" + symbolSILVER] - }, accountSN1, []) + self.accountSN1: ["100@" + self.symbolGOLD, "500@" + self.symbolSILVER] + }, self.accountSN1, []) self.nodes[1].generate(1) self.sync_blocks([self.nodes[0], self.nodes[1]]) - #list_poolshares = self.nodes[0].listpoolshares() - #print (list_poolshares) - - goldCheckN0 = self.nodes[0].getaccount(accountGN0, {}, True)[idGold] - silverCheckN0 = self.nodes[0].getaccount(accountGN0, {}, True)[idSilver] + goldCheckN0 = self.nodes[0].getaccount(self.accountGN0, {}, True)[self.idGold] + silverCheckN0 = self.nodes[0].getaccount(self.accountGN0, {}, True)[self.idSilver] - # 5 Checking that liquidity is correct assert_equal(goldCheckN0, 700) assert_equal(silverCheckN0, 500) list_pool = self.nodes[0].listpoolpairs() - #print (list_pool) assert_equal(list_pool['1']['reserveA'], 200) # GOLD assert_equal(list_pool['1']['reserveB'], 1000) # SILVER - # 6 Trying to poolswap - + def turn_off_pool_and_try_swap(self): self.nodes[0].updatepoolpair({"pool": "GS", "status": False}) self.nodes[0].generate(1) try: self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 10, - "to": accountSN1, - "tokenTo": symbolGOLD, + "to": self.accountSN1, + "tokenTo": self.symbolGOLD, }, []) except JSONRPCException as e: errorString = e.error['message'] assert("turned off" in errorString) + + def turn_on_pool_and_swap(self): self.nodes[0].updatepoolpair({"pool": "GS", "status": True}) self.nodes[0].generate(1) - goldCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idGold] - silverCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idSilver] - testPoolSwapRes = self.nodes[0].testpoolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 10, - "to": accountSN1, - "tokenTo": symbolGOLD, + "to": self.accountSN1, + "tokenTo": self.symbolGOLD, }) - # this acc will be - goldCheckPS = self.nodes[2].getaccount(accountSN1, {}, True)[idGold] + self.goldCheckPS = self.nodes[2].getaccount(self.accountSN1, {}, True)[self.idGold] testPoolSwapSplit = str(testPoolSwapRes).split("@", 2) - psTestAmount = testPoolSwapSplit[0] + self.psTestAmount = testPoolSwapSplit[0] psTestTokenId = testPoolSwapSplit[1] - assert_equal(psTestTokenId, idGold) + assert_equal(psTestTokenId, self.idGold) testPoolSwapVerbose = self.nodes[0].testpoolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 10, - "to": accountSN1, - "tokenTo": symbolGOLD, + "to": self.accountSN1, + "tokenTo": self.symbolGOLD, }, "direct", True) assert_equal(testPoolSwapVerbose["path"], "direct") - assert_equal(testPoolSwapVerbose["pools"][0], idGS) + assert_equal(testPoolSwapVerbose["pools"][0], self.idGS) assert_equal(testPoolSwapVerbose["amount"], testPoolSwapRes) + def test_swap_and_live_dex_data(self): self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 10, - "to": accountSN1, - "tokenTo": symbolGOLD, + "to": self.accountSN1, + "tokenTo": self.symbolGOLD, }, []) self.nodes[0].generate(1) - - # 7 Sync self.sync_blocks([self.nodes[0], self.nodes[2]]) - # 8 Checking that poolswap is correct - goldCheckN0 = self.nodes[2].getaccount(accountGN0, {}, True)[idGold] - silverCheckN0 = self.nodes[2].getaccount(accountGN0, {}, True)[idSilver] + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + # silver is tokenB + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(self.idGS)], Decimal('9.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(self.idGS)], Decimal('1.0')) - goldCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idGold] - silverCheckN1 = self.nodes[2].getaccount(accountSN1, {}, True)[idSilver] + goldCheckN0 = self.nodes[2].getaccount(self.accountGN0, {}, True)[self.idGold] + silverCheckN0 = self.nodes[2].getaccount(self.accountGN0, {}, True)[self.idSilver] - list_pool = self.nodes[2].listpoolpairs() - #print (list_pool) + goldCheckN1 = self.nodes[2].getaccount(self.accountSN1, {}, True)[self.idGold] + silverCheckN1 = self.nodes[2].getaccount(self.accountSN1, {}, True)[self.idSilver] - self.nodes[0].listpoolshares() - #print (list_poolshares) + list_pool = self.nodes[2].listpoolpairs() assert_equal(goldCheckN0, 700) assert_equal(str(silverCheckN0), "490.49999997") # TODO: calculate "true" values with trading fee! assert_equal(list_pool['1']['reserveA'] + goldCheckN1 , 300) - assert_equal(Decimal(goldCheckPS) + Decimal(psTestAmount), Decimal(goldCheckN1)) + assert_equal(Decimal(self.goldCheckPS) + Decimal(self.psTestAmount), Decimal(goldCheckN1)) assert_equal(str(silverCheckN1), "500.50000000") assert_equal(list_pool['1']['reserveB'], 1009) #1010 - 1 (commission) - # 9 Fail swap: price higher than indicated + def test_price_higher_than_indicated(self): + list_pool = self.nodes[2].listpoolpairs() price = list_pool['1']['reserveA/reserveB'] try: self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 10, - "to": accountSN1, - "tokenTo": symbolGOLD, + "to": self.accountSN1, + "tokenTo": self.symbolGOLD, "maxPrice": price - Decimal('0.1'), }, []) except JSONRPCException as e: errorString = e.error['message'] assert("Price is higher than indicated." in errorString) - # activate max price protection + def test_max_price(self): maxPrice = self.nodes[0].listpoolpairs()['1']['reserveB/reserveA'] self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 200, - "to": accountGN0, - "tokenTo": symbolGOLD, + "to": self.accountGN0, + "tokenTo": self.symbolGOLD, "maxPrice": maxPrice, }) assert_raises_rpc_error(-26, 'Price is higher than indicated', self.nodes[0].poolswap, { - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 200, - "to": accountGN0, - "tokenTo": symbolGOLD, + "to": self.accountGN0, + "tokenTo": self.symbolGOLD, "maxPrice": maxPrice, } ) self.nodes[0].generate(1) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(self.idGS)], Decimal('189.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(self.idGS)], Decimal('21.0')) + maxPrice = self.nodes[0].listpoolpairs()['1']['reserveB/reserveA'] # exchange tokens each other should work self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolGOLD, + "from": self.accountGN0, + "tokenFrom": self.symbolGOLD, "amountFrom": 200, - "to": accountGN0, - "tokenTo": symbolSILVER, + "to": self.accountGN0, + "tokenTo": self.symbolSILVER, "maxPrice": maxPrice, }) self.nodes[0].generate(1) self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 200, - "to": accountGN0, - "tokenTo": symbolGOLD, + "to": self.accountGN0, + "tokenTo": self.symbolGOLD, "maxPrice": maxPrice, }) self.nodes[0].generate(1) - # Test fort canning max price change + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_a'%(self.idGS)], Decimal('180.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_a'%(self.idGS)], Decimal('20.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(self.idGS)], Decimal('369.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(self.idGS)], Decimal('41.0')) + + def test_fort_canning_max_price_change(self): disconnect_nodes(self.nodes[0], 1) disconnect_nodes(self.nodes[0], 2) + destination = self.nodes[0].getnewaddress("", "legacy") - swap_from = 200 - coin = 100000000 + self.swap_from = 200 + self.coin = 100000000 self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolGOLD, - "amountFrom": swap_from, + "from": self.accountGN0, + "tokenFrom": self.symbolGOLD, + "amountFrom": self.swap_from, "to": destination, - "tokenTo": symbolSILVER + "tokenTo": self.symbolSILVER }) self.nodes[0].generate(1) - silver_received = self.nodes[0].getaccount(destination, {}, True)[idSilver] - silver_per_gold = round((swap_from * coin) / (silver_received * coin), 8) + silver_received = self.nodes[0].getaccount(destination, {}, True)[self.idSilver] + self.silver_per_gold = round((self.swap_from * self.coin) / (silver_received * self.coin), 8) # Reset swap and try again with max price set to expected amount self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) self.nodes[0].clearmempool() self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolGOLD, - "amountFrom": swap_from, + "from": self.accountGN0, + "tokenFrom": self.symbolGOLD, + "amountFrom": self.swap_from, "to": destination, - "tokenTo": symbolSILVER, - "maxPrice": silver_per_gold, + "tokenTo": self.symbolSILVER, + "maxPrice": self.silver_per_gold, }) self.nodes[0].generate(1) + def test_fort_canning_max_price_one_satoshi_below(self): # Reset swap and try again with max price set to one Satoshi below self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) self.nodes[0].clearmempool() + destination = self.nodes[0].getnewaddress("", "legacy") try: self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolGOLD, - "amountFrom": swap_from, + "from": self.accountGN0, + "tokenFrom": self.symbolGOLD, + "amountFrom": 200, "to": destination, - "tokenTo": symbolSILVER, - "maxPrice": silver_per_gold - Decimal('0.00000001'), + "tokenTo": self.symbolSILVER, + "maxPrice": self.silver_per_gold - Decimal('0.00000001'), }) except JSONRPCException as e: errorString = e.error['message'] assert("Price is higher than indicated" in errorString) - # Test round up setup + def setup_new_pool_BTC_LTC(self): self.nodes[0].createtoken({ "symbol": "BTC", "name": "Bitcoin", - "collateralAddress": accountGN0 + "collateralAddress": self.accountGN0 }) self.nodes[0].createtoken({ "symbol": "LTC", "name": "Litecoin", - "collateralAddress": accountGN0 + "collateralAddress": self.accountGN0 }) self.nodes[0].generate(1) - symbolBTC = "BTC#" + self.get_id_token("BTC") - symbolLTC = "LTC#" + self.get_id_token("LTC") - idBTC = list(self.nodes[0].gettoken(symbolBTC).keys())[0] - idLTC = list(self.nodes[0].gettoken(symbolLTC).keys())[0] + self.symbolBTC = "BTC#" + self.get_id_token("BTC") + self.symbolLTC = "LTC#" + self.get_id_token("LTC") + self.idBTC = list(self.nodes[0].gettoken(self.symbolBTC).keys())[0] + self.idLTC = list(self.nodes[0].gettoken(self.symbolLTC).keys())[0] - self.nodes[0].minttokens("1@" + symbolBTC) - self.nodes[0].minttokens("111@" + symbolLTC) + self.nodes[0].minttokens("1@" + self.symbolBTC) + self.nodes[0].minttokens("111@" + self.symbolLTC) self.nodes[0].generate(1) self.nodes[0].createpoolpair({ - "tokenA": symbolBTC, - "tokenB": symbolLTC, + "tokenA": self.symbolBTC, + "tokenB": self.symbolLTC, "commission": 0.01, "status": True, - "ownerAddress": owner, + "ownerAddress": self.owner, "pairSymbol": "BTC-LTC", }, []) self.nodes[0].generate(1) - idBL = list(self.nodes[0].gettoken("BTC-LTC").keys())[0] + self.idBL = list(self.nodes[0].gettoken("BTC-LTC").keys())[0] self.nodes[0].addpoolliquidity({ - accountGN0: ["1@" + symbolBTC, "100@" + symbolLTC] - }, accountGN0, []) + self.accountGN0: ["1@" + self.symbolBTC, "100@" + self.symbolLTC] + }, self.accountGN0, []) self.nodes[0].generate(1) - # Test round up + def one_satoshi_swap(self): new_dest = self.nodes[0].getnewaddress("", "legacy") self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolLTC, + "from": self.accountGN0, + "tokenFrom": self.symbolLTC, "amountFrom": 0.00000001, "to": new_dest, - "tokenTo": symbolBTC + "tokenTo": self.symbolBTC }) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000001')) + assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[self.idBTC], Decimal('0.00000001')) + def test_two_satoshi_round_up(self, FCP=False): # Reset swap self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) self.nodes[0].clearmempool() - # Test for 2 Sat round up + new_dest = self.nodes[0].getnewaddress("", "legacy") self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolLTC, + "from": self.accountGN0, + "tokenFrom": self.symbolLTC, "amountFrom": 0.00000190, "to": new_dest, - "tokenTo": symbolBTC + "tokenTo": self.symbolBTC }) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000002')) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_a'%(self.idBL)], Decimal('0.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_a'%(self.idBL)], Decimal('0.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(self.idBL)], Decimal('0.00000189')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(self.idBL)], Decimal('1E-8')) - # Reset swap and move to Fort Canning Park Height and try swap again + expected_round_up = FCP and Decimal('0.00000001') or Decimal('0.00000002') + assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[self.idBTC], expected_round_up) + + def reset_swap_move_to_FCP_and_swap(self): self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) self.nodes[0].clearmempool() self.nodes[0].generate(170 - self.nodes[0].getblockcount()) + new_dest = self.nodes[0].getnewaddress("", "legacy") # Test swap now results in zero amount self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolLTC, + "from": self.accountGN0, + "tokenFrom": self.symbolLTC, "amountFrom": 0.00000001, "to": new_dest, - "tokenTo": symbolBTC - }) - self.nodes[0].generate(1) - - assert(idBTC not in self.nodes[0].getaccount(new_dest, {}, True)) - - # Reset swap - self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) - self.nodes[0].clearmempool() - - # Test previous 2 Sat swap now results in 1 Sat - self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolLTC, - "amountFrom": 0.00000190, - "to": new_dest, - "tokenTo": symbolBTC + "tokenTo": self.symbolBTC }) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000001')) + assert(self.idBTC not in self.nodes[0].getaccount(new_dest, {}, True)) - self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(idGS): '0.05', 'v0/poolpairs/%s/token_b_fee_pct'%(idGS): '0.08'}}) + def set_token_and_pool_fees(self): + self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(self.idGS): '0.05', 'v0/poolpairs/%s/token_b_fee_pct'%(self.idGS): '0.08'}}) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'], {'v0/poolpairs/%s/token_a_fee_pct'%(idGS): '0.05', 'v0/poolpairs/%s/token_b_fee_pct'%(idGS): '0.08'}) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/poolpairs/%s/token_a_fee_pct'%(self.idGS)], '0.05') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(self.idGS)], '0.08') - result = self.nodes[0].getpoolpair(idGS) - assert_equal(result[idGS]['dexFeePctTokenA'], Decimal('0.05')) - assert_equal(result[idGS]['dexFeePctTokenB'], Decimal('0.08')) + result = self.nodes[0].getpoolpair(self.idGS) + assert_equal(result[self.idGS]['dexFeePctTokenA'], Decimal('0.05')) + assert_equal(result[self.idGS]['dexFeePctTokenB'], Decimal('0.08')) + def test_listaccounthistory_and_burninfo(self): + destination = self.nodes[0].getnewaddress("", "legacy") self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolGOLD, - "amountFrom": swap_from, + "from": self.accountGN0, + "tokenFrom": self.symbolGOLD, + "amountFrom": self.swap_from, "to": destination, - "tokenTo": symbolSILVER, + "tokenTo": self.symbolSILVER, }) - commission = round((swap_from * 0.1), 8) - amountA = swap_from - commission + commission = round((self.swap_from * 0.1), 8) + amountA = self.swap_from - commission dexinfee = round(amountA * 0.05, 8) amountA = amountA - dexinfee - pool = self.nodes[0].getpoolpair("GS")[idGS] + pool = self.nodes[0].getpoolpair("GS")[self.idGS] reserveA = pool['reserveA'] reserveB = pool['reserveB'] self.nodes[0].generate(1) - pool = self.nodes[0].getpoolpair("GS")[idGS] + pool = self.nodes[0].getpoolpair("GS")[self.idGS] assert_equal(pool['reserveA'] - reserveA, amountA) - swapped = self.nodes[0].getaccount(destination, {}, True)[idSilver] + swapped = self.nodes[0].getaccount(destination, {}, True)[self.idSilver] amountB = reserveB - pool['reserveB'] dexoutfee = round(amountB * Decimal(0.08), 8) assert_equal(amountB - dexoutfee, swapped) - assert_equal(self.nodes[0].listaccounthistory(accountGN0, {'token':symbolGOLD})[0]['amounts'], ['-200.00000000@'+symbolGOLD]) + assert_equal(self.nodes[0].listaccounthistory(self.accountGN0, {'token':self.symbolGOLD})[0]['amounts'], ['-200.00000000@'+self.symbolGOLD]) + + assert_equal(self.nodes[0].getburninfo()['dexfeetokens'].sort(), ['%.8f'%(dexinfee)+self.symbolGOLD, '%.8f'%(dexoutfee)+self.symbolSILVER].sort()) - assert_equal(self.nodes[0].getburninfo()['dexfeetokens'].sort(), ['%.8f'%(dexinfee)+symbolGOLD, '%.8f'%(dexoutfee)+symbolSILVER].sort()) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/fee_burn_a'%(self.idGS)], dexinfee) + assert_equal(attributes['v0/live/economy/dex/%s/fee_burn_b'%(self.idGS)], dexoutfee) - # set 1% token dex fee and commission - self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(idGS): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idGS): '0.01'}}) + def update_comission_and_fee_to_1pct_pool1(self): + self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(self.idGS): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(self.idGS): '0.01'}}) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'], {'v0/poolpairs/%s/token_a_fee_pct'%(idGS): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idGS): '0.01'}) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/poolpairs/%s/token_a_fee_pct'%(self.idGS)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(self.idGS)], '0.01') self.nodes[0].updatepoolpair({"pool": "GS", "commission": 0.01}) self.nodes[0].generate(1) + destination = self.nodes[0].getnewaddress("", "legacy") # swap 1 sat self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolSILVER, + "from": self.accountGN0, + "tokenFrom": self.symbolSILVER, "amountFrom": 0.00000001, "to": destination, - "tokenTo": symbolGOLD, + "tokenTo": self.symbolGOLD, }) - pool = self.nodes[0].getpoolpair("GS")[idGS] + pool = self.nodes[0].getpoolpair("GS")[self.idGS] reserveA = pool['reserveA'] self.nodes[0].generate(1) - pool = self.nodes[0].getpoolpair("GS")[idGS] + pool = self.nodes[0].getpoolpair("GS")[self.idGS] assert_equal(reserveA, pool['reserveA']) - self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(idBL): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idBL): '0.01'}}) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/poolpairs/%s/token_a_fee_pct'%(self.idBL): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(self.idBL): '0.01'}}) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'], {'v0/poolpairs/%s/token_a_fee_pct'%(idGS): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idGS): '0.01', 'v0/poolpairs/%s/token_a_fee_pct'%(idBL): '0.01', 'v0/poolpairs/%s/token_b_fee_pct'%(idBL): '0.01'}) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/poolpairs/%s/token_a_fee_pct'%(self.idGS)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(self.idGS)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_a_fee_pct'%(self.idBL)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(self.idBL)], '0.01') + def update_comission_and_fee_to_1pct_pool2(self): self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) self.nodes[0].clearmempool() self.nodes[0].generate(1) - self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/%s/dex_in_fee_pct'%(idLTC): '0.02', 'v0/token/%s/dex_out_fee_pct'%(idBTC): '0.05'}}) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/%s/dex_in_fee_pct'%(self.idLTC): '0.02', 'v0/token/%s/dex_out_fee_pct'%(self.idBTC): '0.05'}}) self.nodes[0].generate(1) - result = self.nodes[0].getpoolpair(idBL) - assert_equal(result[idBL]['dexFeeInPctTokenB'], Decimal('0.02')) - assert_equal(result[idBL]['dexFeeOutPctTokenA'], Decimal('0.05')) + result = self.nodes[0].getpoolpair(self.idBL) + assert_equal(result[self.idBL]['dexFeeInPctTokenB'], Decimal('0.02')) + assert_equal(result[self.idBL]['dexFeeOutPctTokenA'], Decimal('0.05')) destBTC = self.nodes[0].getnewaddress("", "legacy") swapltc = 10 self.nodes[0].poolswap({ - "from": accountGN0, - "tokenFrom": symbolLTC, + "from": self.accountGN0, + "tokenFrom": self.symbolLTC, "amountFrom": swapltc, "to": destBTC, - "tokenTo": symbolBTC + "tokenTo": self.symbolBTC }) commission = round((swapltc * 0.01), 8) amountB = Decimal(swapltc - commission) dexinfee = amountB * Decimal(0.02) amountB = amountB - dexinfee - pool = self.nodes[0].getpoolpair("BTC-LTC")[idBL] + pool = self.nodes[0].getpoolpair("BTC-LTC")[self.idBL] reserveA = pool['reserveA'] reserveB = pool['reserveB'] self.nodes[0].generate(1) - pool = self.nodes[0].getpoolpair("BTC-LTC")[idBL] + pool = self.nodes[0].getpoolpair("BTC-LTC")[self.idBL] assert_equal(pool['reserveB'] - reserveB, round(amountB, 8)) - swapped = self.nodes[0].getaccount(destBTC, {}, True)[idBTC] + swapped = self.nodes[0].getaccount(destBTC, {}, True)[self.idBTC] amountA = reserveA - pool['reserveA'] - dexoutfee = round(trunc(amountA * Decimal(0.05) * coin) / coin, 8) + dexoutfee = round(trunc(amountA * Decimal(0.05) * self.coin) / self.coin, 8) assert_equal(round(amountA - Decimal(dexoutfee), 8), round(swapped, 8)) - # REVERTING: - #======================== - print ("Reverting...") - # Reverting creation! + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/fee_burn_b'%(self.idBL)], round(dexinfee, 8)) + assert_equal(attributes['v0/live/economy/dex/%s/fee_burn_a'%(self.idBL)], Decimal(str(round(dexoutfee, 8)))) + + def revert_to_initial_state(self): self.start_node(3) self.nodes[3].generate(30) @@ -541,5 +546,28 @@ def run_test(self): self.sync_blocks() assert_equal(len(self.nodes[0].listpoolpairs()), 0) + + def run_test(self): + self.setup() + self.test_swap_with_no_liquidity() + self.test_add_liquidity_from_different_nodes() + self.turn_off_pool_and_try_swap() + self.turn_on_pool_and_swap() + self.test_swap_and_live_dex_data() + self.test_price_higher_than_indicated() + self.test_max_price() + self.test_fort_canning_max_price_change() + self.test_fort_canning_max_price_one_satoshi_below() + self.setup_new_pool_BTC_LTC() + self.one_satoshi_swap() + self.test_two_satoshi_round_up() + self.reset_swap_move_to_FCP_and_swap() + self.test_two_satoshi_round_up(FCP=True) + self.set_token_and_pool_fees() + self.test_listaccounthistory_and_burninfo() + self.update_comission_and_fee_to_1pct_pool1() + self.update_comission_and_fee_to_1pct_pool2() + self.revert_to_initial_state() + if __name__ == '__main__': PoolPairTest ().main () diff --git a/test/functional/feature_poolswap_mainnet.py b/test/functional/feature_poolswap_mainnet.py index 88e247a0d4..53da32f440 100755 --- a/test/functional/feature_poolswap_mainnet.py +++ b/test/functional/feature_poolswap_mainnet.py @@ -15,11 +15,12 @@ class PoolPairTest (DefiTestFramework): def set_test_params(self): - self.FC_HEIGHT = 170 + self.FCH_HEIGHT = 170 + self.FCR_HEIGHT = 200 self.num_nodes = 1 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight='+str(self.FC_HEIGHT), '-simulatemainnet', '-jellyfish_regtest=1'] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight='+str(self.FCH_HEIGHT), '-fortcanningroadheight='+str(self.FCR_HEIGHT),'-simulatemainnet', '-jellyfish_regtest=1'] ] def create_tokens(self): @@ -119,7 +120,7 @@ def add_1satoshi_liquidity_non_empty_pool(self): def setup(self): - self.nodes[0].generate(self.FC_HEIGHT) + self.nodes[0].generate(self.FCH_HEIGHT) self.create_tokens() self.mint_tokens(100000000) self.create_pool_pairs() @@ -174,18 +175,24 @@ def test_simple_swap_1Satoshi(self): "tokenTo": self.symbol_key_SILVER, },[]) self.nodes[0].generate(1) + from_account = self.nodes[0].getaccount(from_address) - to_account = self.nodes[0].getaccount(to_address) assert_equal(from_account[1], '44999999.99999999@GOLD#128') + + to_account = self.nodes[0].getaccount(to_address) assert_equal(to_account, []) - def test_200_simple_swaps_1Satoshi(self): + poolpair_info = self.nodes[0].getpoolpair("GS") + assert_equal(poolpair_info['1']['reserveA'], Decimal('5000000.00000001')) + assert_equal(poolpair_info['1']['reserveB'], Decimal('500000.00000000')) + + def test_50_simple_swaps_1Satoshi(self): from_address = self.account_gs from_account = self.nodes[0].getaccount(from_address) to_address = self.nodes[0].getnewaddress("") assert_equal(from_account[1], '44999999.99999999@GOLD#128') - for _ in range(200): + for _ in range(50): self.nodes[0].poolswap({ "from": self.account_gs, "tokenFrom": self.symbol_key_GOLD, @@ -194,16 +201,22 @@ def test_200_simple_swaps_1Satoshi(self): "tokenTo": self.symbol_key_SILVER, },[]) self.nodes[0].generate(1) + from_account = self.nodes[0].getaccount(from_address) + assert_equal(from_account[1], '44999999.99999949@GOLD#128') + to_account = self.nodes[0].getaccount(to_address) - assert_equal(from_account[1], '44999999.99999799@GOLD#128') assert_equal(to_account, []) + poolpair_info = self.nodes[0].getpoolpair("GS") + assert_equal(poolpair_info['1']['reserveA'], Decimal('5000000.00000051')) + assert_equal(poolpair_info['1']['reserveB'], Decimal('500000.00000000')) + def test_compositeswap_1Satoshi(self): from_address = self.account_gs from_account = self.nodes[0].getaccount(from_address) to_address = self.nodes[0].getnewaddress("") - assert_equal(from_account[1], '44999999.99999799@GOLD#128') + assert_equal(from_account[1], '44999999.99999949@GOLD#128') testPoolSwapRes = self.nodes[0].testpoolswap({ "from": from_address, @@ -223,18 +236,29 @@ def test_compositeswap_1Satoshi(self): "tokenTo": self.symbol_key_DOGE, },[]) self.nodes[0].generate(1) + from_account = self.nodes[0].getaccount(from_address) + assert_equal(from_account[1], '44999999.99999948@GOLD#128') + to_account = self.nodes[0].getaccount(to_address) - assert_equal(from_account[1], '44999999.99999798@GOLD#128') assert_equal(to_account, []) - def test_200_compositeswaps_1Satoshi(self): + poolpair_info_GS = self.nodes[0].getpoolpair("GS") + assert_equal(poolpair_info_GS['1']['reserveA'], Decimal('5000000.00000052')) + assert_equal(poolpair_info_GS['1']['reserveB'], Decimal('500000.00000000')) + + # Second pool nmo changes as 1 sat is lost in comissions in first swap + poolpair_info_SD = self.nodes[0].getpoolpair("DS") + assert_equal(poolpair_info_SD['2']['reserveA'], Decimal('1000000.00000000')) + assert_equal(poolpair_info_SD['2']['reserveB'], Decimal('100000.00000000')) + + def test_50_compositeswaps_1Satoshi(self): from_address = self.account_gs from_account = self.nodes[0].getaccount(from_address) to_address = self.nodes[0].getnewaddress("") - assert_equal(from_account[1], '44999999.99999798@GOLD#128') + assert_equal(from_account[1], '44999999.99999948@GOLD#128') - for _ in range(200): + for _ in range(50): testPoolSwapRes = self.nodes[0].testpoolswap({ "from": from_address, "tokenFrom": self.symbol_key_GOLD, @@ -244,6 +268,7 @@ def test_200_compositeswaps_1Satoshi(self): }, "auto", True) assert_equal(testPoolSwapRes["amount"], '0.00000000@130') assert_equal(len(testPoolSwapRes["pools"]), 2) + self.nodes[0].compositeswap({ "from": self.account_gs, "tokenFrom": self.symbol_key_GOLD, @@ -251,20 +276,75 @@ def test_200_compositeswaps_1Satoshi(self): "to": to_address, "tokenTo": self.symbol_key_DOGE, },[]) + self.nodes[0].generate(1) + from_account = self.nodes[0].getaccount(from_address) + assert_equal(from_account[1], '44999999.99999898@GOLD#128') + to_account = self.nodes[0].getaccount(to_address) - assert_equal(from_account[1], '44999999.99999598@GOLD#128') assert_equal(to_account, []) + poolpair_info_GS = self.nodes[0].getpoolpair("GS") + assert_equal(poolpair_info_GS['1']['reserveA'], Decimal('5000000.00000102')) + assert_equal(poolpair_info_GS['1']['reserveB'], Decimal('500000.00000000')) + + # Second pool nmo changes as 1 sat is lost in comissions in first swap + poolpair_info_SD = self.nodes[0].getpoolpair("DS") + assert_equal(poolpair_info_SD['2']['reserveA'], Decimal('1000000.00000000')) + assert_equal(poolpair_info_SD['2']['reserveB'], Decimal('100000.00000000')) + + def goto(self, height): + current_block = self.nodes[0].getblockcount() + self.nodes[0].generate((height - current_block) + 1) + + def test_swap_full_amount_of_one_side_of_pool(self): + from_address = self.account_gs + from_account = self.nodes[0].getaccount(from_address) + to_address = self.nodes[0].getnewaddress("") + assert_equal(from_account[1], '44999999.99999898@GOLD#128') + + testPoolSwapRes = self.nodes[0].testpoolswap({ + "from": from_address, + "tokenFrom": self.symbol_key_GOLD, + "amountFrom": Decimal('5000000.00000102'), + "to": to_address, + "tokenTo": self.symbol_key_SILVER, + }, "auto", True) + assert_equal(testPoolSwapRes["amount"], '248743.71859296@129') + assert_equal(len(testPoolSwapRes["pools"]), 1) + + self.nodes[0].compositeswap({ + "from": self.account_gs, + "tokenFrom": self.symbol_key_GOLD, + "amountFrom": Decimal('5000000.00000102'), + "to": to_address, + "tokenTo": self.symbol_key_SILVER, + },[]) + self.nodes[0].generate(1) + + from_account = self.nodes[0].getaccount(from_address) + assert_equal(from_account[1], '40049999.99999765@GOLD#128') + + to_account = self.nodes[0].getaccount(to_address) + assert_equal(to_account, ['248743.71859296@SILVER#129']) + + poolpair_info_GS = self.nodes[0].getpoolpair("GS") + assert_equal(poolpair_info_GS['1']['reserveA'], Decimal('9950000.00000203')) + assert_equal(poolpair_info_GS['1']['reserveB'], Decimal('251256.28140704')) + def run_test(self): self.setup() self.test_swap_with_wrong_amounts() self.test_simple_swap_1Satoshi() - self.test_200_simple_swaps_1Satoshi() + self.test_50_simple_swaps_1Satoshi() self.test_compositeswap_1Satoshi() - self.test_200_compositeswaps_1Satoshi() + self.test_50_compositeswaps_1Satoshi() + + self.goto(self.FCR_HEIGHT) # Move to FCR + + self.test_swap_full_amount_of_one_side_of_pool() if __name__ == '__main__': PoolPairTest ().main () diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh index 5b8bf08fee..6910c2bb1f 100755 --- a/test/lint/lint-circular-dependencies.sh +++ b/test/lint/lint-circular-dependencies.sh @@ -28,7 +28,7 @@ EXPECTED_CIRCULAR_DEPENDENCIES=( "consensus/tx_verify -> masternodes/masternodes -> validation -> consensus/tx_verify" "consensus/tx_verify -> masternodes/mn_checks -> txmempool -> consensus/tx_verify" "index/txindex -> validation -> index/txindex" - "init -> miner -> masternodes/govvariables/attributes -> masternodes/mn_rpc -> wallet/rpcwallet -> init" + "init -> masternodes/govvariables/attributes -> masternodes/mn_rpc -> wallet/rpcwallet -> init" "masternodes/accountshistory -> masternodes/masternodes -> masternodes/accountshistory" "masternodes/accountshistory -> masternodes/masternodes -> masternodes/mn_checks -> masternodes/accountshistory" "masternodes/accountshistory -> masternodes/masternodes -> validation -> masternodes/accountshistory"