diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 1fe3fd1bb6d..3fdc63324b5 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -38,8 +38,15 @@ jobs: TRAVIS_BUILD_DIR: ${{ github.workspace }} TRAVIS_COMMIT: ${{ github.sha }} steps: - - name: Checkout - uses: actions/checkout@v1 + - name: Checkout base branch and/or merge + if: github.event_name != 'pull_request' + uses: actions/checkout@v2 + + - name: Checkout pull request head commit + if: github.event_name == 'pull_request' + uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} # This script has been modified to pipe variables to GITHUB_ENV, which allows them to be # used by subsequent steps. diff --git a/src/arith_uint256.cpp b/src/arith_uint256.cpp index b11fffed10e..b290c6b3b09 100644 --- a/src/arith_uint256.cpp +++ b/src/arith_uint256.cpp @@ -5,12 +5,16 @@ #include +#include #include #include #include #include +template +constexpr int base_uint::WIDTH; + template base_uint::base_uint(const std::string& str) { @@ -65,6 +69,27 @@ base_uint& base_uint::operator*=(uint32_t b32) return *this; } +template +base_uint& base_uint::operator*=(int32_t b32) +{ + (*this) *= uint32_t(b32); + return *this; +} + +template +base_uint& base_uint::operator*=(int64_t b64) +{ + (*this) *= base_uint(b64); + return *this; +} + +template +base_uint& base_uint::operator*=(uint64_t b64) +{ + (*this) *= base_uint(b64); + return *this; +} + template base_uint& base_uint::operator*=(const base_uint& b) { @@ -148,13 +173,21 @@ double base_uint::getdouble() const template std::string base_uint::GetHex() const { - return ArithToUint256(*this).GetHex(); + base_blob b; + + for(int x=0; x void base_uint::SetHex(const char* psz) { - *this = UintToArith256(uint256S(psz)); + base_blob a; + + a.SetHex(psz); + for(int x=0; x @@ -207,22 +240,11 @@ base_uint base_uint::sqrt() const return res; } +// Explicit instantiations for base_uint<128> +template class base_uint<128>; + // Explicit instantiations for base_uint<256> -template base_uint<256>::base_uint(const std::string&); -template base_uint<256>& base_uint<256>::operator<<=(unsigned int); -template base_uint<256>& base_uint<256>::operator>>=(unsigned int); -template base_uint<256>& base_uint<256>::operator*=(uint32_t b32); -template base_uint<256>& base_uint<256>::operator*=(const base_uint<256>& b); -template base_uint<256>& base_uint<256>::operator/=(const base_uint<256>& b); -template int base_uint<256>::CompareTo(const base_uint<256>&) const; -template bool base_uint<256>::EqualTo(uint64_t) const; -template double base_uint<256>::getdouble() const; -template std::string base_uint<256>::GetHex() const; -template std::string base_uint<256>::ToString() const; -template void base_uint<256>::SetHex(const char*); -template void base_uint<256>::SetHex(const std::string&); -template unsigned int base_uint<256>::bits() const; -template base_uint<256> base_uint<256>::sqrt() const; +template class base_uint<256>; // This implementation directly uses shifts instead of going // through an intermediate MPI representation. diff --git a/src/arith_uint256.h b/src/arith_uint256.h index 196c000d765..f46d9ec84c4 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -30,6 +30,8 @@ class base_uint uint32_t pn[WIDTH]; public: + template friend class base_uint; + base_uint() { static_assert(BITS/32 > 0 && BITS%32 == 0, "Template parameter BITS must be a positive multiple of 32."); @@ -42,15 +44,7 @@ class base_uint { static_assert(BITS/32 > 0 && BITS%32 == 0, "Template parameter BITS must be a positive multiple of 32."); - for (int i = 0; i < WIDTH; i++) - pn[i] = b.pn[i]; - } - - base_uint& operator=(const base_uint& b) - { - for (int i = 0; i < WIDTH; i++) - pn[i] = b.pn[i]; - return *this; + (*this) = b; } base_uint(uint64_t b) @@ -63,6 +57,12 @@ class base_uint pn[i] = 0; } + template + base_uint(const base_uint& b) + { + (*this) = b; + } + explicit base_uint(const std::string& str); const base_uint operator~() const @@ -84,6 +84,24 @@ class base_uint double getdouble() const; + template + base_uint& operator=(const base_uint& b) + { + auto width = std::min(WIDTH, base_uint::WIDTH); + for (int i = 0; i < width; i++) + pn[i] = b.pn[i]; + for (int i = width; i < WIDTH; i++) + pn[i] = 0; + return *this; + } + + base_uint& operator=(const base_uint& b) + { + for (int i = 0; i < WIDTH; i++) + pn[i] = b.pn[i]; + return *this; + } + base_uint& operator=(uint64_t b) { pn[0] = (unsigned int)b; @@ -165,7 +183,10 @@ class base_uint return *this; } + base_uint& operator*=(int32_t b32); base_uint& operator*=(uint32_t b32); + base_uint& operator*=(int64_t b64); + base_uint& operator*=(uint64_t b64); base_uint& operator*=(const base_uint& b); base_uint& operator/=(const base_uint& b); @@ -215,7 +236,10 @@ class base_uint friend inline const base_uint operator^(const base_uint& a, const base_uint& b) { return base_uint(a) ^= b; } friend inline const base_uint operator>>(const base_uint& a, int shift) { return base_uint(a) >>= shift; } friend inline const base_uint operator<<(const base_uint& a, int shift) { return base_uint(a) <<= shift; } + friend inline const base_uint operator*(const base_uint& a, int32_t b) { return base_uint(a) *= b; } friend inline const base_uint operator*(const base_uint& a, uint32_t b) { return base_uint(a) *= b; } + friend inline const base_uint operator*(const base_uint& a, int64_t b) { return base_uint(a) *= b; } + friend inline const base_uint operator*(const base_uint& a, uint64_t b) { return base_uint(a) *= b; } friend inline bool operator==(const base_uint& a, const base_uint& b) { return memcmp(a.pn, b.pn, sizeof(a.pn)) == 0; } friend inline bool operator!=(const base_uint& a, const base_uint& b) { return memcmp(a.pn, b.pn, sizeof(a.pn)) != 0; } friend inline bool operator>(const base_uint& a, const base_uint& b) { return a.CompareTo(b) > 0; } @@ -248,6 +272,18 @@ class base_uint static_assert(WIDTH >= 2, "Assertion WIDTH >= 2 failed (WIDTH = BITS / 32). BITS is a template parameter."); return pn[0] | (uint64_t)pn[1] << 32; } + + template + void Serialize(Stream& s) const + { + s.write((char*)pn, sizeof(pn)); + } + + template + void Unserialize(Stream& s) + { + s.read((char*)pn, sizeof(pn)); + } }; /** 256-bit unsigned big integer. */ diff --git a/src/masternodes/loan.cpp b/src/masternodes/loan.cpp index a53f302ca2b..75f6bf3b25a 100644 --- a/src/masternodes/loan.cpp +++ b/src/masternodes/loan.cpp @@ -169,15 +169,13 @@ Res CLoanView::EraseLoanScheme(const std::string& loanSchemeID) std::vector loanUpdateHeights; ForEachDelayedLoanScheme([&](const std::pair& key, const CLoanSchemeMessage&) { - if (key.first == loanSchemeID) { + if (key.first == loanSchemeID) loanUpdateHeights.push_back(key.second); - } return true; }); - for (const auto& height : loanUpdateHeights) { + for (const auto& height : loanUpdateHeights) EraseDelayedLoanScheme(loanSchemeID, height); - } // Delete loan scheme EraseBy(loanSchemeID); @@ -195,95 +193,165 @@ void CLoanView::EraseDelayedDestroyScheme(const std::string& loanSchemeID) EraseBy(loanSchemeID); } -boost::optional CLoanView::GetInterestRate(const CVaultId& vaultId, DCT_ID id) +boost::optional CLoanView::GetInterestRate(const CVaultId& vaultId, DCT_ID id, uint32_t height) { - return ReadBy(std::make_pair(vaultId, id)); + if (height >= Params().GetConsensus().FortCanningHillHeight) + return ReadBy(std::make_pair(vaultId, id)); + + if (auto rate = ReadBy(std::make_pair(vaultId, id))) + return ConvertInterestRateToV2(*rate); + + return {}; } -inline CAmount InterestPerBlock(CAmount amount, CAmount tokenInterest, CAmount schemeInterest) +// precision COIN +template +inline T InterestPerBlockCalculationV1(CAmount amount, CAmount tokenInterest, CAmount schemeInterest) { auto netInterest = (tokenInterest + schemeInterest) / 100; // in % - return MultiplyAmounts(netInterest, amount) / (365 * Params().GetConsensus().blocksPerDay()); + static const auto blocksPerYear = T(365) * Params().GetConsensus().blocksPerDay(); + return MultiplyAmounts(netInterest, amount) / blocksPerYear; } -inline float InterestPerBlockFloat(CAmount amount, CAmount tokenInterest, CAmount schemeInterest) +// precisoin COIN ^3 +inline base_uint<128> InterestPerBlockCalculationV2(CAmount amount, CAmount tokenInterest, CAmount schemeInterest) { auto netInterest = (tokenInterest + schemeInterest) / 100; // in % - return MultiplyAmounts(netInterest, amount) / (365.f * Params().GetConsensus().blocksPerDay()); + static const auto blocksPerYear = 365 * Params().GetConsensus().blocksPerDay(); + return arith_uint256(amount) * netInterest * COIN / blocksPerYear; } -CAmount TotalInterest(const CInterestRate& rate, uint32_t height) +static base_uint<128> InterestPerBlockCalculation(CAmount amount, CAmount tokenInterest, CAmount schemeInterest, uint32_t height) { - CAmount interest = rate.interestToHeight + ((height - rate.height) * rate.interestPerBlock); + if (int(height) >= Params().GetConsensus().FortCanningHillHeight) + return InterestPerBlockCalculationV2(amount, tokenInterest, schemeInterest); + + if (int(height) >= Params().GetConsensus().FortCanningMuseumHeight) + return std::ceil(InterestPerBlockCalculationV1(amount, tokenInterest, schemeInterest)); + + return InterestPerBlockCalculationV1(amount, tokenInterest, schemeInterest); +} + +CAmount CeilInterest(const base_uint<128>& value, uint32_t height) +{ + if (int(height) >= Params().GetConsensus().FortCanningHillHeight) { + CAmount amount = (value / base_uint<128>(HIGH_PRECISION_SCALER)).GetLow64(); + amount += CAmount(value != base_uint<128>(amount) * HIGH_PRECISION_SCALER); + return amount; + } + return value.GetLow64(); +} - LogPrint(BCLog::LOAN, "%s(): CInterestRate{.height=%d, .perBlock=%d, .toHeight=%d}, height %d - totalInterest %d\n", __func__, rate.height, rate.interestPerBlock, rate.interestToHeight, height, interest); +base_uint<128> TotalInterestCalculation(const CInterestRateV2& rate, uint32_t height) +{ + auto interest = rate.interestToHeight + ((height - rate.height) * rate.interestPerBlock); + + LogPrint(BCLog::LOAN, "%s(): CInterestRate{.height=%d, .perBlock=%d, .toHeight=%d}, height %d - totalInterest %d\n", __func__, rate.height, InterestPerBlock(rate, height), CeilInterest(rate.interestToHeight, height), height, CeilInterest(interest, height)); return interest; } +static base_uint<128> ToHigherPrecision(CAmount amount, uint32_t height) +{ + base_uint<128> amountHP = amount; + if (int(height) >= Params().GetConsensus().FortCanningHillHeight) + amountHP *= HIGH_PRECISION_SCALER; + + return amountHP; +} + +CAmount TotalInterest(const CInterestRateV2& rate, uint32_t height) +{ + return CeilInterest(TotalInterestCalculation(rate, height), height); +} + +CAmount InterestPerBlock(const CInterestRateV2& rate, uint32_t height) +{ + return CeilInterest(rate.interestPerBlock, height); +} + +void CLoanView::WriteInterestRate(const std::pair& pair, const CInterestRateV2& rate, uint32_t height) +{ + if (height >= Params().GetConsensus().FortCanningHillHeight) + WriteBy(pair, rate); + else + WriteBy(pair, ConvertInterestRateToV1(rate)); +} + Res CLoanView::StoreInterest(uint32_t height, const CVaultId& vaultId, const std::string& loanSchemeID, DCT_ID id, CAmount loanIncreased) { auto scheme = GetLoanScheme(loanSchemeID); - if (!scheme) { + if (!scheme) return Res::Err("No such scheme id %s", loanSchemeID); - } + auto token = GetLoanTokenByID(id); - if (!token) { + if (!token) return Res::Err("No such loan token id %s", id.ToString()); - } - CInterestRate rate{}; - ReadBy(std::make_pair(vaultId, id), rate); + CInterestRateV2 rate{}; + if (auto readRate = GetInterestRate(vaultId, id, height)) + rate = *readRate; - if (rate.height > height || height == 0) { + if (rate.height > height || height == 0) return Res::Err("Cannot store height in the past"); - } + if (rate.height) { LogPrint(BCLog::LOAN,"%s():\n", __func__); - rate.interestToHeight = TotalInterest(rate, height); + rate.interestToHeight = TotalInterestCalculation(rate, height); } - if (int(height) >= Params().GetConsensus().FortCanningMuseumHeight) { - rate.interestPerBlock += std::ceil(InterestPerBlockFloat(loanIncreased, token->interest, scheme->rate)); - } else { - rate.interestPerBlock += InterestPerBlock(loanIncreased, token->interest, scheme->rate); - } + if (int(height) >= Params().GetConsensus().FortCanningHillHeight) { + CBalances amounts; + ReadBy(vaultId, amounts); + rate.interestPerBlock = InterestPerBlockCalculation(amounts.balances[id], token->interest, scheme->rate, height); + } else + rate.interestPerBlock += InterestPerBlockCalculation(loanIncreased, token->interest, scheme->rate, height); + rate.height = height; - WriteBy(std::make_pair(vaultId, id), rate); + WriteInterestRate(std::make_pair(vaultId, id), rate, height); return Res::Ok(); } Res CLoanView::EraseInterest(uint32_t height, const CVaultId& vaultId, const std::string& loanSchemeID, DCT_ID id, CAmount loanDecreased, CAmount interestDecreased) { auto scheme = GetLoanScheme(loanSchemeID); - if (!scheme) { + if (!scheme) return Res::Err("No such scheme id %s", loanSchemeID); - } + auto token = GetLoanTokenByID(id); - if (!token) { + if (!token) return Res::Err("No such loan token id %s", id.ToString()); - } - CInterestRate rate{}; - ReadBy(std::make_pair(vaultId, id), rate); + CInterestRateV2 rate{}; + if (auto readRate = GetInterestRate(vaultId, id, height)) + rate = *readRate; - if (rate.height > height) { + if (rate.height > height) return Res::Err("Cannot store height in the past"); - } - if (rate.height == 0) { + + if (rate.height == 0) return Res::Err("Data mismatch height == 0"); - } + + auto interestDecreasedHP = ToHigherPrecision(interestDecreased, height); + LogPrint(BCLog::LOAN,"%s():\n", __func__); - rate.interestToHeight = std::max(CAmount{0}, TotalInterest(rate, height) - interestDecreased); + auto interestToHeight = TotalInterestCalculation(rate, height); + rate.interestToHeight = interestToHeight < interestDecreasedHP ? 0 + : interestToHeight - interestDecreasedHP; rate.height = height; - if (int(height) >= Params().GetConsensus().FortCanningMuseumHeight) { - rate.interestPerBlock = std::max(CAmount{0}, rate.interestPerBlock - CAmount(std::ceil(InterestPerBlockFloat(loanDecreased, token->interest, scheme->rate)))); + + if (int(height) >= Params().GetConsensus().FortCanningHillHeight) { + CBalances amounts; + ReadBy(vaultId, amounts); + rate.interestPerBlock = InterestPerBlockCalculation(amounts.balances[id], token->interest, scheme->rate, height); } else { - rate.interestPerBlock = std::max(CAmount{0}, rate.interestPerBlock - InterestPerBlock(loanDecreased, token->interest, scheme->rate)); + auto interestPerBlock = InterestPerBlockCalculation(loanDecreased, token->interest, scheme->rate, height); + rate.interestPerBlock = rate.interestPerBlock < interestPerBlock ? 0 + : rate.interestPerBlock - interestPerBlock; } - WriteBy(std::make_pair(vaultId, id), rate); + WriteInterestRate(std::make_pair(vaultId, id), rate, height); return Res::Ok(); } @@ -294,52 +362,95 @@ void CLoanView::ForEachVaultInterest(std::function callback, const CVaultId& vaultId, DCT_ID id) +{ + ForEach, CInterestRateV2>([&](const std::pair& pair, CInterestRateV2 rate) { + return callback(pair.first, pair.second, rate); + }, std::make_pair(vaultId, id)); +} + +template +void DeleteInterest(CLoanView& view, const CVaultId& vaultId) { std::vector> keysToDelete; - auto it = LowerBound(std::make_pair(vaultId, DCT_ID{0})); - for (; it.Valid() && it.Key().first == vaultId; it.Next()) { + auto it = view.LowerBound(std::make_pair(vaultId, DCT_ID{0})); + for (; it.Valid() && it.Key().first == vaultId; it.Next()) keysToDelete.push_back(it.Key()); - } - for (const auto& key : keysToDelete) { - EraseBy(key); - } + for (const auto& key : keysToDelete) + view.EraseBy(key); +} + +Res CLoanView::DeleteInterest(const CVaultId& vaultId, uint32_t height) +{ + if (height >= Params().GetConsensus().FortCanningHillHeight) + ::DeleteInterest(*this, vaultId); + else + ::DeleteInterest(*this, vaultId); + return Res::Ok(); } +void CLoanView::RevertInterestRateToV1() +{ + std::vector> pairs; + + ForEach, CInterestRateV2>([&](const std::pair& pair, CLazySerialize) { + pairs.emplace_back(pair); + return true; + }); + + for (auto it = pairs.begin(); it != pairs.end(); it = pairs.erase(it)) { + EraseBy(*it); + } +} + +void CLoanView::MigrateInterestRateToV2(CVaultView &view, uint32_t height) +{ + ForEachVaultInterest([&](const CVaultId& vaultId, DCT_ID tokenId, CInterestRate rate) { + auto newRate = ConvertInterestRateToV2(rate); + newRate.interestPerBlock *= HIGH_PRECISION_SCALER; + newRate.interestToHeight *= HIGH_PRECISION_SCALER; + WriteBy(std::make_pair(vaultId, tokenId), newRate); + + const auto vault = view.GetVault(vaultId); + StoreInterest(height, vaultId, vault->schemeId, tokenId, 0); + return true; + }); +} + Res CLoanView::AddLoanToken(const CVaultId& vaultId, CTokenAmount amount) { - if (!GetLoanTokenByID(amount.nTokenId)) { + if (!GetLoanTokenByID(amount.nTokenId)) return Res::Err("No such loan token id %s", amount.nTokenId.ToString()); - } + CBalances amounts; ReadBy(vaultId, amounts); auto res = amounts.Add(amount); - if (!res) { + if (!res) return res; - } - if (!amounts.balances.empty()) { + + if (!amounts.balances.empty()) WriteBy(vaultId, amounts); - } + return Res::Ok(); } Res CLoanView::SubLoanToken(const CVaultId& vaultId, CTokenAmount amount) { - if (!GetLoanTokenByID(amount.nTokenId)) { + if (!GetLoanTokenByID(amount.nTokenId)) return Res::Err("No such loan token id %s", amount.nTokenId.ToString()); - } + auto amounts = GetLoanTokens(vaultId); - if (!amounts || !amounts->Sub(amount)) { + if (!amounts || !amounts->Sub(amount)) return Res::Err("Loan token for vault <%s> not found", vaultId.GetHex()); - } - if (amounts->balances.empty()) { + + if (amounts->balances.empty()) EraseBy(vaultId); - } else { + else WriteBy(vaultId, *amounts); - } + return Res::Ok(); } @@ -362,8 +473,8 @@ Res CLoanView::SetLoanLiquidationPenalty(CAmount penalty) CAmount CLoanView::GetLoanLiquidationPenalty() { CAmount penalty; - if (Read(LoanLiquidationPenalty::prefix(), penalty)) { + if (Read(LoanLiquidationPenalty::prefix(), penalty)) return penalty; - } + return 5 * COIN / 100; } diff --git a/src/masternodes/loan.h b/src/masternodes/loan.h index 8a114c69369..33cb7cfdb83 100644 --- a/src/masternodes/loan.h +++ b/src/masternodes/loan.h @@ -223,7 +223,48 @@ struct CInterestRate } }; -CAmount TotalInterest(const CInterestRate& rate, uint32_t height); +struct CInterestRateV2 +{ + uint32_t height; + base_uint<128> interestPerBlock; + base_uint<128> interestToHeight; + + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) { + READWRITE(height); + READWRITE(interestPerBlock); + READWRITE(interestToHeight); + } +}; + +inline CInterestRate ConvertInterestRateToV1(const CInterestRateV2& rate1) +{ + CInterestRate rate2{}; + rate2.height = rate1.height; + rate2.interestPerBlock = rate1.interestPerBlock.GetLow64(); + rate2.interestToHeight = rate1.interestToHeight.GetLow64(); + + return rate2; +} + +inline CInterestRateV2 ConvertInterestRateToV2(const CInterestRate& rate1) +{ + CInterestRateV2 rate2{}; + rate2.height = rate1.height; + rate2.interestPerBlock = rate1.interestPerBlock; + rate2.interestToHeight = rate1.interestToHeight; + + return rate2; +} + +static const CAmount HIGH_PRECISION_SCALER = COIN * COIN; // 1,0000,0000,0000,0000 + +CAmount TotalInterest(const CInterestRateV2& rate, uint32_t height); +CAmount InterestPerBlock(const CInterestRateV2& rate, uint32_t height); +base_uint<128> TotalInterestCalculation(const CInterestRateV2& rate, uint32_t height); +CAmount CeilInterest(const base_uint<128>& value, uint32_t height); class CLoanTakeLoanMessage { @@ -290,11 +331,15 @@ class CLoanView : public virtual CStorageView { void ForEachDelayedLoanScheme(std::function&, const CLoanSchemeMessage&)> callback); void ForEachDelayedDestroyScheme(std::function callback); - Res DeleteInterest(const CVaultId& vaultId); - boost::optional GetInterestRate(const CVaultId& loanSchemeID, DCT_ID id); + Res DeleteInterest(const CVaultId& vaultId, uint32_t height); + boost::optional GetInterestRate(const CVaultId& loanSchemeID, DCT_ID id, uint32_t height); + void WriteInterestRate(const std::pair& pair, const CInterestRateV2& rate, uint32_t height); Res StoreInterest(uint32_t height, const CVaultId& vaultId, const std::string& loanSchemeID, DCT_ID id, CAmount loanIncreased); Res EraseInterest(uint32_t height, const CVaultId& vaultId, const std::string& loanSchemeID, DCT_ID id, CAmount loanDecreased, CAmount interestDecreased); void ForEachVaultInterest(std::function callback, const CVaultId& vaultId = uint256(), DCT_ID id = {0}); + void ForEachVaultInterestV2(std::function callback, const CVaultId& vaultId = uint256(), DCT_ID id = {0}); + void RevertInterestRateToV1(); + void MigrateInterestRateToV2(CVaultView &view, uint32_t height); Res AddLoanToken(const CVaultId& vaultId, CTokenAmount amount); Res SubLoanToken(const CVaultId& vaultId, CTokenAmount amount); @@ -315,6 +360,7 @@ class CLoanView : public virtual CStorageView { struct LoanInterestByVault { static constexpr uint8_t prefix() { return 0x18; } }; struct LoanTokenAmount { static constexpr uint8_t prefix() { return 0x19; } }; struct LoanLiquidationPenalty { static constexpr uint8_t prefix() { return 0x1A; } }; + struct LoanInterestV2ByVault { static constexpr uint8_t prefix() { return 0x1B; } }; }; #endif // DEFI_MASTERNODES_LOAN_H diff --git a/src/masternodes/masternodes.cpp b/src/masternodes/masternodes.cpp index d3f62143ded..86b528af9c0 100644 --- a/src/masternodes/masternodes.cpp +++ b/src/masternodes/masternodes.cpp @@ -993,7 +993,7 @@ Res CCustomCSView::PopulateLoansData(CCollateralLoans& result, CVaultId const& v if (!token) return Res::Err("Loan token with id (%s) does not exist!", loanTokenId.ToString()); - auto rate = GetInterestRate(vaultId, loanTokenId); + auto rate = GetInterestRate(vaultId, loanTokenId, height); if (!rate) return Res::Err("Cannot get interest rate for token (%s)!", token->symbol); diff --git a/src/masternodes/masternodes.h b/src/masternodes/masternodes.h index cd30b183cc4..178dd486043 100644 --- a/src/masternodes/masternodes.h +++ b/src/masternodes/masternodes.h @@ -380,7 +380,7 @@ class CCustomCSView ICXOrderStatus, ICXOfferStatus, ICXSubmitDFCHTLCStatus, ICXSubmitEXTHTLCStatus, ICXVariables, CLoanView :: LoanSetCollateralTokenCreationTx, LoanSetCollateralTokenKey, LoanSetLoanTokenCreationTx, LoanSetLoanTokenKey, LoanSchemeKey, DefaultLoanSchemeKey, DelayedLoanSchemeKey, - DestroyLoanSchemeKey, LoanInterestByVault, LoanTokenAmount, LoanLiquidationPenalty, + DestroyLoanSchemeKey, LoanInterestByVault, LoanTokenAmount, LoanLiquidationPenalty, LoanInterestV2ByVault, CVaultView :: VaultKey, OwnerVaultKey, CollateralKey, AuctionBatchKey, AuctionHeightKey, AuctionBidKey >(); } diff --git a/src/masternodes/mn_checks.cpp b/src/masternodes/mn_checks.cpp index b0e3fe7164f..9bf291eb71d 100644 --- a/src/masternodes/mn_checks.cpp +++ b/src/masternodes/mn_checks.cpp @@ -1431,19 +1431,19 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor return std::move(resVal); } - CDataStructureV0 premiumKey{AttributeTypes::Param, ParamIDs::DFIP2201, DFIP2201Keys::Premium}; - CAmount premium{2500000}; + CDataStructureV0 feePctKey{AttributeTypes::Param, ParamIDs::DFIP2201, DFIP2201Keys::Premium}; + CAmount feePct{2500000}; try { - const auto& value = attrs.at(premiumKey); + const auto& value = attrs.at(feePctKey); auto valueV0 = boost::get(&value); if (valueV0) { - if (auto storedPremium = boost::get(valueV0)) { - premium = *storedPremium; + if (auto storedFeePct = boost::get(valueV0)) { + feePct = *storedFeePct; } } } catch (const std::out_of_range&) {} - const auto& btcPrice = MultiplyAmounts(resVal.val.get(), premium + COIN); + const auto& btcPrice = MultiplyAmounts(resVal.val.get(), feePct + COIN); resVal = mnview.GetValidatedIntervalPrice(dfiUsd, useNextPrice, requireLivePrice); if (!resVal) { @@ -2533,7 +2533,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor } // delete all interest to vault - res = mnview.DeleteInterest(obj.vaultId); + res = mnview.DeleteInterest(obj.vaultId, height); if (!res) return res; @@ -2902,7 +2902,7 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (it == loanAmounts->balances.end()) return Res::Err("There is no loan on token (%s) in this vault!", loanToken->symbol); - auto rate = mnview.GetInterestRate(obj.vaultId, tokenId); + auto rate = mnview.GetInterestRate(obj.vaultId, tokenId, height); if (!rate) return Res::Err("Cannot get interest rate for this token (%s)!", loanToken->symbol); @@ -2931,12 +2931,12 @@ class CCustomTxApplyVisitor : public CCustomTxVisitor if (static_cast(height) >= consensus.FortCanningMuseumHeight && subLoan < it->second) { - rate = mnview.GetInterestRate(obj.vaultId, tokenId); - if (!rate) + auto newRate = mnview.GetInterestRate(obj.vaultId, tokenId, height); + if (!newRate) return Res::Err("Cannot get interest rate for this token (%s)!", loanToken->symbol); - if (rate->interestPerBlock == 0) - return Res::Err("Cannot payback this amount of loan for %s, either payback full amount or less than this amount!", loanToken->symbol); + if (newRate->interestPerBlock == 0) + return Res::Err("Cannot payback this amount of loan for %s, either payback full amount or less than this amount!", loanToken->symbol); } CalculateOwnerRewards(obj.from); @@ -3145,6 +3145,13 @@ class CCustomTxRevertVisitor : public CCustomTxVisitor return EraseHistory(obj.from); } + Res operator()(const CSmartContractMessage& obj) const { + for (const auto& account : obj.accounts) { + EraseHistory(account.first); + } + return Res::Ok(); + } + Res operator()(const CAnyAccountsToAccountsMessage& obj) const { for (const auto& account : obj.to) { EraseHistory(account.first); @@ -3302,6 +3309,19 @@ Res CustomMetadataParse(uint32_t height, const Consensus::Params& consensus, con } Res CustomTxVisit(CCustomCSView& mnview, const CCoinsViewCache& coins, const CTransaction& tx, uint32_t height, const Consensus::Params& consensus, const CCustomTxMessage& txMessage, uint64_t time) { + if (height == Params().GetConsensus().FortCanningHillHeight -1 || height == Params().GetConsensus().FortCanningHillHeight) + { + TBytes dummy; + switch(GuessCustomTxType(tx, dummy)) + { + case CustomTxType::TakeLoan: case CustomTxType::PaybackLoan: case CustomTxType::DepositToVault: + case CustomTxType::WithdrawFromVault: case CustomTxType::UpdateVault: + return Res::Err("This type of transaction is not possible around hard fork height"); + break; + default: + break; + } + } try { return boost::apply_visitor(CCustomTxApplyVisitor(tx, height, coins, mnview, consensus, time), txMessage); } catch (const std::exception& e) { diff --git a/src/masternodes/mn_rpc.cpp b/src/masternodes/mn_rpc.cpp index 1e2643c1852..eb7d0280212 100644 --- a/src/masternodes/mn_rpc.cpp +++ b/src/masternodes/mn_rpc.cpp @@ -443,6 +443,12 @@ void execTestTx(const CTransaction& tx, uint32_t height, CTransactionRef optAuth } } +void RPCCheckFortCanningHillConstraint(int height) +{ + if (height == Params().GetConsensus().FortCanningHillHeight -1 || height == Params().GetConsensus().FortCanningHillHeight) + throw JSONRPCError(RPC_INVALID_PARAMETER, "This type of transaction is not possible around hard fork height"); +} + CWalletCoinsUnlocker GetWallet(const JSONRPCRequest& request) { auto wallet = GetWalletForJSONRPCRequest(request); diff --git a/src/masternodes/mn_rpc.h b/src/masternodes/mn_rpc.h index 96952525b03..d3396b11ecf 100644 --- a/src/masternodes/mn_rpc.h +++ b/src/masternodes/mn_rpc.h @@ -59,5 +59,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); +void RPCCheckFortCanningHillConstraint(int height); #endif // DEFI_MASTERNODES_MN_RPC_H diff --git a/src/masternodes/poolpairs.cpp b/src/masternodes/poolpairs.cpp index da6f4a5947c..30ac316b4f5 100644 --- a/src/masternodes/poolpairs.cpp +++ b/src/masternodes/poolpairs.cpp @@ -218,6 +218,23 @@ void ReadValueMoveToNext(TIterator & it, DCT_ID poolId, ValueType & value, uint3 } } +template +void FindSuitablePoolRewards(TIterator & it, PoolHeightKey poolKey, uint32_t endHeight, uint32_t & height) { + + static const auto poolStartHeight = uint32_t(Params().GetConsensus().FortCanningHillHeight); + poolKey.height = std::max(poolKey.height, poolStartHeight); + + while (!it.Valid() && poolKey.height < endHeight) { + poolKey.height++; + it.Seek(poolKey); + } + if (it.Valid() && it.Key().poolID == poolKey.poolID) { + height = it.Key().height; + } else { + height = UINT_MAX; + } +} + void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function onLiquidity, uint32_t begin, uint32_t end, std::function onReward) { if (begin >= end) { return; @@ -232,10 +249,14 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function(poolKey); + FindSuitablePoolRewards(itPoolReward, poolKey, end, nextPoolReward); + + auto nextPoolLoanReward = begin; auto itPoolLoanReward = LowerBound(poolKey); + FindSuitablePoolRewards(itPoolLoanReward, poolKey, end, nextPoolLoanReward); CAmount totalLiquidity = 0; auto nextTotalLiquidity = begin; @@ -244,14 +265,13 @@ void CPoolPairView::CalculatePoolRewards(DCT_ID const & poolId, std::function(poolKey); + FindSuitablePoolRewards(itCustomRewards, poolKey, end, nextCustomRewards); PoolSwapValue poolSwap; auto nextPoolSwap = UINT_MAX; auto poolSwapHeight = UINT_MAX; auto itPoolSwap = LowerBound(poolKey); - if (itPoolSwap.Valid() && itPoolSwap.Key().poolID == poolId) { - nextPoolSwap = itPoolSwap.Key().height; - } + FindSuitablePoolRewards(itPoolSwap, poolKey, end, nextPoolSwap); for (auto height = begin; height < end;) { // find suitable pool liquidity diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index 0e86251a459..535311f13f9 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -1266,8 +1266,9 @@ UniValue getloaninfo(const JSONRPCRequest& request) { LOCK(cs_main); - auto height = ::ChainActive().Height() + 1; + RPCCheckFortCanningHillConstraint(height); + bool useNextPrice = false, requireLivePrice = true; auto lastBlockTime = ::ChainActive().Tip()->GetBlockTime(); uint64_t totalCollateralValue = 0, totalLoanValue = 0, totalVaults = 0, totalAuctions = 0; @@ -1362,10 +1363,12 @@ UniValue getinterest(const JSONRPCRequest& request) { UniValue ret(UniValue::VARR); uint32_t height = ::ChainActive().Height() + 1; - std::map > interest; + RPCCheckFortCanningHillConstraint(height); + + std::map, base_uint<128>> > interest; LogPrint(BCLog::LOAN,"%s():\n", __func__); - pcustomcsview->ForEachVaultInterest([&](const CVaultId& vaultId, DCT_ID tokenId, CInterestRate rate) + auto vaultInterest = [&](const CVaultId& vaultId, DCT_ID tokenId, CInterestRateV2 rate) { auto vault = pcustomcsview->GetVault(vaultId); if (!vault || vault->schemeId != loanSchemeId) @@ -1378,19 +1381,32 @@ UniValue getinterest(const JSONRPCRequest& request) { return true; LogPrint(BCLog::LOAN,"\t\tVault(%s)->", vaultId.GetHex()); /* Continued */ - interest[tokenId].first += TotalInterest(rate, height); + interest[tokenId].first += TotalInterestCalculation(rate, height); interest[tokenId].second += rate.interestPerBlock; return true; - }); + }; + + if (height >= Params().GetConsensus().FortCanningHillHeight) { + pcustomcsview->ForEachVaultInterestV2(vaultInterest); + } else { + pcustomcsview->ForEachVaultInterest([&](const CVaultId& vaultId, DCT_ID tokenId, CInterestRate rate) { + return vaultInterest(vaultId, tokenId, ConvertInterestRateToV2(rate)); + }); + } UniValue obj(UniValue::VOBJ); - for (std::map >::iterator it=interest.begin(); it!=interest.end(); ++it) + for (std::map, base_uint<128> > >::iterator it=interest.begin(); it!=interest.end(); ++it) { auto token = pcustomcsview->GetToken(it->first); obj.pushKV("token", token->CreateSymbolKey(it->first)); - obj.pushKV("totalInterest", ValueFromAmount(it->second.first)); - obj.pushKV("interestPerBlock", ValueFromAmount(it->second.second)); + obj.pushKV("totalInterest", ValueFromAmount(CeilInterest(it->second.first, height))); + obj.pushKV("interestPerBlock", ValueFromAmount(CeilInterest(it->second.second, height))); + if (height >= Params().GetConsensus().FortCanningHillHeight) + { + auto subSatoshiInterest = (it->second.second - ((it->second.second / HIGH_PRECISION_SCALER) * HIGH_PRECISION_SCALER)).GetLow64(); + obj.pushKV("immatureInterest", UniValue(UniValue::VNUM, strprintf("0.%de-8", subSatoshiInterest))); + } ret.push_back(obj); } diff --git a/src/masternodes/rpc_vault.cpp b/src/masternodes/rpc_vault.cpp index 75550560544..4554a710cd6 100644 --- a/src/masternodes/rpc_vault.cpp +++ b/src/masternodes/rpc_vault.cpp @@ -43,6 +43,8 @@ namespace { bool WillLiquidateNext(const CVaultId& vaultId, const CVaultData& vault) { auto height = ::ChainActive().Height(); + RPCCheckFortCanningHillConstraint(height); + auto blockTime = ::ChainActive()[height]->GetBlockTime(); auto collaterals = pcustomcsview->GetVaultCollaterals(vaultId); @@ -152,7 +154,7 @@ namespace { for (const auto& loan : loanTokens->balances) { auto token = pcustomcsview->GetLoanTokenByID(loan.first); if (!token) continue; - auto rate = pcustomcsview->GetInterestRate(vaultId, loan.first); + auto rate = pcustomcsview->GetInterestRate(vaultId, loan.first, height); if (!rate) continue; LogPrint(BCLog::LOAN,"%s()->%s->", __func__, token->symbol); /* Continued */ auto totalInterest = TotalInterest(*rate, height + 1); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index e6d506137d3..e533e1a23ec 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -1883,8 +1884,15 @@ static UniValue getblockstats(const JSONRPCRequest& request) CAmount tx_total_out = 0; if (loop_outputs) { - for (const CTxOut& out : tx->vout) { - tx_total_out += out.nValue; + auto mintingOutputsStart = ~0u; + if (auto accountToUtxos = GetAccountToUtxosMsg(*tx)) { + mintingOutputsStart = accountToUtxos->mintingOutputsStart; + } + for (size_t i = 0; i < tx->vout.size(); ++i) { + const auto& out = tx->vout[i]; + if (i < mintingOutputsStart) { + tx_total_out += out.nValue; + } utxo_size_inc += GetSerializeSize(out, PROTOCOL_VERSION) + PER_UTXO_OVERHEAD; } } diff --git a/src/test/arith_uint256_tests.cpp b/src/test/arith_uint256_tests.cpp index 7133572c9dd..0b2550d3290 100644 --- a/src/test/arith_uint256_tests.cpp +++ b/src/test/arith_uint256_tests.cpp @@ -119,6 +119,8 @@ BOOST_AUTO_TEST_CASE( basics ) // constructors, equality, inequality tmpL = ~R1L; BOOST_CHECK(tmpL == ~R1L); tmpL = ~R2L; BOOST_CHECK(tmpL == ~R2L); tmpL = ~MaxL; BOOST_CHECK(tmpL == ~MaxL); + + BOOST_CHECK(OneL == base_uint<256>(base_uint<128>(OneL))); } static void shiftArrayRight(unsigned char* to, const unsigned char* from, unsigned int arrayLength, unsigned int bitsToShift) @@ -336,10 +338,14 @@ BOOST_AUTO_TEST_CASE( multiply ) BOOST_CHECK(MaxL * MaxL == OneL); - BOOST_CHECK((R1L * 0) == 0); - BOOST_CHECK((R1L * 1) == R1L); - BOOST_CHECK((R1L * 3).ToString() == "7759b1c0ed14047f961ad09b20ff83687876a0181a367b813634046f91def7d4"); - BOOST_CHECK((R2L * 0x87654321UL).ToString() == "23f7816e30c4ae2017257b7a0fa64d60402f5234d46e746b61c960d09a26d070"); + BOOST_CHECK((R1L * 0u) == 0u); + BOOST_CHECK((R1L * 1u) == R1L); + BOOST_CHECK((R1L * 3u).ToString() == "7759b1c0ed14047f961ad09b20ff83687876a0181a367b813634046f91def7d4"); + BOOST_CHECK((R2L * 0x87654321).ToString() == "23f7816e30c4ae2017257b7a0fa64d60402f5234d46e746b61c960d09a26d070"); + + BOOST_CHECK((R1L * 10000000) == (R1L * arith_uint256(10000000))); + BOOST_CHECK((R1L * COIN) == (R1L * arith_uint256(COIN))); + BOOST_CHECK((R1L * (COIN * COIN)) == (R1L * arith_uint256(COIN * COIN))); } BOOST_AUTO_TEST_CASE( divide ) diff --git a/src/test/loan_tests.cpp b/src/test/loan_tests.cpp index 80f3d2cb30c..2bd1581ee6f 100644 --- a/src/test/loan_tests.cpp +++ b/src/test/loan_tests.cpp @@ -91,32 +91,32 @@ BOOST_AUTO_TEST_CASE(loan_iterest_rate) auto vault_id = NextTx(); BOOST_REQUIRE(mnview.StoreInterest(1, vault_id, id, token_id, 1 * COIN)); - auto rate = mnview.GetInterestRate(vault_id, token_id); + auto rate = mnview.GetInterestRate(vault_id, token_id, 1); BOOST_REQUIRE(rate); - BOOST_CHECK_EQUAL(rate->interestToHeight, 0); + BOOST_CHECK_EQUAL(rate->interestToHeight.GetLow64(), 0); BOOST_CHECK_EQUAL(rate->height, 1); auto interestPerBlock = rate->interestPerBlock; BOOST_REQUIRE(mnview.StoreInterest(5, vault_id, id, token_id, 1 * COIN)); - rate = mnview.GetInterestRate(vault_id, token_id); + rate = mnview.GetInterestRate(vault_id, token_id, 5); BOOST_REQUIRE(rate); BOOST_CHECK_EQUAL(rate->height, 5); - BOOST_CHECK_EQUAL(rate->interestToHeight, 4 * interestPerBlock); + BOOST_CHECK_EQUAL(rate->interestToHeight.GetLow64(), 4 * interestPerBlock.GetLow64()); auto interestToHeight = rate->interestToHeight; interestPerBlock = rate->interestPerBlock; - BOOST_REQUIRE(mnview.EraseInterest(6, vault_id, id, token_id, 1 * COIN, interestToHeight + interestPerBlock)); - rate = mnview.GetInterestRate(vault_id, token_id); + BOOST_REQUIRE(mnview.EraseInterest(6, vault_id, id, token_id, 1 * COIN, (interestToHeight + interestPerBlock).GetLow64())); + rate = mnview.GetInterestRate(vault_id, token_id, 6); BOOST_REQUIRE(rate); - BOOST_CHECK_EQUAL(rate->interestToHeight, 0); + BOOST_CHECK_EQUAL(rate->interestToHeight.GetLow64(), 0); BOOST_REQUIRE(mnview.EraseInterest(6, vault_id, id, token_id, 1 * COIN, 0)); - rate = mnview.GetInterestRate(vault_id, token_id); + rate = mnview.GetInterestRate(vault_id, token_id, 6); BOOST_REQUIRE(rate); - BOOST_CHECK_EQUAL(rate->interestToHeight, 0); + BOOST_CHECK_EQUAL(rate->interestToHeight.GetLow64(), 0); } BOOST_AUTO_TEST_CASE(collateralization_ratio) diff --git a/src/test/storage_tests.cpp b/src/test/storage_tests.cpp index 601ac297e6a..bba10c6a629 100644 --- a/src/test/storage_tests.cpp +++ b/src/test/storage_tests.cpp @@ -463,6 +463,22 @@ BOOST_AUTO_TEST_CASE(LowerBoundTest) BOOST_CHECK(it.Valid()); BOOST_CHECK(it.Value().as() == 2); } + + { + CCustomCSView view(*pcustomcsview); + view.WriteBy(TestBackward{5}, 1); + view.WriteBy(TestBackward{6}, 2); + + auto start = TestBackward{0}; + auto it = view.LowerBound(start); + while (!it.Valid()) { + start.n++; + it.Seek(start); + } + BOOST_REQUIRE(it.Valid()); + BOOST_CHECK_EQUAL(start.n, 5); + BOOST_CHECK_EQUAL(it.Key().n, 5); + } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/uint256.cpp b/src/uint256.cpp index 22cdf66ecb8..688364e907f 100644 --- a/src/uint256.cpp +++ b/src/uint256.cpp @@ -63,16 +63,11 @@ std::string base_blob::ToString() const return (GetHex()); } +// Explicit instantiations for base_blob<128> +template class base_blob<128>; + // Explicit instantiations for base_blob<160> -template base_blob<160>::base_blob(const std::vector&); -template std::string base_blob<160>::GetHex() const; -template std::string base_blob<160>::ToString() const; -template void base_blob<160>::SetHex(const char*); -template void base_blob<160>::SetHex(const std::string&); +template class base_blob<160>; // Explicit instantiations for base_blob<256> -template base_blob<256>::base_blob(const std::vector&); -template std::string base_blob<256>::GetHex() const; -template std::string base_blob<256>::ToString() const; -template void base_blob<256>::SetHex(const char*); -template void base_blob<256>::SetHex(const std::string&); +template class base_blob<256>; diff --git a/src/validation.cpp b/src/validation.cpp index ae95ae73b88..87fbdad52b0 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1790,6 +1790,14 @@ DisconnectResult CChainState::DisconnectBlock(const CBlock& block, const CBlockI } } + // one time downgrade to revert CInterestRateV2 structure + if (pindex->nHeight == Params().GetConsensus().FortCanningHillHeight) { + auto time = GetTimeMillis(); + LogPrintf("Interest rate reverting ...\n"); + mnview.RevertInterestRateToV1(); + LogPrint(BCLog::BENCH, " - Interest rate reverting took: %dms\n", GetTimeMillis() - time); + } + // Remove burn balance transfers if (pindex->nHeight == Params().GetConsensus().EunosHeight) { @@ -2307,6 +2315,15 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl return true; } + // one time upgrade to convert the old CInterestRate data structure + // we don't neeed it in undos + if (pindex->nHeight == chainparams.GetConsensus().FortCanningHillHeight) { + auto time = GetTimeMillis(); + LogPrintf("Interest rate migration ...\n"); + mnview.MigrateInterestRateToV2(mnview,(uint32_t)pindex->nHeight); + LogPrint(BCLog::BENCH, " - Interest rate migration took: %dms\n", GetTimeMillis() - time); + } + CKeyID minterKey; boost::optional nodeId; boost::optional nodePtr; @@ -2878,6 +2895,15 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl LogPrintf("Pruning undo data finished.\n"); LogPrint(BCLog::BENCH, " - Pruning undo data takes: %dms\n", GetTimeMillis() - time); } + // we can safety delete old interest keys + if (it->first > chainparams.GetConsensus().FortCanningHillHeight) { + CCustomCSView view(mnview); + mnview.ForEachVaultInterest([&](const CVaultId& vaultId, DCT_ID tokenId, CInterestRate) { + view.EraseBy(std::make_pair(vaultId, tokenId)); + return true; + }); + view.Flush(); + } } int64_t nTime5 = GetTimeMicros(); nTimeIndex += nTime5 - nTime4; @@ -3114,13 +3140,13 @@ void CChainState::ProcessLoanEvents(const CBlockIndex* pindex, CCustomCSView& ca for (auto& loan : loanTokens->balances) { auto tokenId = loan.first; auto tokenValue = loan.second; - auto rate = cache.GetInterestRate(vaultId, tokenId); + auto rate = cache.GetInterestRate(vaultId, tokenId, pindex->nHeight); assert(rate); LogPrint(BCLog::LOAN,"\t\t"); /* Continued */ auto subInterest = TotalInterest(*rate, pindex->nHeight); totalInterest.Add({tokenId, subInterest}); - // Remove the interests from the vault and the storage respectively + // Remove the interests from the vault and the storage respectively cache.SubLoanToken(vaultId, {tokenId, tokenValue}); LogPrint(BCLog::LOAN,"\t\t"); /* Continued */ cache.EraseInterest(pindex->nHeight, vaultId, vault->schemeId, tokenId, tokenValue, subInterest); diff --git a/test/functional/feature_accounts_validation.py b/test/functional/feature_accounts_validation.py index 7695738a963..5265d8a50d2 100755 --- a/test/functional/feature_accounts_validation.py +++ b/test/functional/feature_accounts_validation.py @@ -46,6 +46,10 @@ def run_test(self): node.generate(1) self.sync_all() + stats = node.getblockstats(blockcount + 1) + assert_equal(stats["total_out"], 18199952120) + assert_equal(stats["totalfee"], 25880) + # Check the blockchain has extended as expected assert_equal(node1.getblockcount(), blockcount + 1) diff --git a/test/functional/feature_loan_low_interest.py b/test/functional/feature_loan_low_interest.py new file mode 100755 index 00000000000..681d0c80b9c --- /dev/null +++ b/test/functional/feature_loan_low_interest.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2019 The Bitcoin Core developers +# Copyright (c) DeFi Blockchain Developers +# Distributed under the MIT software license, see the accompanying +# file LICENSE or http://www.opensource.org/licenses/mit-license.php. +"""Test Loan - interest test.""" + +from test_framework.test_framework import DefiTestFramework + +from test_framework.util import assert_equal + +import calendar +import time +from decimal import Decimal + +FCH_HEIGHT = 1200 + +class LowInterestTest (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=2', '-fortcanningheight=3', '-fortcanningmuseumheight=4', '-fortcanningparkheight=5', f'-fortcanninghillheight={FCH_HEIGHT}', '-jellyfish_regtest=1', '-txindex=1', '-simulatemainnet'] + ] + + account0 = None + oracle_id1 = None + symbolDFI = "DFI" + symbolDOGE = "DOGE" + symboldUSD = "DUSD" + idDFI = 0 + iddUSD = 0 + idDOGE = 0 + tokenInterest = 0 + loanInterest = 0 + + def test_load_account0_with_DFI(self): + print('loading up account0 with DFI token...') + self.nodes[0].generate(100) # get initial UTXO balance from immature to trusted -> check getbalances() + self.account0 = self.nodes[0].get_genesis_keys().ownerAuthAddress + # UTXO -> token + self.nodes[0].utxostoaccount({self.account0: "199999900@" + self.symbolDFI}) + self.nodes[0].generate(1) + account0_balance = self.nodes[0].getaccount(self.account0) + assert_equal(account0_balance, ['199999900.00000000@DFI']) + + def test_setup_oracles(self): + print('setting up oracles...') + oracle_address1 = self.nodes[0].getnewaddress("", "legacy") + price_feeds1 = [{"currency": "USD", "token": "DFI"}, + {"currency": "USD", "token": "DOGE"}] + self.oracle_id1 = self.nodes[0].appointoracle(oracle_address1, price_feeds1, 10) + self.nodes[0].generate(1) + oracle1_prices = [{"currency": "USD", "tokenAmount": "0.0001@DOGE"}, + {"currency": "USD", "tokenAmount": "10@DFI"}] + timestamp = calendar.timegm(time.gmtime()) + self.nodes[0].setoracledata(self.oracle_id1, timestamp, oracle1_prices) + self.nodes[0].generate(120) # let active price update + oracle_data = self.nodes[0].getoracledata(self.oracle_id1) + assert_equal(len(oracle_data["priceFeeds"]), 2) + assert_equal(len(oracle_data["tokenPrices"]), 2) + + def test_setup_tokens(self): + print('setting up loan and collateral tokens...') + self.nodes[0].setloantoken({ + 'symbol': self.symboldUSD, + 'name': "DUSD stable token", + 'fixedIntervalPriceId': "DUSD/USD", + 'mintable': True, + 'interest': 0}) + + self.tokenInterest = Decimal(1) + self.nodes[0].setloantoken({ + 'symbol': self.symbolDOGE, + 'name': "DOGE token", + 'fixedIntervalPriceId': "DOGE/USD", + 'mintable': True, + 'interest': Decimal(self.tokenInterest*100)}) + self.nodes[0].generate(1) + + # Set token ids + self.iddUSD = list(self.nodes[0].gettoken(self.symboldUSD).keys())[0] + self.idDFI = list(self.nodes[0].gettoken(self.symbolDFI).keys())[0] + self.idDOGE = list(self.nodes[0].gettoken(self.symbolDOGE).keys())[0] + + # Mint tokens + self.nodes[0].minttokens("1000000@DOGE") + self.nodes[0].generate(1) + self.nodes[0].minttokens("2000000@" + self.symboldUSD) # necessary for pools + self.nodes[0].generate(1) + + # Setup collateral tokens + self.nodes[0].setcollateraltoken({ + 'token': self.idDFI, + 'factor': 1, + 'fixedIntervalPriceId': "DFI/USD"}) + self.nodes[0].generate(300) + + assert_equal(len(self.nodes[0].listtokens()), 3) + assert_equal(len(self.nodes[0].listloantokens()), 2) + assert_equal(len(self.nodes[0].listcollateraltokens()), 1) + + + def test_setup_poolpairs(self): + print("setting up pool pairs...") + poolOwner = self.nodes[0].getnewaddress("", "legacy") + self.nodes[0].createpoolpair({ + "tokenA": self.iddUSD, + "tokenB": self.idDFI, + "commission": Decimal('0.002'), + "status": True, + "ownerAddress": poolOwner, + "pairSymbol": "DUSD-DFI", + }, []) + + self.nodes[0].createpoolpair({ + "tokenA": self.iddUSD, + "tokenB": self.idDOGE, + "commission": Decimal('0.002'), + "status": True, + "ownerAddress": poolOwner, + "pairSymbol": "DUSD-DOGE", + }, []) + self.nodes[0].generate(1) + + self.nodes[0].addpoolliquidity({ + self.account0: ["1000000@" + self.symboldUSD, "1000000@" + self.symbolDFI] + }, self.account0, []) + self.nodes[0].generate(1) + + self.nodes[0].addpoolliquidity({ + self.account0: ["1000000@" + self.symboldUSD, "1000@" + self.symbolDOGE] + }, self.account0, []) + self.nodes[0].generate(1) + assert_equal(len(self.nodes[0].listpoolpairs()), 2) + + def test_setup_loan_scheme(self): + print("creating loan scheme...") + self.loanInterest = Decimal(1) + # Create loan schemes and vaults + self.nodes[0].createloanscheme(150, Decimal(self.loanInterest*100), 'LOAN150') + self.nodes[0].generate(1) + assert_equal(len(self.nodes[0].listloanschemes()), 1) + + + def setup(self): + print('Generating initial chain...') + self.test_load_account0_with_DFI() + self.test_setup_oracles() + self.test_setup_tokens() + self.test_setup_poolpairs() + self.test_setup_loan_scheme() + + def get_new_vault_and_deposit(self, loan_scheme=None, amount=10000): + vault_id = self.nodes[0].createvault(self.account0, loan_scheme) + self.nodes[0].generate(1) + self.nodes[0].deposittovault(vault_id, self.account0, str(amount)+"@DFI") # enough collateral to take loans + self.nodes[0].generate(1) + return vault_id + + def go_to_FCH(self): + blocksUntilFCH = FCH_HEIGHT - self.nodes[0].getblockcount() + 2 + self.nodes[0].generate(blocksUntilFCH) + blockChainInfo = self.nodes[0].getblockchaininfo() + assert_equal(blockChainInfo["softforks"]["fortcanninghill"]["active"], True) + + def is_FCH(self): + return self.nodes[0].getblockcount() > FCH_HEIGHT + + def test_new_loan_with_interest_lower_than_1satoshi(self, payback=False): + vault_id = self.get_new_vault_and_deposit() + + self.nodes[0].takeloan({ + 'vaultId': vault_id, + 'amounts': "0.001314@DOGE"}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"][0], '0.00000001@DOGE') + + self.nodes[0].generate(35) + vault_data = self.nodes[0].getvault(vault_id) + expected_interest = self.is_FCH() and '0.00000009' or '0.00000036' + assert_equal(vault_data["interestAmounts"][0], expected_interest+'@DOGE') + + if not payback: + return + + # Payback + expected_payback = self.is_FCH() and '0.00131409' or '0.00131436' + self.nodes[0].paybackloan({ + 'vaultId': vault_id, + 'from': self.account0, + 'amounts': expected_payback+'@DOGE'}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"], []) + + + def test_new_loan_with_interest_exactly_25satoshi(self, payback=False): + vault_id = self.get_new_vault_and_deposit() + self.nodes[0].takeloan({ + 'vaultId': vault_id, + 'amounts': "0.1314@DOGE"}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"][0], '0.00000025@DOGE') + + self.nodes[0].generate(35) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"][0], '0.00000900@DOGE') + + if not payback: + return + + # Payback + self.nodes[0].paybackloan({ + 'vaultId': vault_id, + 'from': self.account0, + 'amounts': '0.13140900@DOGE'}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"], []) + + def test_new_loan_with_interest_over_1satoshi(self, payback=False): + vault_id = self.get_new_vault_and_deposit() + self.nodes[0].takeloan({ + 'vaultId': vault_id, + 'amounts': "100@DOGE"}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"][0], '0.00019026@DOGE') + + self.nodes[0].generate(35) + vault_data = self.nodes[0].getvault(vault_id) + expected_interest = self.is_FCH() and '0.00684932' or '0.00684936' + assert_equal(vault_data["interestAmounts"][0], expected_interest+'@DOGE') + + if not payback: + return + # Payback + expected_payback = self.is_FCH() and '100.00684932' or '100.00684936' + self.nodes[0].paybackloan({ + 'vaultId': vault_id, + 'from': self.account0, + 'amounts': expected_payback+'@DOGE'}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"], []) + + def test_low_loan(self, payback=False): + vault_id = self.get_new_vault_and_deposit() + self.nodes[0].takeloan({ + 'vaultId': vault_id, + 'amounts': "0.00000001@DOGE"}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"][0], '0.00000001@DOGE') + + self.nodes[0].generate(35) + vault_data = self.nodes[0].getvault(vault_id) + expected_interest = self.is_FCH() and '0.00000001' or '0.00000036' + assert_equal(vault_data["interestAmounts"][0], expected_interest+'@DOGE') + + if not payback: + return + # Payback + expected_payback = self.is_FCH() and '0.00000002' or '0.00000037' + self.nodes[0].paybackloan({ + 'vaultId': vault_id, + 'from': self.account0, + 'amounts': expected_payback+'@DOGE'}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"], []) + + def test_high_loan(self, payback=False): + vault_id = self.get_new_vault_and_deposit() + self.nodes[0].takeloan({ + 'vaultId': vault_id, + 'amounts': "2345.225@DOGE"}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"][0], '0.00446200@DOGE') + + self.nodes[0].generate(35) + vault_data = self.nodes[0].getvault(vault_id) + expected_interest = self.is_FCH() and '0.16063185' or '0.16063200' + assert_equal(vault_data["interestAmounts"][0], expected_interest+'@DOGE') + + if not payback: + return + # Payback + expected_payback = self.is_FCH() and '2345.38563185' or '2345.38563200' + self.nodes[0].paybackloan({ + 'vaultId': vault_id, + 'from': self.account0, + 'amounts': expected_payback+'@DOGE'}) + self.nodes[0].generate(1) + vault_data = self.nodes[0].getvault(vault_id) + assert_equal(vault_data["interestAmounts"], []) + + + def run_test(self): + self.setup() + + # PRE FCH + self.test_new_loan_with_interest_lower_than_1satoshi(payback=True) + self.test_new_loan_with_interest_exactly_25satoshi(payback=True) + self.test_new_loan_with_interest_over_1satoshi(payback=True) + self.test_new_loan_with_interest_lower_than_1satoshi() + self.test_new_loan_with_interest_exactly_25satoshi() + self.test_new_loan_with_interest_over_1satoshi() + # limit amounts pre FCH + self.test_low_loan(payback=True) + self.test_high_loan(payback=True) + self.test_low_loan() + self.test_high_loan() + + self.go_to_FCH() + + self.test_new_loan_with_interest_lower_than_1satoshi(payback=True) + self.test_new_loan_with_interest_exactly_25satoshi(payback=True) + self.test_new_loan_with_interest_over_1satoshi(payback=True) + self.test_new_loan_with_interest_lower_than_1satoshi() + self.test_new_loan_with_interest_exactly_25satoshi() + self.test_new_loan_with_interest_over_1satoshi() + # limit amounts post FCH + self.test_low_loan(payback=True) + self.test_high_loan(payback=True) + self.test_low_loan() + self.test_high_loan() + +if __name__ == '__main__': + LowInterestTest().main() diff --git a/test/functional/feature_loan_vault.py b/test/functional/feature_loan_vault.py index 90933f2921f..2411052113f 100755 --- a/test/functional/feature_loan_vault.py +++ b/test/functional/feature_loan_vault.py @@ -18,8 +18,8 @@ def set_test_params(self): self.num_nodes = 2 self.setup_clean_chain = True self.extra_args = [ - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1', '-fortcanninghillheight=1', '-jellyfish_regtest=1'], - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1', '-fortcanninghillheight=1', '-jellyfish_regtest=1'] + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1', '-fortcanninghillheight=300', '-jellyfish_regtest=1'], + ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-bayfrontgardensheight=1', '-eunosheight=1', '-txindex=1', '-fortcanningheight=1', '-fortcanninghillheight=300', '-jellyfish_regtest=1'] ] def run_test(self): diff --git a/test/functional/feature_smart_contracts.py b/test/functional/feature_smart_contracts.py index ed05397be3f..2eebf857b3a 100644 --- a/test/functional/feature_smart_contracts.py +++ b/test/functional/feature_smart_contracts.py @@ -13,34 +13,29 @@ class SmartContractTest(DefiTestFramework): def set_test_params(self): - self.num_nodes = 2 + self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1010', '-subsidytest=1', '-txindex=1', '-jellyfish_regtest=1'], - ['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1010', '-subsidytest=1', '-txindex=1', '-jellyfish_regtest=1']] + self.extra_args = [['-txnotokens=0', '-amkheight=1', '-bayfrontheight=1', '-eunosheight=1', '-fortcanningheight=1', '-fortcanninghillheight=1010', '-subsidytest=1', '-txindex=1', '-jellyfish_regtest=1']] def rollback(self, count): block = self.nodes[0].getblockhash(count) self.nodes[0].invalidateblock(block) - self.nodes[1].invalidateblock(block) self.nodes[0].clearmempool() - self.nodes[1].clearmempool() - self.sync_blocks() def run_test(self): self.nodes[0].generate(1000) - self.sync_blocks() - address = self.nodes[1].getnewaddress("", "legacy") - invalid_address = self.nodes[0].getnewaddress("", "legacy") + address = self.nodes[0].getnewaddress("", "legacy") + invalid_address = 'mrV4kYRhyrfiCcpfXDSZuhe56kCQu84gqh' dfi_amount = 1000 btc_amount = 1 dfip = 'dbtcdfiswap' # Check invalid calls - assert_raises_rpc_error(-5, 'Incorrect authorization for {}'.format(invalid_address), self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@2', invalid_address) + assert_raises_rpc_error(-5, 'Incorrect authorization for {}'.format(invalid_address), self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@2', invalid_address) assert_raises_rpc_error(-4, 'Insufficient funds', self.nodes[0].executesmartcontract, dfip, str(dfi_amount) + '@0') - assert_raises_rpc_error(-8, 'Specified smart contract not found', self.nodes[1].executesmartcontract, 'DFIP9999', str(dfi_amount) + '@0') - assert_raises_rpc_error(-8, 'BTC source address must be provided for DFIP2201', self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@2') + assert_raises_rpc_error(-8, 'Specified smart contract not found', self.nodes[0].executesmartcontract, 'DFIP9999', str(dfi_amount) + '@0') + assert_raises_rpc_error(-8, 'BTC source address must be provided for DFIP2201', self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@2') # Create tokens self.nodes[0].createtoken({ @@ -59,48 +54,43 @@ def run_test(self): self.nodes[0].generate(1) # Create and fund address with BTC - address = self.nodes[1].getnewaddress("", "legacy") + address = self.nodes[0].getnewaddress("", "legacy") self.nodes[0].minttokens("100000@BTC") self.nodes[0].generate(1) self.nodes[0].accounttoaccount(self.nodes[0].get_genesis_keys().ownerAuthAddress, {address: "20000@BTC"}) self.nodes[0].generate(1) - for _ in range(20): - self.nodes[0].sendtoaddress(address, 0.1) - self.nodes[0].generate(1) - self.sync_blocks() # Check invalid calls - assert_raises_rpc_error(-32600, 'called before FortCanningHill height', self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@2', address) + assert_raises_rpc_error(-32600, 'called before FortCanningHill height', self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@2', address) # Move to FortCanningHill self.nodes[0].generate(1010 - self.nodes[0].getblockcount()) - self.sync_blocks() # Check invalid call - assert_raises_rpc_error(-32600, 'DFIP2201 smart contract is not enabled', self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@2', address) + assert_raises_rpc_error(-32600, 'DFIP2201 smart contract is not enabled', self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@2', address) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/active':'false'}}) self.nodes[0].generate(1) # Check invalid call - assert_raises_rpc_error(-32600, 'DFIP2201 smart contract is not enabled', self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@2', address) + assert_raises_rpc_error(-32600, 'DFIP2201 smart contract is not enabled', self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@2', address) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/active':'true'}}) self.nodes[0].generate(1) # Check invalid calls - assert_raises_rpc_error(-32600, 'is less than', self.nodes[1].executesmartcontract, dfip, '20000.00000001@2', address) - assert_raises_rpc_error(-3, 'Amount out of range', self.nodes[1].executesmartcontract, dfip, '0@2', address) - assert_raises_rpc_error(-32600, 'Specified token not found', self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@9999', address) - assert_raises_rpc_error(-32600, 'Only Bitcoin can be swapped in DFIP2201', self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@1', address) - assert_raises_rpc_error(-32600, 'fixedIntervalPrice with id not found', self.nodes[1].executesmartcontract, dfip, str(btc_amount) + '@2', address) + assert_raises_rpc_error(-32600, 'is less than', self.nodes[0].executesmartcontract, dfip, '20000.00000001@2', address) + assert_raises_rpc_error(-3, 'Amount out of range', self.nodes[0].executesmartcontract, dfip, '0@2', address) + assert_raises_rpc_error(-32600, 'Specified token not found', self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@9999', address) + assert_raises_rpc_error(-32600, 'Only Bitcoin can be swapped in DFIP2201', self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@1', address) + assert_raises_rpc_error(-32600, 'fixedIntervalPrice with id not found', self.nodes[0].executesmartcontract, dfip, str(btc_amount) + '@2', address) # Test min swap self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/minswap':'0.00001'}}) self.nodes[0].generate(1) # Check invalid calls - assert_raises_rpc_error(-32600, 'Below minimum swapable amount, must be at least 0.00001000 BTC', self.nodes[1].executesmartcontract, dfip, '0.00000999@2', address) + assert_raises_rpc_error(-32600, 'Below minimum swapable amount, must be at least 0.00001000 BTC', self.nodes[0].executesmartcontract, dfip, '0.00000999@2', address) # Set up oracles oracle_address = self.nodes[0].getnewaddress("", "legacy") @@ -125,14 +115,13 @@ def run_test(self): 'fixedIntervalPriceId': "BTC/USD"}) self.nodes[0].generate(7) - self.sync_blocks() # Import community balance - self.nodes[1].importprivkey('cMv1JaaZ9Mbb3M3oNmcFvko8p7EcHJ8XD7RCQjzNaMs7BWRVZTyR') - balance = self.nodes[1].getbalance() + self.nodes[0].importprivkey('cMv1JaaZ9Mbb3M3oNmcFvko8p7EcHJ8XD7RCQjzNaMs7BWRVZTyR') + balance = self.nodes[0].getbalance() # Try and fund more than is in community balance - assert_raises_rpc_error(-4, 'Insufficient funds', self.nodes[1].executesmartcontract, dfip, '18336.22505381@0', address) + assert_raises_rpc_error(-4, 'Insufficient funds', self.nodes[0].executesmartcontract, dfip, '18336.22505381@0', address) # Check smart contract details and balance result = self.nodes[0].listsmartcontracts() @@ -142,9 +131,8 @@ def run_test(self): assert('0' not in result[0]) # Fund smart contract - tx = self.nodes[1].executesmartcontract(dfip, '18336.225@0') - self.nodes[1].generate(1) - self.sync_blocks() + tx = self.nodes[0].executesmartcontract(dfip, '18336.225@0') + self.nodes[0].generate(1) # Check smart contract details and balance result = self.nodes[0].listsmartcontracts() @@ -152,21 +140,21 @@ def run_test(self): # Check balance has changed as expected block = self.nodes[0].getblock(self.nodes[0].getblockhash(self.nodes[0].getblockcount() - 100)) - community_reward = self.nodes[0].getrawtransaction(block['tx'][0], 1)['vout'][1]['value'] - fee = self.nodes[1].gettransaction(tx)['fee'] - assert_equal(balance + community_reward - Decimal('18336.225') + fee, self.nodes[1].getbalance()) + rewards = self.nodes[0].getrawtransaction(block['tx'][0], 1)['vout'] + staker_reward = rewards[0]['value'] + community_reward = rewards[1]['value'] + fee = self.nodes[0].gettransaction(tx)['fee'] + assert_equal(balance + staker_reward + community_reward - Decimal('18336.225') + fee, self.nodes[0].getbalance()) # Test swap for more than in community fund by 1 Sat block = self.nodes[0].getblockcount() + 1 self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/premium':'0.00000000'}}) self.nodes[0].generate(1) - self.sync_blocks() - assert_raises_rpc_error(-32600, 'amount 18336.22500000 is less than 18336.22500001', self.nodes[1].executesmartcontract, dfip, '18336.22500001@2', address) + assert_raises_rpc_error(-32600, 'amount 18336.22500000 is less than 18336.22500001', self.nodes[0].executesmartcontract, dfip, '18336.22500001@2', address) # Test again for full amount in community balance - self.nodes[1].executesmartcontract(dfip, '18336.22500000@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '18336.22500000@2', address) + self.nodes[0].generate(1) assert_equal(18336.22500000, float(self.nodes[0].getaccount(address)[0].split('@')[0])) assert('0' not in self.nodes[0].listsmartcontracts()) @@ -179,80 +167,68 @@ def run_test(self): # Test default 2.5% premium block = self.nodes[0].getblockcount() + 1 - self.nodes[1].executesmartcontract(dfip, '0.09999999@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.09999999@2', address) + self.nodes[0].generate(1) assert_equal(2049.999795, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Test 5% premium self.rollback(block) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/premium':'0.05'}}) self.nodes[0].generate(1) - self.sync_blocks() - self.nodes[1].executesmartcontract(dfip, '0.09999999@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.09999999@2', address) + self.nodes[0].generate(1) assert_equal(2099.99979, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Test 0.1% premium self.rollback(block) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/premium':'0.001'}}) self.nodes[0].generate(1) - self.sync_blocks() - self.nodes[1].executesmartcontract(dfip, '0.09999999@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.09999999@2', address) + self.nodes[0].generate(1) assert_equal(2001.9997998, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Test 0.000001% premium self.rollback(block) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/premium':'0.00000001'}}) self.nodes[0].generate(1) - self.sync_blocks() - self.nodes[1].executesmartcontract(dfip, '0.1@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.1@2', address) + self.nodes[0].generate(1) assert_equal(2000.00002, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Test 0% premium self.rollback(block) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/premium':'0.00000000'}}) self.nodes[0].generate(1) - self.sync_blocks() - self.nodes[1].executesmartcontract(dfip, '0.1@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.1@2', address) + self.nodes[0].generate(1) assert_equal(2000, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Swap min amount self.rollback(block) - self.nodes[1].executesmartcontract(dfip, '0.00001@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.00001@2', address) + self.nodes[0].generate(1) assert_equal(0.205, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Test smallest min amount self.rollback(block) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/minswap':'0.00000001'}}) self.nodes[0].generate(1) - self.nodes[1].executesmartcontract(dfip, '0.00000001@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.00000001@2', address) + self.nodes[0].generate(1) assert_equal(0.000205, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Test no smallest min amount self.rollback(block) self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/minswap':'0.00000001'}}) self.nodes[0].generate(1) - self.nodes[1].executesmartcontract(dfip, '0.00000001@2', address) - self.nodes[1].generate(1) - self.sync_blocks() + self.nodes[0].executesmartcontract(dfip, '0.00000001@2', address) + self.nodes[0].generate(1) assert_equal(0.000205, float(self.nodes[0].getaccount(address)[0].split('@')[0])) # Test disabling DFIP201 self.nodes[0].setgov({"ATTRIBUTES":{'v0/params/dfip2201/active':'false'}}) self.nodes[0].generate(1) - assert_raises_rpc_error(-32600, 'DFIP2201 smart contract is not enabled', self.nodes[1].executesmartcontract, dfip, '1@2', address) + assert_raises_rpc_error(-32600, 'DFIP2201 smart contract is not enabled', self.nodes[0].executesmartcontract, dfip, '1@2', address) if __name__ == '__main__': SmartContractTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index da3ddf1acc9..383e5520fb7 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -263,6 +263,7 @@ 'feature_loan_priceupdate.py', 'feature_loan_vaultstate.py', 'feature_loan.py', + 'feature_loan_low_interest.py', 'feature_loan_estimatecollateral.py', 'p2p_node_network_limited.py', 'p2p_permissions.py', diff --git a/test/lint/lint-python-dead-code-whitelist b/test/lint/lint-python-dead-code-whitelist index ddfdbf03eaa..f1751bba76d 100644 --- a/test/lint/lint-python-dead-code-whitelist +++ b/test/lint/lint-python-dead-code-whitelist @@ -46,3 +46,4 @@ SpendTooMuch # unused class (test/functional/data/invalid_txs.py) TooManySigops # unused class (test/functional/data/invalid_txs.py) verify_ecdsa # unused function (test/functional/test_framework/key.py) AnchorRewardsTest # unused class (test/functional/feature_anchor_rewards.py) +_.prec # unused attribute (test/functional/feature_loan_low_interest.py)