diff --git a/src/masternodes/loan.cpp b/src/masternodes/loan.cpp index 7f48338ee8..0e57152da4 100644 --- a/src/masternodes/loan.cpp +++ b/src/masternodes/loan.cpp @@ -481,38 +481,44 @@ CAmount CLoanView::GetLoanLiquidationPenalty() return 5 * COIN / 100; } -std::string GetInterestPerBlockHighPrecisionString(base_uint<128> value) { +boost::optional GetInterestPerBlockHighPrecisionString(const base_uint<128>& value) { struct HighPrecisionInterestValue { typedef boost::multiprecision::int128_t int128; typedef int64_t int64; int128 value; - HighPrecisionInterestValue(base_uint<128> val) { + explicit HighPrecisionInterestValue(const base_uint<128>& val) { value = int128("0x" + val.GetHex()); } - int64 GetInterestPerBlockSat() { + int64 GetInterestPerBlockSat() const { return int64(value / HIGH_PRECISION_SCALER); } - int64 GetInterestPerBlockSubSat() { + int64 GetInterestPerBlockSubSat() const { return int64(value % HIGH_PRECISION_SCALER); } - int64 GetInterestPerBlockMagnitude() { + int64 GetInterestPerBlockMagnitude() const { return int64(value / HIGH_PRECISION_SCALER / COIN); } - int128 GetInterestPerBlockDecimal() { + int128 GetInterestPerBlockDecimal() const { auto v = GetInterestPerBlockSat(); return v == 0 ? value : value % (int128(HIGH_PRECISION_SCALER) * COIN); } - std::string GetInterestPerBlockString() { + boost::optional GetInterestPerBlockString() const { std::ostringstream result; - result << GetInterestPerBlockMagnitude() << "."; - result << std::setw(24) << std::setfill('0') << GetInterestPerBlockDecimal(); + auto mag = GetInterestPerBlockMagnitude(); + auto dec = GetInterestPerBlockDecimal(); + // While these can happen theoretically, they should be out of range of + // operating interest. If this happens, something else went wrong. + if (mag < 0 || dec < 0) + return {}; + + result << mag << "." << std::setw(24) << std::setfill('0') << dec; return result.str(); } }; diff --git a/src/masternodes/loan.h b/src/masternodes/loan.h index 541990d20b..cf79e07083 100644 --- a/src/masternodes/loan.h +++ b/src/masternodes/loan.h @@ -266,7 +266,7 @@ 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); -std::string GetInterestPerBlockHighPrecisionString(base_uint<128> value); +boost::optional GetInterestPerBlockHighPrecisionString(const base_uint<128>& value); class CLoanTakeLoanMessage { diff --git a/src/masternodes/rpc_loan.cpp b/src/masternodes/rpc_loan.cpp index 887a7a9cda..8e3b971733 100644 --- a/src/masternodes/rpc_loan.cpp +++ b/src/masternodes/rpc_loan.cpp @@ -1400,7 +1400,7 @@ UniValue getinterest(const JSONRPCRequest& request) { } UniValue obj(UniValue::VOBJ); - for (std::map, base_uint<128> > >::iterator it=interest.begin(); it!=interest.end(); ++it) + for (auto it=interest.begin(); it != interest.end(); ++it) { auto tokenId = it->first; auto interestRate = it->second; @@ -1413,7 +1413,13 @@ UniValue getinterest(const JSONRPCRequest& request) { obj.pushKV("interestPerBlock", ValueFromAmount(CeilInterest(interestPerBlock, height))); if (height >= Params().GetConsensus().FortCanningHillHeight) { - obj.pushKV("realizedInterestPerBlock", UniValue(UniValue::VNUM, GetInterestPerBlockHighPrecisionString(interestPerBlock))); + auto realizedInterestStr = GetInterestPerBlockHighPrecisionString(interestPerBlock); + // Ideally would be better to have a universal graceful shutdown methodology to force the node to + // stop for these unexpected state errors that violate operating params but still not enough + // memory inconsistency to crash risking wallet and data corruption. + if (!realizedInterestStr) + throw JSONRPCError(RPC_MISC_ERROR, "Invalid GetInterestPerBlockHighPrecisionString."); + obj.pushKV("realizedInterestPerBlock", UniValue(UniValue::VNUM, *realizedInterestStr)); } ret.push_back(obj); } diff --git a/src/test/loan_tests.cpp b/src/test/loan_tests.cpp index b40cc5a215..c9c8c95a65 100644 --- a/src/test/loan_tests.cpp +++ b/src/test/loan_tests.cpp @@ -4,8 +4,9 @@ #include #include - #include +#include +#include inline uint256 NextTx() { @@ -74,25 +75,191 @@ extern std::vector CollectAuctionBatches(const CCollateralLoans& BOOST_FIXTURE_TEST_SUITE(loan_tests, TestChain100Setup) -BOOST_AUTO_TEST_CASE(high_precision_interest_rate_tests) +BOOST_AUTO_TEST_CASE(high_precision_interest_rate_to_string_tests) { - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(0)), "0.000000000000000000000000"); - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(1)), "0.000000000000000000000001"); - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(1)), "0.000000000000000000000001"); - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(42058)), "0.000000000000000000042058"); - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(95129375)), "0.000000000000000095129375"); - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(117009132)), "0.000000000000000117009132"); - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(11700913242)), "0.000000000000011700913242"); - - base_uint<128> num; - num.SetHex("21012F95D4094B33"); // 2378234398782343987 - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(num)), "0.000002378234398782343987"); - num.SetHex("3CDC4CA64879921C03BF061156E455BC"); // 80897539693407360060932882613242451388 - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(num)), "80897539693407.360060932882613242451388"); - num.SetHex("10E5FBB8CA9E273D0B0353C23D90A6"); // 87741364994776235347880977943597222 - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(num)), "87741364994.776235347880977943597222"); - num.SetHex("2D5C78FF9C3FE70F9F0B0C7"); // 877413626032608048611111111 - BOOST_CHECK_EQUAL(GetInterestPerBlockHighPrecisionString(base_uint<128>(num)), "877.413626032608048611111111"); + std::map, std::string>, std::string> testMap = { + { 0, "0.000000000000000000000000" }, + { 1, "0.000000000000000000000001" }, + { 42058, "0.000000000000000000042058" }, + { 95129375, "0.000000000000000095129375" }, + { 117009132, "0.000000000000000117009132" }, + { 11700913242, "0.000000000000011700913242" }, + // 2378234398782343987 + { "21012F95D4094B33", "0.000002378234398782343987" }, + // 80897539693407360060932882613242451388 + { "3CDC4CA64879921C03BF061156E455BC" , "80897539693407.360060932882613242451388" }, + // 87741364994776235347880977943597222 + { "10E5FBB8CA9E273D0B0353C23D90A6" , "87741364994.776235347880977943597222" }, + // 877413626032608048611111111 + { "2D5C78FF9C3FE70F9F0B0C7" , "877.413626032608048611111111" }, + { std::numeric_limits::min(), "0.000000000000000000000000" }, + { std::numeric_limits::max(), "0.000018446744073709551615" }, + { std::numeric_limits::min(), "0.000009223372036854775808" }, + { std::numeric_limits::max(), "0.000009223372036854775807" }, + + // Full list by rotating 1s all over.. The reason for adding this to full spectrum + // test is since we use arbitrary bit ranges to achieve COIN ^ 3 precision. One vector of + // common mistakes would be due to improper cast and the first high 1 bit being interpreted + // as 2s complement and as such result in a negative error. This check verifies the entire + // range to ensure this doesn't happen. + // + { "80000000000000000000000000000000", "170141183460469.231731687303715884105728" }, + { "40000000000000000000000000000000", "85070591730234.615865843651857942052864" }, + { "20000000000000000000000000000000", "42535295865117.307932921825928971026432" }, + { "10000000000000000000000000000000", "21267647932558.653966460912964485513216" }, + { "08000000000000000000000000000000", "10633823966279.326983230456482242756608" }, + { "04000000000000000000000000000000", "5316911983139.663491615228241121378304" }, + { "02000000000000000000000000000000", "2658455991569.831745807614120560689152" }, + { "01000000000000000000000000000000", "1329227995784.915872903807060280344576" }, + { "00800000000000000000000000000000", "664613997892.457936451903530140172288" }, + { "00400000000000000000000000000000", "332306998946.228968225951765070086144" }, + { "00200000000000000000000000000000", "166153499473.114484112975882535043072" }, + { "00100000000000000000000000000000", "83076749736.557242056487941267521536" }, + { "00080000000000000000000000000000", "41538374868.278621028243970633760768" }, + { "00040000000000000000000000000000", "20769187434.139310514121985316880384" }, + { "00020000000000000000000000000000", "10384593717.069655257060992658440192" }, + { "00010000000000000000000000000000", "5192296858.534827628530496329220096" }, + { "00008000000000000000000000000000", "2596148429.267413814265248164610048" }, + { "00004000000000000000000000000000", "1298074214.633706907132624082305024" }, + { "00002000000000000000000000000000", "649037107.316853453566312041152512" }, + { "00001000000000000000000000000000", "324518553.658426726783156020576256" }, + { "00000800000000000000000000000000", "162259276.829213363391578010288128" }, + { "00000400000000000000000000000000", "81129638.414606681695789005144064" }, + { "00000200000000000000000000000000", "40564819.207303340847894502572032" }, + { "00000100000000000000000000000000", "20282409.603651670423947251286016" }, + { "00000080000000000000000000000000", "10141204.801825835211973625643008" }, + { "00000040000000000000000000000000", "5070602.400912917605986812821504" }, + { "00000020000000000000000000000000", "2535301.200456458802993406410752" }, + { "00000010000000000000000000000000", "1267650.600228229401496703205376" }, + { "00000008000000000000000000000000", "633825.300114114700748351602688" }, + { "00000004000000000000000000000000", "316912.650057057350374175801344" }, + { "00000002000000000000000000000000", "158456.325028528675187087900672" }, + { "00000001000000000000000000000000", "79228.162514264337593543950336" }, + { "00000000800000000000000000000000", "39614.081257132168796771975168" }, + { "00000000400000000000000000000000", "19807.040628566084398385987584" }, + { "00000000200000000000000000000000", "9903.520314283042199192993792" }, + { "00000000100000000000000000000000", "4951.760157141521099596496896" }, + { "00000000080000000000000000000000", "2475.880078570760549798248448" }, + { "00000000040000000000000000000000", "1237.940039285380274899124224" }, + { "00000000020000000000000000000000", "618.970019642690137449562112" }, + { "00000000010000000000000000000000", "309.485009821345068724781056" }, + { "00000000008000000000000000000000", "154.742504910672534362390528" }, + { "00000000004000000000000000000000", "77.371252455336267181195264" }, + { "00000000002000000000000000000000", "38.685626227668133590597632" }, + { "00000000001000000000000000000000", "19.342813113834066795298816" }, + { "00000000000800000000000000000000", "9.671406556917033397649408" }, + { "00000000000400000000000000000000", "4.835703278458516698824704" }, + { "00000000000200000000000000000000", "2.417851639229258349412352" }, + { "00000000000100000000000000000000", "1.208925819614629174706176" }, + { "00000000000080000000000000000000", "0.604462909807314587353088" }, + { "00000000000040000000000000000000", "0.302231454903657293676544" }, + { "00000000000020000000000000000000", "0.151115727451828646838272" }, + { "00000000000010000000000000000000", "0.075557863725914323419136" }, + { "00000000000008000000000000000000", "0.037778931862957161709568" }, + { "00000000000004000000000000000000", "0.018889465931478580854784" }, + { "00000000000002000000000000000000", "0.009444732965739290427392" }, + { "00000000000001000000000000000000", "0.004722366482869645213696" }, + { "00000000000000800000000000000000", "0.002361183241434822606848" }, + { "00000000000000400000000000000000", "0.001180591620717411303424" }, + { "00000000000000200000000000000000", "0.000590295810358705651712" }, + { "00000000000000100000000000000000", "0.000295147905179352825856" }, + { "00000000000000080000000000000000", "0.000147573952589676412928" }, + { "00000000000000040000000000000000", "0.000073786976294838206464" }, + { "00000000000000020000000000000000", "0.000036893488147419103232" }, + { "00000000000000010000000000000000", "0.000018446744073709551616" }, + { "00000000000000008000000000000000", "0.000009223372036854775808" }, + { "00000000000000004000000000000000", "0.000004611686018427387904" }, + { "00000000000000002000000000000000", "0.000002305843009213693952" }, + { "00000000000000001000000000000000", "0.000001152921504606846976" }, + { "00000000000000000800000000000000", "0.000000576460752303423488" }, + { "00000000000000000400000000000000", "0.000000288230376151711744" }, + { "00000000000000000200000000000000", "0.000000144115188075855872" }, + { "00000000000000000100000000000000", "0.000000072057594037927936" }, + { "00000000000000000080000000000000", "0.000000036028797018963968" }, + { "00000000000000000040000000000000", "0.000000018014398509481984" }, + { "00000000000000000020000000000000", "0.000000009007199254740992" }, + { "00000000000000000010000000000000", "0.000000004503599627370496" }, + { "00000000000000000008000000000000", "0.000000002251799813685248" }, + { "00000000000000000004000000000000", "0.000000001125899906842624" }, + { "00000000000000000002000000000000", "0.000000000562949953421312" }, + { "00000000000000000001000000000000", "0.000000000281474976710656" }, + { "00000000000000000000800000000000", "0.000000000140737488355328" }, + { "00000000000000000000400000000000", "0.000000000070368744177664" }, + { "00000000000000000000200000000000", "0.000000000035184372088832" }, + { "00000000000000000000100000000000", "0.000000000017592186044416" }, + { "00000000000000000000080000000000", "0.000000000008796093022208" }, + { "00000000000000000000040000000000", "0.000000000004398046511104" }, + { "00000000000000000000020000000000", "0.000000000002199023255552" }, + { "00000000000000000000010000000000", "0.000000000001099511627776" }, + { "00000000000000000000008000000000", "0.000000000000549755813888" }, + { "00000000000000000000004000000000", "0.000000000000274877906944" }, + { "00000000000000000000002000000000", "0.000000000000137438953472" }, + { "00000000000000000000001000000000", "0.000000000000068719476736" }, + { "00000000000000000000000800000000", "0.000000000000034359738368" }, + { "00000000000000000000000400000000", "0.000000000000017179869184" }, + { "00000000000000000000000200000000", "0.000000000000008589934592" }, + { "00000000000000000000000100000000", "0.000000000000004294967296" }, + { "00000000000000000000000080000000", "0.000000000000002147483648" }, + { "00000000000000000000000040000000", "0.000000000000001073741824" }, + { "00000000000000000000000020000000", "0.000000000000000536870912" }, + { "00000000000000000000000010000000", "0.000000000000000268435456" }, + { "00000000000000000000000008000000", "0.000000000000000134217728" }, + { "00000000000000000000000004000000", "0.000000000000000067108864" }, + { "00000000000000000000000002000000", "0.000000000000000033554432" }, + { "00000000000000000000000001000000", "0.000000000000000016777216" }, + { "00000000000000000000000000800000", "0.000000000000000008388608" }, + { "00000000000000000000000000400000", "0.000000000000000004194304" }, + { "00000000000000000000000000200000", "0.000000000000000002097152" }, + { "00000000000000000000000000100000", "0.000000000000000001048576" }, + { "00000000000000000000000000080000", "0.000000000000000000524288" }, + { "00000000000000000000000000040000", "0.000000000000000000262144" }, + { "00000000000000000000000000020000", "0.000000000000000000131072" }, + { "00000000000000000000000000010000", "0.000000000000000000065536" }, + { "00000000000000000000000000008000", "0.000000000000000000032768" }, + { "00000000000000000000000000004000", "0.000000000000000000016384" }, + { "00000000000000000000000000002000", "0.000000000000000000008192" }, + { "00000000000000000000000000001000", "0.000000000000000000004096" }, + { "00000000000000000000000000000800", "0.000000000000000000002048" }, + { "00000000000000000000000000000400", "0.000000000000000000001024" }, + { "00000000000000000000000000000200", "0.000000000000000000000512" }, + { "00000000000000000000000000000100", "0.000000000000000000000256" }, + { "00000000000000000000000000000080", "0.000000000000000000000128" }, + { "00000000000000000000000000000040", "0.000000000000000000000064" }, + { "00000000000000000000000000000020", "0.000000000000000000000032" }, + { "00000000000000000000000000000010", "0.000000000000000000000016" }, + { "00000000000000000000000000000008", "0.000000000000000000000008" }, + { "00000000000000000000000000000004", "0.000000000000000000000004" }, + { "00000000000000000000000000000002", "0.000000000000000000000002" }, + { "00000000000000000000000000000001", "0.000000000000000000000001" }, + }; + + for (const auto& kv: testMap) { + auto key = kv.first; + auto expectedResult = kv.second; + + base_uint<128> input; + auto typeKind = key.which(); + if (typeKind == 0) input = boost::get>(key); + else if (typeKind == 1) input = base_uint<128>(boost::get(key)); + else BOOST_TEST_FAIL("unknown type"); + + auto res = GetInterestPerBlockHighPrecisionString(input); + if (!res) BOOST_TEST_FAIL("negatives detected"); + BOOST_CHECK_EQUAL(*res, expectedResult); + } + + // Quick way to generate the nums and verify + // std::vector> nums; + // std::string hexStr = "8000 0000 0000 0000 0000 0000 0000 0000"; + // hexStr.erase(std::remove(hexStr.begin(), hexStr.end(), ' '), hexStr.end()); + // auto i = base_uint<128>(hexStr); + // for (auto n = 0; n < 128; n++) { + // nums.push_back(i); + // std::cout << " { \"" << i.GetHex() << "\", \""; + // std::cout << GetInterestPerBlockHighPrecisionString(i); + // std::cout << "\" }," << std::endl; + // i = i >> 1; + // } } BOOST_AUTO_TEST_CASE(loan_iterest_rate)