From ab54a80469e265db5417c467a4ee130f950ef090 Mon Sep 17 00:00:00 2001 From: jouzo Date: Sun, 18 Sep 2022 22:04:14 +0100 Subject: [PATCH 1/6] Fix testpoolswap --- src/masternodes/rpc_poolpair.cpp | 110 ++++++++++--------------------- 1 file changed, 35 insertions(+), 75 deletions(-) diff --git a/src/masternodes/rpc_poolpair.cpp b/src/masternodes/rpc_poolpair.cpp index e32655dce02..bcbec590417 100644 --- a/src/masternodes/rpc_poolpair.cpp +++ b/src/masternodes/rpc_poolpair.cpp @@ -1082,100 +1082,60 @@ UniValue testpoolswap(const JSONRPCRequest& request) { LOCK(cs_main); CCustomCSView mnview_dummy(*pcustomcsview); // create dummy cache for test state writing - int targetHeight = ::ChainActive().Height() + 1; - + uint32_t targetHeight = ::ChainActive().Height() + 1; + auto poolSwap = CPoolSwap({poolSwapMsg, targetHeight}); + std::vector poolIds; auto poolPair = mnview_dummy.GetPoolPair(poolSwapMsg.idTokenFrom, poolSwapMsg.idTokenTo); + if (poolPair && poolPair->second.status && path == "auto") path = "direct"; // If no direct swap found search for composite swap if (path == "direct") { if (!poolPair) - throw JSONRPCError(RPC_INVALID_REQUEST, std::string{"Direct pool pair not found. Use 'auto' mode to use composite swap."}); - - if (poolSwapMsg.amountFrom <= 0) - throw JSONRPCError(RPC_INVALID_REQUEST, "Input amount should be positive"); - - CPoolPair pp = poolPair->second; - - if (mnview_dummy.AreTokensLocked({pp.idTokenA.v, pp.idTokenB.v})) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Pool currently disabled due to locked token"); - } - - auto dexfeeInPct = mnview_dummy.GetDexFeeInPct(poolPair->first, poolSwapMsg.idTokenFrom); - - const auto attributes = mnview_dummy.GetAttributes(); - assert(attributes); - - CDataStructureV0 dirAKey{AttributeTypes::Poolpairs, poolPair->first.v, PoolKeys::TokenAFeeDir}; - CDataStructureV0 dirBKey{AttributeTypes::Poolpairs, poolPair->first.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); + throw JSONRPCError(RPC_INVALID_REQUEST, "Direct pool pair not found. Use 'auto' mode to use composite swap."); - res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, dexfeeInPct, poolSwapMsg.maxPrice, asymmetricFee, [&] (const CTokenAmount &, const CTokenAmount &tokenAmount) { - auto resPP = mnview_dummy.SetPoolPair(poolPair->first, targetHeight, pp); - if (!resPP) { - return resPP; - } - - auto resultAmount = tokenAmount; - auto dexfeeOutPct = mnview_dummy.GetDexFeeOutPct(poolPair->first, tokenAmount.nTokenId); - if (dexfeeOutPct > 0) { - auto dexfeeOutAmount = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct); - resultAmount.nValue -= dexfeeOutAmount; - } - - return Res::Ok(resultAmount.ToString()); - }, targetHeight); - - if (!res) - throw JSONRPCError(RPC_VERIFY_ERROR, res.msg); - - pools.push_back(poolPair->first.ToString()); + poolIds.push_back(poolPair->first); + } else if (path == "auto" || path == "composite") { + poolIds = poolSwap.CalculateSwaps(mnview_dummy, true); } else { - auto compositeSwap = CPoolSwap(poolSwapMsg, targetHeight); + path = "custom"; - std::vector poolIds; - if (path == "auto" || path == "composite") { - poolIds = compositeSwap.CalculateSwaps(mnview_dummy, true); + UniValue poolArray(UniValue::VARR); + if (request.params[1].isArray()) { + poolArray = request.params[1].get_array(); } else { - path = "custom"; + poolArray.read(request.params[1].getValStr().c_str()); + } - UniValue poolArray(UniValue::VARR); - if (request.params[1].isArray()) { - poolArray = request.params[1].get_array(); - } else { - poolArray.read(request.params[1].getValStr().c_str()); - } + for (const auto& id : poolArray.getValues()) { + poolIds.push_back(DCT_ID::FromString(id.getValStr())); + } - for (const auto& id : poolArray.getValues()) { - poolIds.push_back(DCT_ID::FromString(id.getValStr())); - } + auto availablePaths = poolSwap.CalculatePoolPaths(mnview_dummy); - auto availablePaths = compositeSwap.CalculatePoolPaths(mnview_dummy); - if (std::find(availablePaths.begin(), availablePaths.end(), poolIds) == availablePaths.end()) { - throw JSONRPCError(RPC_INVALID_REQUEST, "Custom pool path is invalid."); - } + if (std::find(availablePaths.begin(), availablePaths.end(), poolIds) == availablePaths.end()) { + throw JSONRPCError(RPC_INVALID_REQUEST, "Custom pool path is invalid."); } + } - res = compositeSwap.ExecuteSwap(mnview_dummy, poolIds, true); - if (!res) { - std::string errorMsg{"Cannot find usable pool pair."}; - if (!compositeSwap.errors.empty()) { - errorMsg += " Details: ("; - for (size_t i{0}; i < compositeSwap.errors.size(); ++i) { - errorMsg += '"' + compositeSwap.errors[i].first + "\":\"" + compositeSwap.errors[i].second + '"' + (i + 1 < compositeSwap.errors.size() ? "," : ""); - } - errorMsg += ')'; + res = poolSwap.ExecuteSwap(mnview_dummy, poolIds, true); + if (!res) { + std::string errorMsg{"Cannot find usable pool pair."}; + if (!poolSwap.errors.empty()) { + errorMsg += " Details: ("; + for (size_t i{0}; i < poolSwap.errors.size(); ++i) { + errorMsg += '"' + poolSwap.errors[i].first + "\":\"" + poolSwap.errors[i].second + '"' + (i + 1 < poolSwap.errors.size() ? "," : ""); } - throw JSONRPCError(RPC_INVALID_REQUEST, errorMsg); - } - for (const auto& id : poolIds) { - pools.push_back(id.ToString()); + errorMsg += ')'; } - res.msg = compositeSwap.GetResult().ToString(); + throw JSONRPCError(RPC_INVALID_REQUEST, errorMsg); } + for (const auto& id : poolIds) { + pools.push_back(id.ToString()); + } + res.msg = poolSwap.GetResult().ToString(); } + if (verbose) { UniValue swapObj{UniValue::VOBJ}; swapObj.pushKV("path", path); From e251d0f25a5f50cd5c4bb7f555f870cafdea88a8 Mon Sep 17 00:00:00 2001 From: jouzo Date: Sun, 18 Sep 2022 22:04:39 +0100 Subject: [PATCH 2/6] Fix CalculatePoolPaths when direct path exists --- src/masternodes/mn_checks.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 2689a05f1f7..a0edafac55d 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -4097,9 +4097,15 @@ std::vector CPoolSwap::CalculateSwaps(CCustomCSView& view, bool testOnly std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& view) { + DCT_ID directPool{0}; // For tokens to be traded get all pairs and pool IDs std::multimap fromPoolsID, toPoolsID; view.ForEachPoolPair([&](DCT_ID const & id, const CPoolPair& pool) { + if ((obj.idTokenFrom == pool.idTokenA && obj.idTokenTo == pool.idTokenB) + || (obj.idTokenTo == pool.idTokenA && obj.idTokenFrom == pool.idTokenB)) { + directPool = id; + } + if (pool.idTokenA == obj.idTokenFrom) { fromPoolsID.emplace(pool.idTokenB.v, id); } else if (pool.idTokenB == obj.idTokenFrom) { @@ -4118,6 +4124,10 @@ std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& vi return {}; } + if (directPool != DCT_ID{0}) { + return {{directPool}}; + } + // Find intersection on key std::map commonPairs; set_intersection(fromPoolsID.begin(), fromPoolsID.end(), toPoolsID.begin(), toPoolsID.end(), @@ -4129,7 +4139,6 @@ std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& vi // Loop through all common pairs and record direct pool to pool swaps std::vector> poolPaths; for (const auto& item : commonPairs) { - // Loop through all source/intermediate pools matching common pairs const auto poolFromIDs = fromPoolsID.equal_range(item.first); for (auto fromID = poolFromIDs.first; fromID != poolFromIDs.second; ++fromID) { @@ -4137,7 +4146,6 @@ std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& vi // Loop through all destination pools matching common pairs const auto poolToIDs = toPoolsID.equal_range(item.first); for (auto toID = poolToIDs.first; toID != poolToIDs.second; ++toID) { - // Add to pool paths poolPaths.push_back({fromID->second, toID->second}); } @@ -4533,8 +4541,8 @@ Res PaybackWithCollateral(CCustomCSView& view, const CVaultData& vault, const CV burnAmount = subCollateralAmount; } else { - // Postive interest: Loan + interest > collateral. - // Negative interest: Loan - abs(interest) > collateral. + // Postive interest: Loan + interest > collateral. + // Negative interest: Loan - abs(interest) > collateral. if (loanDUSD + subInterest > collateralDUSD) { subLoanAmount = collateralDUSD - subInterest; subCollateralAmount = collateralDUSD; @@ -4544,7 +4552,7 @@ Res PaybackWithCollateral(CCustomCSView& view, const CVaultData& vault, const CV subCollateralAmount = loanDUSD + subInterest; } - if (subLoanAmount > 0) { + if (subLoanAmount > 0) { res = view.SubLoanToken(vaultId, {dUsdToken->first, subLoanAmount}); if (!res) return res; } @@ -4575,9 +4583,9 @@ Res PaybackWithCollateral(CCustomCSView& view, const CVaultData& vault, const CV if (!collateralsLoans) return std::move(collateralsLoans); - // The check is required to do a ratio check safe guard, or the vault of ratio is unreliable. + // The check is required to do a ratio check safe guard, or the vault of ratio is unreliable. // This can later be removed, if all edge cases of price deviations and max collateral factor for DUSD (1.5 currently) - // can be tested for economical stability. Taking the safer approach for now. + // can be tested for economical stability. Taking the safer approach for now. if (!IsVaultPriceValid(view, vaultId, height)) return Res::Err("Cannot payback vault with non-DUSD assets while any of the asset's price is invalid"); From 476c524d6aa583607c12f85c95bf4d41cb5cf778 Mon Sep 17 00:00:00 2001 From: jouzo Date: Sun, 18 Sep 2022 22:04:48 +0100 Subject: [PATCH 3/6] Tests for testpoolswap --- test/functional/feature_testpoolswap.py | 315 ++++++++++++++++++++++++ test/functional/test_runner.py | 1 + 2 files changed, 316 insertions(+) create mode 100755 test/functional/feature_testpoolswap.py diff --git a/test/functional/feature_testpoolswap.py b/test/functional/feature_testpoolswap.py new file mode 100755 index 00000000000..79166587daa --- /dev/null +++ b/test/functional/feature_testpoolswap.py @@ -0,0 +1,315 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test poolpair - testpoolswap.""" + +from test_framework.test_framework import DefiTestFramework + +from test_framework.util import assert_equal +import calendar +import time + +class PoolPairTestPoolSwapTest (DefiTestFramework): + + def set_test_params(self): + self.num_nodes = 1 + self.setup_clean_chain = True + self.extra_args = [[ + '-txnotokens=0', + '-amkheight=1', + '-bayfrontheight=1', + '-eunosheight=1', + '-fortcanningheight=1', + '-fortcanninghillheight=1', + '-fortcanningspringheight=1', + '-jellyfish_regtest=1', + '-simulatemainnet=1' + ]] + + def createOracles(self): + self.oracle_address1 = self.nodes[0].getnewaddress("", "legacy") + price_feeds = [{"currency": "USD", "token": "DFI"}, + {"currency": "USD", "token": "DUSD"}, + {"currency": "USD", "token": "TSLA"}, + {"currency": "USD", "token": "BTC"}] + self.oracle_id1 = self.nodes[0].appointoracle(self.oracle_address1, price_feeds, 10) + self.nodes[0].generate(1) + + # feed oracle + oracle_prices = [{"currency": "USD", "tokenAmount": "1@TSLA"}, + {"currency": "USD", "tokenAmount": "1@DUSD"}, + {"currency": "USD", "tokenAmount": "1@BTC"}, + {"currency": "USD", "tokenAmount": "10@DFI"}] + + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(self.oracle_id1, timestamp, oracle_prices) + + self.oracle_address2 = self.nodes[0].getnewaddress("", "legacy") + self.oracle_id2 = self.nodes[0].appointoracle(self.oracle_address2, price_feeds, 10) + self.nodes[0].generate(1) + + # feed oracle + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(self.oracle_id2, timestamp, oracle_prices) + self.nodes[0].generate(120) + + def setup(self): + self.nodes[0].generate(120) + + self.createOracles() + + self.mn_address = self.nodes[0].get_genesis_keys().ownerAuthAddress + self.account0 = self.nodes[0].getnewaddress() + + self.swapAmount = 1000 + + self.symbolDFI = "DFI" + self.symbolTSLA = "TSLA" + self.symbolDUSD = "DUSD" + self.symbolBTC = "BTC" + + self.nodes[0].setloantoken({ + 'symbol': "DUSD", + 'name': "DUSD", + 'fixedIntervalPriceId': "DUSD/USD", + 'mintable': True, + 'interest': 0 + }) + + self.nodes[0].setloantoken({ + 'symbol': "TSLA", + 'name': "TSLA", + 'fixedIntervalPriceId': "TSLA/USD", + 'mintable': True, + 'interest': 0 + }) + + self.nodes[0].setloantoken({ + 'symbol': "BTC", + 'name': "BTC", + 'fixedIntervalPriceId': "BTC/USD", + 'mintable': True, + 'interest': 0 + }) + self.nodes[0].generate(120) + + # Mint DUSD + self.nodes[0].minttokens("100000@DUSD") + self.nodes[0].minttokens("100000@TSLA") + self.nodes[0].minttokens("100000@BTC") + self.nodes[0].generate(1) + + # Create DFI tokens + self.nodes[0].utxostoaccount({self.mn_address: "100000@" + self.symbolDFI}) + self.nodes[0].generate(1) + + # Create pool pair + self.nodes[0].createpoolpair({ + "tokenA": self.symbolDUSD, + "tokenB": self.symbolDFI, + "commission": 0, + "status": True, + "ownerAddress": self.mn_address + }) + + self.nodes[0].createpoolpair({ + "tokenA": self.symbolDFI, + "tokenB": self.symbolTSLA, + "commission": 0, + "status": True, + "ownerAddress": self.mn_address + }) + + self.nodes[0].createpoolpair({ + "tokenA": self.symbolBTC, + "tokenB": self.symbolTSLA, + "commission": 0, + "status": True, + "ownerAddress": self.mn_address + }) + self.nodes[0].generate(1) + + self.DUSD_DFIPoolID = list(self.nodes[0].getpoolpair("DUSD-DFI"))[0] + self.DFI_TSLAPoolID = list(self.nodes[0].getpoolpair("DFI-TSLA"))[0] + self.BTC_TSLAPoolID = list(self.nodes[0].getpoolpair("BTC-TSLA"))[0] + + # Add pool liquidity + self.nodes[0].addpoolliquidity({ + self.mn_address: [ + '10000@' + self.symbolDFI, + '10000@' + self.symbolDUSD] + }, self.mn_address) + self.nodes[0].addpoolliquidity({ + self.mn_address: [ + '10000@' + self.symbolTSLA, + '10000@' + self.symbolDFI] + }, self.mn_address) + self.nodes[0].addpoolliquidity({ + self.mn_address: [ + '10000@' + self.symbolTSLA, + '10000@' + self.symbolBTC] + }, self.mn_address) + self.nodes[0].generate(1) + + self.nodes[0].accounttoaccount(self.mn_address, {self.account0: str(self.swapAmount * 2) + "@" + self.symbolDFI}) + self.nodes[0].accounttoaccount(self.mn_address, {self.account0: str(self.swapAmount * 2) + "@" + self.symbolTSLA}) + self.nodes[0].accounttoaccount(self.mn_address, {self.account0: str(self.swapAmount * 2) + "@" + self.symbolBTC}) + self.nodes[0].generate(1) + + def assert_testpoolswap_amount(self, swap_fn, tokenFrom, path): + params = { + "from": self.account0, + "tokenFrom": tokenFrom, + "amountFrom": self.swapAmount, + "to": self.account0, + "tokenTo": self.symbolDUSD, + } + [amountSwapped, _] = self.nodes[0].testpoolswap(params, path).split("@") + + swap_fn(params) + self.nodes[0].generate(1) + + account = self.nodes[0].getaccount(self.account0) + assert_equal(account[2], f'{amountSwapped}@{self.symbolDUSD}') + + def test_testpoolswap_no_fee(self, swap_fn, tokenFrom, path): + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_a_fee_in(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_direction': 'in' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_b_fee_in(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_direction': 'in' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_a_fee_out(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_direction': 'out' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_b_fee_out(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_direction': 'out' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_a_fee_in_token_b_fee_in(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_direction': 'in', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_direction': 'in' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_a_fee_in_token_b_fee_out(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_direction': 'in', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_direction': 'out' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_a_fee_out_token_b_fee_in(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_direction': 'out', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_direction': 'in' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_token_a_fee_out_token_b_fee_out(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_direction': 'out', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_b_fee_direction': 'out' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def test_testpoolswap_with_multi_pool_fee(self, swap_fn, tokenFrom, path): + self.nodes[0].setgov({"ATTRIBUTES":{ + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DUSD_DFIPoolID}/token_a_fee_direction': 'in', + f'v0/poolpairs/{self.DFI_TSLAPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.DFI_TSLAPoolID}/token_a_fee_direction': 'out', + f'v0/poolpairs/{self.BTC_TSLAPoolID}/token_a_fee_pct': '0.10', + f'v0/poolpairs/{self.BTC_TSLAPoolID}/token_a_fee_direction': 'out' + }}) + self.nodes[0].generate(1) + self.assert_testpoolswap_amount(swap_fn, tokenFrom, path) + + def run_test(self): + self.setup() + + # List of (swap, tokenFrom, path) tuples + # swap should be one of poolswap or compositeswap RPC call + # path should be one of ["direct", "auto", "composite"] or a list of pool ids + combinations = [ + # poolswap DFI -> DUSD through DFI-DUSD pool + (self.nodes[0].poolswap, self.symbolDFI, "direct"), + (self.nodes[0].poolswap, self.symbolDFI, "auto"), + (self.nodes[0].poolswap, self.symbolDFI, "composite"), + (self.nodes[0].poolswap, self.symbolDFI, [str(self.DUSD_DFIPoolID)]), + + # compositeswap DFI -> DUSD through DFI-DUSD pool + (self.nodes[0].compositeswap, self.symbolDFI, "direct"), + (self.nodes[0].compositeswap, self.symbolDFI, "auto"), + (self.nodes[0].compositeswap, self.symbolDFI, "composite"), + (self.nodes[0].compositeswap, self.symbolDFI, [str(self.DUSD_DFIPoolID)]), + + # comspositeswap TSLA -> DUSD swap through TSLA-DFI -> DFI-DUSD pools + (self.nodes[0].compositeswap, self.symbolTSLA, "auto"), + (self.nodes[0].compositeswap, self.symbolTSLA, "composite"), + (self.nodes[0].compositeswap, self.symbolTSLA, [str(self.DFI_TSLAPoolID), str(self.DUSD_DFIPoolID)]), + + # comspositeswap BTC -> DUSD swap through BTC-TSLA -> TSLA-DFI -> DFI-DUSD pools + (self.nodes[0].compositeswap, self.symbolBTC, "auto"), + (self.nodes[0].compositeswap, self.symbolBTC, "composite"), + (self.nodes[0].compositeswap, self.symbolBTC, [str(self.BTC_TSLAPoolID), str(self.DFI_TSLAPoolID), str(self.DUSD_DFIPoolID)]), + ] + + testCases = [ + self.test_testpoolswap_no_fee, + self.test_testpoolswap_with_token_a_fee_in, + self.test_testpoolswap_with_token_b_fee_in, + self.test_testpoolswap_with_token_a_fee_out, + self.test_testpoolswap_with_token_b_fee_out, + self.test_testpoolswap_with_token_a_fee_in_token_b_fee_in, + self.test_testpoolswap_with_token_a_fee_out_token_b_fee_in, + self.test_testpoolswap_with_token_a_fee_in_token_b_fee_out, + self.test_testpoolswap_with_token_a_fee_out_token_b_fee_out, + self.test_testpoolswap_with_multi_pool_fee + ] + + height = self.nodes[0].getblockcount() + + for (swap_fn, tokenFrom, path) in combinations: + for test in testCases: + test(swap_fn, tokenFrom, path) + self.rollback_to(height) + +if __name__ == '__main__': + PoolPairTestPoolSwapTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 559fe773422..e397fee8b98 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -298,6 +298,7 @@ 'feature_burn_address.py', 'feature_eunos_balances.py', 'feature_sendutxosfrom.py', + 'feature_testpoolswap.py', 'feature_update_mn.py', 'feature_block_reward.py', 'feature_negative_interest.py', From 93a6430c497cfd412755d99d58920bb59134c554 Mon Sep 17 00:00:00 2001 From: jouzo Date: Tue, 20 Sep 2022 12:06:13 +0100 Subject: [PATCH 4/6] Remove testpoolswap tests from feature_poolswap_composite.py --- test/functional/feature_poolswap_composite.py | 111 ------------------ 1 file changed, 111 deletions(-) diff --git a/test/functional/feature_poolswap_composite.py b/test/functional/feature_poolswap_composite.py index 6b74ad83b03..86a94fdae08 100755 --- a/test/functional/feature_poolswap_composite.py +++ b/test/functional/feature_poolswap_composite.py @@ -244,115 +244,6 @@ def run_test(self): }, collateral, []) self.nodes[0].generate(1) - estimateCompositePathsRes = self.nodes[0].testpoolswap({ - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": ltc_to_doge_from, - "to": destination, - "tokenTo": symbolDOGE, - }, "auto", True) - - assert_equal(estimateCompositePathsRes['path'], 'auto') - - poolLTC_USDC = list(self.nodes[0].getpoolpair("LTC-USDC").keys())[0] - poolDOGE_USDC = list(self.nodes[0].getpoolpair("DOGE-USDC").keys())[0] - assert_equal(estimateCompositePathsRes['pools'], [poolLTC_USDC, poolDOGE_USDC]) - - testCPoolSwapRes = self.nodes[0].testpoolswap({ - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": ltc_to_doge_from, - "to": destination, - "tokenTo": symbolDOGE, - }, "auto") - - testCPoolSwapRes = str(testCPoolSwapRes).split("@", 2) - - psTestAmount = testCPoolSwapRes[0] - psTestTokenId = testCPoolSwapRes[1] - assert_equal(psTestTokenId, idDOGE) - - customPathPoolSwap = self.nodes[0].testpoolswap({ - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": ltc_to_doge_from, - "to": destination, - "tokenTo": symbolDOGE, - }, [poolLTC_USDC, poolDOGE_USDC]) - - customPathPoolSwap = str(customPathPoolSwap).split("@", 2) - - psTestAmount = customPathPoolSwap[0] - psTestTokenId = customPathPoolSwap[1] - assert_equal(psTestTokenId, idDOGE) - - poolLTC_DFI = list(self.nodes[0].getpoolpair("LTC-DFI").keys())[0] - poolDOGE_DFI = list(self.nodes[0].getpoolpair("DOGE-DFI").keys())[0] - customPathPoolSwap = self.nodes[0].testpoolswap({ - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": ltc_to_doge_from, - "to": destination, - "tokenTo": symbolDOGE, - }, [poolLTC_DFI, poolDOGE_DFI]) - - customPathPoolSwap = str(customPathPoolSwap).split("@", 2) - - psTestTokenId = customPathPoolSwap[1] - assert_equal(psTestTokenId, idDOGE) - - estimateCompositePathsResDirect = self.nodes[0].testpoolswap({ - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": 1, - "to": destination, - "tokenTo": symbolDFI, - }, "direct", True) - - estimateCompositePathsResAuto = self.nodes[0].testpoolswap({ - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": 1, - "to": destination, - "tokenTo": symbolDFI, - }, "auto", True) - - assert_equal(estimateCompositePathsResAuto['path'], "direct") - assert_equal(estimateCompositePathsResDirect, estimateCompositePathsResAuto) - - estimateCompositePathsResComposite = self.nodes[0].testpoolswap({ - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": 1, - "to": destination, - "tokenTo": symbolDFI, - }, "composite", True) - - assert_equal(estimateCompositePathsResComposite['path'], "composite") - - poolLTC_USDC = list(self.nodes[0].getpoolpair("LTC-USDC").keys())[0] - poolDOGE_USDC = list(self.nodes[0].getpoolpair("DOGE-USDC").keys())[0] - poolDOGE_DFI = list(self.nodes[0].getpoolpair("DOGE-DFI").keys())[0] - assert_equal(estimateCompositePathsResComposite['pools'], [poolLTC_USDC, poolDOGE_USDC, poolDOGE_DFI]) - - assert_raises_rpc_error(-32600, "Cannot find usable pool pair.", self.nodes[0].testpoolswap, - { - "from": source, - "tokenFrom": symbolDUSD, - "amountFrom": 100, - "to": destination, - "tokenTo": symbolDFI, - }, "composite") - - assert_raises_rpc_error(-32600, "Custom pool path is invalid.", self.nodes[0].testpoolswap, - { - "from": source, - "tokenFrom": symbolLTC, - "amountFrom": ltc_to_doge_from, - "to": destination, - "tokenTo": symbolDOGE, - }, [poolLTC_DFI, "100"]) - self.nodes[0].compositeswap({ "from": source, "tokenFrom": symbolLTC, @@ -372,8 +263,6 @@ def run_test(self): dest_balance = self.nodes[0].getaccount(destination, {}, True) assert_equal(dest_balance[idDOGE], doge_received * 2) assert_equal(len(dest_balance), 1) - # Check test swap correctness - assert_equal(Decimal(psTestAmount), dest_balance[idDOGE]) # Set up addresses for swapping source = self.nodes[0].getnewaddress("", "legacy") From 9b5576c83fb9a2a3026615655925c70c3d916b8e Mon Sep 17 00:00:00 2001 From: jouzo Date: Tue, 20 Sep 2022 12:08:41 +0100 Subject: [PATCH 5/6] Add direct poolpair to poolPaths when available --- src/masternodes/mn_checks.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index a066fc9d631..d29a2c75829 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -4101,13 +4101,15 @@ std::vector CPoolSwap::CalculateSwaps(CCustomCSView& view, bool testOnly std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& view) { - DCT_ID directPool{0}; + std::vector> poolPaths; + // For tokens to be traded get all pairs and pool IDs std::multimap fromPoolsID, toPoolsID; view.ForEachPoolPair([&](DCT_ID const & id, const CPoolPair& pool) { if ((obj.idTokenFrom == pool.idTokenA && obj.idTokenTo == pool.idTokenB) || (obj.idTokenTo == pool.idTokenA && obj.idTokenFrom == pool.idTokenB)) { - directPool = id; + // Push poolId when direct path + poolPaths.push_back({{id}}); } if (pool.idTokenA == obj.idTokenFrom) { @@ -4128,10 +4130,6 @@ std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& vi return {}; } - if (directPool != DCT_ID{0}) { - return {{directPool}}; - } - // Find intersection on key std::map commonPairs; set_intersection(fromPoolsID.begin(), fromPoolsID.end(), toPoolsID.begin(), toPoolsID.end(), @@ -4141,7 +4139,6 @@ std::vector> CPoolSwap::CalculatePoolPaths(CCustomCSView& vi }); // Loop through all common pairs and record direct pool to pool swaps - std::vector> poolPaths; for (const auto& item : commonPairs) { // Loop through all source/intermediate pools matching common pairs const auto poolFromIDs = fromPoolsID.equal_range(item.first); From 6837c24e5569f3c2f2506d8b0e7bb91dad7749d6 Mon Sep 17 00:00:00 2001 From: jouzo Date: Tue, 20 Sep 2022 12:28:30 +0100 Subject: [PATCH 6/6] Fix lint --- test/functional/feature_poolswap_composite.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/functional/feature_poolswap_composite.py b/test/functional/feature_poolswap_composite.py index 86a94fdae08..25f8d932c44 100755 --- a/test/functional/feature_poolswap_composite.py +++ b/test/functional/feature_poolswap_composite.py @@ -68,7 +68,6 @@ def run_test(self): self.setup_tokens(tokens) disconnect_nodes(self.nodes[0], 1) - symbolDFI = "DFI" symbolDOGE = "DOGE#" + self.get_id_token("DOGE") symbolTSLA = "TSLA#" + self.get_id_token("TSLA") symbolDUSD = "DUSD#" + self.get_id_token("DUSD")