From 89fa3a705889e3a40ef254eeee13c818109e234b Mon Sep 17 00:00:00 2001 From: Peter John Bushnell Date: Sat, 25 Jun 2022 07:08:25 +0100 Subject: [PATCH] DFI to DUSD FutureSwap (#1355) * DFI-to-DUSD FutureSwap * Change commission fix fork * Rename interest burn attribute keys Co-authored-by: Prasanna Loganathar --- src/chainparams.cpp | 4 + src/chainparams.h | 1 + src/masternodes/accounts.cpp | 25 +- src/masternodes/accounts.h | 8 +- src/masternodes/govvariables/attributes.cpp | 218 ++++++++---- src/masternodes/govvariables/attributes.h | 15 +- src/masternodes/masternodes.cpp | 28 +- src/masternodes/mn_checks.cpp | 141 +++++--- src/masternodes/mn_checks.h | 4 +- src/masternodes/mn_rpc.cpp | 12 +- src/masternodes/mn_rpc.h | 7 +- src/masternodes/poolpairs.cpp | 2 +- src/masternodes/rpc_accounts.cpp | 95 ++++- src/masternodes/rpc_oracles.cpp | 39 ++- src/validation.cpp | 213 ++++++++++-- src/validation.h | 2 + test/functional/feature_commission_fix.py | 6 +- test/functional/feature_futures.py | 363 +++++++++++++++++++- test/functional/feature_loan_payback_dfi.py | 4 +- test/functional/feature_setgov.py | 67 +++- 20 files changed, 1035 insertions(+), 219 deletions(-) diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 06cdd631f1a..4451cac6928 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -253,6 +253,7 @@ class CMainParams : public CChainParams { consensus.smartContracts.clear(); consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); consensus.smartContracts[SMART_CONTRACT_DFIP_2203] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}))); + consensus.smartContracts[SMART_CONTRACT_DFIP2206F] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2}))); // owner base58, operator base58 vMasternodes.push_back({"8PuErAcazqccCVzRcc8vJ3wFaZGm4vFbLe", "8J846CKFF83Jcj5m4EReJmxiaJ6Jy1Y6Ea"}); @@ -480,6 +481,7 @@ class CTestNetParams : public CChainParams { consensus.smartContracts.clear(); consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); consensus.smartContracts[SMART_CONTRACT_DFIP_2203] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}))); + consensus.smartContracts[SMART_CONTRACT_DFIP2206F] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2}))); // owner base58, operator base58 vMasternodes.push_back({"7LMorkhKTDjbES6DfRxX2RiNMbeemUkxmp", "7KEu9JMKCx6aJ9wyg138W3p42rjg19DR5D"}); @@ -666,6 +668,7 @@ class CDevNetParams : public CChainParams { consensus.smartContracts.clear(); consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); consensus.smartContracts[SMART_CONTRACT_DFIP_2203] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}))); + consensus.smartContracts[SMART_CONTRACT_DFIP2206F] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2}))); // owner base58, operator base58 vMasternodes.push_back({"7M3g9CSERjLdXisE5pv2qryDbURUj9Vpi1", "7Grgx69MZJ4wDKRx1bBxLqTnU9T3quKW7n"}); @@ -858,6 +861,7 @@ class CRegTestParams : public CChainParams { consensus.smartContracts.clear(); consensus.smartContracts[SMART_CONTRACT_DFIP_2201] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}))); consensus.smartContracts[SMART_CONTRACT_DFIP_2203] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}))); + consensus.smartContracts[SMART_CONTRACT_DFIP2206F] = GetScriptForDestination(CTxDestination(WitnessV0KeyHash(std::vector{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2}))); // owner base58, operator base58 vMasternodes.push_back({"mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU", "mswsMVsyGMj1FzDMbbxw2QW3KvQAv2FKiy"}); diff --git a/src/chainparams.h b/src/chainparams.h index 3a9c419412f..d462243c8f4 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -128,6 +128,7 @@ class CChainParams const auto SMART_CONTRACT_DFIP_2201 = "DFIP2201"; const auto SMART_CONTRACT_DFIP_2203 = "DFIP2203"; +const auto SMART_CONTRACT_DFIP2206F = "DFIP2206F"; /** * Creates and returns a std::unique_ptr of the chosen chain. diff --git a/src/masternodes/accounts.cpp b/src/masternodes/accounts.cpp index 23459f803df..3e578d026a0 100644 --- a/src/masternodes/accounts.cpp +++ b/src/masternodes/accounts.cpp @@ -122,22 +122,25 @@ Res CAccountsView::EraseFuturesUserValues(const CFuturesUserKey& key) return Res::Ok(); } -std::optional CAccountsView::GetMostRecentFuturesHeight() +Res CAccountsView::StoreFuturesDUSD(const CFuturesUserKey& key, const CAmount& amount) { - const CFuturesUserKey key{std::numeric_limits::max(), {}, std::numeric_limits::max()}; - auto it = LowerBound(key); - if (it.Valid()) { - return it.Key().height; + if (!WriteBy(key, amount)) { + return Res::Err("Failed to store futures"); } - return {}; + return Res::Ok(); +} + +void CAccountsView::ForEachFuturesDUSD(std::function callback, const CFuturesUserKey& start) +{ + ForEach(callback, start); } -ResVal CAccountsView::GetFuturesUserValues(const CFuturesUserKey& key) { - CFuturesUserValue source; - if (!ReadBy(key, source)) { - return Res::Err("Failed to read futures source"); +Res CAccountsView::EraseFuturesDUSD(const CFuturesUserKey& key) +{ + if (!EraseBy(key)) { + return Res::Err("Failed to erase futures"); } - return {source, Res::Ok()}; + return Res::Ok(); } diff --git a/src/masternodes/accounts.h b/src/masternodes/accounts.h index 01f6445e3f0..54d1a8a1004 100644 --- a/src/masternodes/accounts.h +++ b/src/masternodes/accounts.h @@ -70,16 +70,20 @@ class CAccountsView : public virtual CStorageView Res UpdateBalancesHeight(CScript const & owner, uint32_t height); Res StoreFuturesUserValues(const CFuturesUserKey& key, const CFuturesUserValue& futures); - ResVal GetFuturesUserValues(const CFuturesUserKey& key); Res EraseFuturesUserValues(const CFuturesUserKey& key); - std::optional GetMostRecentFuturesHeight(); void ForEachFuturesUserValues(std::function callback, const CFuturesUserKey& start = {std::numeric_limits::max(), {}, std::numeric_limits::max()}); + Res StoreFuturesDUSD(const CFuturesUserKey& key, const CAmount& amount); + Res EraseFuturesDUSD(const CFuturesUserKey& key); + void ForEachFuturesDUSD(std::function callback, const CFuturesUserKey& start = + {std::numeric_limits::max(), {}, std::numeric_limits::max()}); + // tags struct ByBalanceKey { static constexpr uint8_t prefix() { return 'a'; } }; struct ByHeightKey { static constexpr uint8_t prefix() { return 'b'; } }; struct ByFuturesSwapKey { static constexpr uint8_t prefix() { return 'J'; } }; + struct ByFuturesDUSDKey { static constexpr uint8_t prefix() { return 'm'; } }; private: Res SetBalance(CScript const & owner, CTokenAmount amount); diff --git a/src/masternodes/govvariables/attributes.cpp b/src/masternodes/govvariables/attributes.cpp index d5b65ca1bee..46c0485a874 100644 --- a/src/masternodes/govvariables/attributes.cpp +++ b/src/masternodes/govvariables/attributes.cpp @@ -78,7 +78,8 @@ const std::map& ATTRIBUTES::allowedParamIDs() { static const std::map params{ {"dfip2201", ParamIDs::DFIP2201}, {"dfip2203", ParamIDs::DFIP2203}, - {"dfip2206a", ParamIDs::DFIP2206A}, + {"dfip2206a", ParamIDs::DFIP2206A}, + {"dfip2206f", ParamIDs::DFIP2206F}, }; return params; } @@ -94,7 +95,8 @@ const std::map& ATTRIBUTES::displayParamsIDs() { static const std::map params{ {ParamIDs::DFIP2201, "dfip2201"}, {ParamIDs::DFIP2203, "dfip2203"}, - {ParamIDs::DFIP2206A, "dfip2206a"}, + {ParamIDs::DFIP2206A, "dfip2206a"}, + {ParamIDs::DFIP2206F, "dfip2206f"}, {ParamIDs::TokenID, "token"}, {ParamIDs::Economy, "economy"}, }; @@ -148,8 +150,9 @@ const std::map>& ATTRIBUTES::allowedKeys {"premium", DFIPKeys::Premium}, {"reward_pct", DFIPKeys::RewardPct}, {"block_period", DFIPKeys::BlockPeriod}, - {"direct_interest_dusd_burn", DFIPKeys::DirectInterestDUSDBurn}, - {"direct_loan_dusd_burn", DFIPKeys::DirectLoanDUSDBurn}, + {"dusd_interest_burn", DFIPKeys::DUSDInterestBurn}, + {"dusd_loan_burn", DFIPKeys::DUSDLoanBurn}, + {"start_block", DFIPKeys::StartBlock}, } }, }; @@ -192,8 +195,9 @@ const std::map>& ATTRIBUTES::displayKeys {DFIPKeys::MinSwap, "minswap"}, {DFIPKeys::RewardPct, "reward_pct"}, {DFIPKeys::BlockPeriod, "block_period"}, - {DFIPKeys::DirectInterestDUSDBurn, "direct_interest_dusd_burn"}, - {DFIPKeys::DirectLoanDUSDBurn, "direct_loan_dusd_burn"}, + {DFIPKeys::DUSDInterestBurn, "dusd_interest_burn"}, + {DFIPKeys::DUSDLoanBurn, "dusd_loan_burn"}, + {DFIPKeys::StartBlock, "start_block"}, } }, { @@ -202,6 +206,9 @@ const std::map>& ATTRIBUTES::displayKeys {EconomyKeys::DFIP2203Current, "dfip2203_current"}, {EconomyKeys::DFIP2203Burned, "dfip2203_burned"}, {EconomyKeys::DFIP2203Minted, "dfip2203_minted"}, + {EconomyKeys::DFIP2206FCurrent, "dfip2206f_current"}, + {EconomyKeys::DFIP2206FBurned, "dfip2206f_burned"}, + {EconomyKeys::DFIP2206FMinted, "dfip2206f_minted"}, } }, }; @@ -350,8 +357,9 @@ const std::map GetFutureSwapContractAddress() { +ResVal GetFutureSwapContractAddress(const std::string& contract) { CScript contractAddress; try { - contractAddress = Params().GetConsensus().smartContracts.at(SMART_CONTRACT_DFIP_2203); + contractAddress = Params().GetConsensus().smartContracts.at(contract); } catch (const std::out_of_range&) { return Res::Err("Failed to get smart contract address from chainparams"); } @@ -482,18 +490,24 @@ Res ATTRIBUTES::ProcessVariable(const std::string& key, const std::string& value typeKey != DFIPKeys::MinSwap ) { return Res::Err("Unsupported type for DFIP2201 {%d}", typeKey); } - } else if (typeId == ParamIDs::DFIP2203) { + } else if (typeId == ParamIDs::DFIP2203 || + typeId == ParamIDs::DFIP2206F) { if (typeKey != DFIPKeys::Active && typeKey != DFIPKeys::RewardPct && - typeKey != DFIPKeys::BlockPeriod) { - return Res::Err("Unsupported type for DFIP2203 {%d}", typeKey); + typeKey != DFIPKeys::BlockPeriod && typeKey != DFIPKeys::StartBlock) { + return Res::Err("Unsupported type for this DFIP {%d}", typeKey); } - if (typeKey == DFIPKeys::BlockPeriod) { - futureBlockUpdated = true; + if (typeKey == DFIPKeys::BlockPeriod || + typeKey == DFIPKeys::StartBlock) { + if (typeId == ParamIDs::DFIP2203) { + futureUpdated = true; + } else { + futureDUSDUpdated = true; + } } } else if (typeId == ParamIDs::DFIP2206A) { - if (typeKey != DFIPKeys::DirectInterestDUSDBurn && - typeKey != DFIPKeys::DirectLoanDUSDBurn) { + if (typeKey != DFIPKeys::DUSDInterestBurn && + typeKey != DFIPKeys::DUSDLoanBurn) { return Res::Err("Unsupported type for DFIP2206A {%d}", typeKey); } } else { @@ -554,7 +568,7 @@ Res ATTRIBUTES::RefundFuturesContracts(CCustomCSView &mnview, const uint32_t hei return true; }, {height, {}, std::numeric_limits::max()}); - const auto contractAddressValue = GetFutureSwapContractAddress(); + const auto contractAddressValue = GetFutureSwapContractAddress(SMART_CONTRACT_DFIP_2203); if (!contractAddressValue) { return contractAddressValue; } @@ -598,6 +612,60 @@ Res ATTRIBUTES::RefundFuturesContracts(CCustomCSView &mnview, const uint32_t hei return Res::Ok(); } +Res ATTRIBUTES::RefundFuturesDUSD(CCustomCSView &mnview, const uint32_t height) +{ + CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2206F, DFIPKeys::BlockPeriod}; + const auto blockPeriod = GetValue(blockKey, CAmount{}); + if (blockPeriod == 0) { + return Res::Ok(); + } + + std::map userFuturesValues; + + mnview.ForEachFuturesDUSD([&](const CFuturesUserKey& key, const CAmount& amount) { + userFuturesValues[key] = amount; + return true; + }, {height, {}, std::numeric_limits::max()}); + + const auto contractAddressValue = GetFutureSwapContractAddress(SMART_CONTRACT_DFIP2206F); + if (!contractAddressValue) { + return contractAddressValue; + } + + CDataStructureV0 liveKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2206FCurrent}; + auto balances = GetValue(liveKey, CBalances{}); + + for (const auto& [key, amount] : userFuturesValues) { + + mnview.EraseFuturesDUSD(key); + + CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter subView(mnview, height, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); + auto res = subView.SubBalance(*contractAddressValue, {DCT_ID{}, amount}); + if (!res) { + return res; + } + subView.Flush(); + + CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter addView(mnview, height, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); + res = addView.AddBalance(key.owner, {DCT_ID{}, amount}); + if (!res) { + return res; + } + addView.Flush(); + + res = balances.Sub({DCT_ID{}, amount}); + if (!res) { + return res; + } + } + + attributes[liveKey] = balances; + + return Res::Ok(); +} + Res ATTRIBUTES::Import(const UniValue & val) { if (!val.isObject()) { return Res::Err("Object of values expected"); @@ -714,7 +782,8 @@ UniValue ATTRIBUTES::ExportFiltered(GovVarsFilter filter, const std::string &pre if (const auto bool_val = std::get_if(&attribute.second)) { ret.pushKV(key, *bool_val ? "true" : "false"); } else if (const auto amount = std::get_if(&attribute.second)) { - if (attrV0->typeId == DFIP2203 && attrV0->key == DFIPKeys::BlockPeriod) { + if ((attrV0->typeId == DFIP2203 || attrV0->typeId == DFIP2206F) && + (attrV0->key == DFIPKeys::BlockPeriod || attrV0->key == DFIPKeys::StartBlock)) { ret.pushKV(key, KeyBuilder(*amount)); } else { auto decimalStr = GetDecimaleString(*amount); @@ -771,8 +840,8 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const if (view.GetLastHeight() < Params().GetConsensus().FortCanningHillHeight) return Res::Err("Cannot be set before FortCanningHill"); - for (const auto& attribute : attributes) { - const auto attrV0 = std::get_if(&attribute.first); + for (const auto& [key, value] : attributes) { + const auto attrV0 = std::get_if(&key); if (!attrV0) { return Res::Err("Unsupported version"); } @@ -853,7 +922,7 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const return Res::Err("Cannot be set before FortCanningCrunch"); } if (attrV0->typeId == OracleIDs::Splits) { - const auto splitMap = std::get_if(&attribute.second); + const auto splitMap = std::get_if(&value); if (!splitMap) { return Res::Err("Unsupported value"); } @@ -903,12 +972,14 @@ Res ATTRIBUTES::Validate(const CCustomCSView & view) const break; case AttributeTypes::Param: - if (attrV0->typeId == ParamIDs::DFIP2206A) { - if (view.GetLastHeight() < Params().GetConsensus().FortCanningGardensHeight) - return Res::Err("Cannot be set before FortCanningGarden"); + if (attrV0->typeId == ParamIDs::DFIP2206F || attrV0->key == DFIPKeys::StartBlock || attrV0->typeId == ParamIDs::DFIP2206A) { + if (view.GetLastHeight() < Params().GetConsensus().FortCanningGardensHeight) { + return Res::Err("Cannot be set before FortCanningGardensHeight"); + } } else if (attrV0->typeId == ParamIDs::DFIP2203) { - if (view.GetLastHeight() < Params().GetConsensus().FortCanningRoadHeight) - return Res::Err("Cannot be set before FortCanningRoad"); + if (view.GetLastHeight() < Params().GetConsensus().FortCanningRoadHeight) { + return Res::Err("Cannot be set before FortCanningRoadHeight"); + } } else if (attrV0->typeId != ParamIDs::DFIP2201) { return Res::Err("Unrecognised param id"); } @@ -978,8 +1049,7 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height) if (auto res = mnview.SetDexFeePct(tokenA, tokenB, *valuePct); !res) { return res; } - } - if (attrV0->key == TokenKeys::FixedIntervalPriceId) { + } else if (attrV0->key == TokenKeys::FixedIntervalPriceId) { if (const auto ¤cyPair = std::get_if(&attribute.second)) { // Already exists, skip. if (auto it = mnview.LowerBound(*currencyPair); @@ -1007,15 +1077,7 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height) } else { return Res::Err("Unrecognised value for FixedIntervalPriceId"); } - } - if (attrV0->key == TokenKeys::DFIP2203Enabled) { - - // Skip on block period change to avoid refunding and erasing entries. - // Block period change will check for conflicting entries, deleting them - // via RefundFuturesContracts will fail that check. - if (futureBlockUpdated) { - continue; - } + } else if (attrV0->key == TokenKeys::DFIP2203Enabled) { const auto value = std::get_if(&attribute.second); if (!value) { @@ -1042,43 +1104,69 @@ Res ATTRIBUTES::Apply(CCustomCSView & mnview, const uint32_t height) return res; } } - } else if (attrV0->type == AttributeTypes::Param && attrV0->typeId == ParamIDs::DFIP2203) { - if (attrV0->key == DFIPKeys::Active) { + } else if (attrV0->type == AttributeTypes::Param) { + if (attrV0->typeId == ParamIDs::DFIP2203) { + if (attrV0->key == DFIPKeys::Active) { - // Skip on block period change to avoid refunding and erasing entries. - // Block period change will check for conflicting entries, deleting them - // via RefundFuturesContracts will fail that check. - if (futureBlockUpdated) { - continue; - } + const auto value = std::get_if(&attribute.second); + if (!value) { + return Res::Err("Unexpected type"); + } - const auto value = std::get_if(&attribute.second); - if (!value) { - return Res::Err("Unexpected type"); - } + if (*value) { + continue; + } - if (*value) { - continue; - } + Res res = RefundFuturesContracts(mnview, height); + if (!res) { + return res; + } - auto res = RefundFuturesContracts(mnview, height); - if (!res) { - return res; - } + } else if (attrV0->key == DFIPKeys::BlockPeriod || attrV0->key == DFIPKeys::StartBlock) { - } else if (attrV0->key == DFIPKeys::BlockPeriod) { + // Only check this when block period has been set, otherwise + // it will fail when DFIP2203 active is set to true. + if (!futureUpdated) { + continue; + } - // Only check this when block period has been set, otherwise - // it will fail when DFIP2203 active is set to true. - if (!futureBlockUpdated) { - continue; + CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::Active}; + if (GetValue(activeKey, false)) { + return Res::Err("Cannot set block period while DFIP2203 is active"); + } } + } else if (attrV0->typeId == ParamIDs::DFIP2206F) { + if (attrV0->key == DFIPKeys::Active) { + + const auto value = std::get_if(&attribute.second); + if (!value) { + return Res::Err("Unexpected type"); + } + + if (*value) { + continue; + } - CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::Active}; - if (GetValue(activeKey, false)) { - return Res::Err("Cannot set block period while DFIP2203 is active"); + Res res = RefundFuturesDUSD(mnview, height); + if (!res) { + return res; + } + + } else if (attrV0->key == DFIPKeys::BlockPeriod) { + + // Only check this when block period has been set, otherwise + // it will fail when DFIP2206F active is set to true. + if (!futureDUSDUpdated) { + continue; + } + + CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2206F, DFIPKeys::Active}; + if (GetValue(activeKey, false)) { + return Res::Err("Cannot set block period while DFIP2206F is active"); + } } } + } else if (attrV0->type == AttributeTypes::Oracles && attrV0->typeId == OracleIDs::Splits) { const auto value = std::get_if(&attribute.second); if (!value) { diff --git a/src/masternodes/govvariables/attributes.h b/src/masternodes/govvariables/attributes.h index 6e00169dee9..245c6c9a154 100644 --- a/src/masternodes/govvariables/attributes.h +++ b/src/masternodes/govvariables/attributes.h @@ -29,6 +29,7 @@ enum ParamIDs : uint8_t { TokenID = 'c', Economy = 'e', DFIP2206A = 'f', + DFIP2206F = 'g', }; enum OracleIDs : uint8_t { @@ -41,6 +42,9 @@ enum EconomyKeys : uint8_t { DFIP2203Current = 'c', DFIP2203Burned = 'd', DFIP2203Minted = 'e', + DFIP2206FCurrent = 'f', + DFIP2206FBurned = 'g', + DFIP2206FMinted = 'h', }; enum DFIPKeys : uint8_t { @@ -49,8 +53,9 @@ enum DFIPKeys : uint8_t { MinSwap = 'c', RewardPct = 'd', BlockPeriod = 'e', - DirectInterestDUSDBurn = 'g', - DirectLoanDUSDBurn = 'h', + DUSDInterestBurn = 'g', + DUSDLoanBurn = 'h', + StartBlock = 'i', }; enum TokenKeys : uint8_t { @@ -143,7 +148,7 @@ struct CFeeDir { } }; -ResVal GetFutureSwapContractAddress(); +ResVal GetFutureSwapContractAddress(const std::string& contract); enum FeeDirValues : uint8_t { Both, @@ -268,7 +273,8 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator tokenSplits{}; std::set changed; std::map attributes; @@ -285,6 +291,7 @@ class ATTRIBUTES : public GovVariable, public AutoRegistrator applyVariable); + Res RefundFuturesDUSD(CCustomCSView &mnview, const uint32_t height); }; #endif // DEFI_MASTERNODES_GOVVARIABLES_ATTRIBUTES_H diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index 8191dc4be15..ea9f65fd3b5 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -1148,24 +1148,20 @@ std::map AmISignerNow(int height, CAnchorData::CTeam const & team) } std::optional CCustomCSView::GetLoanTokenFromAttributes(const DCT_ID& id) const { - if (const auto token = GetToken(id)) { - if (const auto attributes = GetAttributes()) { - CLoanView::CLoanSetLoanTokenImpl loanToken; - - CDataStructureV0 pairKey{AttributeTypes::Token, id.v, TokenKeys::FixedIntervalPriceId}; - CDataStructureV0 interestKey{AttributeTypes::Token, id.v, TokenKeys::LoanMintingInterest}; - CDataStructureV0 mintableKey{AttributeTypes::Token, id.v, TokenKeys::LoanMintingEnabled}; - - if (attributes->CheckKey(pairKey) && attributes->CheckKey(interestKey) && attributes->CheckKey(mintableKey)) { + if (const auto attributes = GetAttributes()) { - loanToken.fixedIntervalPriceId = attributes->GetValue(pairKey, CTokenCurrencyPair{}); - loanToken.interest = attributes->GetValue(interestKey, CAmount{0}); - loanToken.mintable = attributes->GetValue(mintableKey, false); - loanToken.symbol = token->symbol; - loanToken.name = token->name; + CDataStructureV0 pairKey{AttributeTypes::Token, id.v, TokenKeys::FixedIntervalPriceId}; + CDataStructureV0 interestKey{AttributeTypes::Token, id.v, TokenKeys::LoanMintingInterest}; + CDataStructureV0 mintableKey{AttributeTypes::Token, id.v, TokenKeys::LoanMintingEnabled}; - return loanToken; - } + if (const auto token = GetToken(id); token && attributes->CheckKey(pairKey) && attributes->CheckKey(interestKey) && attributes->CheckKey(mintableKey)) { + CLoanView::CLoanSetLoanTokenImpl loanToken; + loanToken.fixedIntervalPriceId = attributes->GetValue(pairKey, CTokenCurrencyPair{}); + loanToken.interest = attributes->GetValue(interestKey, CAmount{}); + loanToken.mintable = attributes->GetValue(mintableKey, false); + loanToken.symbol = token->symbol; + loanToken.name = token->name; + return loanToken; } } diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index 4f8dda90f9e..a0db063ef59 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -49,7 +49,7 @@ std::string ToString(CustomTxType type) { case CustomTxType::AccountToAccount: return "AccountToAccount"; case CustomTxType::AnyAccountsToAccounts: return "AnyAccountsToAccounts"; case CustomTxType::SmartContract: return "SmartContract"; - case CustomTxType::DFIP2203: return "DFIP2203"; + case CustomTxType::FutureSwap: return "DFIP2203"; case CustomTxType::SetGovVariable: return "SetGovVariable"; case CustomTxType::SetGovVariableHeight:return "SetGovVariableHeight"; case CustomTxType::AppointOracle: return "AppointOracle"; @@ -148,7 +148,7 @@ CCustomTxMessage customTypeToMessage(CustomTxType txType) { case CustomTxType::AccountToAccount: return CAccountToAccountMessage{}; case CustomTxType::AnyAccountsToAccounts: return CAnyAccountsToAccountsMessage{}; case CustomTxType::SmartContract: return CSmartContractMessage{}; - case CustomTxType::DFIP2203: return CFutureSwapMessage{}; + case CustomTxType::FutureSwap: return CFutureSwapMessage{}; case CustomTxType::SetGovVariable: return CGovernanceMessage{}; case CustomTxType::SetGovVariableHeight: return CGovernanceHeightMessage{}; case CustomTxType::AppointOracle: return CAppointOracleMessage{}; @@ -251,13 +251,6 @@ class CCustomMetadataParseVisitor return Res::Ok(); } - Res isPostFortCanningCrunchFork() const { - if(static_cast(height) < consensus.FortCanningCrunchHeight) { - return Res::Err("called before FortCanningCrunch height"); - } - return Res::Ok(); - } - Res isPostGreatWorldFork() const { if(static_cast(height) < consensus.GreatWorldHeight) { return Res::Err("called before GreatWorldHeight height"); @@ -1489,16 +1482,21 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return Res::Err("Attributes unavailable"); } - CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::Active}; - const auto active = attributes->GetValue(activeKey, false); - if (!active) { - return Res::Err("DFIP2203 not currently active"); + bool dfiToDUSD = !obj.source.nTokenId.v; + const auto paramID = dfiToDUSD ? ParamIDs::DFIP2206F : ParamIDs::DFIP2203; + + CDataStructureV0 activeKey{AttributeTypes::Param, paramID, DFIPKeys::Active}; + CDataStructureV0 blockKey{AttributeTypes::Param, paramID, DFIPKeys::BlockPeriod}; + CDataStructureV0 rewardKey{AttributeTypes::Param, paramID, DFIPKeys::RewardPct}; + if (!attributes->GetValue(activeKey, false) || + !attributes->CheckKey(blockKey) || + !attributes->CheckKey(rewardKey)) { + return Res::Err("%s not currently active", dfiToDUSD ? "DFIP2206F" : "DFIP2203"); } - CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::BlockPeriod}; - CDataStructureV0 rewardKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::RewardPct}; - if (!attributes->CheckKey(blockKey) || !attributes->CheckKey(rewardKey)) { - return Res::Err("DFIP2203 not currently active"); + CDataStructureV0 startKey{AttributeTypes::Param, paramID, DFIPKeys::StartBlock}; + if (const auto startBlock = attributes->GetValue(startKey, CAmount{}); height < startBlock) { + return Res::Err("%s not active until block %d", dfiToDUSD ? "DFIP2206F" : "DFIP2203", startBlock); } if (obj.source.nValue <= 0) { @@ -1506,11 +1504,11 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor } const auto source = mnview.GetLoanTokenByID(obj.source.nTokenId); - if (!source) { + if (!dfiToDUSD && !source) { return Res::Err("Could not get source loan token %d", obj.source.nTokenId.v); } - if (source->symbol == "DUSD") { + if (!dfiToDUSD && source->symbol == "DUSD") { CDataStructureV0 tokenKey{AttributeTypes::Token, obj.destination, TokenKeys::DFIP2203Enabled}; const auto enabled = attributes->GetValue(tokenKey, true); if (!enabled) { @@ -1526,54 +1524,86 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return Res::Err("Cannot create future swap for locked token"); } } else { - if (obj.destination != 0) { - return Res::Err("Destination should not be set when source amount is a dToken"); - } + if (!dfiToDUSD) { + if (obj.destination != 0) { + return Res::Err("Destination should not be set when source amount is dToken or DFI"); + } - if (mnview.AreTokensLocked({obj.source.nTokenId.v})) { - return Res::Err("Cannot create future swap for locked token"); - } + if (mnview.AreTokensLocked({obj.source.nTokenId.v})) { + return Res::Err("Cannot create future swap for locked token"); + } - CDataStructureV0 tokenKey{AttributeTypes::Token, obj.source.nTokenId.v, TokenKeys::DFIP2203Enabled}; - const auto enabled = attributes->GetValue(tokenKey, true); - if (!enabled) { - return Res::Err("DFIP2203 currently disabled for token %s", obj.source.nTokenId.ToString()); + CDataStructureV0 tokenKey{AttributeTypes::Token, obj.source.nTokenId.v, TokenKeys::DFIP2203Enabled}; + const auto enabled = attributes->GetValue(tokenKey, true); + if (!enabled) { + return Res::Err("DFIP2203 currently disabled for token %s", obj.source.nTokenId.ToString()); + } + } else { + DCT_ID id{}; + const auto token = mnview.GetTokenGuessId("DUSD", id); + if (!token) { + return Res::Err("No DUSD token defined"); + } + + if (!mnview.GetFixedIntervalPrice({"DFI", "USD"})) { + return Res::Err("DFI / DUSD fixed interval price not found"); + } + + if (obj.destination != id.v) { + return Res::Err("Incorrect destination defined for DFI swap, DUSD destination expected id: %d", id.v); + } } } - const auto contractAddressValue = GetFutureSwapContractAddress(); + const auto contractType = dfiToDUSD ? SMART_CONTRACT_DFIP2206F : SMART_CONTRACT_DFIP_2203; + const auto contractAddressValue = GetFutureSwapContractAddress(contractType); if (!contractAddressValue) { return contractAddressValue; } - CDataStructureV0 liveKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2203Current}; + const auto economyKey = dfiToDUSD ? EconomyKeys::DFIP2206FCurrent : EconomyKeys::DFIP2203Current; + CDataStructureV0 liveKey{AttributeTypes::Live, ParamIDs::Economy, economyKey}; auto balances = attributes->GetValue(liveKey, CBalances{}); - // Can be removed after the hard fork, since it will be backward compatible - // but have to keep it around for pre 2.8.0 nodes for now if (height >= static_cast(consensus.FortCanningCrunchHeight)) { CalculateOwnerRewards(obj.owner); } if (obj.withdraw) { - std::map userFuturesValues; - - mnview.ForEachFuturesUserValues([&](const CFuturesUserKey& key, const CFuturesUserValue& futuresValues) { - if (key.owner == obj.owner && - futuresValues.source.nTokenId == obj.source.nTokenId && - futuresValues.destination == obj.destination) { - userFuturesValues[key] = futuresValues; - } - - return true; - }, {height, obj.owner, std::numeric_limits::max()}); CTokenAmount totalFutures{}; totalFutures.nTokenId = obj.source.nTokenId; - for (const auto& [key, value] : userFuturesValues) { - totalFutures.Add(value.source.nValue); - mnview.EraseFuturesUserValues(key); + if (!dfiToDUSD) { + std::map userFuturesValues; + + mnview.ForEachFuturesUserValues([&](const CFuturesUserKey& key, const CFuturesUserValue& futuresValues) { + if (key.owner == obj.owner && + futuresValues.source.nTokenId == obj.source.nTokenId && + futuresValues.destination == obj.destination) { + userFuturesValues[key] = futuresValues; + } + return true; + }, {height, obj.owner, std::numeric_limits::max()}); + + for (const auto& [key, value] : userFuturesValues) { + totalFutures.Add(value.source.nValue); + mnview.EraseFuturesUserValues(key); + } + } else { + std::map userFuturesValues; + + mnview.ForEachFuturesDUSD([&](const CFuturesUserKey& key, const CAmount& futuresValues) { + if (key.owner == obj.owner) { + userFuturesValues[key] = futuresValues; + } + return true; + }, {height, obj.owner, std::numeric_limits::max()}); + + for (const auto& [key, amount] : userFuturesValues) { + totalFutures.Add(amount); + mnview.EraseFuturesDUSD(key); + } } auto res = totalFutures.Sub(obj.source.nValue); @@ -1582,7 +1612,12 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor } if (totalFutures.nValue > 0) { - auto res = mnview.StoreFuturesUserValues({height, obj.owner, txn}, {totalFutures, obj.destination}); + Res res{}; + if (!dfiToDUSD) { + res = mnview.StoreFuturesUserValues({height, obj.owner, txn}, {totalFutures, obj.destination}); + } else { + res = mnview.StoreFuturesDUSD({height, obj.owner, txn}, totalFutures.nValue); + } if (!res) { return res; } @@ -1603,7 +1638,11 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return res; } - res = mnview.StoreFuturesUserValues({height, obj.owner, txn}, {obj.source, obj.destination}); + if (!dfiToDUSD) { + res = mnview.StoreFuturesUserValues({height, obj.owner, txn}, {obj.source, obj.destination}); + } else { + res = mnview.StoreFuturesDUSD({height, obj.owner, txn}, obj.source.nValue); + } if (!res) { return res; } @@ -3375,7 +3414,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor 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); - CDataStructureV0 directBurnKey{AttributeTypes::Param, ParamIDs::DFIP2206A, DFIPKeys::DirectLoanDUSDBurn}; + CDataStructureV0 directBurnKey{AttributeTypes::Param, ParamIDs::DFIP2206A, DFIPKeys::DUSDLoanBurn}; auto directLoanBurn = attributes->GetValue(directBurnKey, false); res = SwapToDFIorDUSD(mnview, paybackTokenId, subInToken, obj.from, consensus.burnAddress, height, !directLoanBurn); @@ -4106,7 +4145,7 @@ Res SwapToDFIorDUSD(CCustomCSView & mnview, DCT_ID tokenId, CAmount amount, CSc if (!attributes) { return Res::Err("Attributes unavailable"); } - CDataStructureV0 directBurnKey{AttributeTypes::Param, ParamIDs::DFIP2206A, DFIPKeys::DirectInterestDUSDBurn}; + CDataStructureV0 directBurnKey{AttributeTypes::Param, ParamIDs::DFIP2206A, DFIPKeys::DUSDInterestBurn}; // Direct swap from DUSD to DFI as defined in the CPoolSwapMessage. if (tokenId == dUsdToken->first) { diff --git a/src/masternodes/mn_checks.h b/src/masternodes/mn_checks.h index d761a0c2f03..979a0b54ecd 100644 --- a/src/masternodes/mn_checks.h +++ b/src/masternodes/mn_checks.h @@ -65,7 +65,7 @@ enum class CustomTxType : uint8_t AccountToAccount = 'B', AnyAccountsToAccounts = 'a', SmartContract = 'K', - DFIP2203 = 'Q', + FutureSwap = 'Q', //set governance variable SetGovVariable = 'G', SetGovVariableHeight = 'j', @@ -129,7 +129,7 @@ inline CustomTxType CustomTxCodeToType(uint8_t ch) { case CustomTxType::AccountToAccount: case CustomTxType::AnyAccountsToAccounts: case CustomTxType::SmartContract: - case CustomTxType::DFIP2203: + case CustomTxType::FutureSwap: case CustomTxType::SetGovVariable: case CustomTxType::SetGovVariableHeight: case CustomTxType::AutoAuthPrep: diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index 521f0c3677e..3aba67f1935 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -452,7 +452,7 @@ CWalletCoinsUnlocker GetWallet(const JSONRPCRequest& request) { return CWalletCoinsUnlocker{std::move(wallet)}; } -std::optional GetFuturesBlock() +std::optional GetFuturesBlock(const uint32_t typeId) { LOCK(cs_main); @@ -461,19 +461,21 @@ std::optional GetFuturesBlock() return {}; } - CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::Active}; + CDataStructureV0 activeKey{AttributeTypes::Param, typeId, DFIPKeys::Active}; const auto active = attributes->GetValue(activeKey, false); if (!active) { return {}; } - CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::BlockPeriod}; - CDataStructureV0 rewardKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::RewardPct}; + CDataStructureV0 blockKey{AttributeTypes::Param, typeId, DFIPKeys::BlockPeriod}; + CDataStructureV0 rewardKey{AttributeTypes::Param, typeId, DFIPKeys::RewardPct}; if (!attributes->CheckKey(blockKey) || !attributes->CheckKey(rewardKey)) { return {}; } - return attributes->GetValue(blockKey, CAmount{}); + CDataStructureV0 startKey{AttributeTypes::Param, typeId, DFIPKeys::StartBlock}; + + return FutureSwapHeightInfo{attributes->GetValue(startKey, CAmount{}), attributes->GetValue(blockKey, CAmount{})}; } UniValue setgov(const JSONRPCRequest& request) { diff --git a/src/masternodes/mn_rpc.h b/src/masternodes/mn_rpc.h index 46a15477fe2..c2eabb9802f 100644 --- a/src/masternodes/mn_rpc.h +++ b/src/masternodes/mn_rpc.h @@ -59,6 +59,11 @@ class CWalletCoinsUnlocker { void AddLockedCoin(const COutPoint& coin); }; +struct FutureSwapHeightInfo { + CAmount startBlock; + CAmount blockPeriod; +}; + // common functions bool IsSkippedTx(const uint256& hash); int chainHeight(interfaces::Chain::Lock& locked_chain); @@ -72,6 +77,6 @@ CAccounts SelectAccountsByTargetBalances(const CAccounts& accounts, const CBalan void execTestTx(const CTransaction& tx, uint32_t height, CTransactionRef optAuthTx = {}); CScript CreateScriptForHTLC(const JSONRPCRequest& request, uint32_t &blocks, std::vector& image); CPubKey PublickeyFromString(const std::string &pubkey); -std::optional GetFuturesBlock(); +std::optional GetFuturesBlock(const uint32_t typeId); #endif // DEFI_MASTERNODES_MN_RPC_H diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index fb79d74e565..d4106d54f99 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -229,7 +229,7 @@ auto InitPoolVars(CPoolPairView & view, PoolHeightKey poolKey, uint32_t end) { auto it = view.LowerBound(poolKey); auto height = poolKey.height; - static const uint32_t startHeight = Params().GetConsensus().GreatWorldHeight; + static const uint32_t startHeight = Params().GetConsensus().FortCanningGardensHeight; poolKey.height = std::max(height, startHeight); while (!MatchPoolId(it, poolId) && poolKey.height < end) { diff --git a/src/masternodes/rpc_accounts.cpp b/src/masternodes/rpc_accounts.cpp index a0db0b8c796..ac527022e55 100644 --- a/src/masternodes/rpc_accounts.cpp +++ b/src/masternodes/rpc_accounts.cpp @@ -1815,6 +1815,7 @@ UniValue getburninfo(const JSONRPCRequest& request) { CBalances paybacktokens; CBalances dfi2203Tokens; CBalances dfipaybacktokens; + CBalances dfiToDUSDTokens; LOCK(cs_main); @@ -1842,6 +1843,9 @@ UniValue getburninfo(const JSONRPCRequest& request) { liveKey = {AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2203Burned}; dfi2203Tokens = attributes->GetValue(liveKey, CBalances{}); + + liveKey = {AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2206FBurned}; + dfiToDUSDTokens = attributes->GetValue(liveKey, CBalances{}); } for (const auto& kv : Params().GetConsensus().newNonUTXOSubsidies) { @@ -1919,6 +1923,7 @@ UniValue getburninfo(const JSONRPCRequest& request) { result.pushKV("emissionburn", ValueFromAmount(burnt)); result.pushKV("dfip2203", AmountsToJSON(dfi2203Tokens.balances)); + result.pushKV("dfip2206f", AmountsToJSON(dfiToDUSDTokens.balances)); return GetRPCResultCache() .Set(request, result); @@ -2125,7 +2130,7 @@ UniValue futureswap(const JSONRPCRequest& request) { // Encode CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::DFIP2203) + metadata << static_cast(CustomTxType::FutureSwap) << msg; CScript scriptMeta; @@ -2216,7 +2221,7 @@ UniValue withdrawfutureswap(const JSONRPCRequest& request) { // Encode CDataStream metadata(DfTxMarker, SER_NETWORK, PROTOCOL_VERSION); - metadata << static_cast(CustomTxType::DFIP2203) + metadata << static_cast(CustomTxType::FutureSwap) << msg; CScript scriptMeta; @@ -2426,6 +2431,90 @@ UniValue logaccountbalances(const JSONRPCRequest& request) { return result; } +UniValue listpendingdusdswaps(const JSONRPCRequest& request) { + RPCHelpMan{"listpendingdusdswaps", + "Get all pending DFI-to_DUSD swaps.\n", + {}, + RPCResult{ + "\"json\" (string) array containing json-objects having following fields:\n" + "[{\n" + " owner : \"address\"\n" + " amount : n.nnnnnnnn\n" + "}...]\n" + }, + RPCExamples{ + HelpExampleCli("listpendingdusdswaps", "") + }, + }.Check(request); + + if (auto res = GetRPCResultCache().TryGet(request)) return *res; + UniValue listFutures{UniValue::VARR}; + + LOCK(cs_main); + + pcustomcsview->ForEachFuturesDUSD([&](const CFuturesUserKey& key, const CAmount& amount){ + CTxDestination dest; + ExtractDestination(key.owner, dest); + if (!IsValidDestination(dest)) { + return true; + } + + UniValue value{UniValue::VOBJ}; + value.pushKV("owner", EncodeDestination(dest)); + value.pushKV("amount", ValueFromAmount(amount)); + + listFutures.push_back(value); + + return true; + }); + + return GetRPCResultCache().Set(request, listFutures); +} + +UniValue getpendingdusdswaps(const JSONRPCRequest& request) { + RPCHelpMan{"getpendingdusdswaps", + "Get specific pending DFI-to-DUSD swap.\n", + { + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "Address to get all pending future swaps"}, + }, + RPCResult{ + "{\n" + " owner : \"address\"\n" + " amount : n.nnnnnnnn\n" + "}\n" + }, + RPCExamples{ + HelpExampleCli("getpendingfutureswaps", "address") + }, + }.Check(request); + + if (auto res = GetRPCResultCache().TryGet(request)) return *res; + UniValue listValues{UniValue::VARR}; + + const auto owner = DecodeScript(request.params[0].get_str()); + + LOCK(cs_main); + + CAmount total{}; + pcustomcsview->ForEachFuturesDUSD([&](const CFuturesUserKey& key, const CAmount& amount) { + + if (key.owner == owner) { + total += amount; + } + + return true; + }, {static_cast(::ChainActive().Height()), owner, std::numeric_limits::max()}); + + UniValue obj{UniValue::VOBJ}; + if (total) { + obj.pushKV("owner", request.params[0].get_str()); + obj.pushKV("amount", ValueFromAmount(total)); + } + + return GetRPCResultCache().Set(request, obj); +} + + static const CRPCCommand commands[] = { // category name actor (function) params @@ -2449,6 +2538,8 @@ static const CRPCCommand commands[] = {"accounts", "withdrawfutureswap", &withdrawfutureswap, {"address", "amount", "destination", "inputs"}}, {"accounts", "listpendingfutureswaps", &listpendingfutureswaps, {}}, {"accounts", "getpendingfutureswaps", &getpendingfutureswaps, {"address"}}, + {"accounts", "listpendingdusdswaps", &listpendingdusdswaps, {}}, + {"accounts", "getpendingdusdswaps", &getpendingdusdswaps, {"address"}}, {"hidden", "logaccountbalances", &logaccountbalances, {"logfile", "rpcresult"}}, }; diff --git a/src/masternodes/rpc_oracles.cpp b/src/masternodes/rpc_oracles.cpp index 34bef72b729..85a52fa791d 100644 --- a/src/masternodes/rpc_oracles.cpp +++ b/src/masternodes/rpc_oracles.cpp @@ -4,6 +4,8 @@ #include +#include + extern CTokenCurrencyPair DecodePriceFeedUni(const UniValue& value); extern CTokenCurrencyPair DecodePriceFeedString(const std::string& value); /// names of oracle json fields @@ -1153,16 +1155,44 @@ UniValue getfutureswapblock(const JSONRPCRequest& request) { const auto currentHeight = ::ChainActive().Height(); - const auto block = GetFuturesBlock(); - if (!block) { + const auto block = GetFuturesBlock(ParamIDs::DFIP2203); + if (!block || block->blockPeriod == 0) { return 0; } - auto res = currentHeight + (*block - (currentHeight % *block)); + auto res = currentHeight < block->startBlock ? block->startBlock + block->blockPeriod : + currentHeight + (block->blockPeriod - ((currentHeight - block->startBlock) % block->blockPeriod)); return GetRPCResultCache().Set(request, res); } +UniValue getdusdswapblock(const JSONRPCRequest& request) { + RPCHelpMan{"getdusdswapblock", + "Get the next block that DFI to DUSD swap will execute on.\n", + {}, + RPCResult{ + "n (numeric) DFI to DUSD swap execution block. Zero if not set.\n" + }, + RPCExamples{ + HelpExampleCli("getdusdswapblock", "") + }, + }.Check(request); + + if (auto res = GetRPCResultCache().TryGet(request)) return *res; + LOCK(cs_main); + + const auto currentHeight = ::ChainActive().Height(); + + const auto block = GetFuturesBlock(ParamIDs::DFIP2206F); + if (!block || block->blockPeriod == 0) { + return 0; + } + + auto res = currentHeight < block->startBlock ? block->startBlock + block->blockPeriod : + currentHeight + (block->blockPeriod - ((currentHeight - block->startBlock) % block->blockPeriod)); + return GetRPCResultCache().Set(request, res); +} + static const CRPCCommand commands[] = { @@ -1179,7 +1209,8 @@ static const CRPCCommand commands[] = {"oracles", "listprices", &listprices, {"pagination"}}, {"oracles", "getfixedintervalprice", &getfixedintervalprice, {"fixedIntervalPriceId"}}, {"oracles", "listfixedintervalprices", &listfixedintervalprices, {"pagination"}}, - {"oracles", "getfutureswapblock", &getfutureswapblock, {}}, + {"oracles", "getfutureswapblock", &getfutureswapblock, {}}, + {"oracles", "getdusdswapblock", &getdusdswapblock, {}}, }; void RegisterOraclesRPCCommands(CRPCTable& tableRPC) { diff --git a/src/validation.cpp b/src/validation.cpp index b80e500da58..d79a71604af 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -286,7 +286,7 @@ bool CheckSequenceLocks(const CTxMemPool& pool, const CTransaction& tx, int flag index.pprev = tip; // CheckSequenceLocks() uses ::ChainActive().Height()+1 to evaluate // height based locks because when SequenceLocks() is called within - // ConnectBlock(), the height of the block *being* + // ConnectBlock (), the height of the block *being* // evaluated is what is used. // Thus if we want to know if a transaction can be part of the // *next* block, we need to use one more than ::ChainActive().Height() @@ -1922,7 +1922,7 @@ static bool WriteUndoDataForBlock(const CBlockUndo& blockundo, CValidationState& if (pindex->GetUndoPos().IsNull()) { FlatFilePos _pos; if (!FindUndoPos(state, pindex->nFile, _pos, ::GetSerializeSize(blockundo, CLIENT_VERSION) + 40)) - return error("ConnectBlock(): FindUndoPos failed"); + return error("%s: FindUndoPos failed", __func__); if (!UndoWriteToDisk(blockundo, _pos, pindex->pprev->GetBlockHash(), chainparams.MessageStart())) return AbortNode(state, "Failed to write undo data"); @@ -2460,7 +2460,7 @@ bool StopOrInterruptConnect(const CBlockIndex *pIndex, CValidationState& state) } state.Invalid( ValidationInvalidReason::CONSENSUS, - error("ConnectBlock(): user interrupt"), + error("%s: user interrupt", __func__), REJECT_INVALID, "user-interrupt-request"); return true; @@ -2470,7 +2470,7 @@ bool StopOrInterruptConnect(const CBlockIndex *pIndex, CValidationState& state) } /** Apply the effects of this block (with given index) on the UTXO set represented by coins. - * Validity checks that depend on the UTXO set are also done; ConnectBlock() + * Validity checks that depend on the UTXO set are also done; ConnectBlock () * can fail if those validity checks fail (among other reasons). */ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, CCustomCSView& mnview, const CChainParams& chainparams, std::vector & rewardedAnchors, bool fJustCheck) @@ -2576,15 +2576,15 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (nodePtr->mintedBlocks + 1 != block.mintedBlocks) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): masternode's %s mintedBlocks should be %d, got %d!", - nodeId->ToString(), nodePtr->mintedBlocks + 1, block.mintedBlocks), REJECT_INVALID, "bad-minted-blocks"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: masternode's %s mintedBlocks should be %d, got %d!", + __func__, nodeId->ToString(), nodePtr->mintedBlocks + 1, block.mintedBlocks), REJECT_INVALID, "bad-minted-blocks"); } uint256 stakeModifierPrevBlock = pindex->pprev == nullptr ? uint256() : pindex->pprev->stakeModifier; if (block.stakeModifier != pos::ComputeStakeModifier(stakeModifierPrevBlock, nodePtr->operatorAuthAddress)) { return state.Invalid( ValidationInvalidReason::CONSENSUS, - error("ConnectBlock(): block's stake Modifier should be %d, got %d!", - block.stakeModifier.ToString(), pos::ComputeStakeModifier(stakeModifierPrevBlock, nodePtr->operatorAuthAddress).ToString()), + error("%s: block's stake Modifier should be %d, got %d!", + __func__, block.stakeModifier.ToString(), pos::ComputeStakeModifier(stakeModifierPrevBlock, nodePtr->operatorAuthAddress).ToString()), REJECT_INVALID, "bad-minted-blocks"); } @@ -2704,7 +2704,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl for (const auto& tx : block.vtx) { for (size_t o = 0; o < tx->vout.size(); o++) { if (view.HaveCoin(COutPoint(tx->GetHash(), o))) { - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): tried to overwrite transaction"), REJECT_INVALID, "bad-txns-BIP30"); + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: tried to overwrite transaction", __func__), REJECT_INVALID, "bad-txns-BIP30"); } } } @@ -2785,7 +2785,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // * witness (when witness enabled in flags and excludes coinbase) nSigOpsCost += GetTransactionSigOpCost(tx, view, flags); if (nSigOpsCost > MAX_BLOCK_SIGOPS_COST) - return state.Invalid(ValidationInvalidReason::CONSENSUS, error("ConnectBlock(): too many sigops"), + return state.Invalid(ValidationInvalidReason::CONSENSUS, error("%s: too many sigops", __func__), REJECT_INVALID, "bad-blk-sigops"); txdata.emplace_back(tx); @@ -2804,8 +2804,8 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl state.Invalid(ValidationInvalidReason::CONSENSUS, false, state.GetRejectCode(), state.GetRejectReason(), state.GetDebugMessage()); } - return error("ConnectBlock(): CheckInputs on %s failed with %s", - tx.GetHash().ToString(), FormatStateMessage(state)); + return error("%s: CheckInputs on %s failed with %s", + __func__, tx.GetHash().ToString(), FormatStateMessage(state)); } CHistoryWriters writers{paccountHistoryDB.get(), pburnHistoryDB.get(), pvaultHistoryDB.get()}; @@ -2813,12 +2813,12 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl if (!res.ok && (res.code & CustomTxErrCodes::Fatal)) { if (pindex->nHeight >= chainparams.GetConsensus().EunosHeight) { return state.Invalid(ValidationInvalidReason::CONSENSUS, - error("ConnectBlock(): ApplyCustomTx on %s failed with %s", - tx.GetHash().ToString(), res.msg), REJECT_CUSTOMTX, "bad-custom-tx"); + error("%s: ApplyCustomTx on %s failed with %s", + __func__, tx.GetHash().ToString(), res.msg), REJECT_CUSTOMTX, "bad-custom-tx"); } else { // we will never fail, but skip, unless transaction mints UTXOs - return error("ConnectBlock(): ApplyCustomTx on %s failed with %s", - tx.GetHash().ToString(), res.msg); + return error("%s: ApplyCustomTx on %s failed with %s", + __func__, tx.GetHash().ToString(), res.msg); } } // log @@ -2840,7 +2840,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl ResVal res = ApplyAnchorRewardTxPlus(mnview, tx, pindex->nHeight, metadata, chainparams.GetConsensus()); if (!res.ok) { return state.Invalid(ValidationInvalidReason::CONSENSUS, - error("ConnectBlock(): %s", res.msg), + error("%s: %s", __func__, res.msg), REJECT_INVALID, res.dbgMsg); } rewardedAnchors.push_back(*res.val); @@ -2854,7 +2854,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl ResVal res = ApplyAnchorRewardTx(mnview, tx, pindex->nHeight, pindex->pprev ? pindex->pprev->stakeModifier : uint256(), metadata, chainparams.GetConsensus()); if (!res.ok) { return state.Invalid(ValidationInvalidReason::CONSENSUS, - error("ConnectBlock(): %s", res.msg), + error("%s: %s", __func__, res.msg), REJECT_INVALID, res.dbgMsg); } rewardedAnchors.push_back(*res.val); @@ -2888,7 +2888,7 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl Res res = ApplyGeneralCoinbaseTx(accountsView, *block.vtx[0], pindex->nHeight, nFees, chainparams.GetConsensus()); if (!res.ok) { return state.Invalid(ValidationInvalidReason::CONSENSUS, - error("ConnectBlock(): %s", res.msg), + error("%s: %s", __func__, res.msg), REJECT_INVALID, res.dbgMsg); } @@ -3024,6 +3024,9 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // Loan splits ProcessTokenSplits(block, pindex, cache, creationTxs, chainparams); + // DFI-to-DUSD swaps + ProcessFuturesDUSD(pindex, cache, chainparams); + // construct undo auto& flushable = cache.GetStorage(); auto undo = CUndo::Construct(mnview.GetStorage(), flushable.GetRaw()); @@ -3487,19 +3490,22 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache } CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::Active}; - const auto active = attributes->GetValue(activeKey, false); - if (!active) { + CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::BlockPeriod}; + CDataStructureV0 rewardKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::RewardPct}; + if (!attributes->GetValue(activeKey, false) || + !attributes->CheckKey(blockKey) || + !attributes->CheckKey(rewardKey)) { return; } - CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::BlockPeriod}; - CDataStructureV0 rewardKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::RewardPct}; - if (!attributes->CheckKey(blockKey) || !attributes->CheckKey(rewardKey)) { + CDataStructureV0 startKey{AttributeTypes::Param, ParamIDs::DFIP2203, DFIPKeys::StartBlock}; + const auto startBlock = attributes->GetValue(startKey, CAmount{}); + if (pindex->nHeight < startBlock) { return; } const auto blockPeriod = attributes->GetValue(blockKey, CAmount{}); - if (pindex->nHeight % blockPeriod != 0) { + if ((pindex->nHeight - startBlock) % blockPeriod != 0) { return; } @@ -3598,7 +3604,7 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache burned.Add(futuresValues.source); minted.Add(destination); dUsdToTokenSwapsCounter++; - LogPrint(BCLog::FUTURESWAP, "ProcessFutures(): Owner %s source %s destination %s\n", + LogPrint(BCLog::FUTURESWAP, "ProcessFutures (): Owner %s source %s destination %s\n", key.owner.GetHex(), futuresValues.source.ToString(), destination.ToString()); } } catch (const std::out_of_range&) { @@ -3618,7 +3624,7 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache burned.Add(futuresValues.source); minted.Add(destination); tokenTodUsdSwapsCounter++; - LogPrint(BCLog::FUTURESWAP, "ProcessFutures(): Payment Owner %s source %s destination %s\n", + LogPrint(BCLog::FUTURESWAP, "ProcessFutures (): Payment Owner %s source %s destination %s\n", key.owner.GetHex(), futuresValues.source.ToString(), destination.ToString()); } catch (const std::out_of_range&) { unpaidContracts.emplace(key, futuresValues); @@ -3630,7 +3636,7 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache return true; }, {static_cast(pindex->nHeight), {}, std::numeric_limits::max()}); - const auto contractAddressValue = GetFutureSwapContractAddress(); + const auto contractAddressValue = GetFutureSwapContractAddress(SMART_CONTRACT_DFIP_2203); assert(contractAddressValue); CDataStructureV0 liveKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2203Current}; @@ -3652,8 +3658,8 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache addView.AddBalance(key.owner, value.source); addView.Flush(); - LogPrint(BCLog::FUTURESWAP, "ProcessFutures(): Refund Owner %s source %s destination %s\n", - key.owner.GetHex(), value.source.ToString(), value.source.ToString()); + LogPrint(BCLog::FUTURESWAP, "%s: Refund Owner %s value %s\n", + __func__, key.owner.GetHex(), value.source.ToString()); balances.Sub(value.source); } @@ -3676,6 +3682,143 @@ void CChainState::ProcessFutures(const CBlockIndex* pindex, CCustomCSView& cache cache.SetVariable(*attributes); } + +void CChainState::ProcessFuturesDUSD(const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams) +{ + if (pindex->nHeight < chainparams.GetConsensus().FortCanningGardensHeight) { + return; + } + + auto attributes = cache.GetAttributes(); + if (!attributes) { + return; + } + + CDataStructureV0 activeKey{AttributeTypes::Param, ParamIDs::DFIP2206F, DFIPKeys::Active}; + CDataStructureV0 blockKey{AttributeTypes::Param, ParamIDs::DFIP2206F, DFIPKeys::BlockPeriod}; + CDataStructureV0 rewardKey{AttributeTypes::Param, ParamIDs::DFIP2206F, DFIPKeys::RewardPct}; + if (!attributes->GetValue(activeKey, false) || + !attributes->CheckKey(blockKey) || + !attributes->CheckKey(rewardKey)) { + return; + } + + CDataStructureV0 startKey{AttributeTypes::Param, ParamIDs::DFIP2206F, DFIPKeys::StartBlock}; + const auto startBlock = attributes->GetValue(startKey, CAmount{}); + if (pindex->nHeight < startBlock) { + return; + } + + const auto blockPeriod = attributes->GetValue(blockKey, CAmount{}); + if ((pindex->nHeight - startBlock) % blockPeriod != 0) { + return; + } + + auto time = GetTimeMillis(); + LogPrintf("Future swap DUSD settlement in progress.. (height: %d)\n", pindex->nHeight); + + const auto rewardPct = attributes->GetValue(rewardKey, CAmount{}); + const auto discount{COIN - rewardPct}; + + const auto useNextPrice{false}, requireLivePrice{true}; + const auto discountPrice = cache.GetAmountInCurrency(discount, {"DFI", "USD"}, useNextPrice, requireLivePrice); + + CDataStructureV0 liveKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2206FCurrent}; + auto balances = attributes->GetValue(liveKey, CBalances{}); + + const auto contractAddressValue = GetFutureSwapContractAddress(SMART_CONTRACT_DFIP2206F); + assert(contractAddressValue); + + const auto dfiID{DCT_ID{}}; + + if (!discountPrice) { + std::vector> refunds; + + cache.ForEachFuturesDUSD([&](const CFuturesUserKey& key, const CAmount& amount){ + refunds.emplace_back(key, amount); + return true; + }, {static_cast(pindex->nHeight), {}, std::numeric_limits::max()}); + + for (const auto& [key, amount] : refunds) { + cache.EraseFuturesDUSD(key); + + const CTokenAmount source{dfiID, amount}; + + CHistoryWriters subWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter subView(cache, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &subWriters); + subView.SubBalance(*contractAddressValue, source); + subView.Flush(); + + CHistoryWriters addWriters{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter addView(cache, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapRefund), &addWriters); + addView.AddBalance(key.owner, source); + addView.Flush(); + + LogPrint(BCLog::FUTURESWAP, "%s: Refund Owner %s value %s\n", + __func__, key.owner.GetHex(), source.ToString()); + balances.Sub(source); + } + + if (!refunds.empty()) { + attributes->SetValue(liveKey, std::move(balances)); + } + + cache.SetVariable(*attributes); + + LogPrintf("Future swap DUSD refunded due to no live price: (%d refunds (height: %d, time: %dms)\n", + refunds.size(), pindex->nHeight, GetTimeMillis() - time); + + return; + } + + CDataStructureV0 burnKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2206FBurned}; + CDataStructureV0 mintedKey{AttributeTypes::Live, ParamIDs::Economy, EconomyKeys::DFIP2206FMinted}; + + auto burned = attributes->GetValue(burnKey, CBalances{}); + auto minted = attributes->GetValue(mintedKey, CBalances{}); + + std::set deletionPending; + + auto swapCounter{0}; + + cache.ForEachFuturesDUSD([&](const CFuturesUserKey& key, const CAmount& amount){ + + CHistoryWriters writers{paccountHistoryDB.get(), nullptr, nullptr}; + CAccountsHistoryWriter view(cache, pindex->nHeight, GetNextAccPosition(), {}, uint8_t(CustomTxType::FutureSwapExecution), &writers); + + deletionPending.insert(key); + + const auto tokenDUSD = view.GetToken("DUSD"); + assert(tokenDUSD); + + const auto total = MultiplyAmounts(amount, discountPrice); + view.AddMintedTokens(tokenDUSD->first, total); + CTokenAmount destination{tokenDUSD->first, total}; + view.AddBalance(key.owner, destination); + burned.Add({dfiID, amount}); + minted.Add(destination); + ++swapCounter; + LogPrint(BCLog::FUTURESWAP, "ProcessFuturesDUSD (): Payment Owner %s source %d destination %s\n", + key.owner.GetHex(), amount, destination.ToString()); + + view.Flush(); + + return true; + }, {static_cast(pindex->nHeight), {}, std::numeric_limits::max()}); + + for (const auto& key : deletionPending) { + cache.EraseFuturesDUSD(key); + } + + attributes->SetValue(burnKey, std::move(burned)); + attributes->SetValue(mintedKey, std::move(minted)); + + LogPrintf("Future swap DUSD settlement completed: (%d swaps (height: %d, time: %dms)\n", + swapCounter, pindex->nHeight, GetTimeMillis() - time); + + cache.SetVariable(*attributes); +} + void CChainState::ProcessOracleEvents(const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams){ if (pindex->nHeight < chainparams.GetConsensus().FortCanningHeight) { return; @@ -6006,11 +6149,11 @@ std::vector GenerateCoinbaseCommitment(CBlock& block, const CBloc /** Context-dependent validity checks. * By "context", we mean only the previous block headers, but not the UTXO - * set; UTXO-related validity checks are done in ConnectBlock(). - * NOTE: This function is not currently invoked by ConnectBlock(), so we + * set; UTXO-related validity checks are done in ConnectBlock (). + * NOTE: This function is not currently invoked by ConnectBlock (), so we * should consider upgrade issues if we change which consensus rules are * enforced in this function (eg by adding a new consensus rule). See comment - * in ConnectBlock(). + * in ConnectBlock (). * Note that -reindex-chainstate skips the validation that happens here! */ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& params, const CBlockIndex* pindexPrev, int64_t nAdjustedTime) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -6064,10 +6207,10 @@ static bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationSta return true; } -/** NOTE: This function is not currently invoked by ConnectBlock(), so we +/** NOTE: This function is not currently invoked by ConnectBlock (), so we * should consider upgrade issues if we change which consensus rules are * enforced in this function (eg by adding a new consensus rule). See comment - * in ConnectBlock(). + * in ConnectBlock (). * Note that -reindex-chainstate skips the validation that happens here! */ static bool ContextualCheckBlock(const CBlock& block, CValidationState& state, const Consensus::Params& consensusParams, const CBlockIndex* pindexPrev) diff --git a/src/validation.h b/src/validation.h index e39c859f9ec..84a0336e6ef 100644 --- a/src/validation.h +++ b/src/validation.h @@ -780,6 +780,8 @@ class CChainState { static void ProcessTokenToGovVar(const CBlockIndex *pindex, CCustomCSView &cache, const CChainParams &chainparams); static void ProcessTokenSplits(const CBlock& block, const CBlockIndex* pindex, CCustomCSView& cache, const CreationTxs& creationTxs, const CChainParams& chainparams); + + static void ProcessFuturesDUSD(const CBlockIndex* pindex, CCustomCSView& cache, const CChainParams& chainparams); }; /** Mark a block as precious and reorganize. diff --git a/test/functional/feature_commission_fix.py b/test/functional/feature_commission_fix.py index 5108133aab3..6da059f0702 100755 --- a/test/functional/feature_commission_fix.py +++ b/test/functional/feature_commission_fix.py @@ -15,9 +15,9 @@ class CommissionFixTest(DefiTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.great_world = 200 + self.fortcanninggardens = 200 self.extra_args = [ - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanningmuseumheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', '-fortcanningcrunchheight=1', f'-greatworldheight={self.great_world}', '-subsidytest=1']] + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanningmuseumheight=1', '-fortcanninghillheight=1', '-fortcanningroadheight=1', '-fortcanningcrunchheight=1', f'-fortcanninggardensheight={self.fortcanninggardens}', '-subsidytest=1']] def run_test(self): # Set up test tokens @@ -211,7 +211,7 @@ def pool_commission(self): assert_equal(result[f'{self.idGD}']['reserveA'], Decimal('0')) # Move to fork - self.nodes[0].generate(self.great_world - self.nodes[0].getblockcount()) + self.nodes[0].generate(self.fortcanninggardens - self.nodes[0].getblockcount()) # Add pool liquidity self.nodes[0].addpoolliquidity({ diff --git a/test/functional/feature_futures.py b/test/functional/feature_futures.py index 77565312bb6..fb79cd6b4fc 100755 --- a/test/functional/feature_futures.py +++ b/test/functional/feature_futures.py @@ -18,7 +18,7 @@ class FuturesTest(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', '-fortcanningroadheight=150', '-subsidytest=1']] + self.extra_args = [['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1', '-fortcanningcrunchheight=150', '-fortcanningroadheight=150', '-fortcanninggardensheight=500', '-subsidytest=1']] def run_test(self): self.nodes[0].generate(101) @@ -56,11 +56,24 @@ def run_test(self): # Test list future swap history self.rpc_history() + # Test start block + self.start_block() + + # Test DFI-to-DUSD swap + self.dfi_to_dusd() + + # Test DUSD withdrawals + self.check_withdrawals_dusd() + + # Test refunding of unpaid DFI-to-DUSD contract + self.unpaid_contract_dusd() + def setup_test(self): # Store addresses self.address = self.nodes[0].get_genesis_keys().ownerAuthAddress self.contract_address = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpsqgljc' + self.contract_address_dusd = 'bcrt1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqz7nafu8' # Store interval self.futures_interval = 25 @@ -79,17 +92,18 @@ def setup_test(self): # Setup oracle oracle_address = self.nodes[0].getnewaddress("", "legacy") - price_feeds = [ + self.price_feeds = [ {"currency": "USD", "token": self.symbolDFI}, {"currency": "USD", "token": self.symbolTSLA}, {"currency": "USD", "token": self.symbolGOOGL}, {"currency": "USD", "token": self.symbolTWTR}, {"currency": "USD", "token": self.symbolMSFT} ] - self.oracle_id = self.nodes[0].appointoracle(oracle_address, price_feeds, 10) + self.oracle_id = self.nodes[0].appointoracle(oracle_address, self.price_feeds, 10) self.nodes[0].generate(1) # Create Oracle prices + self.price_dfi = 1 self.price_tsla = 870 self.price_googl = 2600 self.price_twtr = 37 @@ -115,13 +129,14 @@ def setup_test(self): }) # Feed oracle - oracle_prices = [ + self.oracle_prices = [ + {"currency": "USD", "tokenAmount": f'{self.price_dfi}@{self.symbolDFI}'}, {"currency": "USD", "tokenAmount": f'{self.price_tsla}@{self.symbolTSLA}'}, {"currency": "USD", "tokenAmount": f'{self.price_googl}@{self.symbolGOOGL}'}, {"currency": "USD", "tokenAmount": f'{self.price_twtr}@{self.symbolTWTR}'}, {"currency": "USD", "tokenAmount": f'{self.price_msft}@{self.symbolMSFT}'}, ] - self.nodes[0].setoracledata(self.oracle_id, int(time.time()), oracle_prices) + self.nodes[0].setoracledata(self.oracle_id, int(time.time()), self.oracle_prices) self.nodes[0].generate(10) # Set up non-loan token for failure test @@ -175,6 +190,7 @@ def setup_test(self): self.nodes[0].generate(1) # Set token ids + self.idDFI = list(self.nodes[0].gettoken(self.symbolDFI).keys())[0] self.idDUSD = list(self.nodes[0].gettoken(self.symbolDUSD).keys())[0] self.idTSLA = list(self.nodes[0].gettoken(self.symbolTSLA).keys())[0] self.idGOOGL = list(self.nodes[0].gettoken(self.symbolGOOGL).keys())[0] @@ -198,6 +214,10 @@ def futures_setup(self): # Create addresses for futures address = self.nodes[0].getnewaddress("", "legacy") + # Set DFI/DUSD fixed price interval + self.nodes[0].setgov({"ATTRIBUTES":{f'v0/token/{self.idDFI}/fixed_interval_price_id':f'{self.symbolDFI}/USD'}}) + self.nodes[0].generate(1) + # Try futureswap before feature is active assert_raises_rpc_error(-32600, "DFIP2203 not currently active", self.nodes[0].futureswap, address, f'1@{self.symbolTWTR}') @@ -256,7 +276,7 @@ def test_dtoken_to_dusd(self): assert_raises_rpc_error(-32600, f'Could not get source loan token {self.idBTC}', self.nodes[0].futureswap, self.address, f'1@{self.symbolBTC}') assert_raises_rpc_error(-32600, f'DFIP2203 currently disabled for token {self.idDUSD}', self.nodes[0].futureswap, self.address, f'1@{self.symbolDUSD}', int(self.idDUSD)) assert_raises_rpc_error(-32600, f'Could not get destination loan token {self.idBTC}. Set valid destination.', self.nodes[0].futureswap, self.address, f'1@{self.symbolDUSD}', int(self.idBTC)) - assert_raises_rpc_error(-32600, 'Destination should not be set when source amount is a dToken', self.nodes[0].futureswap, self.address, f'1@{self.symbolTSLA}', int(self.idBTC)) + assert_raises_rpc_error(-32600, 'Destination should not be set when source amount is dToken or DFI', self.nodes[0].futureswap, self.address, f'1@{self.symbolTSLA}', int(self.idBTC)) assert_raises_rpc_error(-32600, 'amount 0.00000000 is less than 1.00000000', self.nodes[0].futureswap, address_twtr, f'1@{self.symbolTSLA}') # Create user futures contracts @@ -1023,6 +1043,337 @@ def rpc_history(self): assert_equal(result[0]['owner'], self.list_history[0]['swaps'][0]['address']) assert_equal(result[0]['amounts'], [self.list_history[0]['swaps'][0]['destination']]) + def start_block(self): + + # Restore Oracle + oracle_address = self.nodes[0].getnewaddress("", "legacy") + self.oracle_id = self.nodes[0].appointoracle(oracle_address, self.price_feeds, 10) + self.nodes[0].generate(1) + self.nodes[0].setoracledata(self.oracle_id, int(time.time()), self.oracle_prices) + self.nodes[0].generate(10) + + # Create addresses for futures + address = self.nodes[0].getnewaddress("", "legacy") + + # Fund addresses + self.nodes[0].accounttoaccount(self.address, {address: f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}'}) + self.nodes[0].generate(1) + + # Move to fork block + self.nodes[0].generate(500 - self.nodes[0].getblockcount()) + + # Test setting start block while DFIP2203 still active + assert_raises_rpc_error(-32600, 'Cannot set block period while DFIP2203 is active', self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/start_block':f'{self.nodes[0].getblockcount() + 1}'}}) + + # Disable DFIP2203 + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2203/active':'false'}}) + self.nodes[0].generate(1) + + # Set start block height + self.start_block = self.nodes[0].getblockcount() + 10 + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2203/start_block':f'{self.start_block}'}}) + self.nodes[0].generate(1) + + # Enable DFIP2203 + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2203/active':'true'}}) + self.nodes[0].generate(1) + + # Test cannot create a future swap until active + assert_raises_rpc_error(-32600, f'DFIP2203 not active until block {self.start_block}', self.nodes[0].futureswap, address, f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}', int(self.idTSLA)) + + # Check next future swap block reported correctly via RPC + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval - ((self.nodes[0].getblockcount() - self.start_block) % self.futures_interval)) + self.futures_interval + assert_equal(next_futures_block, self.nodes[0].getfutureswapblock()) + + # Move to futures start height + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + + # Create user futures contract + self.nodes[0].futureswap(address, f'{self.prices[0]["premiumPrice"]}@{self.symbolDUSD}', int(self.idTSLA)) + self.nodes[0].generate(1) + + # Move to one before next future swap block + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval - ((self.nodes[0].getblockcount() - self.start_block) % self.futures_interval)) + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount() - 1) + + # Check calculation is correct for second swap height after setting start block + assert_equal(next_futures_block, self.nodes[0].getfutureswapblock()) + + # Check account still empty + result = self.nodes[0].getaccount(address) + assert_equal(result, []) + + # Move to future swap block + self.nodes[0].generate(1) + + # Check that futures have been executed + result = self.nodes[0].getaccount(address) + assert_equal(result, [f'1.00000000@{self.symbolTSLA}']) + + def dfi_to_dusd(self): + + # Create addresses for futures + address = self.nodes[0].getnewaddress("", "legacy") + + # Fund addresses + self.nodes[0].utxostoaccount({address: f'10@{self.symbolDFI}'}) + self.nodes[0].generate(1) + + # Test swap before DFIP2206F defined + assert_raises_rpc_error(-32600, f'DFIP2206F not currently active', self.nodes[0].futureswap, address, f'1@{self.symbolDFI}') + + # Define DFI-to-DUSD swap period and start block + self.futures_interval_dusd = 10 + self.start_block_dusd = self.nodes[0].getblockcount() + 10 + + # Set DFI-to-DUSD Gov vars + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/dfip2206f/reward_pct': '0.01', + 'v0/params/dfip2206f/block_period': str(self.futures_interval_dusd), + 'v0/params/dfip2206f/start_block': f'{self.start_block_dusd}' + }}) + self.nodes[0].generate(1) + + # Enable DFIP2206F + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206f/active':'true'}}) + self.nodes[0].generate(1) + + # Test cannot create a future swap until active + assert_raises_rpc_error(-32600, f'DFIP2206F not active until block {self.start_block_dusd}', self.nodes[0].futureswap, address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + + # Check next future swap block reported correctly via RPC + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval_dusd - ((self.nodes[0].getblockcount() - self.start_block_dusd) % self.futures_interval_dusd)) + self.futures_interval_dusd + assert_equal(next_futures_block, self.nodes[0].getdusdswapblock()) + + # Move to futures start height + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + + # Check error when DUSD destination not set or incorrect + assert_raises_rpc_error(-32600, f'Incorrect destination defined for DFI swap, DUSD destination expected id: {self.idDUSD}', self.nodes[0].futureswap, address, f'1@{self.symbolDFI}') + assert_raises_rpc_error(-32600, f'Incorrect destination defined for DFI swap, DUSD destination expected id: {self.idDUSD}', self.nodes[0].futureswap, address, f'1@{self.symbolDFI}', f'{self.symbolGOOGL}') + + # Test swap of DFI to DUSD + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].generate(1) + + # Check list pending swaps + result = self.nodes[0].listpendingdusdswaps() + assert_equal(len(result), 1) + assert_equal(result[0]['owner'], address) + assert_equal(result[0]['amount'], Decimal('1.00000000')) + + # Check get pending swaps + result = self.nodes[0].getpendingdusdswaps(address) + assert_equal(result['owner'], address) + assert_equal(result['amount'], Decimal('1.00000000')) + + # Move to swap height + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval_dusd - ((self.nodes[0].getblockcount() - self.start_block_dusd) % self.futures_interval_dusd)) + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + + # Check swap in history + result = self.nodes[0].listaccounthistory('all', {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':1, 'txtype':'q'}) + assert_equal(result[0]['owner'], address) + assert_equal(result[0]['type'], 'FutureSwapExecution') + assert_equal(result[0]['amounts'], [f'0.99000000@{self.symbolDUSD}']) + + # Check result + result = self.nodes[0].getaccount(address)[1] + assert_equal(result, f'0.99000000@{self.symbolDUSD}') + + # Check get/list calls now empty + list = self.nodes[0].listpendingdusdswaps() + get = self.nodes[0].getpendingdusdswaps(address) + assert_equal(list, []) + assert_equal(get, {}) + + # Test swap when DFIP2203 disabled + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/dfip2203/active': 'false', + }}) + self.nodes[0].generate(1) + + # Test swap of DFI to DUSD + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].generate(1) + + # Move to swap height + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval_dusd - ((self.nodes[0].getblockcount() - self.start_block_dusd) % self.futures_interval_dusd)) + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + + # Check result + result = self.nodes[0].getaccount(address)[1] + assert_equal(result, f'{Decimal("0.99000000") * 2}@{self.symbolDUSD}') + + # Check contract address + result = self.nodes[0].getaccount(self.contract_address_dusd) + assert_equal(result, [f'2.00000000@{self.symbolDFI}']) + + # Check live attrs + result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES'] + assert_equal(result['v0/live/economy/dfip2206f_current'], [f'2.00000000@{self.symbolDFI}']) + assert_equal(result['v0/live/economy/dfip2206f_burned'], [f'2.00000000@{self.symbolDFI}']) + assert_equal(result['v0/live/economy/dfip2206f_minted'], [f'{Decimal("0.99000000") * 2}@{self.symbolDUSD}']) + + # Check burn info + result = self.nodes[0].getburninfo() + assert_equal(result['dfip2206f'], [f'2.00000000@{self.symbolDFI}']) + + def check_withdrawals_dusd(self): + + # Create addresses for futures + address = self.nodes[0].getnewaddress("", "legacy") + + # Fund addresses + self.nodes[0].utxostoaccount({address: f'10@{self.symbolDFI}'}) + self.nodes[0].generate(1) + + # Test swap of DFI to DUSD + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].generate(1) + + # Check withdrawal failures + assert_raises_rpc_error(-32600, 'amount 2.00000000 is less than 2.00000001', self.nodes[0].withdrawfutureswap, address, f'2.00000001@{self.symbolDFI}', self.symbolDUSD) + + # Withdraw both contracts + self.nodes[0].withdrawfutureswap(address, f'2.00000000@{self.symbolDFI}', self.symbolDUSD) + self.nodes[0].generate(1) + + # Check user pending swap is empty + result = self.nodes[0].getpendingdusdswaps(address) + assert_equal(result, {}) + + # Try and withdraw smallest amount now contract empty + assert_raises_rpc_error(-32600, 'amount 0.00000000 is less than 0.00000001', self.nodes[0].withdrawfutureswap, address, f'0.00000001@{self.symbolDFI}', self.symbolDUSD) + + # Create new future swap + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].generate(1) + + # Withdraw frm GOOGL everything but one Sat + self.nodes[0].withdrawfutureswap(address, f'0.99999999@{self.symbolDFI}', self.symbolDUSD) + self.nodes[0].generate(1) + + # Check user pending swap + result = self.nodes[0].getpendingdusdswaps(address) + assert_equal(result['owner'], address) + assert_equal(result['amount'], Decimal('0.00000001')) + + # Move to swap height + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval_dusd - ((self.nodes[0].getblockcount() - self.start_block_dusd) % self.futures_interval_dusd)) + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + + # Check result, should just be DFI + result = self.nodes[0].getaccount(address) + assert_equal(len(result), 1) + + # Create two more test swaps + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].generate(1) + + # Withdraw one contract plus 1 Sat of the second one + self.nodes[0].withdrawfutureswap(address, f'1.00000001@{self.symbolDFI}', self.symbolDUSD) + self.nodes[0].generate(1) + + # Check user pending swap + result = self.nodes[0].getpendingdusdswaps(address) + assert_equal(result['owner'], address) + assert_equal(result['amount'], Decimal('0.99999999')) + + # Withdraw one Sat + self.nodes[0].withdrawfutureswap(address, f'0.00000001@{self.symbolDFI}', self.symbolDUSD) + self.nodes[0].generate(1) + + # Check user pending swap + result = self.nodes[0].getpendingdusdswaps(address) + assert_equal(result['owner'], address) + assert_equal(result['amount'], Decimal('0.99999998')) + + # Withdraw all but 2 Sats + self.nodes[0].withdrawfutureswap(address, f'0.99999996@{self.symbolDFI}', self.symbolDUSD) + self.nodes[0].generate(1) + + # Check user pending swap + result = self.nodes[0].getpendingdusdswaps(address) + assert_equal(result['owner'], address) + assert_equal(result['amount'], Decimal('0.00000002')) + + # Move to swap height + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval_dusd - ((self.nodes[0].getblockcount() - self.start_block_dusd) % self.futures_interval_dusd)) + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + + # Check result. + result = self.nodes[0].getaccount(address)[1] + assert_equal(result, f'0.00000001@{self.symbolDUSD}') + + # Check contract address + result = self.nodes[0].getaccount(self.contract_address_dusd) + assert_equal(result, [f'2.00000003@{self.symbolDFI}']) + + # Check live attrs + result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES'] + assert_equal(result['v0/live/economy/dfip2206f_current'], [f'2.00000003@{self.symbolDFI}']) + assert_equal(result['v0/live/economy/dfip2206f_burned'], [f'2.00000003@{self.symbolDFI}']) + assert_equal(result['v0/live/economy/dfip2206f_minted'], [f'{Decimal("0.99000000") * 2 + Decimal("0.00000001")}@{self.symbolDUSD}']) + + # Check burn info + result = self.nodes[0].getburninfo() + assert_equal(result['dfip2206f'], [f'2.00000003@{self.symbolDFI}']) + + def unpaid_contract_dusd(self): + + # Create addresses for futures + address = self.nodes[0].getnewaddress("", "legacy") + + # Fund address + self.nodes[0].utxostoaccount({address: f'1@{self.symbolDFI}'}) + self.nodes[0].generate(1) + + # Create user futures contract + self.nodes[0].futureswap(address, f'1@{self.symbolDFI}', f'{self.symbolDUSD}') + self.nodes[0].generate(1) + + # Remove Oracle + self.nodes[0].removeoracle(self.oracle_id) + self.nodes[0].generate(1) + + # Check balance empty + result = self.nodes[0].getaccount(address) + assert_equal(result, []) + + # Move to next futures block + next_futures_block = self.nodes[0].getblockcount() + (self.futures_interval_dusd - ((self.nodes[0].getblockcount() - self.start_block_dusd) % self.futures_interval_dusd)) + self.nodes[0].generate(next_futures_block - self.nodes[0].getblockcount()) + + # Check user has been refunded + result = self.nodes[0].getaccount(address) + assert_equal(result, [f'1.00000000@{self.symbolDFI}']) + + # Check refund in history + result = self.nodes[0].listaccounthistory('all', {"maxBlockHeight":self.nodes[0].getblockcount(), 'depth':1, 'txtype':'w'}) + result.sort(key = sort_history, reverse = True) + assert_equal(result[0]['owner'], self.contract_address_dusd) + assert_equal(result[0]['type'], 'FutureSwapRefund') + assert_equal(result[0]['amounts'], [f'-1.00000000@{self.symbolDFI}']) + assert_equal(result[1]['owner'], address) + assert_equal(result[1]['type'], 'FutureSwapRefund') + assert_equal(result[1]['amounts'], [f'1.00000000@{self.symbolDFI}']) + + # Check contract address + result = self.nodes[0].getaccount(self.contract_address_dusd) + assert_equal(result, [f'2.00000003@{self.symbolDFI}']) + + # Check live attrs + result = self.nodes[0].listgovs()[8][0]['ATTRIBUTES'] + assert_equal(result['v0/live/economy/dfip2206f_current'], [f'2.00000003@{self.symbolDFI}']) + assert_equal(result['v0/live/economy/dfip2206f_burned'], [f'2.00000003@{self.symbolDFI}']) + assert_equal(result['v0/live/economy/dfip2206f_minted'], [f'{Decimal("0.99000000") * 2 + Decimal("0.00000001")}@{self.symbolDUSD}']) + + # Check burn info + result = self.nodes[0].getburninfo() + assert_equal(result['dfip2206f'], [f'2.00000003@{self.symbolDFI}']) if __name__ == '__main__': FuturesTest().main() diff --git a/test/functional/feature_loan_payback_dfi.py b/test/functional/feature_loan_payback_dfi.py index d41fcaeefaa..aef885be612 100755 --- a/test/functional/feature_loan_payback_dfi.py +++ b/test/functional/feature_loan_payback_dfi.py @@ -377,7 +377,7 @@ def run_test(self): {account0: ["100@" + symbolBTC, "1000@" + symboldUSD]}, account0) self.nodes[0].generate(1) - self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/direct_interest_dusd_burn':'true', 'v0/token/'+idTSLA+'/loan_payback/'+idBTC: 'true', 'v0/token/'+idTSLA+'/loan_payback/'+iddUSD: 'true'}}) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/dusd_interest_burn':'true', 'v0/token/'+idTSLA+'/loan_payback/'+idBTC: 'true', 'v0/token/'+idTSLA+'/loan_payback/'+iddUSD: 'true'}}) self.nodes[0].generate(1) burnAddress = "mfburnZSAM7Gs1hpDeNaMotJXSGA7edosG" @@ -436,7 +436,7 @@ def run_test(self): balanceDFIBefore = balanceDFIAfter burn_before = burn_after - self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/direct_loan_dusd_burn':'true'}}) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/dusd_loan_burn':'true'}}) self.nodes[0].generate(1) self.nodes[0].paybackloan({ diff --git a/test/functional/feature_setgov.py b/test/functional/feature_setgov.py index d341031538d..75d8ea98afc 100755 --- a/test/functional/feature_setgov.py +++ b/test/functional/feature_setgov.py @@ -574,8 +574,8 @@ def run_test(self): assert_raises_rpc_error(-5, "Value must be a positive integer", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/block_period':'-1'}}) assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/5/dfip2203':'not_a_bool'}}) assert_raises_rpc_error(-32600, "No such loan token", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/token/4/dfip2203':'true'}}) - assert_raises_rpc_error(-5, "Unsupported type for DFIP2203", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/premium': '0.025'}}) - assert_raises_rpc_error(-5, "Unsupported type for DFIP2203", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/minswap': '0.025'}}) + assert_raises_rpc_error(-5, "Unsupported type for this DFIP", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/premium': '0.025'}}) + assert_raises_rpc_error(-5, "Unsupported type for this DFIP", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/minswap': '0.025'}}) # Test setting FCR ATTRBIUTES self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2203/reward_pct':'0.05','v0/params/dfip2203/block_period':'20160','v0/token/5/dfip2203':'true'}}) @@ -688,8 +688,8 @@ def run_test(self): self.nodes[0].generate(1) # Check locks - attriutes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] - assert_equal(attriutes['v0/locks/token/5'], 'true') + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/locks/token/5'], 'true') # Set loan token for 4 self.nodes[0].setgov({"ATTRIBUTES":{f'v0/token/4/fixed_interval_price_id':'TSLA/USD', f'v0/token/4/loan_minting_enabled':'true', f'v0/token/4/loan_minting_interest':'1'}}) @@ -764,22 +764,36 @@ def run_test(self): # Try and set Gov vars before fork assert_raises_rpc_error(-32600, "Cannot be set before FortCanningGardensHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/poolpairs/3/token_a_fee_direction': 'both'}}) assert_raises_rpc_error(-32600, "Cannot be set before FortCanningGardensHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/poolpairs/3/token_b_fee_direction': 'both'}}) + assert_raises_rpc_error(-32600, "Cannot be set before FortCanningGardensHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/active':'true'}}) + assert_raises_rpc_error(-32600, "Cannot be set before FortCanningGardensHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/reward_pct':'0.05'}}) + assert_raises_rpc_error(-32600, "Cannot be set before FortCanningGardensHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/block_period':'2880'}}) + assert_raises_rpc_error(-32600, "Cannot be set before FortCanningGardensHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/start_block':'0'}}) + assert_raises_rpc_error(-32600, "Cannot be set before FortCanningGardensHeight", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/start_block':'0'}}) # Move to fork self.nodes[0].generate(1250 - self.nodes[0].getblockcount()) # Test invalid calls assert_raises_rpc_error(-5, "Fee direction value must be both, in or out", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/poolpairs/3/token_a_fee_direction': 'invalid'}}) - assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206a/direct_interest_dusd_burn':'not_a_bool'}}) - assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206a/direct_loan_dusd_burn':'not_a_bool'}}) + assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/active':'not_a_bool'}}) + assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/reward_pct':'not_a_number'}}) + assert_raises_rpc_error(-5, "Amount must be a positive value", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/reward_pct':'-1'}}) + assert_raises_rpc_error(-5, "Percentage exceeds 100%", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/reward_pct':'2'}}) + assert_raises_rpc_error(-5, "Value must be a positive integer", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/block_period':'not_a_number'}}) + assert_raises_rpc_error(-5, "Value must be a positive integer", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/block_period':'-1'}}) + assert_raises_rpc_error(-5, "Unsupported type for this DFIP", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/premium': '0.025'}}) + assert_raises_rpc_error(-5, "Unsupported type for this DFIP", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/minswap': '0.025'}}) + assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot set block period while DFIP2203 is active", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2203/start_block':'0'}}) + assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206a/dusd_interest_burn':'not_a_bool'}}) + assert_raises_rpc_error(-5, "Boolean value must be either \"true\" or \"false\"", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206a/dusd_loan_burn':'not_a_bool'}}) - self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/direct_interest_dusd_burn':'true', 'v0/params/dfip2206a/direct_loan_dusd_burn':'true'}}) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206a/dusd_interest_burn':'true', 'v0/params/dfip2206a/dusd_loan_burn':'true'}}) self.nodes[0].generate(1) # Verify FCR results result = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] - assert_equal(result['v0/params/dfip2206a/direct_interest_dusd_burn'], 'true') - assert_equal(result['v0/params/dfip2206a/direct_loan_dusd_burn'], 'true') + assert_equal(result['v0/params/dfip2206a/dusd_interest_burn'], 'true') + assert_equal(result['v0/params/dfip2206a/dusd_loan_burn'], 'true') # Set fee direction Gov vars self.nodes[0].setgov({"ATTRIBUTES":{ @@ -817,5 +831,40 @@ def run_test(self): assert_equal(result['v0/poolpairs/3/token_a_fee_direction'], 'out') assert_equal(result['v0/poolpairs/3/token_b_fee_direction'], "out") + # Test setting ATTRBIUTES + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206f/reward_pct':'0.05','v0/params/dfip2206f/block_period':'20160'}}) + self.nodes[0].generate(1) + self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2206f/active':'true'}}) + self.nodes[0].generate(1) + + # Verify results + result = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(result['v0/params/dfip2206f/active'], 'true') + assert_equal(result['v0/params/dfip2206f/reward_pct'], '0.05') + assert_equal(result['v0/params/dfip2206f/block_period'], '20160') + + # Check error now DFIP2206F active + assert_raises_rpc_error(-32600, "ATTRIBUTES: Cannot set block period while DFIP2206F is active", self.nodes[0].setgov, {"ATTRIBUTES":{'v0/params/dfip2206f/start_block':'0'}}) + + # Disable DFIP2203 + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/dfip2203/active':'false', + 'v0/params/dfip2206f/active':'false' + }}) + self.nodes[0].generate(1) + + # Set start block height + start_block = self.nodes[0].getblockcount() + 100 + self.nodes[0].setgov({"ATTRIBUTES":{ + 'v0/params/dfip2203/start_block':f'{start_block}', + 'v0/params/dfip2206f/start_block':f'{start_block}' + }}) + self.nodes[0].generate(1) + + # Check start block set as expected + attributes = self.nodes[0].getgov('ATTRIBUTES')['ATTRIBUTES'] + assert_equal(attributes['v0/params/dfip2203/start_block'], f'{start_block}') + assert_equal(attributes['v0/params/dfip2206f/start_block'], f'{start_block}') + if __name__ == '__main__': GovsetTest ().main ()