From 0a54d5a03612be0304048716432d00d2d74b5398 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Wed, 6 Apr 2022 14:16:50 +0300 Subject: [PATCH] Live dex statistics Signed-off-by: Anthony Fieroni --- src/masternodes/consensus/loans.cpp | 4 +- src/masternodes/consensus/smartcontracts.cpp | 2 +- src/masternodes/govvariables/attributes.cpp | 19 +++++-- src/masternodes/govvariables/attributes.h | 53 ++++++++++++++++++-- src/masternodes/gv.cpp | 24 ++++++++- src/masternodes/mn_checks.cpp | 34 +++++++++++++ src/validation.cpp | 6 +-- test/functional/feature_poolswap.py | 43 ++++++++++++++-- 8 files changed, 169 insertions(+), 16 deletions(-) diff --git a/src/masternodes/consensus/loans.cpp b/src/masternodes/consensus/loans.cpp index cabaf734817..5f8c44f7ead 100644 --- a/src/masternodes/consensus/loans.cpp +++ b/src/masternodes/consensus/loans.cpp @@ -579,7 +579,7 @@ Res CLoansConsensus::operator()(const CLoanPaybackLoanV2Message& obj) const { balances.Add(CTokenAmount{loanTokenId, subAmount}); balances.Add(CTokenAmount{paybackTokenId, penalty}); - attributes->attributes[liveKey] = balances; + attributes->SetValue(liveKey, std::move(balances)); LogPrint(BCLog::LOAN, "CLoanPaybackLoanMessage(): Burning interest and loan in %s directly - total loan %lld (%lld %s), height - %d\n", paybackToken->symbol, subLoan + subInterest, subInToken, paybackToken->symbol, height); @@ -592,7 +592,7 @@ Res CLoansConsensus::operator()(const CLoanPaybackLoanV2Message& obj) const { balances.tokensPayback.Add(CTokenAmount{loanTokenId, subAmount}); balances.tokensFee.Add(CTokenAmount{paybackTokenId, penalty}); - attributes->attributes[liveKey] = balances; + attributes->SetValue(liveKey, balances); LogPrint(BCLog::LOAN, "CLoanPaybackLoanMessage(): Swapping %s to DFI and burning it - total loan %lld (%lld %s), height - %d\n", paybackToken->symbol, subLoan + subInterest, subInToken, paybackToken->symbol, height); diff --git a/src/masternodes/consensus/smartcontracts.cpp b/src/masternodes/consensus/smartcontracts.cpp index 88872318e17..ac0db808e97 100644 --- a/src/masternodes/consensus/smartcontracts.cpp +++ b/src/masternodes/consensus/smartcontracts.cpp @@ -183,7 +183,7 @@ Res CSmartContractsConsensus::operator()(const CFutureSwapMessage& obj) const { balances.Add(obj.source); } - attributes->attributes[liveKey] = balances; + attributes->SetValue(liveKey, std::move(balances)); return mnview.SetVariable(*attributes); } diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index 8f09b1ad53d..04d1dae1e6b 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -170,6 +170,7 @@ const std::map>& ATTRIBUTES::displayKeys {EconomyKeys::DFIP2203Current, "dfip2203_current"}, {EconomyKeys::DFIP2203Burned, "dfip2203_burned"}, {EconomyKeys::DFIP2203Minted, "dfip2203_minted"}, + {EconomyKeys::DexTokens, "dex"}, } }, }; @@ -491,7 +492,7 @@ Res ATTRIBUTES::RefundFuturesContracts(CCustomCSView &mnview, const uint32_t hei } } - attributes[liveKey] = balances; + SetValue(liveKey, std::move(balances)); return Res::Ok(); } @@ -519,11 +520,11 @@ Res ATTRIBUTES::Import(const UniValue & val) { } else { newAttr.key = TokenKeys::PaybackDFIFeePCT; } - attributes[newAttr] = attrValue; + SetValue(newAttr, attrValue); return Res::Ok(); } } - attributes[attribute] = attrValue; + SetValue(attribute, attrValue); return Res::Ok(); } ); @@ -581,6 +582,18 @@ UniValue ATTRIBUTES::Export() const { result.pushKV("paybackfees", AmountsToJSON(paybacks->tokensFee.balances)); result.pushKV("paybacktokens", AmountsToJSON(paybacks->tokensPayback.balances)); ret.pushKV(key, result); + } else if (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)); + } } } catch (const std::out_of_range&) { // Should not get here, that's mean maps are mismatched diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 22ef9e0084d..94f227b8fbf 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -37,6 +37,7 @@ enum EconomyKeys : uint8_t { DFIP2203Current = 'c', DFIP2203Burned = 'd', DFIP2203Minted = 'e', + DexTokens = 'f', }; enum DFIPKeys : uint8_t { @@ -111,8 +112,38 @@ struct CTokenPayback { } }; +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); + } +}; + +using CDexBalances = std::map; using CAttributeType = std::variant; -using CAttributeValue = std::variant; +using CAttributeValue = std::variant; class ATTRIBUTES : public GovVariable, public AutoRegistrator { @@ -150,6 +181,21 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator + void SetValue(const K& key, T&& value) { + static_assert(std::is_convertible_v); + static_assert(std::is_convertible_v); + changed.insert(key); + attributes[key] = std::forward(value); + } + + template + void EraseKey(const K& key) { + static_assert(std::is_convertible_v); + changed.insert(key); + attributes.erase(key); + } + template [[nodiscard]] bool CheckKey(const K& key) const { static_assert(std::is_convertible_v); @@ -177,16 +223,17 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator attributes; uint32_t time{0}; - // For formatting in export static const std::map& displayVersions(); static const std::map& displayTypes(); static const std::map& displayParamsIDs(); static const std::map>& displayKeys(); private: + friend class CGovView; bool futureBlockUpdated{}; + std::set changed; + std::map attributes; // Defined allowed arguments static const std::map& allowedVersions(); diff --git a/src/masternodes/gv.cpp b/src/masternodes/gv.cpp index 345d0df54ab..4b992a6e322 100644 --- a/src/masternodes/gv.cpp +++ b/src/masternodes/gv.cpp @@ -15,7 +15,29 @@ Res CGovView::SetVariable(GovVariable const & var) { - return WriteBy(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/mn_checks.cpp b/src/masternodes/mn_checks.cpp index c9e8059f94a..d8a47447014 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -704,6 +705,14 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo mnview.Flush(); } + auto attributes = view.GetAttributes(); + if (!attributes) { + attributes = std::make_shared(); + } + + 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}; @@ -745,6 +754,18 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo 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, [&] (const CTokenAmount& dexfeeInAmount, const CTokenAmount& tokenAmount) { // Save swap amount for next loop @@ -792,6 +813,7 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo if (!res) { return res; } + totalTokenA.feeburn += dexfeeInAmount.nValue; } // burn the dex out amount @@ -800,6 +822,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; @@ -820,6 +850,10 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo } } + if (!testOnly) { + 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 56b626d8314..1e5ad63a9e5 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3403,11 +3403,11 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache cache.EraseFuturesUserValues(key); } - attributes->attributes[burnKey] = burned; - attributes->attributes[mintedKey] = minted; + attributes->SetValue(burnKey, std::move(burned)); + attributes->SetValue(mintedKey, std::move(minted)); if (!unpaidContracts.empty()) { - attributes->attributes[liveKey] = balances; + attributes->SetValue(liveKey, std::move(balances)); } cache.SetVariable(*attributes); diff --git a/test/functional/feature_poolswap.py b/test/functional/feature_poolswap.py index 7566a205ff2..dbe1cb31975 100755 --- a/test/functional/feature_poolswap.py +++ b/test/functional/feature_poolswap.py @@ -196,6 +196,11 @@ def run_test(self): # 7 Sync self.sync_blocks([self.nodes[0], self.nodes[2]]) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + # silver is tokenB + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(idGS)], Decimal('9.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(idGS)], Decimal('1.0')) + # 8 Checking that poolswap is correct goldCheckN0 = self.nodes[2].getaccount(accountGN0, {}, True)[idGold] silverCheckN0 = self.nodes[2].getaccount(accountGN0, {}, True)[idSilver] @@ -253,6 +258,10 @@ def run_test(self): ) self.nodes[0].generate(1) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(idGS)], Decimal('189.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(idGS)], Decimal('21.0')) + maxPrice = self.nodes[0].listpoolpairs()['1']['reserveB/reserveA'] # exchange tokens each other should work self.nodes[0].poolswap({ @@ -274,6 +283,12 @@ def run_test(self): }) self.nodes[0].generate(1) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_a'%(idGS)], Decimal('180.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_a'%(idGS)], Decimal('20.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(idGS)], Decimal('369.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(idGS)], Decimal('41.0')) + # Test fort canning max price change disconnect_nodes(self.nodes[0], 1) disconnect_nodes(self.nodes[0], 2) @@ -390,6 +405,12 @@ def run_test(self): }) self.nodes[0].generate(1) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_a'%(idBL)], Decimal('0.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_a'%(idBL)], Decimal('0.0')) + assert_equal(attributes['v0/live/economy/dex/%s/total_swap_b'%(idBL)], Decimal('0.00000189')) + assert_equal(attributes['v0/live/economy/dex/%s/total_commission_b'%(idBL)], Decimal('1E-8')) + assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000002')) # Reset swap and move to Fort Canning Park Height and try swap again @@ -428,7 +449,9 @@ def run_test(self): 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'}}) 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'%(idGS)], '0.05') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(idGS)], '0.08') result = self.nodes[0].getpoolpair(idGS) assert_equal(result[idGS]['dexFeePctTokenA'], Decimal('0.05')) @@ -461,11 +484,17 @@ def run_test(self): 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'%(idGS)], dexinfee) + assert_equal(attributes['v0/live/economy/dex/%s/fee_burn_b'%(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'}}) 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'%(idGS)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(idGS)], '0.01') self.nodes[0].updatepoolpair({"pool": "GS", "commission": 0.01}) self.nodes[0].generate(1) @@ -489,7 +518,11 @@ def run_test(self): 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].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'%(idGS)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(idGS)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_a_fee_pct'%(idBL)], '0.01') + assert_equal(attributes['v0/poolpairs/%s/token_b_fee_pct'%(idBL)], '0.01') self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) self.nodes[0].clearmempool() @@ -528,6 +561,10 @@ def run_test(self): dexoutfee = round(trunc(amountA * Decimal(0.05) * coin) / coin, 8) assert_equal(round(amountA - Decimal(dexoutfee), 8), round(swapped, 8)) + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/live/economy/dex/%s/fee_burn_b'%(idBL)], round(dexinfee, 8)) + assert_equal(attributes['v0/live/economy/dex/%s/fee_burn_a'%(idBL)], Decimal(str(round(dexoutfee, 8)))) + # REVERTING: #======================== print ("Reverting...")