From 4d06d898eeddf9db5e4f1525a0fe4caf311840dd Mon Sep 17 00:00:00 2001 From: Denis Angell Date: Tue, 5 Sep 2023 12:23:27 +0200 Subject: [PATCH] add halving tests --- src/ripple/app/main/Application.cpp | 152 +++++++++- src/ripple/core/Config.h | 2 +- src/test/app/Import_json.h | 68 +---- src/test/app/Import_test.cpp | 436 +++++++++++++++++++++++++++- 4 files changed, 591 insertions(+), 67 deletions(-) diff --git a/src/ripple/app/main/Application.cpp b/src/ripple/app/main/Application.cpp index 947b2d95f..c62df2228 100644 --- a/src/ripple/app/main/Application.cpp +++ b/src/ripple/app/main/Application.cpp @@ -1105,8 +1105,11 @@ private: std::shared_ptr loadLedgerFromFile(std::string const& ledgerID); + std::shared_ptr + loadLedgerFromJson(std::string const& jsonValue); + bool - loadOldLedger(std::string const& ledgerID, bool replay, bool isFilename); + loadOldLedger(std::string const& ledgerID, bool replay, bool isFilename, bool isJson); void setMaxDisallowedLedger(); @@ -1234,14 +1237,15 @@ ApplicationImp::setup(boost::program_options::variables_map const& cmdline) } else if ( startUp == Config::LOAD || startUp == Config::LOAD_FILE || - startUp == Config::REPLAY) + startUp == Config::REPLAY || startUp == Config::LOAD_JSON) { JLOG(m_journal.info()) << "Loading specified Ledger"; if (!loadOldLedger( config_->START_LEDGER, startUp == Config::REPLAY, - startUp == Config::LOAD_FILE)) + startUp == Config::LOAD_FILE, + startUp == Config::LOAD_JSON)) { JLOG(m_journal.error()) << "The specified ledger could not be loaded."; @@ -1907,17 +1911,153 @@ ApplicationImp::loadLedgerFromFile(std::string const& name) } } +std::shared_ptr +ApplicationImp::loadLedgerFromJson(std::string const& jsonValue) +{ + try + { + Json::Reader reader; + Json::Value jLedger; + + if (!reader.parse(jsonValue, jLedger)) + { + JLOG(m_journal.fatal()) << "Unable to parse ledger JSON"; + return nullptr; + } + + std::reference_wrapper ledger(jLedger); + + // accept a wrapped ledger + if (ledger.get().isMember("result")) + ledger = ledger.get()["result"]; + + if (ledger.get().isMember("ledger")) + ledger = ledger.get()["ledger"]; + + std::uint32_t seq = 1; + auto closeTime = timeKeeper().closeTime(); + using namespace std::chrono_literals; + auto closeTimeResolution = 30s; + bool closeTimeEstimated = false; + std::uint64_t totalDrops = 0; + + if (ledger.get().isMember("accountState")) + { + if (ledger.get().isMember(jss::ledger_index)) + { + seq = ledger.get()[jss::ledger_index].asUInt(); + } + + if (ledger.get().isMember("close_time")) + { + using tp = NetClock::time_point; + using d = tp::duration; + closeTime = tp{d{ledger.get()["close_time"].asUInt()}}; + } + if (ledger.get().isMember("close_time_resolution")) + { + using namespace std::chrono; + closeTimeResolution = + seconds{ledger.get()["close_time_resolution"].asUInt()}; + } + if (ledger.get().isMember("close_time_estimated")) + { + closeTimeEstimated = + ledger.get()["close_time_estimated"].asBool(); + } + if (ledger.get().isMember("total_coins")) + { + totalDrops = beast::lexicalCastThrow( + ledger.get()["total_coins"].asString()); + } + + ledger = ledger.get()["accountState"]; + } + + if (!ledger.get().isArrayOrNull()) + { + JLOG(m_journal.fatal()) << "State nodes must be an array"; + return nullptr; + } + + auto loadLedger = + std::make_shared(seq, closeTime, *config_, nodeFamily_); + loadLedger->setTotalDrops(totalDrops); + + for (Json::UInt index = 0; index < ledger.get().size(); ++index) + { + Json::Value& entry = ledger.get()[index]; + + if (!entry.isObjectOrNull()) + { + JLOG(m_journal.fatal()) << "Invalid entry in ledger"; + return nullptr; + } + + uint256 uIndex; + + if (!uIndex.parseHex(entry[jss::index].asString())) + { + JLOG(m_journal.fatal()) << "Invalid entry in ledger"; + return nullptr; + } + + entry.removeMember(jss::index); + + STParsedJSONObject stp("sle", ledger.get()[index]); + + if (!stp.object || uIndex.isZero()) + { + JLOG(m_journal.fatal()) << "Invalid entry in ledger"; + return nullptr; + } + + // VFALCO TODO This is the only place that + // constructor is used, try to remove it + STLedgerEntry sle(*stp.object, uIndex); + + if (!loadLedger->addSLE(sle)) + { + JLOG(m_journal.fatal()) + << "Couldn't add serialized ledger: " << uIndex; + return nullptr; + } + } + + loadLedger->stateMap().flushDirty(hotACCOUNT_NODE); + + assert( + loadLedger->info().seq < XRP_LEDGER_EARLIEST_FEES || + loadLedger->read(keylet::fees())); + loadLedger->setAccepted( + closeTime, closeTimeResolution, !closeTimeEstimated); + + return loadLedger; + } + catch (std::exception const& x) + { + JLOG(m_journal.fatal()) << "Ledger contains invalid data: " << x.what(); + return nullptr; + } +} + bool ApplicationImp::loadOldLedger( std::string const& ledgerID, bool replay, - bool isFileName) + bool isFileName, + bool isJson) { try { std::shared_ptr loadLedger, replayLedger; - if (isFileName) + if (isJson) + { + if (!ledgerID.empty()) + loadLedger = loadLedgerFromJson(ledgerID); + } + else if (isFileName) { if (!ledgerID.empty()) loadLedger = loadLedgerFromFile(ledgerID); @@ -1999,7 +2139,7 @@ ApplicationImp::loadOldLedger( using namespace std::chrono_literals; using namespace date; static constexpr NetClock::time_point ledgerWarnTimePoint{ - sys_days{January / 1 / 2018} - sys_days{January / 1 / 2000}}; + sys_days{January / 1 / 2000} - sys_days{January / 1 / 2000}}; if (loadLedger->info().closeTime < ledgerWarnTimePoint) { JLOG(m_journal.fatal()) diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index 81aec79a8..b2928b5b2 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -153,7 +153,7 @@ public: std::map IMPORT_VL_KEYS; // hex string -> class PublicKey (for caching purposes) - enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK }; + enum StartUpType { FRESH, NORMAL, LOAD, LOAD_FILE, REPLAY, NETWORK, LOAD_JSON }; StartUpType START_UP = NORMAL; bool START_VALID = false; diff --git a/src/test/app/Import_json.h b/src/test/app/Import_json.h index fe99b87d1..6f82c89c8 100644 --- a/src/test/app/Import_json.h +++ b/src/test/app/Import_json.h @@ -1158,10 +1158,10 @@ std::string ImportTCPayment::w_seed = R"json({ class ImportTCHalving { public: - static std::string one_one; + static std::string base_genesis; }; -std::string ImportTCHalving::one_one = R"json({ +std::string ImportTCHalving::base_genesis = R"json({ "ledger": { "accepted": true, "accountState": [ @@ -1177,56 +1177,20 @@ std::string ImportTCHalving::one_one = R"json({ "index": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8" }, { - "Amendments": [ - "740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11", - "3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC", - "67A34F2CF55BFC0F93AACD5B281413176FEE195269FA6D95219A2DF738671172", - "F64E1EABBE79D55B3BB82020516CEC2C582A98A6BFE20FBE9BB6A0D233418064", - "157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F97AE4B1F2D1", - "7117E2EC2DBF119CA55181D69819F1999ECEE1A0225A7FD2B9ED47940968479C", - "CA7C02118BA27599528543DFE77BA6838D1B0F43B447D4D7F53523CE6A0E9AC2", - "58BE9B5968C4DA7C59BA900961828B113E5490699B21877DEF9A31E9D0FE5D5F", - "3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194", - "5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE", - "FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288", - "586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D", - "2CD5286D8D687E98B41102BDD797198E81EA41DF7BD104E6561FEB104EFF2561", - "C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD37", - "8F81B066ED20DAECA20DF57187767685EEF3980B228E0667A650BAF24426D3B4", - "621A0B264970359869E3C0363A899909AAB7A887C8B73519E4ECF952D33258A8", - "89308AF3B8B10B7192C4E613E1D2E4D9BA64B2EE2D5232402AE82A6A7220D953", - "00C1FC4A53E60AB02C864641002B3172F38677E29C26C5406685179B37E1EDAC", - "25BA44241B3BD880770BFA4DA21C7180576831855368CBEC6A3154FDE4A7676E", - "1F4AFA8FA1BC8827AD4C0F682C03A8B671DCDF6B5C4DE36D44243A684103EF88", - "4F46DF03559967AC60F2EB272FEFE3928A7594A45FF774B87A7E540DB0F8F068", - "B4E4F5D2D6FB84DF7399960A732309C9FD530EAE5941838160042833625A6076", - "955DF3FA5891195A9DAEFA1DDC6BB244B545DDE1BAA84CBB25D5F12A8DA68A0C", - "AF8DF7465C338AE64B1E937D6C8DA138C0D63AD5134A68792BBBE1F63356C422", - "452F5906C46D46F407883344BFDD90E672B672C5E9943DB4891E3A34FEEEB9DB", - "B6B3EEDC0267AB50491FDC450A398AF30DBCD977CECED8BEF2499CAB5DAC19E2", - "98DECF327BF79997AEC178323AD51A830E457BFC6D454DAF3E46E5EC42DC619F", - "B2A4DB846F0891BF2C76AB2F2ACC8F5B4EC64437135C6E56F3F859DE5FFD5856", - "32A122F1352A4C7B3A6D790362CC34749C5E57FCE896377BFDC6CCD14F6CD627", - "F1ED6B4A411D8B872E65B9DCB4C8B100375B0DD3D62D07192E011D6D7F339013", - "75A7E01C505DD5A179DFE3E000A9B6F1EDDEB55A12F95579A23E15B15DC8BE5A", - "47C3002ABA31628447E8E9A8B315FAA935CE30183F9A9B86845E469CA2CDC3DF", - "93E516234E35E08CA689FA33A6D38E103881F8DCB53023F728C307AA89D515A7", - "2E2FB9CF8A44EB80F4694D38AADAE9B8B7ADAFD2F092E10068E61C98C4F092B0", - "73761231F7F3D94EC3D8C63D91BDD0D89045C6F71B917D1925C01253515A6669", - "AE35ABDEFBDE520372B31C957020B34A7A4A9DC3115A69803A44016477C84D6E", - "ECE6819DBA5DB528F1A241695F5A9811EF99467CDE22510954FD357780BBD078", - "42F8B586B357ABBAAAA1C733C3E7D3B75761395340D0CDF600179E8737E22478", - "919857E4B902A20216E4819B9BD9FD1FD19A66ECF63151C18A4C48C873DB9578", - "ECF412BE0964EC2E71DCF807EEEA6EA8470D3DB15173D46F28AB6E234860AC32", - "86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90", - "3C43D9A973AA4443EF3FC38E42DD306160FBFFDAB901CD8BAA15D09F2597EB87", - "0285B7E5E08E1A8E4C15636F0591D87F73CB6A7B6452A932AD72BBC8E5D1CBE3", - "36799EA497B1369B170805C078AEFE6188345F9B3E324C21E9CA3FF574E3C3D6", - "F5751842D26FC057B92CAA435ABF4F1428C2BCC4180D18775ADE92CB2643BBA3" - ], + "Amendments": [], "Flags": 0, "LedgerEntryType": "Amendments", "index": "7DB0788C020F02780A673DC74757F23823FA3014C1866E72CC4CD8B226CD6EF4" + }, + { + "BaseFee": "A", + "Flags": 0, + "LedgerEntryType": "FeeSettings", + "ReferenceFeeUnits": 10, + "ReserveBase": 1000000, + "ReserveIncrement": 200000, + "XahauActivationLgrSeq": 0, + "index": "4BC50C9B0D8515D3EAAE1E74B29A95804346C491EE1A95BF25E4AAB854A6A651" } ], "account_hash": "5DF3A98772FB73E782B8740E87885C6BAD9BA486422E3626DEF968AD2CB2C514", @@ -1237,16 +1201,16 @@ std::string ImportTCHalving::one_one = R"json({ "closed": true, "hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", "ledger_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", - "ledger_index": "1999998", + "ledger_index": "0", "parent_close_time": 0, "parent_hash": "56DA0940767AC2F17F0E384F04816002403D0756432B9D503DDA20128A2AAF11", - "seqNum": "1999998", + "seqNum": "0", "totalCoins": "0", "total_coins": "0", "transaction_hash": "9A77D1D1A4B36DA77B9C4DC63FDEB8F821741D157802F9C42A6ED86003D8B4A0", "transactions": [] }, - "ledger_current_index": 1999998, + "ledger_current_index": 0, "status": "success", "validated": true })json"; diff --git a/src/test/app/Import_test.cpp b/src/test/app/Import_test.cpp index b7c6b94c5..e6364f927 100644 --- a/src/test/app/Import_test.cpp +++ b/src/test/app/Import_test.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #define BEAST_REQUIRE(x) \ { \ @@ -4554,6 +4555,73 @@ class Import_test : public beast::unit_test::suite }); } + std::unique_ptr + makeGenesisConfig( + FeatureBitset features, + uint32_t networkID, + std::string fee, + std::string a_res, + std::string o_res, + uint32_t ledgerID) + { + using namespace jtx; + + // IMPORT VL KEY + std::vector const keys = { + "ED74D4036C6591A4BDF9C54CEFA39B996A" + "5DCE5F86D11FDA1874481CE9D5A1CDC1"}; + + Json::Value jsonValue; + Json::Reader reader; + reader.parse(ImportTCHalving::base_genesis, jsonValue); + + for (size_t i = 0; i < features.size(); ++i) + { + uint256 const& feature = bitsetIndexToFeature(i); + std::string featureName = featureToName(feature); + std::optional featureHash = getRegisteredFeature(featureName); + if (featureHash.has_value() && feature != featureOwnerPaysFee) + { + std::string hashString = to_string(featureHash.value()); + jsonValue["ledger"]["accountState"][1]["Amendments"].append(hashString); + } + } + + jsonValue["ledger_current_index"] = ledgerID; + jsonValue["ledger"]["ledger_index"] = to_string(ledgerID); + jsonValue["ledger"]["seqNum"] = to_string(ledgerID); + + return envconfig([&](std::unique_ptr cfg) { + cfg->NETWORK_ID = networkID; + cfg->START_LEDGER = jsonValue.toStyledString(); + cfg->START_UP = Config::LOAD_JSON; + Section config; + config.append( + {"reference_fee = " + fee, + "account_reserve = " + a_res, + "owner_reserve = " + o_res}); + auto setup = setup_FeeVote(config); + cfg->FEES = setup; + + for (auto const& strPk : keys) + { + auto pkHex = strUnHex(strPk); + if (!pkHex) + Throw( + "Import VL Key '" + strPk + "' was not valid hex."); + + auto const pkType = publicKeyType(makeSlice(*pkHex)); + if (!pkType) + Throw( + "Import VL Key '" + strPk + + "' was not a valid key type."); + + cfg->IMPORT_VL_KEYS.emplace(strPk, makeSlice(*pkHex)); + } + return cfg; + }); + } + void testHalving(FeatureBitset features) { @@ -4562,16 +4630,17 @@ class Import_test : public beast::unit_test::suite using namespace test::jtx; using namespace std::literals; - // Halving 1:1 + // Halving @ ledger seq 1'999'999 { test::jtx::Env env{ *this, makeGenesisConfig( + features, 21337, "10", "1000000", "200000", - "../src/test/app/halve_1_1.json" + 1999998 ) }; @@ -4581,7 +4650,6 @@ class Import_test : public beast::unit_test::suite auto initCoins = env.current()->info().drops; BEAST_EXPECT(initCoins == 0); auto initSeq = env.current()->info().seq; - std::cout << "initSeq: " << initSeq << "\n"; BEAST_EXPECT(initSeq == 1'999'999); // init env @@ -4593,7 +4661,7 @@ class Import_test : public beast::unit_test::suite BEAST_EXPECT(preAlice == XRP(0)); // import tx - auto const xpopJson = loadXpop(ImportTCAccountSet::min); + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); Json::Value tx = import(alice, xpopJson); tx[jss::Sequence] = 0; tx[jss::Fee] = 0; @@ -4601,8 +4669,361 @@ class Import_test : public beast::unit_test::suite env.close(); // total burn = burn drops + Init Reward - auto const creditDrops = drops(10) + XRP(2); - std::cout << "creditDrops: " << creditDrops << "\n"; + auto const creditDrops = XRP(1'000) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 2'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 1999998 + ) + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + env.close(); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 2'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = XRP(1'000) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 2'000'001 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 1999998 + ) + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + env.close(); + env.close(); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 2'000'001); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(999999964) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 5'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 4999999 + ) + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 5'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(892857142) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 20'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 19999999 + ) + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 20'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(357142857) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 29'999'998 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 29999998 + ) + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 29'999'999); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(35) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 30'000'000 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 29999998 + ) + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + env.close(); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 30'000'000); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(0) + XRP(2); + + // confirm fee was minted + auto const postAlice = env.balance(alice); + BEAST_EXPECT(postAlice == preAlice + creditDrops); + + // confirm total coins header + auto const postCoins = env.current()->info().drops; + BEAST_EXPECT(postCoins == initCoins + creditDrops); + } + + // Halving @ ledger seq 50'000'001 + { + test::jtx::Env env{ + *this, + makeGenesisConfig( + features, + 21337, + "10", + "1000000", + "200000", + 50000000 + ) + }; + + auto const feeDrops = env.current()->fees().base; + + // confirm total coins header + auto initCoins = env.current()->info().drops; + BEAST_EXPECT(initCoins == 0); + auto initSeq = env.current()->info().seq; + BEAST_EXPECT(initSeq == 50'000'001); + + // init env + auto const alice = Account("alice"); + env.memoize(alice); + + // confirm env + auto const preAlice = env.balance(alice); + BEAST_EXPECT(preAlice == XRP(0)); + + // import tx + auto const xpopJson = loadXpop(ImportTCAccountSet::w_seed); + Json::Value tx = import(alice, xpopJson); + tx[jss::Sequence] = 0; + tx[jss::Fee] = 0; + env(tx, alice, ter(tesSUCCESS)); + env.close(); + + // total burn = burn drops + Init Reward + auto const creditDrops = drops(0) + XRP(2); // confirm fee was minted auto const postAlice = env.balance(alice); @@ -4614,7 +5035,6 @@ class Import_test : public beast::unit_test::suite } } - public: void run() override @@ -4647,7 +5067,7 @@ public: testImportSequence(features); testMaxSupply(features); testMinMax(features); - testHalving(features); + testHalving(features - featureOwnerPaysFee); } };