diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index e61fb88dfe..001f190c61 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -945,9 +945,9 @@ CAmount CCollateralLoans::precisionRatio() const return ratio > maxRatio / precision ? -COIN : CAmount(ratio * precision); } -ResVal CCustomCSView::GetAmountInCurrency(CAmount amount, CTokenCurrencyPair priceFeedId, bool useNextPrice, bool requireLivePrice) +ResVal CCustomCSView::GetAmountInCurrency(CAmount amount, CTokenCurrencyPair priceFeedId, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck) { - auto priceResult = GetValidatedIntervalPrice(priceFeedId, useNextPrice, requireLivePrice); + auto priceResult = GetValidatedIntervalPrice(priceFeedId, useNextPrice, requireLivePrice, skipLockedCheck); if (!priceResult) return priceResult; @@ -960,18 +960,18 @@ ResVal CCustomCSView::GetAmountInCurrency(CAmount amount, CTokenCurrenc } ResVal CCustomCSView::GetLoanCollaterals(CVaultId const& vaultId, CBalances const& collaterals, uint32_t height, - int64_t blockTime, bool useNextPrice, bool requireLivePrice) + int64_t blockTime, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck) { const auto vault = GetVault(vaultId); if (!vault || vault->isUnderLiquidation) return Res::Err("Vault is under liquidation"); CCollateralLoans result{}; - auto res = PopulateLoansData(result, vaultId, height, blockTime, useNextPrice, requireLivePrice); + auto res = PopulateLoansData(result, vaultId, height, blockTime, useNextPrice, requireLivePrice, skipLockedCheck); if (!res) return std::move(res); - res = PopulateCollateralData(result, vaultId, collaterals, height, blockTime, useNextPrice, requireLivePrice); + res = PopulateCollateralData(result, vaultId, collaterals, height, blockTime, useNextPrice, requireLivePrice, skipLockedCheck); if (!res) return std::move(res); @@ -981,12 +981,14 @@ ResVal CCustomCSView::GetLoanCollaterals(CVaultId const& vault return {result, Res::Ok()}; } -ResVal CCustomCSView::GetValidatedIntervalPrice(const CTokenCurrencyPair& priceFeedId, bool useNextPrice, bool requireLivePrice) +ResVal CCustomCSView::GetValidatedIntervalPrice(const CTokenCurrencyPair& priceFeedId, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck) { auto tokenSymbol = priceFeedId.first; auto currency = priceFeedId.second; - auto priceFeed = GetFixedIntervalPrice(priceFeedId); + LogPrint(BCLog::ORACLE,"\t\t%s()->for_loans->%s->", __func__, tokenSymbol); /* Continued */ + + auto priceFeed = GetFixedIntervalPrice(priceFeedId, skipLockedCheck); if (!priceFeed) return std::move(priceFeed); @@ -1002,7 +1004,7 @@ ResVal CCustomCSView::GetValidatedIntervalPrice(const CTokenCurrencyPai } Res CCustomCSView::PopulateLoansData(CCollateralLoans& result, CVaultId const& vaultId, uint32_t height, - int64_t blockTime, bool useNextPrice, bool requireLivePrice) + int64_t blockTime, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck) { const auto loanTokens = GetLoanTokens(vaultId); if (!loanTokens) @@ -1025,7 +1027,7 @@ Res CCustomCSView::PopulateLoansData(CCollateralLoans& result, CVaultId const& v if (totalAmount < 0) { totalAmount = 0; } - const auto amountInCurrency = GetAmountInCurrency(totalAmount, token->fixedIntervalPriceId, useNextPrice, requireLivePrice); + const auto amountInCurrency = GetAmountInCurrency(totalAmount, token->fixedIntervalPriceId, useNextPrice, requireLivePrice, skipLockedCheck); if (!amountInCurrency) return amountInCurrency; @@ -1041,7 +1043,7 @@ Res CCustomCSView::PopulateLoansData(CCollateralLoans& result, CVaultId const& v } Res CCustomCSView::PopulateCollateralData(CCollateralLoans& result, CVaultId const& vaultId, CBalances const& collaterals, - uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice) + uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck) { for (const auto& col : collaterals.balances) { auto tokenId = col.first; @@ -1051,7 +1053,7 @@ Res CCustomCSView::PopulateCollateralData(CCollateralLoans& result, CVaultId con if (!token) return Res::Err("Collateral token with id (%s) does not exist!", tokenId.ToString()); - auto amountInCurrency = GetAmountInCurrency(tokenAmount, token->fixedIntervalPriceId, useNextPrice, requireLivePrice); + auto amountInCurrency = GetAmountInCurrency(tokenAmount, token->fixedIntervalPriceId, useNextPrice, requireLivePrice, skipLockedCheck); if (!amountInCurrency) return std::move(amountInCurrency); diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index 2d8adb8077..d2dc3f1486 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -403,8 +403,8 @@ class CCustomCSView >(); } private: - Res PopulateLoansData(CCollateralLoans& result, CVaultId const& vaultId, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice); - Res PopulateCollateralData(CCollateralLoans& result, CVaultId const& vaultId, CBalances const& collaterals, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice); + Res PopulateLoansData(CCollateralLoans& result, CVaultId const& vaultId, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck); + Res PopulateCollateralData(CCollateralLoans& result, CVaultId const& vaultId, CBalances const& collaterals, uint32_t height, int64_t blockTime, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck); std::unique_ptr accHistoryStore; std::unique_ptr vauHistoryStore; @@ -436,11 +436,11 @@ class CCustomCSView bool CalculateOwnerRewards(CScript const & owner, uint32_t height); - ResVal GetAmountInCurrency(CAmount amount, CTokenCurrencyPair priceFeedId, bool useNextPrice = false, bool requireLivePrice = true); + ResVal GetAmountInCurrency(CAmount amount, CTokenCurrencyPair priceFeedId, bool useNextPrice = false, bool requireLivePrice = true, bool skipLockedCheck = false); - ResVal GetLoanCollaterals(CVaultId const & vaultId, CBalances const & collaterals, uint32_t height, int64_t blockTime, bool useNextPrice = false, bool requireLivePrice = true); + ResVal GetLoanCollaterals(CVaultId const & vaultId, CBalances const & collaterals, uint32_t height, int64_t blockTime, bool useNextPrice = false, bool requireLivePrice = true, bool skipLockedCheck = false); - ResVal GetValidatedIntervalPrice(const CTokenCurrencyPair& priceFeedId, bool useNextPrice, bool requireLivePrice); + ResVal GetValidatedIntervalPrice(const CTokenCurrencyPair& priceFeedId, bool useNextPrice, bool requireLivePrice, bool skipLockedCheck = false); [[nodiscard]] bool AreTokensLocked(const std::set& tokenIds) const override; [[nodiscard]] std::optional GetTokenGuessId(const std::string & str, DCT_ID & id) const override; diff --git a/src/masternodes/oracles.cpp b/src/masternodes/oracles.cpp index 6b777800e0..a031470285 100644 --- a/src/masternodes/oracles.cpp +++ b/src/masternodes/oracles.cpp @@ -152,7 +152,7 @@ Res COracleView::SetFixedIntervalPrice(const CFixedIntervalPrice& fixedIntervalP return Res::Ok(); } -ResVal COracleView::GetFixedIntervalPrice(const CTokenCurrencyPair& fixedIntervalPriceId) +ResVal COracleView::GetFixedIntervalPrice(const CTokenCurrencyPair& fixedIntervalPriceId, bool skipLockedCheck) { CFixedIntervalPrice fixedIntervalPrice; if (!ReadBy(fixedIntervalPriceId, fixedIntervalPrice)) { @@ -172,7 +172,7 @@ ResVal COracleView::GetFixedIntervalPrice(const CTokenCurre loanTokens.insert(secondID.v); } - if (AreTokensLocked(loanTokens)) { + if (AreTokensLocked(loanTokens) && !skipLockedCheck) { return Res::Err("Fixed interval price currently disabled due to locked token"); } diff --git a/src/masternodes/oracles.h b/src/masternodes/oracles.h index bb4fc46c09..0cdbf9af4d 100644 --- a/src/masternodes/oracles.h +++ b/src/masternodes/oracles.h @@ -149,7 +149,7 @@ class COracleView : public virtual CStorageView Res SetFixedIntervalPrice(const CFixedIntervalPrice& PriceFeed); - ResVal GetFixedIntervalPrice(const CTokenCurrencyPair& priceFeedId); + ResVal GetFixedIntervalPrice(const CTokenCurrencyPair& priceFeedId, bool skipLockedCheck = false); void ForEachFixedIntervalPrice(std::function)> callback, const CTokenCurrencyPair& start = {}); diff --git a/src/masternodes/rpc_vault.cpp b/src/masternodes/rpc_vault.cpp index 53aa87cf43..239f0655c3 100644 --- a/src/masternodes/rpc_vault.cpp +++ b/src/masternodes/rpc_vault.cpp @@ -109,7 +109,7 @@ namespace { return auctionObj; } - UniValue VaultToJSON(const CVaultId& vaultId, const CVaultData& vault, const bool verbose = false) { + UniValue VaultToJSON(const CVaultId& vaultId, const CVaultData& vault, const bool verbose = false, const bool skipLockedCheck = false) { UniValue result{UniValue::VOBJ}; auto vaultState = GetVaultState(vaultId, vault); auto height = ::ChainActive().Height(); @@ -132,11 +132,11 @@ namespace { if (!collaterals) collaterals = CBalances{}; - auto blockTime = ::ChainActive().Tip()->GetBlockTime(); bool useNextPrice = false, requireLivePrice = vaultState != VaultState::Frozen; + LogPrint(BCLog::LOAN,"%s():\n", __func__); - if (auto rate = pcustomcsview->GetLoanCollaterals(vaultId, *collaterals, height + 1, blockTime, useNextPrice, requireLivePrice)) { + if (auto rate = pcustomcsview->GetLoanCollaterals(vaultId, *collaterals, height + 1, blockTime, useNextPrice, requireLivePrice, skipLockedCheck)) { collValue = ValueFromUint(rate.val->totalCollaterals); loanValue = ValueFromUint(rate.val->totalLoans); ratioValue = ValueFromAmount(rate.val->precisionRatio()); @@ -172,7 +172,7 @@ namespace { auto totalInterest = TotalInterest(*rate, height + 1); auto value = amount + totalInterest; if (value > 0) { - if (auto priceFeed = pcustomcsview->GetFixedIntervalPrice(token->fixedIntervalPriceId)) { + if (auto priceFeed = pcustomcsview->GetFixedIntervalPrice(token->fixedIntervalPriceId, skipLockedCheck)) { auto price = priceFeed.val->priceRecord[0]; if (const auto interestCalculation = MultiplyAmounts(price, totalInterest)) { totalInterests += interestCalculation; @@ -211,7 +211,7 @@ namespace { result.pushKV("collateralAmounts", AmountsToJSON(collaterals->balances)); result.pushKV("loanAmounts", loanBalances); result.pushKV("interestAmounts", interestAmounts); - if (isVaultTokenLocked){ + if (isVaultTokenLocked && !skipLockedCheck){ collValue = -1; loanValue = -1; interestValue = -1; @@ -228,15 +228,13 @@ namespace { result.pushKV("collateralRatio", collateralRatio); if (verbose) { useNextPrice = true; - if (auto rate = pcustomcsview->GetLoanCollaterals(vaultId, *collaterals, height + 1, blockTime, useNextPrice, requireLivePrice)) { + if (auto rate = pcustomcsview->GetLoanCollaterals(vaultId, *collaterals, height + 1, blockTime, useNextPrice, requireLivePrice, skipLockedCheck)) { nextCollateralRatio = int(rate.val->ratio()); result.pushKV("nextCollateralRatio", nextCollateralRatio); } if (height >= Params().GetConsensus().FortCanningHillHeight) { - if(isVaultTokenLocked){ - result.pushKV("interestPerBlockValue", -1); - } else { - result.pushKV("interestPerBlockValue", GetInterestPerBlockHighPrecisionString(interestsPerBlockValueHighPrecision)); + if(!isVaultTokenLocked && skipLockedCheck) { + totalInterestsPerBlockValue = GetInterestPerBlockHighPrecisionString(interestsPerBlockValueHighPrecision); for (const auto& [id, interestPerBlock] : interestsPerBlockHighPrecission) { auto tokenId = id; auto amountStr = GetInterestPerBlockHighPrecisionString(interestPerBlock); @@ -249,9 +247,17 @@ namespace { } else { interestsPerBlockBalances = AmountsToJSON(interestsPerBlock); totalInterestsPerBlockValue = ValueFromAmount(totalInterestsPerBlock); + } + if (!isVaultTokenLocked){ + // IPB can only be calculated when tokens are not locked. + // Value during split period is hard to predict with the current|next price + result.pushKV("interestsPerBlock", interestsPerBlockBalances); result.pushKV("interestPerBlockValue", totalInterestsPerBlockValue); + } else { + result.pushKV("interestsPerBlock", -1); + result.pushKV("interestPerBlockValue", -1); + } - result.pushKV("interestsPerBlock", interestsPerBlockBalances); } return result; } @@ -463,6 +469,10 @@ UniValue listvaults(const JSONRPCRequest& request) { { "verbose", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Flag for verbose list (default = false), otherwise only ids, ownerAddress, loanSchemeIds and state are listed" + }, + { + "skipLockedCheck", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, + "If true, return collateral and loan values even if token is locked" } }, }, @@ -508,6 +518,7 @@ UniValue listvaults(const JSONRPCRequest& request) { std::string loanSchemeId; VaultState state{VaultState::Unknown}; bool verbose{false}; + bool skipPriceChecks{false}; if (request.params.size() > 0) { UniValue optionsObj = request.params[0].get_obj(); if (!optionsObj["ownerAddress"].isNull()) { @@ -519,9 +530,8 @@ UniValue listvaults(const JSONRPCRequest& request) { if (!optionsObj["state"].isNull()) { state = StringToVaultState(optionsObj["state"].getValStr()); } - if (!optionsObj["verbose"].isNull()) { - verbose = optionsObj["verbose"].getBool(); - } + verbose = optionsObj["verbose"].getBool(); + skipPriceChecks = optionsObj["skipLockedCheck"].getBool(); } // parse pagination @@ -571,7 +581,7 @@ UniValue listvaults(const JSONRPCRequest& request) { vaultObj.pushKV("loanSchemeId", data.schemeId); vaultObj.pushKV("state", VaultStateToString(vaultState)); } else { - vaultObj = VaultToJSON(vaultId, data); + vaultObj = VaultToJSON(vaultId, data, verbose, skipPriceChecks); } valueArr.push_back(vaultObj); limit--; diff --git a/test/functional/feature_token_lock.py b/test/functional/feature_token_lock.py index 169809fab5..69d1876e98 100755 --- a/test/functional/feature_token_lock.py +++ b/test/functional/feature_token_lock.py @@ -280,7 +280,7 @@ def vault_lock(self): assert_equal(result['informativeRatio'], -1) assert_equal(result['collateralRatio'], -1) assert_equal(result['interestPerBlockValue'], -1) - assert_equal(result['interestsPerBlock'], []) + assert_equal(result['interestsPerBlock'], -1) # Deposit to vault should fail assert_raises_rpc_error(-32600, "Fixed interval price currently disabled due to locked token", self.nodes[0].deposittovault, self.vault, self.address, f'100000@{self.symbolDUSD}') diff --git a/test/functional/feature_token_split_usd_value.py b/test/functional/feature_token_split_usd_value.py index fcc6fcb5cb..a96df55fd3 100755 --- a/test/functional/feature_token_split_usd_value.py +++ b/test/functional/feature_token_split_usd_value.py @@ -310,12 +310,29 @@ def test_values_non_zero_with_token_locked(self): assert_equal(vault["interestValue"], -1) assert_equal(vault["informativeRatio"], -1) assert_equal(vault["collateralRatio"], -1) + vaults_values = self.nodes[0].listvaults({"skipLockedCheck": False, "verbose": True}) + for vault in vaults_values: + assert_equal(vault["state"], "frozen") + assert_equal(vault["collateralValue"], -1) + assert_equal(vault["loanValue"], -1) + assert_equal(vault["interestValue"], -1) + assert_equal(vault["informativeRatio"], -1) + assert_equal(vault["collateralRatio"], -1) + assert_equal(vault["interestsPerBlock"], -1) + assert_equal(vault["interestPerBlockValue"], -1) + + vaults_values = self.nodes[0].listvaults({"skipLockedCheck": True, "verbose": True}) + for vault in vaults_values: + assert_greater_than_or_equal(vault["collateralValue"], 0) + assert_greater_than_or_equal(vault["loanValue"], 0) + assert_equal(vault["interestsPerBlock"], -1) + assert_equal(vault["interestPerBlockValue"], -1) def test_values_after_token_unlock(self): # Unlock token self.nodes[0].setgov({"ATTRIBUTES":{f'v0/locks/token/{self.idT1}':'false'}}) self.nodes[0].generate(1) - vaults_values = self.get_vaults_usd_values() + vaults_values = self.nodes[0].listvaults({"skipLockedCheck": True, "verbose": True}) for vault in vaults_values: assert_equal(vault["state"], "active") assert_greater_than_or_equal(vault["collateralValue"], 0) @@ -323,6 +340,8 @@ def test_values_after_token_unlock(self): assert_greater_than_or_equal(vault["interestValue"], 0) assert_greater_than_or_equal(vault["informativeRatio"], 0) assert_greater_than_or_equal(vault["collateralRatio"], 0) + assert_greater_than_or_equal(Decimal(vault["interestsPerBlock"][0].split('@')[0]), 0) + assert_greater_than_or_equal(Decimal(vault["interestPerBlockValue"]), 0) def run_test(self): self.setup()