From 85da307fa2229cf60cc1b8a2ad41921c8d0d34e4 Mon Sep 17 00:00:00 2001 From: Anthony Fieroni Date: Wed, 23 Feb 2022 13:53:57 +0200 Subject: [PATCH] Expand dex fee capability Signed-off-by: Anthony Fieroni --- src/masternodes/govvariables/attributes.cpp | 324 +++++++++++++------- src/masternodes/govvariables/attributes.h | 91 +----- src/masternodes/mn_checks.cpp | 11 +- src/masternodes/poolpairs.cpp | 27 +- src/masternodes/poolpairs.h | 3 +- src/masternodes/rpc_poolpair.cpp | 23 +- test/functional/feature_poolswap.py | 62 +++- test/functional/feature_setgov.py | 23 +- 8 files changed, 332 insertions(+), 232 deletions(-) diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index b7ef1c88eb..676fda2b86 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -33,6 +33,113 @@ static std::vector KeyBreaker(const std::string& str){ return strVec; } +const std::map& ATTRIBUTES::allowedVersions() { + static const std::map versions{ + {"v0", VersionTypes::v0}, + }; + return versions; +} + +const std::map& ATTRIBUTES::displayVersions() { + static const std::map versions{ + {VersionTypes::v0, "v0"}, + }; + return versions; +} + +const std::map& ATTRIBUTES::allowedTypes() { + static const std::map types{ + {"params", AttributeTypes::Param}, + {"poolpairs", AttributeTypes::Poolpairs}, + {"token", AttributeTypes::Token}, + }; + return types; +} + +const std::map& ATTRIBUTES::displayTypes() { + static const std::map types{ + {AttributeTypes::Live, "live"}, + {AttributeTypes::Param, "params"}, + {AttributeTypes::Poolpairs, "poolpairs"}, + {AttributeTypes::Token, "token"}, + }; + return types; +} + +const std::map& ATTRIBUTES::allowedParamIDs() { + static const std::map params{ + {"dfip2201", ParamIDs::DFIP2201} + }; + return params; +} + +const std::map& ATTRIBUTES::displayParamsIDs() { + static const std::map params{ + {ParamIDs::DFIP2201, "dfip2201"}, + {ParamIDs::Economy, "economy"}, + }; + return params; +} + +const std::map>& ATTRIBUTES::allowedKeys() { + static const std::map> keys{ + { + AttributeTypes::Token, { + {"payback_dfi", TokenKeys::PaybackDFI}, + {"payback_dfi_fee_pct", TokenKeys::PaybackDFIFeePCT}, + {"dex_in_fee_pct", TokenKeys::DexInFeePct}, + {"dex_out_fee_pct", TokenKeys::DexOutFeePct}, + } + }, + { + AttributeTypes::Poolpairs, { + {"token_a_fee_pct", PoolKeys::TokenAFeePCT}, + {"token_b_fee_pct", PoolKeys::TokenBFeePCT}, + } + }, + { + AttributeTypes::Param, { + {"active", DFIP2201Keys::Active}, + {"minswap", DFIP2201Keys::MinSwap}, + {"premium", DFIP2201Keys::Premium}, + } + }, + }; + return keys; +} + +const std::map>& ATTRIBUTES::displayKeys() { + static const std::map> keys{ + { + AttributeTypes::Token, { + {TokenKeys::PaybackDFI, "payback_dfi"}, + {TokenKeys::PaybackDFIFeePCT, "payback_dfi_fee_pct"}, + {TokenKeys::DexInFeePct, "dex_in_fee_pct"}, + {TokenKeys::DexOutFeePct, "dex_out_fee_pct"}, + } + }, + { + AttributeTypes::Poolpairs, { + {PoolKeys::TokenAFeePCT, "token_a_fee_pct"}, + {PoolKeys::TokenBFeePCT, "token_b_fee_pct"}, + } + }, + { + AttributeTypes::Param, { + {DFIP2201Keys::Active, "active"}, + {DFIP2201Keys::Premium, "premium"}, + {DFIP2201Keys::MinSwap, "minswap"}, + } + }, + { + AttributeTypes::Live, { + {EconomyKeys::PaybackDFITokens, "dfi_payback_tokens"}, + } + }, + }; + return keys; +} + static ResVal VerifyInt32(const std::string& str) { int32_t int32; if (!ParseInt32(str, &int32) || int32 < 0) { @@ -41,7 +148,7 @@ static ResVal VerifyInt32(const std::string& str) { return {int32, Res::Ok()}; } -static ResVal VerifyFloat(const std::string& str) { +static ResVal VerifyFloat(const std::string& str) { CAmount amount = 0; if (!ParseFixedPoint(str, 8, &amount) || amount < 0) { return Res::Err("Amount must be a positive value"); @@ -49,17 +156,54 @@ static ResVal VerifyFloat(const std::string& str) { return {amount, Res::Ok()}; } -static ResVal VerifyPct(const std::string& str) { +static ResVal VerifyPct(const std::string& str) { auto resVal = VerifyFloat(str); if (!resVal) { return resVal; } - if (*resVal.val > COIN) { + if (CAttributeValue{COIN} < *resVal.val) { return Res::Err("Percentage exceeds 100%%"); } return resVal; } +static ResVal VerifyBool(const std::string& str) { + if (str != "true" && str != "false") { + return Res::Err(R"(Boolean value must be either "true" or "false")"); + } + return {str == "true", Res::Ok()}; +} + +const std::map(const std::string&)>>>& ATTRIBUTES::parseValue() { + + static const std::map(const std::string&)>>> parsers{ + { + AttributeTypes::Token, { + {TokenKeys::PaybackDFI, VerifyBool}, + {TokenKeys::PaybackDFIFeePCT, VerifyPct}, + {TokenKeys::DexInFeePct, VerifyPct}, + {TokenKeys::DexOutFeePct, VerifyPct}, + } + }, + { + AttributeTypes::Poolpairs, { + {PoolKeys::TokenAFeePCT, VerifyPct}, + {PoolKeys::TokenBFeePCT, VerifyPct}, + } + }, + { + AttributeTypes::Param, { + {DFIP2201Keys::Active, VerifyBool}, + {DFIP2201Keys::Premium, VerifyPct}, + {DFIP2201Keys::MinSwap, VerifyFloat}, + } + }, + }; + return parsers; +} + static Res ShowError(const std::string& key, const std::map& keys) { std::string error{"Unrecognised " + key + " argument provided, valid " + key + "s are:"}; for (const auto& pair : keys) { @@ -84,8 +228,8 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value return Res::Err("Empty value"); } - auto iver = allowedVersions.find(keys[0]); - if (iver == allowedVersions.end()) { + auto iver = allowedVersions().find(keys[0]); + if (iver == allowedVersions().end()) { return Res::Err("Unsupported version"); } @@ -98,9 +242,9 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value return Res::Err("Incorrect key for . Object of ['//ID/','value'] expected"); } - auto itype = allowedTypes.find(keys[1]); - if (itype == allowedTypes.end()) { - return ::ShowError("type", allowedTypes); + auto itype = allowedTypes().find(keys[1]); + if (itype == allowedTypes().end()) { + return ::ShowError("type", allowedTypes()); } auto type = itype->second; @@ -111,19 +255,17 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value if (!id) { return std::move(id); } - typeId = *id.val; } else { - auto id = allowedParamIDs.find(keys[2]); - if (id == allowedParamIDs.end()) { - return ::ShowError("param", allowedParamIDs); + auto id = allowedParamIDs().find(keys[2]); + if (id == allowedParamIDs().end()) { + return ::ShowError("param", allowedParamIDs()); } - typeId = id->second; } - auto ikey = allowedKeys.find(type); - if (ikey == allowedKeys.end()) { + auto ikey = allowedKeys().find(type); + if (ikey == allowedKeys().end()) { return Res::Err("Unsupported type {%d}", type); } @@ -132,68 +274,18 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value return ::ShowError("key", ikey->second); } - UniValue univalue; auto typeKey = itype->second; - - CAttributeValue attribValue; - - if (type == AttributeTypes::Token) { - if (typeKey == TokenKeys::PaybackDFI) { - if (value != "true" && value != "false") { - return Res::Err("Payback DFI value must be either \"true\" or \"false\""); + try { + if (auto parser = parseValue().at(type).at(typeKey)) { + auto attribValue = parser(value); + if (!attribValue) { + return std::move(attribValue); } - attribValue = value == "true"; - } else if (typeKey == TokenKeys::PaybackDFIFeePCT) { - auto res = VerifyPct(value); - if (!res) { - return std::move(res); - } - attribValue = *res.val; - } else { - return Res::Err("Unrecognised key"); + return applyVariable(CDataStructureV0{type, typeId, typeKey}, *attribValue.val); } - } else if (type == AttributeTypes::Poolpairs) { - if (typeKey == PoolKeys::TokenAFeePCT - || typeKey == PoolKeys::TokenBFeePCT) { - auto res = VerifyPct(value); - if (!res) { - return std::move(res); - } - attribValue = *res.val; - } else { - return Res::Err("Unrecognised key"); - } - } else if (type == AttributeTypes::Param) { - if (typeId == ParamIDs::DFIP2201) { - if (typeKey == DFIP2201Keys::Active) { - if (value != "true" && value != "false") { - return Res::Err("DFIP2201 actve value must be either \"true\" or \"false\""); - } - attribValue = value == "true"; - } else if (typeKey == DFIP2201Keys::Premium) { - auto res = VerifyPct(value); - if (!res) { - return std::move(res); - } - attribValue = *res.val; - } else if (typeKey == DFIP2201Keys::MinSwap) { - auto res = VerifyFloat(value); - if (!res) { - return std::move(res); - } - attribValue = *res.val; - } else { - return Res::Err("Unrecognised key"); - } - } - } else { - return Res::Err("Unrecognised type"); + } catch (const std::out_of_range&) { } - - if (applyVariable) { - return applyVariable(CDataStructureV0{type, typeId, typeKey}, attribValue); - } - return Res::Ok(); + return Res::Err("No parse function {%d, %d}", type, typeKey); } Res ATTRIBUTES::Import(const UniValue & val) { @@ -233,13 +325,13 @@ UniValue ATTRIBUTES::Export() const { try { const auto id = attrV0->type == AttributeTypes::Param || attrV0->type == AttributeTypes::Live - ? displayParamsIDs.at(attrV0->typeId) + ? displayParamsIDs().at(attrV0->typeId) : KeyBuilder(attrV0->typeId); - auto key = KeyBuilder(displayVersions.at(VersionTypes::v0), - displayTypes.at(attrV0->type), + auto key = KeyBuilder(displayVersions().at(VersionTypes::v0), + displayTypes().at(attrV0->type), id, - displayKeys.at(attrV0->type).at(attrV0->key)); + displayKeys().at(attrV0->type).at(attrV0->key)); if (auto bool_val = std::get_if(&attribute.second)) { ret.pushKV(key, *bool_val ? "true" : "false"); @@ -267,40 +359,48 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const return Res::Err("Unsupported version"); } switch (attrV0->type) { - case AttributeTypes::Token: { - if (attrV0->key == TokenKeys::PaybackDFI - || attrV0->key == TokenKeys::PaybackDFIFeePCT) { - uint32_t tokenId = attrV0->typeId; - if (!view.GetLoanTokenByID(DCT_ID{tokenId})) { - return Res::Err("No such loan token (%d)", tokenId); - } - } else { - return Res::Err("Unsupported key"); + case AttributeTypes::Token: + switch (attrV0->key) { + case TokenKeys::PaybackDFI: + case TokenKeys::PaybackDFIFeePCT: + if (!view.GetLoanTokenByID({attrV0->typeId})) { + return Res::Err("No such loan token (%d)", attrV0->typeId); + } + break; + case TokenKeys::DexInFeePct: + case TokenKeys::DexOutFeePct: + if (view.GetLastHeight() < Params().GetConsensus().GreatWorldHeight) { + return Res::Err("Cannot be set before GreatWorld"); + } + if (!view.GetToken(DCT_ID{attrV0->typeId})) { + return Res::Err("No such token (%d)", attrV0->typeId); + } + break; + default: + return Res::Err("Unsupported key"); } - } break; - case AttributeTypes::Poolpairs: { + case AttributeTypes::Poolpairs: if (!std::get_if(&attribute.second)) { return Res::Err("Unsupported value"); } - if (attrV0->key == PoolKeys::TokenAFeePCT - || attrV0->key == PoolKeys::TokenBFeePCT) { - uint32_t poolId = attrV0->typeId; - if (!view.GetPoolPair(DCT_ID{poolId})) { - return Res::Err("No such pool (%d)", poolId); - } - } else { - return Res::Err("Unsupported key"); + switch (attrV0->key) { + case PoolKeys::TokenAFeePCT: + case PoolKeys::TokenBFeePCT: + if (!view.GetPoolPair({attrV0->typeId})) { + return Res::Err("No such pool (%d)", attrV0->typeId); + } + break; + default: + return Res::Err("Unsupported key"); } - } break; - case AttributeTypes::Param: { + case AttributeTypes::Param: if (attrV0->typeId != ParamIDs::DFIP2201) { return Res::Err("Unrecognised param id"); } - } break; // Live is set internally @@ -319,20 +419,34 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height) { for (const auto& attribute : attributes) { auto attrV0 = std::get_if(&attribute.first); - if (attrV0 && attrV0->type == AttributeTypes::Poolpairs) { - uint32_t poolId = attrV0->typeId; - auto pool = mnview.GetPoolPair(DCT_ID{poolId}); + if (!attrV0) { + continue; + } + if (attrV0->type == AttributeTypes::Poolpairs) { + auto poolId = DCT_ID{attrV0->typeId}; + auto pool = mnview.GetPoolPair(poolId); if (!pool) { - return Res::Err("No such pool (%d)", poolId); + return Res::Err("No such pool (%d)", poolId.v); } auto tokenId = attrV0->key == PoolKeys::TokenAFeePCT ? pool->idTokenA : pool->idTokenB; auto valuePct = std::get(attribute.second); - auto res = mnview.SetDexFeePct(DCT_ID{poolId}, tokenId, valuePct); - if (!res) { + if (auto res = mnview.SetDexFeePct(poolId, tokenId, valuePct); !res) { return res; } + } else if (attrV0->type == AttributeTypes::Token) { + if (attrV0->key == TokenKeys::DexInFeePct + || attrV0->key == TokenKeys::DexOutFeePct) { + DCT_ID tokenA{attrV0->typeId}, tokenB{~0u}; + if (attrV0->key == TokenKeys::DexOutFeePct) { + std::swap(tokenA, tokenB); + } + auto valuePct = std::get(attribute.second); + if (auto res = mnview.SetDexFeePct(tokenA, tokenB, valuePct); !res) { + return res; + } + } } } return Res::Ok(); diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 4d74b844f7..c353a3834f 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -40,6 +40,8 @@ enum DFIP2201Keys : uint8_t { enum TokenKeys : uint8_t { PaybackDFI = 'a', PaybackDFIFeePCT = 'b', + DexInFeePct = 'c', + DexOutFeePct = 'd', }; enum PoolKeys : uint8_t { @@ -119,88 +121,21 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator allowedVersions{ - {"v0", VersionTypes::v0}, - }; - - inline static const std::map allowedTypes{ - {"params", AttributeTypes::Param}, - {"poolpairs", AttributeTypes::Poolpairs}, - {"token", AttributeTypes::Token}, - }; - - inline static const std::map allowedParamIDs{ - {"dfip2201", ParamIDs::DFIP2201} - }; - - inline static const std::map> allowedKeys{ - { - AttributeTypes::Token, { - {"payback_dfi", TokenKeys::PaybackDFI}, - {"payback_dfi_fee_pct", TokenKeys::PaybackDFIFeePCT}, - } - }, - { - AttributeTypes::Poolpairs, { - {"token_a_fee_pct", PoolKeys::TokenAFeePCT}, - {"token_b_fee_pct", PoolKeys::TokenBFeePCT}, - } - }, - { - AttributeTypes::Param, { - {"active", DFIP2201Keys::Active}, - {"minswap", DFIP2201Keys::MinSwap}, - {"premium", DFIP2201Keys::Premium}, - } - }, - }; + static const std::map& allowedVersions(); + static const std::map& allowedTypes(); + static const std::map& allowedParamIDs(); + static const std::map>& allowedKeys(); + static const std::map(const std::string&)>>>& parseValue(); // For formatting in export - inline static const std::map displayVersions{ - {VersionTypes::v0, "v0"}, - }; - - inline static const std::map displayTypes{ - {AttributeTypes::Live, "live"}, - {AttributeTypes::Param, "params"}, - {AttributeTypes::Poolpairs, "poolpairs"}, - {AttributeTypes::Token, "token"}, - }; - - inline static const std::map displayParamsIDs{ - {ParamIDs::DFIP2201, "dfip2201"}, - {ParamIDs::Economy, "economy"}, - }; - - inline static const std::map> displayKeys{ - { - AttributeTypes::Token, { - {TokenKeys::PaybackDFI, "payback_dfi"}, - {TokenKeys::PaybackDFIFeePCT, "payback_dfi_fee_pct"}, - } - }, - { - AttributeTypes::Poolpairs, { - {PoolKeys::TokenAFeePCT, "token_a_fee_pct"}, - {PoolKeys::TokenBFeePCT, "token_b_fee_pct"}, - } - }, - { - AttributeTypes::Param, { - {DFIP2201Keys::Active, "active"}, - {DFIP2201Keys::Premium, "premium"}, - {DFIP2201Keys::MinSwap, "minswap"}, - } - }, - { - AttributeTypes::Live, { - {EconomyKeys::PaybackDFITokens, "dfi_payback_tokens"}, - } - }, - }; + static const std::map& displayVersions(); + static const std::map& displayTypes(); + static const std::map& displayParamsIDs(); + static const std::map>& displayKeys(); Res ProcessVariable(const std::string& key, const std::string& value, - std::function applyVariable = {}) const; + std::function applyVariable) const; }; #endif // DEFI_MASTERNODES_GOVVARIABLES_ATTRIBUTES_H diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index cdcd8b04c5..40f4a695e0 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -778,7 +778,7 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo } } - auto dexfeeInPct = view.GetDexFeePct(currentID, swapAmount.nTokenId); + auto dexfeeInPct = view.GetDexFeeInPct(currentID, swapAmount.nTokenId); // Perform swap poolResult = pool->Swap(swapAmount, dexfeeInPct, poolPrice, [&] (const CTokenAmount& dexfeeInAmount, const CTokenAmount& tokenAmount) { @@ -787,11 +787,10 @@ Res CPoolSwap::ExecuteSwap(CCustomCSView& view, std::vector poolIDs, boo CTokenAmount dexfeeOutAmount{tokenAmount.nTokenId, 0}; - if (height >= Params().GetConsensus().FortCanningHillHeight) { - if (auto dexfeeOutPct = view.GetDexFeePct(currentID, tokenAmount.nTokenId)) { - dexfeeOutAmount.nValue = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct); - swapAmountResult.nValue -= dexfeeOutAmount.nValue; - } + auto dexfeeOutPct = view.GetDexFeeOutPct(currentID, tokenAmount.nTokenId); + if (dexfeeOutPct > 0) { + dexfeeOutAmount.nValue = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct); + swapAmountResult.nValue -= dexfeeOutAmount.nValue; } // If we're just testing, don't do any balance transfers. diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index 65039b7c01..060d022bbb 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -407,12 +407,6 @@ Res CPoolPair::Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxP if (in.nTokenId != idTokenA && in.nTokenId != idTokenB) return Res::Err("Error, input token ID (" + in.nTokenId.ToString() + ") doesn't match pool tokens (" + idTokenA.ToString() + "," + idTokenB.ToString() + ")"); - // TODO: The whole block of the fork condition can be removed safely after FCH. - if (height < Params().GetConsensus().FortCanningHillHeight) { - if (in.nValue <= 0) - return Res::Err("Input amount should be positive!"); - } - if (!status) return Res::Err("Pool trading is turned off!"); @@ -444,7 +438,7 @@ Res CPoolPair::Swap(CTokenAmount in, CAmount dexfeeInPct, PoolPrice const & maxP } CTokenAmount dexfeeInAmount{in.nTokenId, 0}; - if (dexfeeInPct > 0 && height >= Params().GetConsensus().FortCanningHillHeight) { + if (dexfeeInPct > 0) { if (dexfeeInPct > COIN) { return Res::Err("Dex fee input percentage over 100%%"); } @@ -726,9 +720,6 @@ void CPoolPairView::ForEachPoolShare(std::function COIN) { return Res::Err("Token dex fee should be in percentage"); } @@ -736,10 +727,16 @@ Res CPoolPairView::SetDexFeePct(DCT_ID poolId, DCT_ID tokenId, CAmount feePct) { return Res::Ok(); } -CAmount CPoolPairView::GetDexFeePct(DCT_ID poolId, DCT_ID tokenId) const { +CAmount CPoolPairView::GetDexFeeInPct(DCT_ID poolId, DCT_ID tokenId) const { uint32_t feePct; - if (ReadBy(std::make_pair(poolId, tokenId), feePct)) { - return feePct; - } - return 0; + return ReadBy(std::make_pair(poolId, tokenId), feePct) + || ReadBy(std::make_pair(tokenId, DCT_ID{~0u}), feePct) + ? feePct : 0; +} + +CAmount CPoolPairView::GetDexFeeOutPct(DCT_ID poolId, DCT_ID tokenId) const { + uint32_t feePct; + return ReadBy(std::make_pair(poolId, tokenId), feePct) + || ReadBy(std::make_pair(DCT_ID{~0u}, tokenId), feePct) + ? feePct : 0; } diff --git a/src/masternodes/poolpairs.h b/src/masternodes/poolpairs.h index 02287cdcfe..a8e4f8bea7 100644 --- a/src/masternodes/poolpairs.h +++ b/src/masternodes/poolpairs.h @@ -262,7 +262,8 @@ class CPoolPairView : public virtual CStorageView bool HasPoolPair(DCT_ID const & poolId) const; Res SetDexFeePct(DCT_ID poolId, DCT_ID tokenId, CAmount feePct); - CAmount GetDexFeePct(DCT_ID poolId, DCT_ID tokenId) const; + CAmount GetDexFeeInPct(DCT_ID poolId, DCT_ID tokenId) const; + CAmount GetDexFeeOutPct(DCT_ID poolId, DCT_ID tokenId) const; std::pair UpdatePoolRewards(std::function onGetBalance, std::function onTransfer, int nHeight = 0); diff --git a/src/masternodes/rpc_poolpair.cpp b/src/masternodes/rpc_poolpair.cpp index 7547842074..e428d02792 100644 --- a/src/masternodes/rpc_poolpair.cpp +++ b/src/masternodes/rpc_poolpair.cpp @@ -9,11 +9,19 @@ UniValue poolToJSON(CCustomCSView& view, DCT_ID const& id, CPoolPair const& pool poolObj.pushKV("idTokenB", pool.idTokenB.ToString()); if (verbose) { - if (const auto dexFee = view.GetDexFeePct(id, pool.idTokenA)) { + if (const auto dexFee = view.GetDexFeeInPct(id, pool.idTokenA)) { poolObj.pushKV("dexFeePctTokenA", ValueFromAmount(dexFee)); + poolObj.pushKV("dexFeeInPctTokenA", ValueFromAmount(dexFee)); } - if (const auto dexFee = view.GetDexFeePct(id, pool.idTokenB)) { + if (const auto dexFee = view.GetDexFeeOutPct(id, pool.idTokenB)) { poolObj.pushKV("dexFeePctTokenB", ValueFromAmount(dexFee)); + poolObj.pushKV("dexFeeOutPctTokenB", ValueFromAmount(dexFee)); + } + if (const auto dexFee = view.GetDexFeeInPct(id, pool.idTokenB)) { + poolObj.pushKV("dexFeeInPctTokenB", ValueFromAmount(dexFee)); + } + if (const auto dexFee = view.GetDexFeeOutPct(id, pool.idTokenA)) { + poolObj.pushKV("dexFeeOutPctTokenA", ValueFromAmount(dexFee)); } poolObj.pushKV("reserveA", ValueFromAmount(pool.reserveA)); poolObj.pushKV("reserveB", ValueFromAmount(pool.reserveB)); @@ -1065,7 +1073,7 @@ UniValue testpoolswap(const JSONRPCRequest& request) { throw JSONRPCError(RPC_INVALID_REQUEST, "Input amount should be positive"); CPoolPair pp = poolPair->second; - auto dexfeeInPct = mnview_dummy.GetDexFeePct(poolPair->first, poolSwapMsg.idTokenFrom); + auto dexfeeInPct = mnview_dummy.GetDexFeeInPct(poolPair->first, poolSwapMsg.idTokenFrom); res = pp.Swap({poolSwapMsg.idTokenFrom, poolSwapMsg.amountFrom}, dexfeeInPct, poolSwapMsg.maxPrice, [&] (const CTokenAmount &, const CTokenAmount &tokenAmount) { auto resPP = mnview_dummy.SetPoolPair(poolPair->first, targetHeight, pp); @@ -1074,11 +1082,10 @@ UniValue testpoolswap(const JSONRPCRequest& request) { } auto resultAmount = tokenAmount; - if (targetHeight >= Params().GetConsensus().FortCanningHillHeight) { - if (auto dexfeeOutPct = mnview_dummy.GetDexFeePct(poolPair->first, tokenAmount.nTokenId)) { - auto dexfeeOutAmount = MultiplyAmounts(tokenAmount.nValue, dexfeeOutPct); - resultAmount.nValue -= dexfeeOutAmount; - } + 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()); diff --git a/test/functional/feature_poolswap.py b/test/functional/feature_poolswap.py index 89b1612ef7..a62bbbbce6 100755 --- a/test/functional/feature_poolswap.py +++ b/test/functional/feature_poolswap.py @@ -19,6 +19,7 @@ ) from decimal import Decimal +from math import trunc class PoolPairTest (DefiTestFramework): def set_test_params(self): @@ -28,10 +29,10 @@ 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', '-acindex=1'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-acindex=1'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170']] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177', '-acindex=1'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177', '-acindex=1'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-bayfrontgardensheight=0', '-dakotaheight=160', '-fortcanningheight=163', '-fortcanninghillheight=170', '-greatworldheight=177']] def run_test(self): @@ -338,10 +339,11 @@ def run_test(self): symbolBTC = "BTC#" + self.get_id_token("BTC") symbolLTC = "LTC#" + self.get_id_token("LTC") - idBitcoin = list(self.nodes[0].gettoken(symbolBTC).keys())[0] + idBTC = list(self.nodes[0].gettoken(symbolBTC).keys())[0] + idLTC = list(self.nodes[0].gettoken(symbolLTC).keys())[0] self.nodes[0].minttokens("1@" + symbolBTC) - self.nodes[0].minttokens("101@" + symbolLTC) + self.nodes[0].minttokens("111@" + symbolLTC) self.nodes[0].generate(1) self.nodes[0].createpoolpair({ @@ -372,7 +374,7 @@ def run_test(self): }) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBitcoin], Decimal('0.00000001')) + assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000001')) # Reset swap self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) @@ -388,7 +390,7 @@ def run_test(self): }) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBitcoin], Decimal('0.00000002')) + 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 self.nodes[0].invalidateblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount())) @@ -405,7 +407,7 @@ def run_test(self): }) self.nodes[0].generate(1) - assert(idBitcoin not in self.nodes[0].getaccount(new_dest, {}, True)) + 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())) @@ -421,7 +423,7 @@ def run_test(self): }) self.nodes[0].generate(1) - assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBitcoin], Decimal('0.00000001')) + assert_equal(self.nodes[0].getaccount(new_dest, {}, True)[idBTC], Decimal('0.00000001')) 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) @@ -487,11 +489,45 @@ 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) - print(self.nodes[0].getblockcount()) - print(self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES']) - 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'}) + 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].generate(1) + + result = self.nodes[0].getpoolpair(idBL) + assert_equal(result[idBL]['dexFeeInPctTokenB'], Decimal('0.02')) + assert_equal(result[idBL]['dexFeeOutPctTokenA'], Decimal('0.05')) + + destBTC = self.nodes[0].getnewaddress("", "legacy") + swapltc = 10 + self.nodes[0].poolswap({ + "from": accountGN0, + "tokenFrom": symbolLTC, + "amountFrom": swapltc, + "to": destBTC, + "tokenTo": 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] + reserveA = pool['reserveA'] + reserveB = pool['reserveB'] + + self.nodes[0].generate(1) + + pool = self.nodes[0].getpoolpair("BTC-LTC")[idBL] + assert_equal(pool['reserveB'] - reserveB, round(amountB, 8)) + swapped = self.nodes[0].getaccount(destBTC, {}, True)[idBTC] + amountA = reserveA - pool['reserveA'] + dexoutfee = round(trunc(amountA * Decimal(0.05) * coin) / coin, 8) + assert_equal(round(amountA - Decimal(dexoutfee), 8), round(swapped, 8)) + # REVERTING: #======================== print ("Reverting...") diff --git a/test/functional/feature_setgov.py b/test/functional/feature_setgov.py index d56ee340f2..55256cad65 100755 --- a/test/functional/feature_setgov.py +++ b/test/functional/feature_setgov.py @@ -21,8 +21,8 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-eunosheight=200', '-fortcanningheight=400', '-fortcanninghillheight=1110', '-subsidytest=1'], - ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-eunosheight=200', '-fortcanningheight=400', '-fortcanninghillheight=1110', '-subsidytest=1']] + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-eunosheight=200', '-fortcanningheight=400', '-fortcanninghillheight=1110', '-greatworldheight=1141', '-subsidytest=1'], + ['-txnotokens=0', '-amkheight=50', '-bayfrontheight=50', '-eunosheight=200', '-fortcanningheight=400', '-fortcanninghillheight=1110', '-greatworldheight=1141', '-subsidytest=1']] def run_test(self): @@ -445,14 +445,14 @@ def run_test(self): assert_raises_rpc_error(-5, "Empty value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/15/payback_dfi':''}}) assert_raises_rpc_error(-5, "Incorrect key for . Object of ['//ID/','value'] expected", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/payback_dfi':'true'}}) assert_raises_rpc_error(-5, "Unrecognised type argument provided, valid types are: params, poolpairs, token,", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/unrecognised/5/payback_dfi':'true'}}) - assert_raises_rpc_error(-5, "Unrecognised key argument provided, valid keys are: payback_dfi, payback_dfi_fee_pct,", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/unrecognised':'true'}}) + assert_raises_rpc_error(-5, "Unrecognised key argument provided, valid keys are: dex_in_fee_pct, dex_out_fee_pct, payback_dfi, payback_dfi_fee_pct,", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/unrecognised':'true'}}) assert_raises_rpc_error(-5, "Identifier must be a positive integer", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/not_a_number/payback_dfi':'true'}}) - assert_raises_rpc_error(-5, 'Payback DFI value must be either "true" or "false"', self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/payback_dfi':'not_a_number'}}) - assert_raises_rpc_error(-5, 'Payback DFI value must be either "true" or "false"', self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/payback_dfi':'unrecognised'}}) + assert_raises_rpc_error(-5, 'Boolean value must be either "true" or "false"', self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/payback_dfi':'not_a_number'}}) + assert_raises_rpc_error(-5, 'Boolean value must be either "true" or "false"', self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/payback_dfi':'unrecognised'}}) assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/payback_dfi_fee_pct':'not_a_number'}}) assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/payback_dfi_fee_pct':'-1'}}) assert_raises_rpc_error(-32600, "ATTRIBUTES: No such loan token (5)", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/payback_dfi':'true'}}) - assert_raises_rpc_error(-5, "DFIP2201 actve value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2201/active':'not_a_bool'}}) + assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2201/active':'not_a_bool'}}) assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2201/minswap':'-1'}}) assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2201/minswap':'not_a_number'}}) assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2201/premium': 'not_a_number'}}) @@ -523,5 +523,16 @@ def run_test(self): assert_equal(self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'], {'v0/params/dfip2201/active': 'true', 'v0/params/dfip2201/premium': '0.025', 'v0/params/dfip2201/minswap': '0.001', 'v0/token/5/payback_dfi': 'true', 'v0/token/5/payback_dfi_fee_pct': '0.01'}) assert_equal(self.nodes[0].listgovs()[8][0]['ATTRIBUTES'], {'v0/params/dfip2201/active': 'true', 'v0/params/dfip2201/premium': '0.025', 'v0/params/dfip2201/minswap': '0.001', 'v0/token/5/payback_dfi': 'true', 'v0/token/5/payback_dfi_fee_pct': '0.01'}) + assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot be set before GreatWorld", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/dex_in_fee_pct': '0.5'}}) + assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot be set before GreatWorld", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/dex_out_fee_pct': '0.5'}}) + self.nodes[0].generate(1) + + self.nodes[0].setgov({"ATTRIBUTES":{'v0/token/5/dex_in_fee_pct':'0.6','v0/token/5/dex_out_fee_pct':'0.12'}}) + self.nodes[0].generate(1) + + attriutes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attriutes['v0/token/5/dex_in_fee_pct'], '0.6') + assert_equal(attriutes['v0/token/5/dex_out_fee_pct'], '0.12') + if __name__ == '__main__': GovsetTest ().main ()