From da76f89a6231d5765ac2f461ff567e64481bf1b3 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 | 173 +++++++++----------- src/masternodes/govvariables/attributes.h | 35 ++++ src/masternodes/mn_checks.cpp | 11 +- src/masternodes/poolpairs.cpp | 27 ++- src/masternodes/poolpairs.h | 3 +- src/masternodes/rpc_poolpair.cpp | 15 +- test/functional/feature_poolswap.py | 3 - test/functional/feature_setgov.py | 23 ++- 8 files changed, 154 insertions(+), 136 deletions(-) diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index b7ef1c88eb9..653b825fced 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -33,15 +33,7 @@ static std::vector KeyBreaker(const std::string& str){ return strVec; } -static ResVal VerifyInt32(const std::string& str) { - int32_t int32; - if (!ParseInt32(str, &int32) || int32 < 0) { - return Res::Err("Identifier must be a positive integer"); - } - return {int32, Res::Ok()}; -} - -static ResVal VerifyFloat(const std::string& str) { +ResVal ATTRIBUTES::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 +41,32 @@ static ResVal VerifyFloat(const std::string& str) { return {amount, Res::Ok()}; } -static ResVal VerifyPct(const std::string& str) { +ResVal ATTRIBUTES::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; } +ResVal ATTRIBUTES::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()}; +} + +static ResVal VerifyInt32(const std::string& str) { + int32_t int32; + if (!ParseInt32(str, &int32) || int32 < 0) { + return Res::Err("Identifier must be a positive integer"); + } + return {int32, Res::Ok()}; +} + 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) { @@ -132,68 +139,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\""); - } - attribValue = value == "true"; - } else if (typeKey == TokenKeys::PaybackDFIFeePCT) { - auto res = VerifyPct(value); - if (!res) { - return std::move(res); + try { + if (auto parser = parseValue.at(type).at(typeKey)) { + auto attribValue = parser(value); + if (!attribValue) { + return std::move(attribValue); } - 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) { @@ -267,40 +224,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 +284,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 4d74b844f7e..95589ed7dc9 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 { @@ -118,6 +120,10 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator attributes; private: + static ResVal VerifyFloat(const std::string& str); + static ResVal VerifyBool(const std::string& str); + static ResVal VerifyPct(const std::string& str); + // Defined allowed arguments inline static const std::map allowedVersions{ {"v0", VersionTypes::v0}, @@ -138,6 +144,8 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator(const std::string&)>>> parseValue{ + { + 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}, + } + }, + }; + // For formatting in export inline static const std::map displayVersions{ {VersionTypes::v0, "v0"}, @@ -177,6 +210,8 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator 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 65039b7c01e..060d022bbb9 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 02287cdcfec..a8e4f8bea7b 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 75478420747..dc232381acd 100644 --- a/src/masternodes/rpc_poolpair.cpp +++ b/src/masternodes/rpc_poolpair.cpp @@ -9,10 +9,10 @@ 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)); } - 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("reserveA", ValueFromAmount(pool.reserveA)); @@ -1065,7 +1065,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 +1074,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 89b1612ef72..72db0821a85 100755 --- a/test/functional/feature_poolswap.py +++ b/test/functional/feature_poolswap.py @@ -487,9 +487,6 @@ 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'}) # REVERTING: diff --git a/test/functional/feature_setgov.py b/test/functional/feature_setgov.py index d56ee340f25..55256cad65e 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 ()