diff --git a/src/ripple/app/tx/impl/Transactor.cpp b/src/ripple/app/tx/impl/Transactor.cpp index 6c9337d07..e2efe37fa 100644 --- a/src/ripple/app/tx/impl/Transactor.cpp +++ b/src/ripple/app/tx/impl/Transactor.cpp @@ -1923,6 +1923,7 @@ Transactor::operator()() uint32_t lgrCur = view().seq(); bool const has240819 = view().rules().enabled(fix240819); + bool const has240820 = view().rules().enabled(fix240820); auto const& sfRewardFields = *(ripple::SField::knownCodeToField.at(917511 - has240819)); @@ -1971,7 +1972,9 @@ Transactor::operator()() uint32_t lgrElapsed = lgrCur - lgrLast; // overflow safety - if (lgrElapsed > lgrCur || lgrElapsed > lgrLast || lgrElapsed == 0) + if (!has240820 && (lgrElapsed > lgrCur || lgrElapsed > lgrLast || lgrElapsed == 0)) + continue; + if (has240820 && (lgrElapsed > lgrCur || lgrElapsed == 0)) continue; uint64_t accum = sle->getFieldU64(sfRewardAccumulator); diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index 1ae142d30..1b244594a 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 71; +static constexpr std::size_t numFeatures = 72; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -359,6 +359,7 @@ extern uint256 const featureRemit; extern uint256 const featureZeroB2M; extern uint256 const fixNSDelete; extern uint256 const fix240819; +extern uint256 const fix240820; } // namespace ripple diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index e8769d977..b04c8c685 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -465,6 +465,7 @@ REGISTER_FEATURE(Remit, Supported::yes, VoteBehavior::De REGISTER_FEATURE(ZeroB2M, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fixNSDelete, Supported::yes, VoteBehavior::DefaultNo); REGISTER_FIX (fix240819, Supported::yes, VoteBehavior::DefaultYes); +REGISTER_FIX (fix240820, Supported::yes, VoteBehavior::DefaultYes); // The following amendments are obsolete, but must remain supported // because they could potentially get enabled. diff --git a/src/test/app/XahauGenesis_test.cpp b/src/test/app/XahauGenesis_test.cpp index ce8a3c115..d0c47762b 100644 --- a/src/test/app/XahauGenesis_test.cpp +++ b/src/test/app/XahauGenesis_test.cpp @@ -5020,6 +5020,443 @@ struct XahauGenesis_test : public beast::unit_test::suite BEAST_EXPECT(asPercent == 4); } + void + testDeposit(FeatureBitset features) + { + using namespace jtx; + using namespace std::chrono_literals; + testcase("test deposit"); + + Env env{ + *this, envconfig(), features - featureXahauGenesis}; + + double const rateDrops = 0.00333333333 * 1'000'000; + STAmount const feesXRP = XRP(1); + + auto const user = Account("user"); + env.fund(XRP(1000), user); + env.close(); + + // setup governance + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const david = Account("david"); + auto const edward = Account("edward"); + + env.fund(XRP(10000), alice, bob, carol, david, edward); + env.close(); + + std::vector initial_members_ids{ + alice.id(), bob.id(), carol.id(), david.id(), edward.id()}; + + setupGov(env, initial_members_ids); + + // update reward delay + { + // this will be the new reward delay + // 100 + std::vector vote_data{ + 0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U}; + + updateTopic( + env, alice, bob, carol, david, edward, 'R', 'D', vote_data); + } + + // verify unl report does not exist + BEAST_EXPECT(hasUNLReport(env) == false); + + // opt in claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + env(pay(alice, user, XRP(1000))); + env.close(); + + // close ledgers + for (int i = 0; i < 10; ++i) + { + env.close(10s); + } + + // close claim ledger & time + STAmount const preUser = env.balance(user); + NetClock::time_point const preTime = lastClose(env); + std::uint32_t const preLedger = env.current()->seq(); + auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user); + + // claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // trigger emitted txn + env.close(); + + // calculate rewards + bool const has240819 = env.current()->rules().enabled(fix240819); + STAmount const netReward = rewardUserAmount(*acctSle, preLedger, rateDrops); + BEAST_EXPECT(netReward == (has240819 ? XRP(6.383333) : XRP(6.663333))); + + // validate account fields + STAmount const postUser = preUser + netReward; + BEAST_EXPECT(expectAccountFields( + env, user, preLedger, preLedger + 1, has240819 ? (preUser - feesXRP) : postUser, preTime)); + BEAST_EXPECT( + postUser == (has240819 ? XRP(2005.383333) : XRP(2005.663333))); + } + + void + testDepositWithdraw(FeatureBitset features) + { + using namespace jtx; + using namespace std::chrono_literals; + testcase("test deposit withdraw"); + + Env env{ + *this, envconfig(), features - featureXahauGenesis}; + + double const rateDrops = 0.00333333333 * 1'000'000; + STAmount const feesXRP = XRP(1); + + auto const user = Account("user"); + env.fund(XRP(1000), user); + env.close(); + + // setup governance + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const david = Account("david"); + auto const edward = Account("edward"); + + env.fund(XRP(10000), alice, bob, carol, david, edward); + env.close(); + + std::vector initial_members_ids{ + alice.id(), bob.id(), carol.id(), david.id(), edward.id()}; + + setupGov(env, initial_members_ids); + + // update reward delay + { + // this will be the new reward delay + // 100 + std::vector vote_data{ + 0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U}; + + updateTopic( + env, alice, bob, carol, david, edward, 'R', 'D', vote_data); + } + + // verify unl report does not exist + BEAST_EXPECT(hasUNLReport(env) == false); + + // opt in claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + env(pay(alice, user, XRP(1000))); + env.close(); + + env(pay(user, alice, XRP(1000))); + env.close(); + + // close ledgers + for (int i = 0; i < 10; ++i) + { + env.close(10s); + } + + // close claim ledger & time + STAmount const preUser = env.balance(user); + NetClock::time_point const preTime = lastClose(env); + std::uint32_t const preLedger = env.current()->seq(); + auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user); + + // claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // trigger emitted txn + env.close(); + + // calculate rewards + bool const has240819 = env.current()->rules().enabled(fix240819); + STAmount const netReward = rewardUserAmount(*acctSle, preLedger, rateDrops); + BEAST_EXPECT(netReward == XRP(3.583333)); + + // validate account fields + STAmount const postUser = preUser + netReward; + BEAST_EXPECT(expectAccountFields( + env, user, preLedger, preLedger + 1, has240819 ? (preUser - feesXRP) : postUser, preTime)); + BEAST_EXPECT( + postUser == XRP(1002.583323)); + } + + void + testDepositLate(FeatureBitset features) + { + using namespace jtx; + using namespace std::chrono_literals; + testcase("test deposit late"); + + Env env{ + *this, envconfig(), features - featureXahauGenesis}; + + double const rateDrops = 0.00333333333 * 1'000'000; + STAmount const feesXRP = XRP(1); + + auto const user = Account("user"); + env.fund(XRP(1000), user); + env.close(); + + // setup governance + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const david = Account("david"); + auto const edward = Account("edward"); + + env.fund(XRP(10000), alice, bob, carol, david, edward); + env.close(); + + std::vector initial_members_ids{ + alice.id(), bob.id(), carol.id(), david.id(), edward.id()}; + + setupGov(env, initial_members_ids); + + // update reward delay + { + // this will be the new reward delay + // 100 + std::vector vote_data{ + 0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U}; + + updateTopic( + env, alice, bob, carol, david, edward, 'R', 'D', vote_data); + } + + // verify unl report does not exist + BEAST_EXPECT(hasUNLReport(env) == false); + + // opt in claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // close ledgers + for (int i = 0; i < 10; ++i) + { + env.close(10s); + } + + env(pay(alice, user, XRP(1000))); + env.close(); + + // close claim ledger & time + STAmount const preUser = env.balance(user); + NetClock::time_point const preTime = lastClose(env); + std::uint32_t const preLedger = env.current()->seq(); + auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user); + + // claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // trigger emitted txn + env.close(); + + // calculate rewards + bool const has240819 = env.current()->rules().enabled(fix240819); + STAmount const netReward = rewardUserAmount(*acctSle, preLedger, rateDrops); + BEAST_EXPECT(netReward == (has240819 ? XRP(3.606666) : XRP(6.663333))); + + // validate account fields + STAmount const postUser = preUser + netReward; + BEAST_EXPECT(expectAccountFields( + env, user, preLedger, preLedger + 1, has240819 ? (preUser - feesXRP) : postUser, preTime)); + BEAST_EXPECT( + postUser == (has240819 ? XRP(2002.606666) : XRP(2005.663333))); + } + + void + testDepositWithdrawLate(FeatureBitset features) + { + using namespace jtx; + using namespace std::chrono_literals; + testcase("test deposit late withdraw"); + + Env env{ + *this, envconfig(), features - featureXahauGenesis}; + + double const rateDrops = 0.00333333333 * 1'000'000; + STAmount const feesXRP = XRP(1); + + auto const user = Account("user"); + env.fund(XRP(1000), user); + env.close(); + + // setup governance + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const david = Account("david"); + auto const edward = Account("edward"); + + env.fund(XRP(10000), alice, bob, carol, david, edward); + env.close(); + + std::vector initial_members_ids{ + alice.id(), bob.id(), carol.id(), david.id(), edward.id()}; + + setupGov(env, initial_members_ids); + + // update reward delay + { + // this will be the new reward delay + // 100 + std::vector vote_data{ + 0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U}; + + updateTopic( + env, alice, bob, carol, david, edward, 'R', 'D', vote_data); + } + + // verify unl report does not exist + BEAST_EXPECT(hasUNLReport(env) == false); + + // opt in claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // close ledgers + for (int i = 0; i < 10; ++i) + { + env.close(10s); + } + + env(pay(alice, user, XRP(1000))); + env.close(); + + env(pay(user, alice, XRP(1000))); + env.close(); + + // close claim ledger & time + STAmount const preUser = env.balance(user); + NetClock::time_point const preTime = lastClose(env); + std::uint32_t const preLedger = env.current()->seq(); + auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user); + + // claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // trigger emitted txn + env.close(); + + // calculate rewards + bool const has240819 = env.current()->rules().enabled(fix240819); + STAmount const netReward = + rewardUserAmount(*acctSle, preLedger, rateDrops); + BEAST_EXPECT(netReward == (has240819 ? XRP(3.583333) : XRP(6.149999))); + + // validate account fields + STAmount const postUser = preUser + netReward; + BEAST_EXPECT(expectAccountFields( + env, + user, + preLedger, + preLedger + 1, + has240819 ? (preUser - feesXRP) : postUser, + preTime)); + BEAST_EXPECT( + postUser == (has240819 ? XRP(1002.583323) : XRP(1005.149989))); + } + + void + testNoClaimLate(FeatureBitset features) + { + using namespace jtx; + using namespace std::chrono_literals; + testcase("test no claim late"); + + Env env{ + *this, envconfig(), features - featureXahauGenesis}; + + double const rateDrops = 0.00333333333 * 1'000'000; + STAmount const feesXRP = XRP(1); + + auto const user = Account("user"); + env.fund(XRP(1000), user); + env.close(); + + // setup governance + auto const alice = Account("alice"); + auto const bob = Account("bob"); + auto const carol = Account("carol"); + auto const david = Account("david"); + auto const edward = Account("edward"); + + env.fund(XRP(10000), alice, bob, carol, david, edward); + env.close(); + + std::vector initial_members_ids{ + alice.id(), bob.id(), carol.id(), david.id(), edward.id()}; + + setupGov(env, initial_members_ids); + + // update reward delay + { + // this will be the new reward delay + // 100 + std::vector vote_data{ + 0x00U, 0x80U, 0xC6U, 0xA4U, 0x7EU, 0x8DU, 0x03U, 0x55U}; + + updateTopic( + env, alice, bob, carol, david, edward, 'R', 'D', vote_data); + } + + // verify unl report does not exist + BEAST_EXPECT(hasUNLReport(env) == false); + + // opt in claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // close ledgers (2 cycles) + for (int i = 0; i < 20; ++i) + { + env.close(10s); + } + + env(pay(alice, user, XRP(1000))); + env.close(); + + // close claim ledger & time + STAmount const preUser = env.balance(user); + NetClock::time_point const preTime = lastClose(env); + std::uint32_t const preLedger = env.current()->seq(); + auto const [acct, acctSle] = accountKeyAndSle(*env.current(), user); + + // claim reward + env(claimReward(user, env.master), fee(feesXRP), ter(tesSUCCESS)); + env.close(); + + // trigger emitted txn + env.close(); + + // calculate rewards + bool const hasFix = env.current()->rules().enabled(fix240819) && env.current()->rules().enabled(fix240820); + STAmount const netReward = rewardUserAmount(*acctSle, preLedger, rateDrops); + BEAST_EXPECT(netReward == (hasFix ? XRP(3.479999) : XRP(6.663333))); + + // validate account fields + STAmount const postUser = preUser + netReward; + BEAST_EXPECT(expectAccountFields( + env, user, preLedger, preLedger + 1, hasFix ? (preUser - feesXRP) : postUser, preTime)); + BEAST_EXPECT( + postUser == (hasFix ? XRP(2002.479999) : XRP(2005.663333))); + } + void testRewardHookWithFeats(FeatureBitset features) { @@ -5038,6 +5475,11 @@ struct XahauGenesis_test : public beast::unit_test::suite testInvalidElapsed0(features); testInvalidElapsedNegative(features); testCompoundInterest(features); + testDeposit(features); + testDepositWithdraw(features); + testDepositLate(features); + testDepositWithdrawLate(features); + testNoClaimLate(features); } void @@ -5056,8 +5498,9 @@ struct XahauGenesis_test : public beast::unit_test::suite using namespace test::jtx; auto const sa = supported_amendments(); testGovernHookWithFeats(sa); - testRewardHookWithFeats(sa - fix240819); testRewardHookWithFeats(sa); + testRewardHookWithFeats(sa - fix240819); + testRewardHookWithFeats(sa - fix240819 - fix240820); } };